mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-08-10 18:53:03 +02:00
Add basic bot functionality
This commit is contained in:
+1
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
+602
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
-542
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -13,8 +13,8 @@
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<script type="module" crossorigin src="/assets/index-DsqXtGVx.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Cxcg9Mr6.css">
|
||||
<script type="module" crossorigin src="/assets/index-DpuVn1aK.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-B-EWrA2Q.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
Generated
+30
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "remoteterm-meshcore-frontend",
|
||||
"version": "1.4.1",
|
||||
"version": "1.5.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "remoteterm-meshcore-frontend",
|
||||
"version": "1.4.1",
|
||||
"version": "1.5.0",
|
||||
"dependencies": {
|
||||
"@michaelhart/meshcore-decoder": "^0.2.7",
|
||||
"@radix-ui/react-checkbox": "^1.3.3",
|
||||
@@ -15,6 +15,7 @@
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"d3-force": "^3.0.0",
|
||||
@@ -22,9 +23,11 @@
|
||||
"lucide-react": "^0.562.0",
|
||||
"meshcore-hashtag-cracker": "^1.6.0",
|
||||
"nosleep.js": "^0.12.0",
|
||||
"prismjs": "^1.30.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-leaflet": "^4.2.1",
|
||||
"react-simple-code-editor": "^0.14.1",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
@@ -1521,6 +1524,12 @@
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/prismjs": {
|
||||
"version": "1.26.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz",
|
||||
"integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.15",
|
||||
"devOptional": true,
|
||||
@@ -3953,6 +3962,15 @@
|
||||
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prismjs": {
|
||||
"version": "1.30.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
|
||||
"integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"dev": true,
|
||||
@@ -4071,6 +4089,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-simple-code-editor": {
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/react-simple-code-editor/-/react-simple-code-editor-0.14.1.tgz",
|
||||
"integrity": "sha512-BR5DtNRy+AswWJECyA17qhUDvrrCZ6zXOCfkQY5zSmb96BVUbpVAv03WpcjcwtCwiLbIANx3gebHOcXYn1EHow==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-style-singleton": {
|
||||
"version": "2.2.3",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"d3-force": "^3.0.0",
|
||||
@@ -29,9 +30,11 @@
|
||||
"lucide-react": "^0.562.0",
|
||||
"meshcore-hashtag-cracker": "^1.6.0",
|
||||
"nosleep.js": "^0.12.0",
|
||||
"prismjs": "^1.30.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-leaflet": "^4.2.1",
|
||||
"react-simple-code-editor": "^0.14.1",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import Editor from 'react-simple-code-editor';
|
||||
import { highlight, languages } from 'prismjs';
|
||||
import 'prismjs/components/prism-python';
|
||||
import 'prismjs/themes/prism-tomorrow.css';
|
||||
import type {
|
||||
AppSettings,
|
||||
AppSettingsUpdate,
|
||||
@@ -73,7 +77,7 @@ export function SettingsModal({
|
||||
onRefreshAppSettings,
|
||||
}: SettingsModalProps) {
|
||||
// Tab state
|
||||
type SettingsTab = 'radio' | 'identity' | 'serial' | 'database' | 'advertise';
|
||||
type SettingsTab = 'radio' | 'identity' | 'serial' | 'database' | 'advertise' | 'bot';
|
||||
const [activeTab, setActiveTab] = useState<SettingsTab>('radio');
|
||||
|
||||
// Radio config state
|
||||
@@ -103,6 +107,31 @@ export function SettingsModal({
|
||||
// Advertisement interval state
|
||||
const [advertInterval, setAdvertInterval] = useState('0');
|
||||
|
||||
// Bot state
|
||||
const DEFAULT_BOT_CODE = `def bot(sender_name, sender_key, message_text, is_dm, channel_key, channel_name, sender_timestamp, path):
|
||||
"""
|
||||
Process incoming messages and optionally return a reply.
|
||||
|
||||
Args:
|
||||
sender_name: Display name of sender (may be None)
|
||||
sender_key: 64-char hex public key (empty for channel msgs)
|
||||
message_text: The message content
|
||||
is_dm: True for direct messages, False for channel
|
||||
channel_key: 32-char hex key for channels, None for DMs
|
||||
channel_name: Channel name with hash (e.g. "#bot"), None for DMs
|
||||
sender_timestamp: Sender's timestamp (unix seconds, may be None)
|
||||
path: Hex-encoded routing path (may be None)
|
||||
|
||||
Returns:
|
||||
None for no reply, or a string to send as reply
|
||||
"""
|
||||
# Example: Only respond in #bot channel to "!pling" command
|
||||
if channel_name == "#bot" and "!pling" in message_text.lower():
|
||||
return "[BOT] Plong!"
|
||||
return None`;
|
||||
const [botEnabled, setBotEnabled] = useState(false);
|
||||
const [botCode, setBotCode] = useState(DEFAULT_BOT_CODE);
|
||||
|
||||
useEffect(() => {
|
||||
if (config) {
|
||||
setName(config.name);
|
||||
@@ -121,6 +150,11 @@ export function SettingsModal({
|
||||
setMaxRadioContacts(String(appSettings.max_radio_contacts));
|
||||
setAutoDecryptOnAdvert(appSettings.auto_decrypt_dm_on_advert);
|
||||
setAdvertInterval(String(appSettings.advert_interval));
|
||||
setBotEnabled(appSettings.bot_enabled);
|
||||
// Only overwrite bot code if user has saved custom code
|
||||
if (appSettings.bot_code) {
|
||||
setBotCode(appSettings.bot_code);
|
||||
}
|
||||
}
|
||||
}, [appSettings]);
|
||||
|
||||
@@ -354,9 +388,25 @@ export function SettingsModal({
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveBotSettings = async () => {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
|
||||
try {
|
||||
await onSaveAppSettings({ bot_enabled: botEnabled, bot_code: botCode });
|
||||
toast.success('Bot settings saved');
|
||||
} catch (err) {
|
||||
console.error('Failed to save bot settings:', err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to save');
|
||||
toast.error('Failed to save settings');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && onClose()}>
|
||||
<DialogContent className="sm:max-w-[500px] max-h-[90vh] overflow-y-auto">
|
||||
<DialogContent className="sm:max-w-[50vw] sm:min-w-[500px] max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Radio & Settings</DialogTitle>
|
||||
<DialogDescription className="sr-only">
|
||||
@@ -366,6 +416,7 @@ export function SettingsModal({
|
||||
{activeTab === 'serial' && 'View serial port connection and configure contact sync'}
|
||||
{activeTab === 'database' && 'View database statistics and clean up old packets'}
|
||||
{activeTab === 'advertise' && 'Send a flood advertisement to announce your presence'}
|
||||
{activeTab === 'bot' && 'Configure automatic message bot with Python code'}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -377,12 +428,13 @@ export function SettingsModal({
|
||||
onValueChange={(v) => setActiveTab(v as SettingsTab)}
|
||||
className="w-full"
|
||||
>
|
||||
<TabsList className="grid w-full grid-cols-5">
|
||||
<TabsList className="grid w-full grid-cols-6">
|
||||
<TabsTrigger value="radio">Radio</TabsTrigger>
|
||||
<TabsTrigger value="identity">Identity</TabsTrigger>
|
||||
<TabsTrigger value="serial">Serial</TabsTrigger>
|
||||
<TabsTrigger value="database">Database</TabsTrigger>
|
||||
<TabsTrigger value="advertise">Advertise</TabsTrigger>
|
||||
<TabsTrigger value="bot">Bot</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* Radio Config Tab */}
|
||||
@@ -747,6 +799,87 @@ export function SettingsModal({
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{/* Bot Tab */}
|
||||
<TabsContent value="bot" className="space-y-4 mt-4">
|
||||
<div className="p-3 bg-yellow-500/10 border border-yellow-500/30 rounded-md">
|
||||
<p className="text-sm text-yellow-500">
|
||||
<strong>Security Warning:</strong> This feature executes arbitrary Python code on
|
||||
the server. Only enable if you understand the security implications.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<label className="flex items-center gap-3 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={botEnabled}
|
||||
onChange={(e) => setBotEnabled(e.target.checked)}
|
||||
className="w-4 h-4 rounded border-input accent-primary"
|
||||
/>
|
||||
<span className="text-sm font-medium">Enable Message Bot</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="bot-code">Bot Code (Python)</Label>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setBotCode(DEFAULT_BOT_CODE)}
|
||||
disabled={!botEnabled}
|
||||
>
|
||||
Reset to Example
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Define a <code className="bg-muted px-1 rounded">bot()</code> function that
|
||||
receives message data and optionally returns a reply string.
|
||||
</p>
|
||||
<div
|
||||
className={`rounded-md border border-input bg-[#2d2d2d] overflow-auto h-64 ${!botEnabled ? 'opacity-50 pointer-events-none' : ''}`}
|
||||
>
|
||||
<Editor
|
||||
value={botCode}
|
||||
onValueChange={setBotCode}
|
||||
highlight={(code) => highlight(code, languages.python, 'python')}
|
||||
padding={12}
|
||||
style={{
|
||||
fontFamily:
|
||||
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
|
||||
fontSize: 13,
|
||||
minHeight: '100%',
|
||||
}}
|
||||
textareaId="bot-code"
|
||||
disabled={!botEnabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-muted-foreground space-y-1">
|
||||
<p>
|
||||
<strong>Available:</strong> Standard Python libraries and any modules installed in
|
||||
the server environment.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Limits:</strong> 10 second timeout, max 3 concurrent executions.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Note:</strong> Bot only responds to incoming messages, not your own. For
|
||||
channel messages, <code>sender_key</code> is <code>None</code>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{error && <div className="text-sm text-destructive">{error}</div>}
|
||||
|
||||
<Button onClick={handleSaveBotSettings} disabled={loading} className="w-full">
|
||||
{loading ? 'Saving...' : 'Save Bot Settings'}
|
||||
</Button>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
)}
|
||||
</DialogContent>
|
||||
|
||||
@@ -134,6 +134,8 @@ export interface AppSettings {
|
||||
last_message_times: Record<string, number>;
|
||||
preferences_migrated: boolean;
|
||||
advert_interval: number;
|
||||
bot_enabled: boolean;
|
||||
bot_code: string;
|
||||
}
|
||||
|
||||
export interface AppSettingsUpdate {
|
||||
@@ -141,6 +143,8 @@ export interface AppSettingsUpdate {
|
||||
auto_decrypt_dm_on_advert?: boolean;
|
||||
sidebar_sort_order?: 'recent' | 'alpha';
|
||||
advert_interval?: number;
|
||||
bot_enabled?: boolean;
|
||||
bot_code?: string;
|
||||
}
|
||||
|
||||
export interface MigratePreferencesRequest {
|
||||
|
||||
Reference in New Issue
Block a user