@pheem49/mint 1.9.0 → 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 +285 -140
- 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 +287 -43
- package/crates/mint-cli/src/gmail.rs +22 -2
- package/crates/mint-cli/src/interactive.rs +2888 -0
- package/crates/mint-cli/src/main.rs +336 -2854
- package/crates/mint-cli/src/markdown.rs +294 -0
- package/crates/mint-cli/src/onboard.rs +44 -1
- 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 +376 -13
- package/crates/mint-core/src/browser.rs +629 -101
- package/crates/mint-core/src/channels.rs +105 -6
- package/crates/mint-core/src/chat.rs +52 -4
- package/crates/mint-core/src/code_tools.rs +29 -11
- package/crates/mint-core/src/config.rs +118 -0
- package/crates/mint-core/src/image_gen.rs +294 -33
- package/crates/mint-core/src/lib.rs +5 -2
- package/crates/mint-core/src/mcp.rs +119 -6
- package/crates/mint-core/src/orchestration.rs +133 -11
- package/crates/mint-core/src/pictures.rs +64 -1
- package/crates/mint-core/src/skills.rs +82 -6
- package/crates/mint-core/src/video_gen.rs +235 -0
- package/package.json +8 -2
- 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/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/markdown.tsx +217 -88
- package/src/renderer/shared/utils/slashCommandProcessor.ts +239 -0
- package/src/renderer/src/components/ChatPanel.tsx +461 -64
- 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 +150 -5
- package/src/renderer/src/components/PicturesLibrary.tsx +156 -15
- package/src/renderer/src/components/Settings/AutomationTab.tsx +316 -18
- package/src/renderer/src/components/Settings/GeneralTab.tsx +762 -634
- 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 +38 -2
- package/src/renderer/src/components/VeoStudioPanel.tsx +549 -0
- 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 +352 -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 +360 -88
- 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 +137 -4
- package/src/renderer/src-web/components/PicturesLibrary.tsx +155 -15
- package/src/renderer/src-web/components/Settings/AutomationTab.tsx +316 -18
- package/src/renderer/src-web/components/Settings/GeneralTab.tsx +762 -629
- 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 +35 -2
- 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 +353 -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/lib.rs +114 -8
- 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'}>
|
|
@@ -34,34 +73,135 @@ export default function PicturesLibrary({ view, pictures, onSetView, onRefreshPi
|
|
|
34
73
|
</button>
|
|
35
74
|
</div>
|
|
36
75
|
</header>
|
|
37
|
-
|
|
76
|
+
|
|
77
|
+
{/* Tabs Filter */}
|
|
78
|
+
<div className="pictures-type-filter">
|
|
79
|
+
<button
|
|
80
|
+
type="button"
|
|
81
|
+
className={`filter-tab-btn ${filterType === 'photo' ? 'active' : ''}`}
|
|
82
|
+
onClick={() => setFilterType('photo')}
|
|
83
|
+
>
|
|
84
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
85
|
+
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
|
86
|
+
<circle cx="8.5" cy="8.5" r="1.5"></circle>
|
|
87
|
+
<polyline points="21 15 16 10 5 21"></polyline>
|
|
88
|
+
</svg>
|
|
89
|
+
Photos
|
|
90
|
+
</button>
|
|
91
|
+
<button
|
|
92
|
+
type="button"
|
|
93
|
+
className={`filter-tab-btn ${filterType === 'video' ? 'active' : ''}`}
|
|
94
|
+
onClick={() => setFilterType('video')}
|
|
95
|
+
>
|
|
96
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
97
|
+
<polygon points="23 7 16 12 23 17 23 7"></polygon>
|
|
98
|
+
<rect x="1" y="5" width="15" height="14" rx="2" ry="2"></rect>
|
|
99
|
+
</svg>
|
|
100
|
+
Videos
|
|
101
|
+
</button>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
{filteredPictures.length === 0 ? (
|
|
38
105
|
<div className="pictures-empty">
|
|
39
106
|
<div className="pictures-empty-icon" style={{ display: 'flex', justifyContent: 'center', marginBottom: '12px', opacity: 0.3 }}>
|
|
40
107
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
41
108
|
<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>
|
|
42
109
|
</svg>
|
|
43
110
|
</div>
|
|
44
|
-
<p>No saved pictures yet</p>
|
|
45
|
-
<span>
|
|
111
|
+
<p>No saved {filterType === 'video' ? 'videos' : 'pictures'} yet</p>
|
|
112
|
+
<span>Items appear here after a message with an attachment is sent successfully.</span>
|
|
46
113
|
</div>
|
|
47
114
|
) : (
|
|
48
115
|
<>
|
|
49
116
|
<div className="pictures-grid">
|
|
50
|
-
{visiblePictures.map((picture, index) =>
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
117
|
+
{visiblePictures.map((picture, index) => {
|
|
118
|
+
const isVideo = filterType === 'video'
|
|
119
|
+
return (
|
|
120
|
+
<article className="picture-card" key={picture.id}>
|
|
121
|
+
{isVideo ? (
|
|
122
|
+
<div style={{ position: 'relative', width: '100%', aspectRatio: '4 / 3', display: 'flex', alignItems: 'center', justifyContent: 'center', backgroundColor: '#1a1a1a' }}>
|
|
123
|
+
{failedImages.has(picture.id) ? (
|
|
124
|
+
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '8px', color: '#666' }}>
|
|
125
|
+
<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 }}>
|
|
126
|
+
<path d="M23 7l-7 5 7 5V7z"></path>
|
|
127
|
+
<rect x="1" y="5" width="15" height="14" rx="2" ry="2"></rect>
|
|
128
|
+
</svg>
|
|
129
|
+
<span style={{ fontSize: '11px', fontWeight: 500, letterSpacing: '0.05em', textTransform: 'uppercase', opacity: 0.8 }}>Video</span>
|
|
130
|
+
</div>
|
|
131
|
+
) : (
|
|
132
|
+
<>
|
|
133
|
+
{picture.thumbnailPath || picture.thumbnailUrl ? (
|
|
134
|
+
<img
|
|
135
|
+
src={getPictureSrc(picture, true)}
|
|
136
|
+
alt={picture.message || picture.filename}
|
|
137
|
+
loading={index < 6 ? 'eager' : 'lazy'}
|
|
138
|
+
decoding="async"
|
|
139
|
+
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
|
140
|
+
onError={() => setFailedImages((prev) => new Set([...prev, picture.id]))}
|
|
141
|
+
/>
|
|
142
|
+
) : (
|
|
143
|
+
<video
|
|
144
|
+
src={getPictureSrc(picture, false)}
|
|
145
|
+
preload="metadata"
|
|
146
|
+
style={{ width: '100%', height: '100%', objectFit: 'cover', pointerEvents: 'none' }}
|
|
147
|
+
onError={() => setFailedImages((prev) => new Set([...prev, picture.id]))}
|
|
148
|
+
/>
|
|
149
|
+
)}
|
|
150
|
+
<div style={{
|
|
151
|
+
position: 'absolute',
|
|
152
|
+
top: '8px',
|
|
153
|
+
left: '8px',
|
|
154
|
+
display: 'flex',
|
|
155
|
+
alignItems: 'center',
|
|
156
|
+
gap: '4px',
|
|
157
|
+
backgroundColor: 'rgba(0, 0, 0, 0.75)',
|
|
158
|
+
color: '#fff',
|
|
159
|
+
padding: '4px 8px',
|
|
160
|
+
borderRadius: '4px',
|
|
161
|
+
fontSize: '11px',
|
|
162
|
+
fontWeight: 600,
|
|
163
|
+
pointerEvents: 'none',
|
|
164
|
+
backdropFilter: 'blur(4px)',
|
|
165
|
+
border: '1px solid rgba(255, 255, 255, 0.1)'
|
|
166
|
+
}}>
|
|
167
|
+
<span>📹 Video</span>
|
|
168
|
+
</div>
|
|
169
|
+
</>
|
|
170
|
+
)}
|
|
171
|
+
</div>
|
|
172
|
+
) : (
|
|
173
|
+
<div style={{ position: 'relative', width: '100%', aspectRatio: '4 / 3', display: 'flex', alignItems: 'center', justifyContent: 'center', backgroundColor: '#1a1a1a' }}>
|
|
174
|
+
{failedImages.has(picture.id) ? (
|
|
175
|
+
<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 }}>
|
|
176
|
+
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
|
177
|
+
<circle cx="8.5" cy="8.5" r="1.5"></circle>
|
|
178
|
+
<polyline points="21 15 16 10 5 21"></polyline>
|
|
179
|
+
</svg>
|
|
180
|
+
) : (
|
|
181
|
+
<img
|
|
182
|
+
src={getPictureSrc(picture, true)}
|
|
183
|
+
alt={picture.message || picture.filename}
|
|
184
|
+
loading={index < 6 ? 'eager' : 'lazy'}
|
|
185
|
+
decoding="async"
|
|
186
|
+
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
|
187
|
+
onError={() => setFailedImages((prev) => new Set([...prev, picture.id]))}
|
|
188
|
+
/>
|
|
189
|
+
)}
|
|
190
|
+
</div>
|
|
191
|
+
)}
|
|
192
|
+
<div className="picture-card-meta"><span>{picture.message || picture.filename}</span></div>
|
|
193
|
+
</article>
|
|
194
|
+
)
|
|
195
|
+
})}
|
|
56
196
|
</div>
|
|
57
|
-
{visibleCount <
|
|
197
|
+
{visibleCount < filteredPictures.length && (
|
|
58
198
|
<div className="pictures-load-more-container">
|
|
59
199
|
<button
|
|
60
200
|
type="button"
|
|
61
201
|
className="pictures-load-more-btn"
|
|
62
202
|
onClick={() => setVisibleCount((prev) => prev + 24)}
|
|
63
203
|
>
|
|
64
|
-
Load More ({
|
|
204
|
+
Load More ({filteredPictures.length - visibleCount} remaining)
|
|
65
205
|
</button>
|
|
66
206
|
</div>
|
|
67
207
|
)}
|
|
@@ -70,3 +210,4 @@ export default function PicturesLibrary({ view, pictures, onSetView, onRefreshPi
|
|
|
70
210
|
</section>
|
|
71
211
|
)
|
|
72
212
|
}
|
|
213
|
+
|
|
@@ -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
|
|