@pheem49/mint 1.7.1 → 1.8.1
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/.github/workflows/release.yml +1 -1
- package/Cargo.lock +34 -3
- package/Cargo.toml +2 -2
- package/README.md +40 -0
- package/Release_Note.md +95 -0
- package/assets/Mint_CLI.png +0 -0
- package/crates/mint-cli/Cargo.toml +1 -0
- package/crates/mint-cli/src/agent.rs +142 -48
- package/crates/mint-cli/src/main.rs +231 -22
- package/crates/mint-core/src/api_server.rs +71 -4
- package/crates/mint-core/src/browser.rs +401 -0
- package/crates/mint-core/src/chat.rs +79 -0
- package/crates/mint-core/src/config.rs +69 -1
- package/crates/mint-core/src/lib.rs +6 -1
- package/crates/mint-core/src/orchestration.rs +137 -1
- package/crates/mint-core/src/skills.rs +113 -4
- package/crates/mint-core/src/workflows.rs +9 -22
- package/package.json +2 -1
- package/src/renderer/src/App.tsx +1 -1
- package/src/renderer/src/components/ChatPanel.tsx +197 -47
- package/src/renderer/src/components/DashboardSidebar.tsx +71 -12
- package/src/renderer/src/components/ImageStudioPanel.tsx +18 -4
- package/src/renderer/src/components/MintDashboard.tsx +239 -7
- package/src/renderer/src/components/PicturesLibrary.tsx +1 -3
- package/src/renderer/src/components/Settings/AudioTab.tsx +97 -0
- package/src/renderer/src/components/Settings/AutomationTab.tsx +195 -0
- package/src/renderer/src/components/Settings/GeneralTab.tsx +950 -0
- package/src/renderer/src/components/Settings/MemoryTab.tsx +60 -0
- package/src/renderer/src/components/Settings/PluginsTab.tsx +632 -0
- package/src/renderer/src/components/Settings/ShortcutsTab.tsx +30 -0
- package/src/renderer/src/components/Settings/ThemeTab.tsx +129 -0
- package/src/renderer/src/components/SettingsWindow.tsx +175 -970
- package/src/renderer/src/components/WorkflowBuilderPanel.tsx +536 -0
- package/src/renderer/src/components/WorkflowsWindow.tsx +46 -0
- package/src/renderer/src/components/WorkspacePanel.tsx +66 -9
- package/src/renderer/src/css/image-studio.css +15 -4
- package/src/renderer/src/css/settings.css +75 -6
- package/src/renderer/src/css/styles.css +419 -49
- package/src/renderer/src/css/workflows.css +542 -0
- package/src/renderer/src/env.d.ts +2 -0
- package/src/renderer/src/index.css +13 -1
- package/src/renderer/src/tauri.ts +140 -2
- package/src/renderer/src-web/components/ChatPanel.tsx +190 -46
- package/src/renderer/src-web/components/DashboardSidebar.tsx +17 -1
- package/src/renderer/src-web/components/ImageStudioPanel.tsx +18 -4
- package/src/renderer/src-web/components/MintDashboard.tsx +197 -3
- package/src/renderer/src-web/components/PicturesLibrary.tsx +1 -3
- package/src/renderer/src-web/components/Settings/AudioTab.tsx +97 -0
- package/src/renderer/src-web/components/Settings/AutomationTab.tsx +196 -0
- package/src/renderer/src-web/components/Settings/GeneralTab.tsx +948 -0
- package/src/renderer/src-web/components/Settings/MemoryTab.tsx +60 -0
- package/src/renderer/src-web/components/Settings/PluginsTab.tsx +632 -0
- package/src/renderer/src-web/components/Settings/ShortcutsTab.tsx +30 -0
- package/src/renderer/src-web/components/Settings/ThemeTab.tsx +129 -0
- package/src/renderer/src-web/components/SettingsWindow.tsx +187 -976
- package/src/renderer/src-web/css/image-studio.css +15 -4
- package/src/renderer/src-web/css/settings.css +75 -6
- package/src/renderer/src-web/css/styles.css +394 -50
- package/src/renderer/src-web/env.d.ts +2 -0
- package/src/renderer/src-web/index.css +12 -1
- package/src/renderer/src-web/tauri.ts +60 -2
- package/src-tauri/src/browser.rs +1 -141
- package/src-tauri/src/headless.rs +3 -2
- package/src-tauri/src/lib.rs +195 -111
- package/src-tauri/src/workflows.rs +56 -4
- package/src-tauri/tauri.conf.json +1 -1
|
@@ -68,15 +68,7 @@ const LOCAL_MODELS = [
|
|
|
68
68
|
'lmstudio-community/gemma-2-9b-it-GGUF'
|
|
69
69
|
]
|
|
70
70
|
|
|
71
|
-
const OLLAMA_MODELS = [
|
|
72
|
-
'llama3:latest',
|
|
73
|
-
'llama3.1:latest',
|
|
74
|
-
'llama3.2:latest',
|
|
75
|
-
'gemma2:latest',
|
|
76
|
-
'mistral:latest',
|
|
77
|
-
'phi3:latest',
|
|
78
|
-
'qwen2.5:latest'
|
|
79
|
-
]
|
|
71
|
+
const OLLAMA_MODELS: string[] = []
|
|
80
72
|
|
|
81
73
|
interface ApprovalDetails {
|
|
82
74
|
title: string
|
|
@@ -122,6 +114,7 @@ interface AgentActivity {
|
|
|
122
114
|
target: string
|
|
123
115
|
kind: 'file' | 'folder' | 'search' | 'terminal' | 'tool'
|
|
124
116
|
state: 'active' | 'done' | 'error'
|
|
117
|
+
action?: string
|
|
125
118
|
}
|
|
126
119
|
|
|
127
120
|
interface AgentActivityView {
|
|
@@ -153,7 +146,20 @@ function describeTool(action: string, input: Record<string, unknown>): AgentActi
|
|
|
153
146
|
const command = activityDetail(input, 'command')
|
|
154
147
|
const name = activityDetail(input, 'name')
|
|
155
148
|
const tool = activityDetail(input, 'tool')
|
|
156
|
-
const
|
|
149
|
+
const rawTarget = path || query || command || name || tool || action.replaceAll('_', ' ')
|
|
150
|
+
|
|
151
|
+
// Append line range for read_file when startLine / endLine are available
|
|
152
|
+
let target = rawTarget
|
|
153
|
+
if (action === 'read_file' && path) {
|
|
154
|
+
const startLine = typeof input.startLine === 'number' ? input.startLine : undefined
|
|
155
|
+
const endLine = typeof input.endLine === 'number' ? input.endLine : undefined
|
|
156
|
+
if (startLine !== undefined && endLine !== undefined) {
|
|
157
|
+
target = `${rawTarget} #L${startLine}-${endLine}`
|
|
158
|
+
} else if (startLine !== undefined) {
|
|
159
|
+
target = `${rawTarget} #L${startLine}`
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
157
163
|
const labels: Record<string, string> = {
|
|
158
164
|
apply_patch: 'Applying patch',
|
|
159
165
|
ask_user: 'Asking user',
|
|
@@ -189,6 +195,7 @@ function describeTool(action: string, input: Record<string, unknown>): AgentActi
|
|
|
189
195
|
target: formatActivityTarget(target),
|
|
190
196
|
kind: activityKind(action, target),
|
|
191
197
|
state: 'active',
|
|
198
|
+
action,
|
|
192
199
|
}
|
|
193
200
|
}
|
|
194
201
|
|
|
@@ -215,7 +222,13 @@ function activitiesFrom(progress: AgentProgress[]): AgentActivityView {
|
|
|
215
222
|
for (let index = activities.length - 1; index >= 0; index -= 1) {
|
|
216
223
|
if (activities[index].state !== 'active') continue
|
|
217
224
|
activities[index].state = event.data.result.startsWith('Error:') ? 'error' : 'done'
|
|
218
|
-
if (activities[index].state === 'error')
|
|
225
|
+
if (activities[index].state === 'error') {
|
|
226
|
+
activities[index].label = 'Failed'
|
|
227
|
+
}
|
|
228
|
+
if (activities[index].action === 'ask_user' && event.data.result.startsWith('User answered:')) {
|
|
229
|
+
const answer = event.data.result.replace('User answered:', '').trim()
|
|
230
|
+
activities[index].target = `(Answered: "${answer}") ${activities[index].target}`
|
|
231
|
+
}
|
|
219
232
|
break
|
|
220
233
|
}
|
|
221
234
|
}
|
|
@@ -224,7 +237,16 @@ function activitiesFrom(progress: AgentProgress[]): AgentActivityView {
|
|
|
224
237
|
return { summary: activitySummary(activities), items }
|
|
225
238
|
}
|
|
226
239
|
|
|
227
|
-
function
|
|
240
|
+
function AgentActivityTable({ activityView }: { activityView: AgentActivityView }) {
|
|
241
|
+
const [expandedIndices, setExpandedIndices] = useState<Record<number, boolean>>({})
|
|
242
|
+
|
|
243
|
+
const toggleExpand = (index: number) => {
|
|
244
|
+
setExpandedIndices(prev => ({
|
|
245
|
+
...prev,
|
|
246
|
+
[index]: !prev[index]
|
|
247
|
+
}))
|
|
248
|
+
}
|
|
249
|
+
|
|
228
250
|
return (
|
|
229
251
|
<div className="agent-activity-list">
|
|
230
252
|
<div className="agent-activity-table-head" aria-hidden="true">
|
|
@@ -233,14 +255,39 @@ function renderAgentActivityTable(activityView: AgentActivityView) {
|
|
|
233
255
|
<span>Target</span>
|
|
234
256
|
<span />
|
|
235
257
|
</div>
|
|
236
|
-
{activityView.items.map((activity, index) =>
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
<
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
258
|
+
{activityView.items.map((activity, index) => {
|
|
259
|
+
const isExpanded = !!expandedIndices[index]
|
|
260
|
+
return (
|
|
261
|
+
<div
|
|
262
|
+
className="agent-activity-item"
|
|
263
|
+
data-kind={activity.kind}
|
|
264
|
+
data-state={activity.state}
|
|
265
|
+
key={`${index}-${activity.label}-${activity.target}`}
|
|
266
|
+
style={{ cursor: 'pointer' }}
|
|
267
|
+
onClick={() => toggleExpand(index)}
|
|
268
|
+
>
|
|
269
|
+
<span className="agent-activity-label">{activity.label}</span>
|
|
270
|
+
<span className="agent-activity-icon" aria-hidden="true" />
|
|
271
|
+
<span
|
|
272
|
+
className="agent-activity-text"
|
|
273
|
+
style={isExpanded ? { whiteSpace: 'normal', wordBreak: 'break-all', overflow: 'visible', textOverflow: 'clip' } : undefined}
|
|
274
|
+
>
|
|
275
|
+
{activity.target}
|
|
276
|
+
</span>
|
|
277
|
+
<span
|
|
278
|
+
className="agent-activity-chevron"
|
|
279
|
+
style={{
|
|
280
|
+
transform: isExpanded ? 'rotate(90deg)' : 'none',
|
|
281
|
+
transition: 'transform 0.2s ease',
|
|
282
|
+
display: 'inline-block'
|
|
283
|
+
}}
|
|
284
|
+
aria-hidden="true"
|
|
285
|
+
>
|
|
286
|
+
>
|
|
287
|
+
</span>
|
|
288
|
+
</div>
|
|
289
|
+
)
|
|
290
|
+
})}
|
|
244
291
|
</div>
|
|
245
292
|
)
|
|
246
293
|
}
|
|
@@ -257,7 +304,7 @@ function renderApprovalDetails(approval: any): ApprovalDetails {
|
|
|
257
304
|
return { title: `Run MCP Tool: ${server}/${tool}`, body: typeof args === 'string' ? args : JSON.stringify(args, null, 2), reason: 'Running external MCP tool.', isDangerous: false }
|
|
258
305
|
}
|
|
259
306
|
if (approval.UserApproval) return { title: approval.UserApproval.title, body: approval.UserApproval.prompt, reason: 'The agent requested explicit approval.', isDangerous: false }
|
|
260
|
-
if (approval.AskUser) return { title: 'Question From Agent', body: approval.AskUser.question, reason: '
|
|
307
|
+
if (approval.AskUser) return { title: 'Question From Agent', body: approval.AskUser.question, reason: 'Type your answer below and submit to respond to the agent.', isDangerous: false }
|
|
261
308
|
return { title: 'Unknown Action', body: JSON.stringify(approval, null, 2), reason: 'Requires approval to proceed.', isDangerous: false }
|
|
262
309
|
}
|
|
263
310
|
|
|
@@ -355,7 +402,7 @@ interface ChatPanelProps {
|
|
|
355
402
|
onSetAgentMode: (enabled: boolean) => void
|
|
356
403
|
onSetProvider: (provider: string) => void
|
|
357
404
|
onSelectWorkspace: () => void
|
|
358
|
-
onApproval: (approved: boolean, autoApproveSession?: boolean) => void
|
|
405
|
+
onApproval: (approved: boolean, autoApproveSession?: boolean, answer?: string) => void
|
|
359
406
|
settingsConfig: any
|
|
360
407
|
onSetModel: (model: string) => void
|
|
361
408
|
}
|
|
@@ -450,7 +497,17 @@ function renderFormattedMessage(text: string) {
|
|
|
450
497
|
</strong>
|
|
451
498
|
)
|
|
452
499
|
}
|
|
453
|
-
|
|
500
|
+
const mentionParts = boldPart.split(/(@[\w\-\.\/]+)/g)
|
|
501
|
+
return mentionParts.map((mentionPart, mentionIndex) => {
|
|
502
|
+
if (mentionIndex % 2 === 1) {
|
|
503
|
+
return (
|
|
504
|
+
<span key={`mention-${mentionIndex}`} className="chat-mention">
|
|
505
|
+
{mentionPart}
|
|
506
|
+
</span>
|
|
507
|
+
)
|
|
508
|
+
}
|
|
509
|
+
return mentionPart
|
|
510
|
+
})
|
|
454
511
|
})
|
|
455
512
|
})
|
|
456
513
|
}
|
|
@@ -977,6 +1034,34 @@ export default function ChatPanel({
|
|
|
977
1034
|
const [openFileDiffs, setOpenFileDiffs] = useState<Record<string, boolean>>({})
|
|
978
1035
|
const [toolMenuOpen, setToolMenuOpen] = useState(false)
|
|
979
1036
|
const [elapsedSeconds, setElapsedSeconds] = useState(0)
|
|
1037
|
+
const [dynamicOllamaModels, setDynamicOllamaModels] = useState<string[]>(OLLAMA_MODELS)
|
|
1038
|
+
const [askAnswer, setAskAnswer] = useState('')
|
|
1039
|
+
|
|
1040
|
+
useEffect(() => {
|
|
1041
|
+
setAskAnswer('')
|
|
1042
|
+
}, [pendingApproval])
|
|
1043
|
+
|
|
1044
|
+
useEffect(() => {
|
|
1045
|
+
const fetchOllamaModels = async () => {
|
|
1046
|
+
if (status?.activeProvider !== 'ollama') return;
|
|
1047
|
+
const host = settingsConfig?.ollamaHost || 'http://localhost:11434';
|
|
1048
|
+
const cleanHost = host.endsWith('/') ? host.slice(0, -1) : host;
|
|
1049
|
+
try {
|
|
1050
|
+
const res = await fetch(`${cleanHost}/api/tags`);
|
|
1051
|
+
if (res.ok) {
|
|
1052
|
+
const data = await res.json();
|
|
1053
|
+
if (data && Array.isArray(data.models)) {
|
|
1054
|
+
setDynamicOllamaModels(data.models.map((m: any) => m.name));
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
} catch (err) {
|
|
1059
|
+
// fallback to default if fetch fails
|
|
1060
|
+
}
|
|
1061
|
+
setDynamicOllamaModels(OLLAMA_MODELS);
|
|
1062
|
+
}
|
|
1063
|
+
fetchOllamaModels();
|
|
1064
|
+
}, [status?.activeProvider, settingsConfig?.ollamaHost])
|
|
980
1065
|
|
|
981
1066
|
useEffect(() => {
|
|
982
1067
|
if (!sending) {
|
|
@@ -1037,7 +1122,7 @@ export default function ChatPanel({
|
|
|
1037
1122
|
case 'local_openai':
|
|
1038
1123
|
return LOCAL_MODELS
|
|
1039
1124
|
case 'ollama':
|
|
1040
|
-
return
|
|
1125
|
+
return dynamicOllamaModels
|
|
1041
1126
|
default:
|
|
1042
1127
|
return []
|
|
1043
1128
|
}
|
|
@@ -1473,7 +1558,7 @@ export default function ChatPanel({
|
|
|
1473
1558
|
const appendWorkspaceReference = (reference: string) => {
|
|
1474
1559
|
const trimmed = reference.trim()
|
|
1475
1560
|
if (!trimmed) return
|
|
1476
|
-
onSetMessage(message.trim() ? `${message.trimEnd()} ${trimmed}` : trimmed)
|
|
1561
|
+
onSetMessage(message.trim() ? `${message.trimEnd()} ${trimmed} ` : `${trimmed} `)
|
|
1477
1562
|
}
|
|
1478
1563
|
const handleWorkspaceDrop = (event: DragEvent<HTMLElement>) => {
|
|
1479
1564
|
const reference =
|
|
@@ -1502,7 +1587,7 @@ export default function ChatPanel({
|
|
|
1502
1587
|
</button>
|
|
1503
1588
|
{isOpen && (
|
|
1504
1589
|
<div className="agent-activity-card agent-activity-card-history">
|
|
1505
|
-
{
|
|
1590
|
+
<AgentActivityTable activityView={activityView} />
|
|
1506
1591
|
</div>
|
|
1507
1592
|
)}
|
|
1508
1593
|
</div>
|
|
@@ -1701,22 +1786,40 @@ export default function ChatPanel({
|
|
|
1701
1786
|
|
|
1702
1787
|
return (
|
|
1703
1788
|
<section className={`conversation-panel ${isEmptyChat ? 'is-empty' : ''}`}>
|
|
1789
|
+
<div className="chat-header">
|
|
1790
|
+
<div className="chat-header-title">
|
|
1791
|
+
<img src="./assets/icon.png" alt="Logo" className="chat-header-logo" />
|
|
1792
|
+
<span>Mint Agent</span>
|
|
1793
|
+
</div>
|
|
1794
|
+
</div>
|
|
1704
1795
|
<div className="chat-container">
|
|
1705
|
-
{interactions.map((interaction) =>
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
<
|
|
1796
|
+
{interactions.map((interaction) => {
|
|
1797
|
+
const isSystemEvent = interaction.provider === 'system' && interaction.model === 'provider_change';
|
|
1798
|
+
if (isSystemEvent) {
|
|
1799
|
+
return (
|
|
1800
|
+
<div key={interaction.id} className="system-event-divider">
|
|
1801
|
+
<div className="system-event-line" />
|
|
1802
|
+
<div className="system-event-pill">
|
|
1803
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
|
1804
|
+
<path d="m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z" />
|
|
1805
|
+
</svg>
|
|
1806
|
+
<span>{interaction.userText}</span>
|
|
1716
1807
|
</div>
|
|
1808
|
+
<div className="system-event-line" />
|
|
1717
1809
|
</div>
|
|
1718
|
-
)
|
|
1719
|
-
|
|
1810
|
+
);
|
|
1811
|
+
}
|
|
1812
|
+
return (
|
|
1813
|
+
<div key={interaction.id} style={{ display: 'flex', flexDirection: 'column', gap: '8px', marginBottom: '16px' }}>
|
|
1814
|
+
{interaction.userText && (
|
|
1815
|
+
<div className="message user-message">
|
|
1816
|
+
<div className="bubble-wrapper">
|
|
1817
|
+
<div className="message-bubble">{renderFormattedMessage(interaction.userText)}</div>
|
|
1818
|
+
<div className="message-time"><span>{new Date(interaction.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</span></div>
|
|
1819
|
+
</div>
|
|
1820
|
+
</div>
|
|
1821
|
+
)}
|
|
1822
|
+
<div className="message ai-message">
|
|
1720
1823
|
<div className="bubble-wrapper">
|
|
1721
1824
|
{renderCompletedActivity(interaction)}
|
|
1722
1825
|
{renderFileChanges(interaction)}
|
|
@@ -1750,7 +1853,8 @@ export default function ChatPanel({
|
|
|
1750
1853
|
</div>
|
|
1751
1854
|
</div>
|
|
1752
1855
|
</div>
|
|
1753
|
-
)
|
|
1856
|
+
);
|
|
1857
|
+
})}
|
|
1754
1858
|
|
|
1755
1859
|
{sending && (
|
|
1756
1860
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px', marginBottom: '16px' }}>
|
|
@@ -1764,7 +1868,7 @@ export default function ChatPanel({
|
|
|
1764
1868
|
{pendingApproval ? 'Waiting for approval' : 'Working'}
|
|
1765
1869
|
</span>
|
|
1766
1870
|
</div>
|
|
1767
|
-
{
|
|
1871
|
+
<AgentActivityTable activityView={agentActivities} />
|
|
1768
1872
|
</div>
|
|
1769
1873
|
</div>
|
|
1770
1874
|
)}
|
|
@@ -1834,6 +1938,7 @@ export default function ChatPanel({
|
|
|
1834
1938
|
const writeFile = pendingApproval.approval?.WriteFile
|
|
1835
1939
|
const applyPatch = pendingApproval.approval?.ApplyPatch
|
|
1836
1940
|
const diffText = writeFile?.diff || applyPatch?.diff
|
|
1941
|
+
const isAskUser = !!pendingApproval.approval?.AskUser
|
|
1837
1942
|
|
|
1838
1943
|
return (
|
|
1839
1944
|
<div className="message ai-message" style={{ width: '100%' }}>
|
|
@@ -1850,12 +1955,54 @@ export default function ChatPanel({
|
|
|
1850
1955
|
) : (
|
|
1851
1956
|
details.reason && <div className="approval-card-reason" style={{ whiteSpace: 'pre-wrap', fontFamily: 'monospace' }}>{details.reason}</div>
|
|
1852
1957
|
)}
|
|
1958
|
+
{isAskUser && (
|
|
1959
|
+
<div className="approval-card-input-container" style={{ marginTop: '12px', width: '100%' }}>
|
|
1960
|
+
<textarea
|
|
1961
|
+
style={{
|
|
1962
|
+
width: '100%',
|
|
1963
|
+
minHeight: '70px',
|
|
1964
|
+
padding: '10px',
|
|
1965
|
+
background: 'rgba(0, 0, 0, 0.2)',
|
|
1966
|
+
border: '1px solid rgba(255, 255, 255, 0.1)',
|
|
1967
|
+
borderRadius: '6px',
|
|
1968
|
+
color: 'var(--text-main, #f8fafc)',
|
|
1969
|
+
fontSize: '0.9rem',
|
|
1970
|
+
fontFamily: 'inherit',
|
|
1971
|
+
resize: 'vertical',
|
|
1972
|
+
outline: 'none',
|
|
1973
|
+
}}
|
|
1974
|
+
placeholder="Type your answer here..."
|
|
1975
|
+
value={askAnswer}
|
|
1976
|
+
onChange={(e) => setAskAnswer(e.target.value)}
|
|
1977
|
+
/>
|
|
1978
|
+
</div>
|
|
1979
|
+
)}
|
|
1853
1980
|
</div>
|
|
1854
|
-
|
|
1855
|
-
<
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1981
|
+
{isAskUser ? (
|
|
1982
|
+
<div className="approval-card-actions">
|
|
1983
|
+
<button
|
|
1984
|
+
type="button"
|
|
1985
|
+
className="approval-btn approval-btn-approve"
|
|
1986
|
+
disabled={!askAnswer.trim()}
|
|
1987
|
+
onClick={() => onApproval(true, false, askAnswer)}
|
|
1988
|
+
>
|
|
1989
|
+
Submit Answer
|
|
1990
|
+
</button>
|
|
1991
|
+
<button
|
|
1992
|
+
type="button"
|
|
1993
|
+
className="approval-btn approval-btn-cancel"
|
|
1994
|
+
onClick={() => onApproval(false)}
|
|
1995
|
+
>
|
|
1996
|
+
Decline
|
|
1997
|
+
</button>
|
|
1998
|
+
</div>
|
|
1999
|
+
) : (
|
|
2000
|
+
<div className="approval-card-actions">
|
|
2001
|
+
<button type="button" className="approval-btn approval-btn-approve" onClick={() => onApproval(true)}>Approve</button>
|
|
2002
|
+
<button type="button" className="approval-btn" style={{ backgroundColor: 'rgba(16, 185, 129, 0.22)', borderColor: 'rgba(16, 185, 129, 0.4)', color: '#a7f3d0' }} onClick={() => onApproval(true, true)}>Approve this session</button>
|
|
2003
|
+
<button type="button" className="approval-btn approval-btn-cancel" onClick={() => onApproval(false)}>Cancel</button>
|
|
2004
|
+
</div>
|
|
2005
|
+
)}
|
|
1859
2006
|
</div>
|
|
1860
2007
|
</div>
|
|
1861
2008
|
</div>
|
|
@@ -2035,7 +2182,7 @@ export default function ChatPanel({
|
|
|
2035
2182
|
return <option key={provider} value={provider}>{displayName}</option>
|
|
2036
2183
|
})}
|
|
2037
2184
|
</select>
|
|
2038
|
-
{availableModels.length > 0 && (
|
|
2185
|
+
{(availableModels.length > 0 || activeModel) && (
|
|
2039
2186
|
<select
|
|
2040
2187
|
value={activeModel}
|
|
2041
2188
|
onChange={(event) => onSetModel(event.target.value)}
|
|
@@ -2093,6 +2240,9 @@ export default function ChatPanel({
|
|
|
2093
2240
|
</button>
|
|
2094
2241
|
</form>
|
|
2095
2242
|
</div>
|
|
2243
|
+
<p className="input-disclaimer">
|
|
2244
|
+
Mint Agent is an AI gateway. Responses via third-party APIs. Verify critical info.
|
|
2245
|
+
</p>
|
|
2096
2246
|
</section>
|
|
2097
2247
|
)
|
|
2098
2248
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
1
|
+
import { useState, useEffect, useRef } from 'react'
|
|
2
2
|
|
|
3
|
-
export type DashboardView = 'chat' | 'pictures' | 'model' | '
|
|
3
|
+
export type DashboardView = 'chat' | 'pictures' | 'model' | 'workspace' | 'imagine' | 'workflows'
|
|
4
4
|
|
|
5
5
|
interface ChatSessionItem {
|
|
6
6
|
id: string
|
|
7
7
|
title: string
|
|
8
8
|
kind: string
|
|
9
|
+
createdAt?: string
|
|
10
|
+
updatedAt?: string
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
interface DashboardSidebarProps {
|
|
@@ -33,6 +35,8 @@ interface DashboardSidebarProps {
|
|
|
33
35
|
onSetInteractionEnabled: (enabled: boolean) => void
|
|
34
36
|
onSetShowInteractionGuide: (visible: boolean) => void
|
|
35
37
|
onShowToast: (message: string) => void
|
|
38
|
+
isSearchOpen: boolean
|
|
39
|
+
onSetSearchOpen: (open: boolean) => void
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
export default function DashboardSidebar({
|
|
@@ -60,9 +64,23 @@ export default function DashboardSidebar({
|
|
|
60
64
|
onSetInteractionEnabled,
|
|
61
65
|
onSetShowInteractionGuide,
|
|
62
66
|
onShowToast,
|
|
67
|
+
isSearchOpen,
|
|
68
|
+
onSetSearchOpen,
|
|
63
69
|
}: DashboardSidebarProps) {
|
|
64
70
|
const [editingSessionId, setEditingSessionId] = useState<string | null>(null)
|
|
65
71
|
const [editTitleValue, setEditTitleValue] = useState('')
|
|
72
|
+
const [isMoreOpen, setIsMoreOpen] = useState(false)
|
|
73
|
+
const moreContainerRef = useRef<HTMLDivElement>(null)
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
function handleClickOutside(event: MouseEvent) {
|
|
77
|
+
if (moreContainerRef.current && !moreContainerRef.current.contains(event.target as Node)) {
|
|
78
|
+
setIsMoreOpen(false)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
document.addEventListener('mousedown', handleClickOutside)
|
|
82
|
+
return () => document.removeEventListener('mousedown', handleClickOutside)
|
|
83
|
+
}, [])
|
|
66
84
|
|
|
67
85
|
const handleSaveRename = (id: string) => {
|
|
68
86
|
if (editTitleValue.trim() && editTitleValue.trim() !== chatSessions.find(s => s.id === id)?.title) {
|
|
@@ -115,6 +133,16 @@ export default function DashboardSidebar({
|
|
|
115
133
|
<span>New Chat</span>
|
|
116
134
|
</button>
|
|
117
135
|
|
|
136
|
+
<button className="sidebar-top-action sidebar-search-btn" onClick={() => onSetSearchOpen(true)}>
|
|
137
|
+
<span aria-hidden="true" style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
138
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
139
|
+
<circle cx="11" cy="11" r="8"></circle>
|
|
140
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
141
|
+
</svg>
|
|
142
|
+
</span>
|
|
143
|
+
<span>Search Chats</span>
|
|
144
|
+
</button>
|
|
145
|
+
|
|
118
146
|
<button className={`sidebar-top-action ${view === 'chat' ? 'is-active' : ''}`} onClick={() => onSetView('chat')}>
|
|
119
147
|
<span aria-hidden="true" style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
120
148
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
@@ -133,22 +161,14 @@ export default function DashboardSidebar({
|
|
|
133
161
|
</span>
|
|
134
162
|
<span>Pictures</span>
|
|
135
163
|
</button>
|
|
136
|
-
<button className={`sidebar-top-action ${view === '
|
|
137
|
-
<span aria-hidden="true" style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
138
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
139
|
-
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
|
|
140
|
-
</svg>
|
|
141
|
-
</span>
|
|
142
|
-
<span>Image Studio</span>
|
|
143
|
-
</button>
|
|
144
|
-
<button className={`sidebar-top-action ${view === 'workspeac' ? 'is-active' : ''}`} onClick={() => onSetView('workspeac')}>
|
|
164
|
+
<button className={`sidebar-top-action ${view === 'workspace' ? 'is-active' : ''}`} onClick={() => onSetView('workspace')}>
|
|
145
165
|
<span aria-hidden="true" style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
146
166
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
147
167
|
<path d="M3 6h7l2 2h9v10a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z"></path>
|
|
148
168
|
<path d="M3 6v12"></path>
|
|
149
169
|
</svg>
|
|
150
170
|
</span>
|
|
151
|
-
<span>
|
|
171
|
+
<span>Workspace</span>
|
|
152
172
|
</button>
|
|
153
173
|
<button className={`sidebar-top-action ${modelVisible ? 'is-active' : ''}`} onClick={onToggleModel}>
|
|
154
174
|
<span aria-hidden="true" style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
@@ -160,6 +180,45 @@ export default function DashboardSidebar({
|
|
|
160
180
|
<span>Live2D Model</span>
|
|
161
181
|
</button>
|
|
162
182
|
|
|
183
|
+
<div className="sidebar-more-container" ref={moreContainerRef}>
|
|
184
|
+
<button className={`sidebar-top-action ${isMoreOpen || view === 'imagine' || view === 'workflows' ? 'is-active' : ''}`} onClick={() => setIsMoreOpen(!isMoreOpen)}>
|
|
185
|
+
<span aria-hidden="true" style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
186
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
187
|
+
<circle cx="12" cy="12" r="1.5"></circle>
|
|
188
|
+
<circle cx="19" cy="12" r="1.5"></circle>
|
|
189
|
+
<circle cx="5" cy="12" r="1.5"></circle>
|
|
190
|
+
</svg>
|
|
191
|
+
</span>
|
|
192
|
+
<span>More</span>
|
|
193
|
+
</button>
|
|
194
|
+
{isMoreOpen && (
|
|
195
|
+
<div className="sidebar-more-popover">
|
|
196
|
+
<button className={`popover-item ${view === 'imagine' ? 'active' : ''}`} onClick={() => { onSetView('imagine'); setIsMoreOpen(false); }}>
|
|
197
|
+
<span aria-hidden="true" style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
198
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
199
|
+
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
|
|
200
|
+
</svg>
|
|
201
|
+
</span>
|
|
202
|
+
<span>Image Studio</span>
|
|
203
|
+
</button>
|
|
204
|
+
<button className={`popover-item ${view === 'workflows' ? 'active' : ''}`} onClick={() => { onSetView('workflows'); setIsMoreOpen(false); }}>
|
|
205
|
+
<span aria-hidden="true" style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
206
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
207
|
+
<path d="M4 12h16"></path>
|
|
208
|
+
<path d="M12 4v16"></path>
|
|
209
|
+
<rect x="2" y="9" width="4" height="6" rx="1"></rect>
|
|
210
|
+
<rect x="18" y="9" width="4" height="6" rx="1"></rect>
|
|
211
|
+
<rect x="10" y="2" width="4" height="4" rx="1"></rect>
|
|
212
|
+
<rect x="10" y="18" width="4" height="4" rx="1"></rect>
|
|
213
|
+
</svg>
|
|
214
|
+
</span>
|
|
215
|
+
<span>Workflow (Beta)</span>
|
|
216
|
+
</button>
|
|
217
|
+
</div>
|
|
218
|
+
)}
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
|
|
163
222
|
{modelVisible && (
|
|
164
223
|
<div className="sidebar-model-controls">
|
|
165
224
|
<button
|
|
@@ -16,6 +16,7 @@ interface ImageStudioPanelProps {
|
|
|
16
16
|
view: string
|
|
17
17
|
onRefreshPictures?: () => void
|
|
18
18
|
onSendToChat?: (dataUri: string, prompt: string) => void
|
|
19
|
+
onToggleMobileSidebar?: () => void
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
const ASPECT_OPTIONS: { value: AspectRatio; label: string; icon: string }[] = [
|
|
@@ -85,7 +86,7 @@ function SkeletonCard() {
|
|
|
85
86
|
)
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
export default function ImageStudioPanel({ view, onRefreshPictures, onSendToChat }: ImageStudioPanelProps) {
|
|
89
|
+
export default function ImageStudioPanel({ view, onRefreshPictures, onSendToChat, onToggleMobileSidebar }: ImageStudioPanelProps) {
|
|
89
90
|
const [prompt, setPrompt] = useState('')
|
|
90
91
|
const [negativePrompt, setNegativePrompt] = useState('')
|
|
91
92
|
const [showNegative, setShowNegative] = useState(false)
|
|
@@ -193,15 +194,28 @@ export default function ImageStudioPanel({ view, onRefreshPictures, onSendToChat
|
|
|
193
194
|
document.body.removeChild(a)
|
|
194
195
|
}
|
|
195
196
|
|
|
196
|
-
if (view !== 'imagine') return null
|
|
197
|
-
|
|
198
197
|
const modelOptions = PROVIDER_MODELS[selectedProvider] ?? []
|
|
199
198
|
|
|
200
199
|
return (
|
|
201
|
-
<div
|
|
200
|
+
<div
|
|
201
|
+
className={`img-studio ${view === 'imagine' ? 'is-visible' : ''}`}
|
|
202
|
+
id="image-studio-panel"
|
|
203
|
+
role="main"
|
|
204
|
+
aria-label="Image Studio"
|
|
205
|
+
aria-hidden={view !== 'imagine'}
|
|
206
|
+
>
|
|
202
207
|
{/* Header */}
|
|
203
208
|
<header className="img-studio-header">
|
|
204
209
|
<div className="img-studio-header-title">
|
|
210
|
+
<button
|
|
211
|
+
className="mobile-menu-btn"
|
|
212
|
+
type="button"
|
|
213
|
+
onClick={onToggleMobileSidebar}
|
|
214
|
+
aria-label="Toggle menu"
|
|
215
|
+
style={{ marginRight: '8px' }}
|
|
216
|
+
>
|
|
217
|
+
☰
|
|
218
|
+
</button>
|
|
205
219
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
|
206
220
|
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>
|
|
207
221
|
</svg>
|