@pheem49/mint 1.8.2 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +55 -3
- package/Cargo.toml +2 -1
- package/Release_Note.md +297 -107
- package/bin/mint +0 -0
- package/crates/mint-cli/Cargo.toml +1 -0
- package/crates/mint-cli/src/actions.rs +233 -0
- package/crates/mint-cli/src/agent.rs +382 -127
- package/crates/mint-cli/src/gmail.rs +22 -2
- package/crates/mint-cli/src/image.rs +8 -8
- package/crates/mint-cli/src/interactive.rs +2888 -0
- package/crates/mint-cli/src/main.rs +391 -2506
- package/crates/mint-cli/src/markdown.rs +294 -0
- package/crates/mint-cli/src/onboard.rs +152 -109
- package/crates/mint-cli/src/setup.rs +143 -143
- package/crates/mint-cli/src/updater.rs +48 -6
- package/crates/mint-core/Cargo.toml +1 -0
- package/crates/mint-core/src/agent_loop.rs +1 -0
- package/crates/mint-core/src/api_server.rs +431 -65
- package/crates/mint-core/src/browser.rs +630 -102
- package/crates/mint-core/src/channels.rs +105 -6
- package/crates/mint-core/src/chat.rs +174 -11
- package/crates/mint-core/src/code_tools.rs +47 -31
- package/crates/mint-core/src/config.rs +302 -17
- package/crates/mint-core/src/image_gen.rs +299 -38
- package/crates/mint-core/src/lib.rs +10 -7
- package/crates/mint-core/src/mcp.rs +119 -6
- package/crates/mint-core/src/memory.rs +6 -1
- package/crates/mint-core/src/orchestration.rs +237 -96
- package/crates/mint-core/src/pictures.rs +67 -5
- package/crates/mint-core/src/semantic.rs +1 -1
- package/crates/mint-core/src/shell.rs +1 -1
- package/crates/mint-core/src/skills.rs +128 -25
- package/crates/mint-core/src/video_gen.rs +235 -0
- package/package.json +8 -2
- package/shared-modules/linux-audio/fluidsynth2-static.json +9 -3
- package/shared-modules/linux-audio/fluidsynth2.json +8 -2
- package/shared-modules/linux-audio/lilv.json +26 -19
- package/shared-modules/linux-audio/lv2.json +0 -1
- package/shared-modules/luajit/luajit.json +2 -2
- package/skills-lock.json +41 -0
- package/src/renderer/index-web.html +1 -1
- package/src/renderer/index.html +1 -1
- package/src/renderer/shared/components/AgentActivityTable.tsx +25 -6
- package/src/renderer/shared/constants/slashCommands.ts +27 -0
- package/src/renderer/shared/platform.ts +3 -0
- package/src/renderer/shared/types.ts +4 -0
- package/src/renderer/shared/utils/approval.tsx +86 -7
- package/src/renderer/shared/utils/fileIcons.ts +135 -0
- package/src/renderer/shared/utils/markdown.tsx +217 -88
- package/src/renderer/shared/utils/slashCommandProcessor.ts +239 -0
- package/src/renderer/src/components/ChatPanel.tsx +486 -69
- package/src/renderer/src/components/DashboardSidebar.tsx +11 -2
- package/src/renderer/src/components/ImageStudioPanel.tsx +87 -4
- package/src/renderer/src/components/MintDashboard.tsx +195 -7
- package/src/renderer/src/components/PicturesLibrary.tsx +156 -15
- package/src/renderer/src/components/Settings/AgentsTab.tsx +21 -2
- package/src/renderer/src/components/Settings/AutomationTab.tsx +316 -18
- package/src/renderer/src/components/Settings/GeneralTab.tsx +795 -413
- package/src/renderer/src/components/Settings/PluginsTab.tsx +304 -70
- package/src/renderer/src/components/Settings/ThemeTab.tsx +6 -1
- package/src/renderer/src/components/SettingsWindow.tsx +106 -3
- package/src/renderer/src/components/VeoStudioPanel.tsx +549 -0
- package/src/renderer/src/components/WorkspacePanel.tsx +7 -125
- package/src/renderer/src/css/image-studio.css +106 -0
- package/src/renderer/src/css/settings.css +158 -0
- package/src/renderer/src/css/styles.css +368 -1
- package/src/renderer/src/css/veo-studio.css +626 -0
- package/src/renderer/src/index.css +1 -0
- package/src/renderer/src/tauri.ts +144 -17
- package/src/renderer/src-web/components/ChatPanel.tsx +385 -93
- package/src/renderer/src-web/components/DashboardSidebar.tsx +16 -1
- package/src/renderer/src-web/components/ImageStudioPanel.tsx +87 -4
- package/src/renderer/src-web/components/MintDashboard.tsx +156 -5
- package/src/renderer/src-web/components/PicturesLibrary.tsx +155 -15
- package/src/renderer/src-web/components/Settings/AgentsTab.tsx +21 -2
- package/src/renderer/src-web/components/Settings/AutomationTab.tsx +316 -18
- package/src/renderer/src-web/components/Settings/GeneralTab.tsx +795 -411
- package/src/renderer/src-web/components/Settings/PluginsTab.tsx +304 -70
- package/src/renderer/src-web/components/Settings/ThemeTab.tsx +6 -1
- package/src/renderer/src-web/components/SettingsWindow.tsx +110 -3
- package/src/renderer/src-web/components/VeoStudioPanel.tsx +549 -0
- package/src/renderer/src-web/css/image-studio.css +106 -0
- package/src/renderer/src-web/css/settings.css +158 -0
- package/src/renderer/src-web/css/styles.css +369 -1
- package/src/renderer/src-web/css/veo-studio.css +650 -0
- package/src/renderer/src-web/index.css +1 -0
- package/src/renderer/src-web/tauri.ts +135 -16
- package/src-tauri/src/desktop.rs +18 -16
- package/src-tauri/src/discord_rpc.rs +1 -1
- package/src-tauri/src/lib.rs +155 -25
- package/src-tauri/src/webhooks.rs +45 -31
- package/src-tauri/src/workflows.rs +1 -0
- package/src-tauri/tauri.conf.json +1 -1
- package/temp_gridgeist/AGENTS.md +8 -0
- package/temp_gridgeist/CHANGELOG.md +130 -0
- package/temp_gridgeist/LICENSE +21 -0
- package/temp_gridgeist/README.md +323 -0
- package/temp_gridgeist/README.th.md +325 -0
- package/temp_gridgeist/skills/gridgeist/SKILL.md +88 -0
- package/temp_gridgeist/skills/gridgeist/agents/openai.yaml +9 -0
- package/temp_gridgeist/skills/gridgeist/assets/gridgeist-small.svg +10 -0
- package/temp_gridgeist/skills/gridgeist/assets/gridgeist.png +0 -0
- package/temp_gridgeist/skills/gridgeist/assets/gridgeist.svg +18 -0
- package/temp_gridgeist/skills/gridgeist/references/design-language.md +88 -0
- package/temp_gridgeist/skills/gridgeist/references/review-checklist.md +128 -0
- package/temp_gridgeist/skills/gridgeist/references/system-contract.md +109 -0
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
import { useEffect, useMemo, useState } from 'react'
|
|
2
|
-
import { type PictureEntry, convertFileSrc } from '../tauri'
|
|
2
|
+
import { type PictureEntry, convertFileSrc, isTauriRuntime, getLocalApiBase } from '../tauri'
|
|
3
3
|
import type { DashboardView } from './DashboardSidebar'
|
|
4
4
|
|
|
5
|
+
const getPictureSrc = (picture: PictureEntry, useThumbnail = false) => {
|
|
6
|
+
if (isTauriRuntime()) {
|
|
7
|
+
if (useThumbnail && (picture.thumbnailPath || picture.thumbnailUrl)) {
|
|
8
|
+
return convertFileSrc(picture.thumbnailPath || picture.thumbnailUrl || '');
|
|
9
|
+
}
|
|
10
|
+
return convertFileSrc(picture.path || picture.url || '');
|
|
11
|
+
} else {
|
|
12
|
+
const apiBase = getLocalApiBase();
|
|
13
|
+
if (useThumbnail && (picture.thumbnailPath || picture.thumbnailUrl)) {
|
|
14
|
+
return `${apiBase}/thumbnails/${picture.id}.thumb.png`;
|
|
15
|
+
}
|
|
16
|
+
return `${apiBase}/pictures/${encodeURIComponent(picture.filename)}`;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
5
20
|
interface PicturesLibraryProps {
|
|
6
21
|
view: DashboardView
|
|
7
22
|
pictures: PictureEntry[]
|
|
@@ -10,15 +25,39 @@ interface PicturesLibraryProps {
|
|
|
10
25
|
}
|
|
11
26
|
|
|
12
27
|
export default function PicturesLibrary({ view, pictures, onSetView, onRefreshPictures }: PicturesLibraryProps) {
|
|
28
|
+
const [filterType, setFilterType] = useState<'photo' | 'video'>('photo')
|
|
13
29
|
const [visibleCount, setVisibleCount] = useState(24)
|
|
30
|
+
const [failedImages, setFailedImages] = useState<Set<string>>(new Set())
|
|
31
|
+
|
|
32
|
+
const filteredPictures = useMemo(() => {
|
|
33
|
+
return pictures.filter((picture) => {
|
|
34
|
+
const pathLower = (picture.path || '').toLowerCase()
|
|
35
|
+
const urlLower = (picture.url || '').toLowerCase()
|
|
36
|
+
const mimeLower = (picture.mimeType || '').toLowerCase()
|
|
37
|
+
|
|
38
|
+
const isVideo =
|
|
39
|
+
mimeLower.startsWith('video/') ||
|
|
40
|
+
pathLower.endsWith('.mp4') ||
|
|
41
|
+
pathLower.endsWith('.webm') ||
|
|
42
|
+
pathLower.endsWith('.mov') ||
|
|
43
|
+
pathLower.endsWith('.avi') ||
|
|
44
|
+
pathLower.endsWith('.mkv') ||
|
|
45
|
+
urlLower.endsWith('.mp4') ||
|
|
46
|
+
urlLower.endsWith('.webm') ||
|
|
47
|
+
urlLower.endsWith('.mov')
|
|
48
|
+
|
|
49
|
+
return filterType === 'video' ? isVideo : !isVideo
|
|
50
|
+
})
|
|
51
|
+
}, [pictures, filterType])
|
|
52
|
+
|
|
14
53
|
const visiblePictures = useMemo(
|
|
15
|
-
() =>
|
|
16
|
-
[
|
|
54
|
+
() => filteredPictures.slice(0, visibleCount),
|
|
55
|
+
[filteredPictures, visibleCount],
|
|
17
56
|
)
|
|
18
57
|
|
|
19
58
|
useEffect(() => {
|
|
20
59
|
setVisibleCount(24)
|
|
21
|
-
}, [view, pictures])
|
|
60
|
+
}, [view, pictures, filterType])
|
|
22
61
|
|
|
23
62
|
return (
|
|
24
63
|
<section className={`pictures-library ${view === 'pictures' ? 'is-visible' : ''}`} aria-hidden={view !== 'pictures'}>
|
|
@@ -31,34 +70,135 @@ export default function PicturesLibrary({ view, pictures, onSetView, onRefreshPi
|
|
|
31
70
|
</button>
|
|
32
71
|
</div>
|
|
33
72
|
</header>
|
|
34
|
-
|
|
73
|
+
|
|
74
|
+
{/* Tabs Filter */}
|
|
75
|
+
<div className="pictures-type-filter">
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
className={`filter-tab-btn ${filterType === 'photo' ? 'active' : ''}`}
|
|
79
|
+
onClick={() => setFilterType('photo')}
|
|
80
|
+
>
|
|
81
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
82
|
+
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
|
83
|
+
<circle cx="8.5" cy="8.5" r="1.5"></circle>
|
|
84
|
+
<polyline points="21 15 16 10 5 21"></polyline>
|
|
85
|
+
</svg>
|
|
86
|
+
Photos
|
|
87
|
+
</button>
|
|
88
|
+
<button
|
|
89
|
+
type="button"
|
|
90
|
+
className={`filter-tab-btn ${filterType === 'video' ? 'active' : ''}`}
|
|
91
|
+
onClick={() => setFilterType('video')}
|
|
92
|
+
>
|
|
93
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
94
|
+
<polygon points="23 7 16 12 23 17 23 7"></polygon>
|
|
95
|
+
<rect x="1" y="5" width="15" height="14" rx="2" ry="2"></rect>
|
|
96
|
+
</svg>
|
|
97
|
+
Videos
|
|
98
|
+
</button>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
{filteredPictures.length === 0 ? (
|
|
35
102
|
<div className="pictures-empty">
|
|
36
103
|
<div className="pictures-empty-icon" style={{ display: 'flex', justifyContent: 'center', marginBottom: '12px', opacity: 0.3 }}>
|
|
37
104
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
38
105
|
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline>
|
|
39
106
|
</svg>
|
|
40
107
|
</div>
|
|
41
|
-
<p>No saved pictures yet</p>
|
|
42
|
-
<span>
|
|
108
|
+
<p>No saved {filterType === 'video' ? 'videos' : 'pictures'} yet</p>
|
|
109
|
+
<span>Items appear here after a message with an attachment is sent successfully.</span>
|
|
43
110
|
</div>
|
|
44
111
|
) : (
|
|
45
112
|
<>
|
|
46
113
|
<div className="pictures-grid">
|
|
47
|
-
{visiblePictures.map((picture, index) =>
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
114
|
+
{visiblePictures.map((picture, index) => {
|
|
115
|
+
const isVideo = filterType === 'video'
|
|
116
|
+
return (
|
|
117
|
+
<article className="picture-card" key={picture.id}>
|
|
118
|
+
{isVideo ? (
|
|
119
|
+
<div style={{ position: 'relative', width: '100%', aspectRatio: '4 / 3', display: 'flex', alignItems: 'center', justifyContent: 'center', backgroundColor: '#1a1a1a' }}>
|
|
120
|
+
{failedImages.has(picture.id) ? (
|
|
121
|
+
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '8px', color: '#666' }}>
|
|
122
|
+
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ opacity: 0.5 }}>
|
|
123
|
+
<path d="M23 7l-7 5 7 5V7z"></path>
|
|
124
|
+
<rect x="1" y="5" width="15" height="14" rx="2" ry="2"></rect>
|
|
125
|
+
</svg>
|
|
126
|
+
<span style={{ fontSize: '11px', fontWeight: 500, letterSpacing: '0.05em', textTransform: 'uppercase', opacity: 0.8 }}>Video</span>
|
|
127
|
+
</div>
|
|
128
|
+
) : (
|
|
129
|
+
<>
|
|
130
|
+
{picture.thumbnailPath || picture.thumbnailUrl ? (
|
|
131
|
+
<img
|
|
132
|
+
src={getPictureSrc(picture, true)}
|
|
133
|
+
alt={picture.message || picture.filename}
|
|
134
|
+
loading={index < 6 ? 'eager' : 'lazy'}
|
|
135
|
+
decoding="async"
|
|
136
|
+
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
|
137
|
+
onError={() => setFailedImages((prev) => new Set([...prev, picture.id]))}
|
|
138
|
+
/>
|
|
139
|
+
) : (
|
|
140
|
+
<video
|
|
141
|
+
src={getPictureSrc(picture, false)}
|
|
142
|
+
preload="metadata"
|
|
143
|
+
style={{ width: '100%', height: '100%', objectFit: 'cover', pointerEvents: 'none' }}
|
|
144
|
+
onError={() => setFailedImages((prev) => new Set([...prev, picture.id]))}
|
|
145
|
+
/>
|
|
146
|
+
)}
|
|
147
|
+
<div style={{
|
|
148
|
+
position: 'absolute',
|
|
149
|
+
top: '8px',
|
|
150
|
+
left: '8px',
|
|
151
|
+
display: 'flex',
|
|
152
|
+
alignItems: 'center',
|
|
153
|
+
gap: '4px',
|
|
154
|
+
backgroundColor: 'rgba(0, 0, 0, 0.75)',
|
|
155
|
+
color: '#fff',
|
|
156
|
+
padding: '4px 8px',
|
|
157
|
+
borderRadius: '4px',
|
|
158
|
+
fontSize: '11px',
|
|
159
|
+
fontWeight: 600,
|
|
160
|
+
pointerEvents: 'none',
|
|
161
|
+
backdropFilter: 'blur(4px)',
|
|
162
|
+
border: '1px solid rgba(255, 255, 255, 0.1)'
|
|
163
|
+
}}>
|
|
164
|
+
<span>📹 Video</span>
|
|
165
|
+
</div>
|
|
166
|
+
</>
|
|
167
|
+
)}
|
|
168
|
+
</div>
|
|
169
|
+
) : (
|
|
170
|
+
<div style={{ position: 'relative', width: '100%', aspectRatio: '4 / 3', display: 'flex', alignItems: 'center', justifyContent: 'center', backgroundColor: '#1a1a1a' }}>
|
|
171
|
+
{failedImages.has(picture.id) ? (
|
|
172
|
+
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ opacity: 0.4 }}>
|
|
173
|
+
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
|
174
|
+
<circle cx="8.5" cy="8.5" r="1.5"></circle>
|
|
175
|
+
<polyline points="21 15 16 10 5 21"></polyline>
|
|
176
|
+
</svg>
|
|
177
|
+
) : (
|
|
178
|
+
<img
|
|
179
|
+
src={getPictureSrc(picture, true)}
|
|
180
|
+
alt={picture.message || picture.filename}
|
|
181
|
+
loading={index < 6 ? 'eager' : 'lazy'}
|
|
182
|
+
decoding="async"
|
|
183
|
+
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
|
184
|
+
onError={() => setFailedImages((prev) => new Set([...prev, picture.id]))}
|
|
185
|
+
/>
|
|
186
|
+
)}
|
|
187
|
+
</div>
|
|
188
|
+
)}
|
|
189
|
+
<div className="picture-card-meta"><span>{picture.message || picture.filename}</span></div>
|
|
190
|
+
</article>
|
|
191
|
+
)
|
|
192
|
+
})}
|
|
53
193
|
</div>
|
|
54
|
-
{visibleCount <
|
|
194
|
+
{visibleCount < filteredPictures.length && (
|
|
55
195
|
<div className="pictures-load-more-container">
|
|
56
196
|
<button
|
|
57
197
|
type="button"
|
|
58
198
|
className="pictures-load-more-btn"
|
|
59
199
|
onClick={() => setVisibleCount((prev) => prev + 24)}
|
|
60
200
|
>
|
|
61
|
-
Load More ({
|
|
201
|
+
Load More ({filteredPictures.length - visibleCount} remaining)
|
|
62
202
|
</button>
|
|
63
203
|
</div>
|
|
64
204
|
)}
|
|
@@ -7,7 +7,8 @@ import {
|
|
|
7
7
|
DEEPSEEK_MODELS,
|
|
8
8
|
ANTHROPIC_MODELS,
|
|
9
9
|
HF_MODELS,
|
|
10
|
-
LOCAL_MODELS
|
|
10
|
+
LOCAL_MODELS,
|
|
11
|
+
CustomProviderConfig,
|
|
11
12
|
} from '../SettingsWindow'
|
|
12
13
|
|
|
13
14
|
interface Agent {
|
|
@@ -21,7 +22,7 @@ interface Agent {
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
interface AgentsTabProps {
|
|
24
|
-
config: typeof DEFAULT_CONFIG & { agents?: Agent[] }
|
|
25
|
+
config: typeof DEFAULT_CONFIG & { agents?: Agent[], customProviders?: CustomProviderConfig[] }
|
|
25
26
|
updateField: (field: any, value: any) => void
|
|
26
27
|
dynamicOllamaModels?: string[]
|
|
27
28
|
}
|
|
@@ -47,6 +48,11 @@ export default function AgentsTab({ config, updateField, dynamicOllamaModels = [
|
|
|
47
48
|
else if (newProvider === 'huggingface') defaultModel = config.hfModel || HF_MODELS[0]
|
|
48
49
|
else if (newProvider === 'local_openai') defaultModel = config.localModelName || LOCAL_MODELS[0]
|
|
49
50
|
else if (newProvider === 'ollama') defaultModel = config.ollamaModel || dynamicOllamaModels[0] || 'llama3:latest'
|
|
51
|
+
else if (newProvider.startsWith('custom:')) {
|
|
52
|
+
const cpId = newProvider.replace(/^custom:/, '')
|
|
53
|
+
const cp = (config.customProviders ?? []).find(p => p.id === cpId)
|
|
54
|
+
defaultModel = cp?.models[0]?.modelId ?? ''
|
|
55
|
+
}
|
|
50
56
|
setModel(defaultModel)
|
|
51
57
|
}
|
|
52
58
|
|
|
@@ -141,6 +147,14 @@ export default function AgentsTab({ config, updateField, dynamicOllamaModels = [
|
|
|
141
147
|
{ value: 'ollama', label: 'Ollama (Local)', hasKey: true },
|
|
142
148
|
{ value: 'huggingface', label: 'Hugging Face', hasKey: !!config.hfApiKey },
|
|
143
149
|
{ value: 'local_openai', label: 'Local (LM Studio / OpenAI Compatible)', hasKey: true },
|
|
150
|
+
// Custom providers — available whenever they have a base URL configured
|
|
151
|
+
...(config.customProviders ?? [])
|
|
152
|
+
.filter(cp => cp.baseUrl.trim().length > 0)
|
|
153
|
+
.map(cp => ({
|
|
154
|
+
value: `custom:${cp.id}`,
|
|
155
|
+
label: `${cp.displayName || cp.id} (Custom)`,
|
|
156
|
+
hasKey: true,
|
|
157
|
+
})),
|
|
144
158
|
].filter(p => p.hasKey);
|
|
145
159
|
|
|
146
160
|
// Find out what models list corresponds to the selected provider
|
|
@@ -170,6 +184,11 @@ export default function AgentsTab({ config, updateField, dynamicOllamaModels = [
|
|
|
170
184
|
} else if (provider === 'ollama') {
|
|
171
185
|
modelList = dynamicOllamaModels
|
|
172
186
|
defaultGeneralModel = config.ollamaModel
|
|
187
|
+
} else if (provider.startsWith('custom:')) {
|
|
188
|
+
const cpId = provider.replace(/^custom:/, '')
|
|
189
|
+
const cp = (config.customProviders ?? []).find(p => p.id === cpId)
|
|
190
|
+
modelList = cp?.models.map(m => m.modelId) ?? []
|
|
191
|
+
defaultGeneralModel = modelList[0] ?? ''
|
|
173
192
|
}
|
|
174
193
|
|
|
175
194
|
// Sort modelList so defaultGeneralModel is first (at the top)
|
|
@@ -119,25 +119,323 @@ export default function AutomationTab({
|
|
|
119
119
|
<p className="section-kicker">Messaging</p>
|
|
120
120
|
<h2 className="section-title">Native Channel Bridges</h2>
|
|
121
121
|
</div>
|
|
122
|
-
<p className="section-description">
|
|
122
|
+
<p className="section-description">
|
|
123
|
+
Configure messaging credentials and webhooks used by Rust channel workers to enable remote control.
|
|
124
|
+
</p>
|
|
123
125
|
</div>
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
<
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
126
|
+
|
|
127
|
+
{/* Global Instant Ack Notification */}
|
|
128
|
+
<div className={`bridge-card ${config.enableBridgeAckNotification !== false ? 'active' : ''}`} style={{ marginBottom: '16px' }}>
|
|
129
|
+
<div className="bridge-card-header">
|
|
130
|
+
<div className="bridge-info">
|
|
131
|
+
<div className="bridge-icon-badge" style={{ background: 'linear-gradient(135deg, #10b981 0%, #059669 100%)' }}>
|
|
132
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
133
|
+
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon>
|
|
134
|
+
</svg>
|
|
135
|
+
</div>
|
|
136
|
+
<div className="bridge-title-group">
|
|
137
|
+
<span className="bridge-title">
|
|
138
|
+
Instant Remote Acknowledgement
|
|
139
|
+
<span className={`bridge-status-pill ${config.enableBridgeAckNotification !== false ? 'active' : 'inactive'}`}>
|
|
140
|
+
{config.enableBridgeAckNotification !== false ? 'Enabled' : 'Disabled'}
|
|
141
|
+
</span>
|
|
142
|
+
</span>
|
|
143
|
+
<span className="bridge-subtitle">Send instant feedback/typing status when remote commands arrive</span>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
<label className="settings-toggle-switch">
|
|
147
|
+
<input
|
|
148
|
+
type="checkbox"
|
|
149
|
+
checked={config.enableBridgeAckNotification !== false}
|
|
150
|
+
onChange={(e) => updateField('enableBridgeAckNotification', e.target.checked)}
|
|
151
|
+
/>
|
|
152
|
+
<span className="settings-toggle-slider"></span>
|
|
153
|
+
</label>
|
|
154
|
+
</div>
|
|
155
|
+
{config.enableBridgeAckNotification !== false && (
|
|
156
|
+
<div className="bridge-inputs">
|
|
157
|
+
<input
|
|
158
|
+
type="text"
|
|
159
|
+
className="bridge-input"
|
|
160
|
+
placeholder="Instant Ack Message (default: [Mint Agent] Remote command received, processing...)"
|
|
161
|
+
value={config.bridgeAckMessage || ''}
|
|
162
|
+
onChange={(e) => updateField('bridgeAckMessage', e.target.value)}
|
|
163
|
+
/>
|
|
164
|
+
</div>
|
|
165
|
+
)}
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<div className="bridge-cards-container">
|
|
169
|
+
{/* Telegram Bot Card */}
|
|
170
|
+
<div className={`bridge-card ${config.enableTelegramBridge ? 'active' : ''}`}>
|
|
171
|
+
<div className="bridge-card-header">
|
|
172
|
+
<div className="bridge-info">
|
|
173
|
+
<div className="bridge-icon-badge bridge-icon-telegram">
|
|
174
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
|
175
|
+
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4.64 6.8c-.15 1.58-.8 5.42-1.13 7.19-.14.75-.42 1-.68 1.03-.58.05-1.02-.38-1.58-.75-.88-.58-1.38-.94-2.23-1.5-.99-.65-.35-1.01.22-1.59.15-.15 2.71-2.48 2.76-2.69.01-.03.01-.14-.07-.2-.08-.06-.19-.04-.27-.02-.12.02-1.96 1.25-5.54 3.69-.52.36-1 .54-1.42.53-.47-.01-1.37-.26-2.03-.48-.82-.27-1.47-.42-1.42-.88.03-.25.38-.51 1.07-.78 4.18-1.82 6.97-3.02 8.37-3.61 3.98-1.66 4.81-1.95 5.35-1.96.12 0 .38.03.55.17.14.12.18.28.2.4.02.11.02.26.01.39z"/>
|
|
176
|
+
</svg>
|
|
177
|
+
</div>
|
|
178
|
+
<div className="bridge-title-group">
|
|
179
|
+
<span className="bridge-title">
|
|
180
|
+
Telegram Bot API
|
|
181
|
+
<span className={`bridge-status-pill ${config.enableTelegramBridge ? 'active' : 'inactive'}`}>
|
|
182
|
+
{config.enableTelegramBridge ? 'Active' : 'Disabled'}
|
|
183
|
+
</span>
|
|
184
|
+
</span>
|
|
185
|
+
<span className="bridge-subtitle">Long Polling Engine (getUpdates)</span>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
<label className="settings-toggle-switch">
|
|
189
|
+
<input
|
|
190
|
+
type="checkbox"
|
|
191
|
+
checked={config.enableTelegramBridge}
|
|
192
|
+
onChange={(e) => updateField('enableTelegramBridge', e.target.checked)}
|
|
193
|
+
/>
|
|
194
|
+
<span className="settings-toggle-slider"></span>
|
|
195
|
+
</label>
|
|
196
|
+
</div>
|
|
197
|
+
<div className="bridge-inputs">
|
|
198
|
+
<input
|
|
199
|
+
type="password"
|
|
200
|
+
className="bridge-input"
|
|
201
|
+
placeholder="Telegram Bot Token (from @BotFather)"
|
|
202
|
+
value={config.telegramBotToken}
|
|
203
|
+
onChange={(e) => updateField('telegramBotToken', e.target.value)}
|
|
204
|
+
/>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
{/* Discord Gateway Card */}
|
|
209
|
+
<div className={`bridge-card ${config.enableDiscordBridge ? 'active' : ''}`}>
|
|
210
|
+
<div className="bridge-card-header">
|
|
211
|
+
<div className="bridge-info">
|
|
212
|
+
<div className="bridge-icon-badge bridge-icon-discord">
|
|
213
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
|
214
|
+
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.061 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.893.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.028zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/>
|
|
215
|
+
</svg>
|
|
216
|
+
</div>
|
|
217
|
+
<div className="bridge-title-group">
|
|
218
|
+
<span className="bridge-title">
|
|
219
|
+
Discord Gateway
|
|
220
|
+
<span className={`bridge-status-pill ${config.enableDiscordBridge ? 'active' : 'inactive'}`}>
|
|
221
|
+
{config.enableDiscordBridge ? 'Active' : 'Disabled'}
|
|
222
|
+
</span>
|
|
223
|
+
</span>
|
|
224
|
+
<span className="bridge-subtitle">WebSocket Gateway (wss://gateway.discord.gg)</span>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
<label className="settings-toggle-switch">
|
|
228
|
+
<input
|
|
229
|
+
type="checkbox"
|
|
230
|
+
checked={config.enableDiscordBridge}
|
|
231
|
+
onChange={(e) => updateField('enableDiscordBridge', e.target.checked)}
|
|
232
|
+
/>
|
|
233
|
+
<span className="settings-toggle-slider"></span>
|
|
234
|
+
</label>
|
|
235
|
+
</div>
|
|
236
|
+
<div className="bridge-inputs">
|
|
237
|
+
<input
|
|
238
|
+
type="password"
|
|
239
|
+
className="bridge-input"
|
|
240
|
+
placeholder="Discord Bot Token"
|
|
241
|
+
value={config.discordBotToken}
|
|
242
|
+
onChange={(e) => updateField('discordBotToken', e.target.value)}
|
|
243
|
+
/>
|
|
244
|
+
<input
|
|
245
|
+
type="text"
|
|
246
|
+
className="bridge-input"
|
|
247
|
+
placeholder="Discord Application ID (for Rich Presence)"
|
|
248
|
+
value={config.discordApplicationId}
|
|
249
|
+
onChange={(e) => updateField('discordApplicationId', e.target.value)}
|
|
250
|
+
/>
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
|
|
254
|
+
{/* Slack Socket Mode Card */}
|
|
255
|
+
<div className={`bridge-card ${config.enableSlackBridge ? 'active' : ''}`}>
|
|
256
|
+
<div className="bridge-card-header">
|
|
257
|
+
<div className="bridge-info">
|
|
258
|
+
<div className="bridge-icon-badge bridge-icon-slack">
|
|
259
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
|
260
|
+
<path d="M6 15a2.5 2.5 0 0 1-2.5-2.5A2.5 2.5 0 0 1 6 10h2.5v2.5A2.5 2.5 0 0 1 6 15zm0-8.5A2.5 2.5 0 0 1 3.5 4 2.5 2.5 0 0 1 6 1.5 2.5 2.5 0 0 1 8.5 4v2.5H6zm8.5 0A2.5 2.5 0 0 1 12 4a2.5 2.5 0 0 1 2.5-2.5A2.5 2.5 0 0 1 17 4v2.5h-2.5zm0 8.5a2.5 2.5 0 0 1 2.5 2.5 2.5 2.5 0 0 1-2.5 2.5A2.5 2.5 0 0 1 12 17.5V15h2.5zm-8.5 2.5A2.5 2.5 0 0 1 6 20a2.5 2.5 0 0 1-2.5-2.5 2.5 2.5 0 0 1 2.5-2.5H8.5V17.5zm8.5-8.5A2.5 2.5 0 0 1 17 6.5 2.5 2.5 0 0 1 19.5 9 2.5 2.5 0 0 1 17 11.5H14.5V9zm-2.5-2.5a2.5 2.5 0 0 1-2.5-2.5A2.5 2.5 0 0 1 12 1.5a2.5 2.5 0 0 1 2.5 2.5V6.5H12z"/>
|
|
261
|
+
</svg>
|
|
262
|
+
</div>
|
|
263
|
+
<div className="bridge-title-group">
|
|
264
|
+
<span className="bridge-title">
|
|
265
|
+
Slack Socket Mode
|
|
266
|
+
<span className={`bridge-status-pill ${config.enableSlackBridge ? 'active' : 'inactive'}`}>
|
|
267
|
+
{config.enableSlackBridge ? 'Active' : 'Disabled'}
|
|
268
|
+
</span>
|
|
269
|
+
</span>
|
|
270
|
+
<span className="bridge-subtitle">WebSocket Socket Mode API</span>
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
<label className="settings-toggle-switch">
|
|
274
|
+
<input
|
|
275
|
+
type="checkbox"
|
|
276
|
+
checked={config.enableSlackBridge}
|
|
277
|
+
onChange={(e) => updateField('enableSlackBridge', e.target.checked)}
|
|
278
|
+
/>
|
|
279
|
+
<span className="settings-toggle-slider"></span>
|
|
280
|
+
</label>
|
|
281
|
+
</div>
|
|
282
|
+
<div className="bridge-inputs">
|
|
283
|
+
<input
|
|
284
|
+
type="password"
|
|
285
|
+
className="bridge-input"
|
|
286
|
+
placeholder="Slack Bot Token (xoxb-...)"
|
|
287
|
+
value={config.slackBotToken}
|
|
288
|
+
onChange={(e) => updateField('slackBotToken', e.target.value)}
|
|
289
|
+
/>
|
|
290
|
+
<input
|
|
291
|
+
type="password"
|
|
292
|
+
className="bridge-input"
|
|
293
|
+
placeholder="Slack App Token (xapp-...)"
|
|
294
|
+
value={config.slackAppToken}
|
|
295
|
+
onChange={(e) => updateField('slackAppToken', e.target.value)}
|
|
296
|
+
/>
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
|
|
300
|
+
{/* LINE Webhook Card */}
|
|
301
|
+
<div className={`bridge-card ${config.enableLineBridge ? 'active' : ''}`}>
|
|
302
|
+
<div className="bridge-card-header">
|
|
303
|
+
<div className="bridge-info">
|
|
304
|
+
<div className="bridge-icon-badge bridge-icon-line">
|
|
305
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
|
306
|
+
<path d="M19.365 9.863c.349 0 .63.285.63.631 0 .345-.281.63-.63.63h-2.286v1.442h2.286c.349 0 .63.285.63.63 0 .346-.281.63-.63.63h-2.917c-.349 0-.63-.284-.63-.63V8.581c0-.346.281-.63.63-.63h2.917c.349 0 .63.284.63.63 0 .346-.281.63-.63.63h-2.286V9.86h2.286zm-5.631 4.156c0 .248-.146.472-.371.57-.083.036-.172.054-.26.054-.154 0-.306-.056-.425-.165l-2.481-3.238v2.783c0 .346-.28.63-.63.63-.348 0-.63-.284-.63-.63V8.581c0-.248.146-.472.371-.57.225-.098.486-.051.664.12l2.48 3.237V8.581c0-.346.28-.63.63-.63.349 0 .63.284.63.63v5.438zm-6.843 0c0 .346-.28.63-.63.63-.349 0-.63-.284-.63-.63V8.581c0-.346.281-.63.63-.63.35 0 .63.284.63.63v5.438zm-2.434 0c0 .346-.28.63-.63.63h-2.417c-.349 0-.63-.284-.63-.63V8.581c0-.346.281-.63.63-.63.349 0 .63.284.63.63v4.808h1.787c.349 0 .63.285.63.63zM24 10.617C24 4.754 18.627 0 12 0S0 4.754 0 10.617c0 5.247 4.264 9.61 10.02 10.435.39.084.922.257 1.057.592.121.3.079.77.039 1.074l-.17.994c-.052.304-.247 1.191 1.044.649 1.292-.542 6.974-4.107 9.513-7.032C23.013 15.602 24 13.23 24 10.617z"/>
|
|
307
|
+
</svg>
|
|
308
|
+
</div>
|
|
309
|
+
<div className="bridge-title-group">
|
|
310
|
+
<span className="bridge-title">
|
|
311
|
+
LINE Messaging Webhook
|
|
312
|
+
<span className={`bridge-status-pill ${config.enableLineBridge ? 'active' : 'inactive'}`}>
|
|
313
|
+
{config.enableLineBridge ? 'Active' : 'Disabled'}
|
|
314
|
+
</span>
|
|
315
|
+
</span>
|
|
316
|
+
<span className="bridge-subtitle">HTTP Webhook Listener (Callback)</span>
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
<label className="settings-toggle-switch">
|
|
320
|
+
<input
|
|
321
|
+
type="checkbox"
|
|
322
|
+
checked={config.enableLineBridge}
|
|
323
|
+
onChange={(e) => updateField('enableLineBridge', e.target.checked)}
|
|
324
|
+
/>
|
|
325
|
+
<span className="settings-toggle-slider"></span>
|
|
326
|
+
</label>
|
|
327
|
+
</div>
|
|
328
|
+
<div className="bridge-inputs">
|
|
329
|
+
<input
|
|
330
|
+
type="password"
|
|
331
|
+
className="bridge-input"
|
|
332
|
+
placeholder="LINE Channel Access Token"
|
|
333
|
+
value={config.lineChannelAccessToken}
|
|
334
|
+
onChange={(e) => updateField('lineChannelAccessToken', e.target.value)}
|
|
335
|
+
/>
|
|
336
|
+
<input
|
|
337
|
+
type="password"
|
|
338
|
+
className="bridge-input"
|
|
339
|
+
placeholder="LINE Channel Secret"
|
|
340
|
+
value={config.lineChannelSecret}
|
|
341
|
+
onChange={(e) => updateField('lineChannelSecret', e.target.value)}
|
|
342
|
+
/>
|
|
343
|
+
<div className="bridge-input-row">
|
|
344
|
+
<input
|
|
345
|
+
type="text"
|
|
346
|
+
className="bridge-input"
|
|
347
|
+
placeholder="Webhook Host (default: 127.0.0.1)"
|
|
348
|
+
value={config.lineWebhookHost || ''}
|
|
349
|
+
onChange={(e) => updateField('lineWebhookHost', e.target.value)}
|
|
350
|
+
/>
|
|
351
|
+
<input
|
|
352
|
+
type="number"
|
|
353
|
+
className="bridge-input"
|
|
354
|
+
placeholder="Port"
|
|
355
|
+
value={config.lineWebhookPort || 3000}
|
|
356
|
+
onChange={(e) => updateField('lineWebhookPort', parseInt(e.target.value) || 3000)}
|
|
357
|
+
style={{ width: '120px' }}
|
|
358
|
+
/>
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
|
|
363
|
+
{/* WhatsApp Cloud API Card */}
|
|
364
|
+
<div className={`bridge-card ${config.enableWhatsappBridge ? 'active' : ''}`}>
|
|
365
|
+
<div className="bridge-card-header">
|
|
366
|
+
<div className="bridge-info">
|
|
367
|
+
<div className="bridge-icon-badge bridge-icon-whatsapp">
|
|
368
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
|
369
|
+
<path d="M.057 24l1.687-6.163c-1.041-1.804-1.588-3.849-1.587-5.946.003-6.556 5.338-11.891 11.893-11.891 3.181.001 6.167 1.24 8.413 3.488 2.245 2.248 3.481 5.236 3.48 8.414-.003 6.557-5.338 11.892-11.893 11.892-1.99-.001-3.951-.5-5.688-1.448l-6.305 1.654zm6.597-3.807c1.676.995 3.276 1.591 5.392 1.592 5.448 0 9.886-4.434 9.889-9.885.002-5.462-4.415-9.89-9.881-9.892-5.452 0-9.887 4.434-9.889 9.884-.001 2.225.651 3.891 1.746 5.634l-.999 3.648 3.742-.981zm11.387-5.464c-.301-.15-1.785-.881-2.062-.982-.276-.101-.477-.15-.677.15-.199.301-.776.982-.952 1.182-.175.201-.351.226-.652.076-.301-.15-1.27-.468-2.42-1.494-.897-.8-1.502-1.788-1.678-2.09-.175-.301-.019-.464.131-.613.136-.135.301-.351.451-.526.15-.176.2-.301.3-.502.1-.201.05-.376-.025-.526-.075-.15-.677-1.631-.928-2.235-.244-.587-.492-.507-.676-.517-.175-.008-.376-.01-.576-.01-.201 0-.526.075-.802.376-.276.301-1.053 1.029-1.053 2.509 0 1.48 1.078 2.909 1.229 3.11.15.201 2.122 3.241 5.141 4.544.718.31 1.279.495 1.716.634.721.229 1.377.197 1.896.12.578-.086 1.785-.729 2.036-1.431.25-.702.25-1.304.175-1.431-.075-.127-.276-.201-.577-.352z"/>
|
|
370
|
+
</svg>
|
|
371
|
+
</div>
|
|
372
|
+
<div className="bridge-title-group">
|
|
373
|
+
<span className="bridge-title">
|
|
374
|
+
WhatsApp Cloud API
|
|
375
|
+
<span className={`bridge-status-pill ${config.enableWhatsappBridge ? 'active' : 'inactive'}`}>
|
|
376
|
+
{config.enableWhatsappBridge ? 'Active' : 'Disabled'}
|
|
377
|
+
</span>
|
|
378
|
+
</span>
|
|
379
|
+
<span className="bridge-subtitle">Meta Cloud API Webhook Listener</span>
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
<label className="settings-toggle-switch">
|
|
383
|
+
<input
|
|
384
|
+
type="checkbox"
|
|
385
|
+
checked={config.enableWhatsappBridge}
|
|
386
|
+
onChange={(e) => updateField('enableWhatsappBridge', e.target.checked)}
|
|
387
|
+
/>
|
|
388
|
+
<span className="settings-toggle-slider"></span>
|
|
389
|
+
</label>
|
|
390
|
+
</div>
|
|
391
|
+
<div className="bridge-inputs">
|
|
392
|
+
<input
|
|
393
|
+
type="password"
|
|
394
|
+
className="bridge-input"
|
|
395
|
+
placeholder="WhatsApp Cloud Access Token"
|
|
396
|
+
value={config.whatsappCloudAccessToken}
|
|
397
|
+
onChange={(e) => updateField('whatsappCloudAccessToken', e.target.value)}
|
|
398
|
+
/>
|
|
399
|
+
<input
|
|
400
|
+
type="text"
|
|
401
|
+
className="bridge-input"
|
|
402
|
+
placeholder="WhatsApp Phone Number ID"
|
|
403
|
+
value={config.whatsappPhoneNumberId}
|
|
404
|
+
onChange={(e) => updateField('whatsappPhoneNumberId', e.target.value)}
|
|
405
|
+
/>
|
|
406
|
+
<input
|
|
407
|
+
type="password"
|
|
408
|
+
className="bridge-input"
|
|
409
|
+
placeholder="WhatsApp Verify Token"
|
|
410
|
+
value={config.whatsappVerifyToken}
|
|
411
|
+
onChange={(e) => updateField('whatsappVerifyToken', e.target.value)}
|
|
412
|
+
/>
|
|
413
|
+
<input
|
|
414
|
+
type="password"
|
|
415
|
+
className="bridge-input"
|
|
416
|
+
placeholder="WhatsApp App Secret (optional HMAC validation)"
|
|
417
|
+
value={config.whatsappAppSecret}
|
|
418
|
+
onChange={(e) => updateField('whatsappAppSecret', e.target.value)}
|
|
419
|
+
/>
|
|
420
|
+
<div className="bridge-input-row">
|
|
421
|
+
<input
|
|
422
|
+
type="text"
|
|
423
|
+
className="bridge-input"
|
|
424
|
+
placeholder="Webhook Host (default: 127.0.0.1)"
|
|
425
|
+
value={config.whatsappWebhookHost || ''}
|
|
426
|
+
onChange={(e) => updateField('whatsappWebhookHost', e.target.value)}
|
|
427
|
+
/>
|
|
428
|
+
<input
|
|
429
|
+
type="number"
|
|
430
|
+
className="bridge-input"
|
|
431
|
+
placeholder="Port"
|
|
432
|
+
value={config.whatsappWebhookPort || 3001}
|
|
433
|
+
onChange={(e) => updateField('whatsappWebhookPort', parseInt(e.target.value) || 3001)}
|
|
434
|
+
style={{ width: '120px' }}
|
|
435
|
+
/>
|
|
436
|
+
</div>
|
|
437
|
+
</div>
|
|
438
|
+
</div>
|
|
141
439
|
</div>
|
|
142
440
|
</section>
|
|
143
441
|
|