@pheem49/mint 1.8.2 → 1.9.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.toml +1 -1
- package/Release_Note.md +48 -3
- package/crates/mint-cli/src/agent.rs +95 -84
- package/crates/mint-cli/src/image.rs +8 -8
- package/crates/mint-cli/src/main.rs +806 -403
- package/crates/mint-cli/src/onboard.rs +108 -108
- package/crates/mint-cli/src/setup.rs +143 -143
- package/crates/mint-core/src/api_server.rs +57 -54
- package/crates/mint-core/src/browser.rs +1 -1
- package/crates/mint-core/src/chat.rs +123 -8
- package/crates/mint-core/src/code_tools.rs +18 -20
- package/crates/mint-core/src/config.rs +184 -17
- package/crates/mint-core/src/image_gen.rs +5 -5
- package/crates/mint-core/src/lib.rs +5 -5
- package/crates/mint-core/src/memory.rs +6 -1
- package/crates/mint-core/src/orchestration.rs +104 -85
- package/crates/mint-core/src/pictures.rs +3 -4
- 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 +46 -19
- package/package.json +1 -1
- 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/src/renderer/shared/components/AgentActivityTable.tsx +25 -6
- package/src/renderer/shared/utils/fileIcons.ts +135 -0
- package/src/renderer/src/components/ChatPanel.tsx +25 -5
- package/src/renderer/src/components/MintDashboard.tsx +45 -2
- package/src/renderer/src/components/Settings/AgentsTab.tsx +21 -2
- package/src/renderer/src/components/Settings/GeneralTab.tsx +256 -2
- package/src/renderer/src/components/SettingsWindow.tsx +68 -1
- package/src/renderer/src/components/WorkspacePanel.tsx +7 -125
- package/src/renderer/src/css/styles.css +16 -0
- package/src/renderer/src-web/components/ChatPanel.tsx +25 -5
- package/src/renderer/src-web/components/MintDashboard.tsx +19 -1
- package/src/renderer/src-web/components/Settings/AgentsTab.tsx +21 -2
- package/src/renderer/src-web/components/Settings/GeneralTab.tsx +254 -3
- package/src/renderer/src-web/components/SettingsWindow.tsx +75 -1
- package/src/renderer/src-web/css/styles.css +16 -0
- package/src-tauri/src/desktop.rs +18 -16
- package/src-tauri/src/discord_rpc.rs +1 -1
- package/src-tauri/src/lib.rs +41 -17
- package/src-tauri/tauri.conf.json +1 -1
|
@@ -8,17 +8,32 @@
|
|
|
8
8
|
"sources": [
|
|
9
9
|
{
|
|
10
10
|
"type": "archive",
|
|
11
|
-
"url": "https://download.drobilla.net/serd-0.
|
|
12
|
-
"sha256": "
|
|
11
|
+
"url": "https://download.drobilla.net/serd-0.32.10.tar.xz",
|
|
12
|
+
"sha256": "b0e93b49e52f01a049475b7886ef140407115a32d3b1e5dc5f95141c88275d1c"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
|
-
"
|
|
16
|
-
"
|
|
15
|
+
"cleanup": [
|
|
16
|
+
"/bin",
|
|
17
|
+
"/include",
|
|
18
|
+
"/lib/pkgconfig",
|
|
19
|
+
"/share/man"
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "zix",
|
|
24
|
+
"buildsystem": "meson",
|
|
25
|
+
"sources": [
|
|
26
|
+
{
|
|
27
|
+
"type": "archive",
|
|
28
|
+
"url": "https://download.drobilla.net/zix-0.8.2.tar.xz",
|
|
29
|
+
"sha256": "4c73aab0f8cbbfe56b00c8c6d648316021e699c5ae6cbf254391ef309047e67b"
|
|
30
|
+
}
|
|
17
31
|
],
|
|
18
32
|
"cleanup": [
|
|
19
33
|
"/bin",
|
|
20
34
|
"/include",
|
|
21
35
|
"/lib/pkgconfig",
|
|
36
|
+
"/share/doc",
|
|
22
37
|
"/share/man"
|
|
23
38
|
]
|
|
24
39
|
},
|
|
@@ -28,13 +43,10 @@
|
|
|
28
43
|
"sources": [
|
|
29
44
|
{
|
|
30
45
|
"type": "archive",
|
|
31
|
-
"url": "https://download.drobilla.net/sord-0.16.
|
|
32
|
-
"sha256": "
|
|
46
|
+
"url": "https://download.drobilla.net/sord-0.16.22.tar.xz",
|
|
47
|
+
"sha256": "bb23b34b216579136795d518cffa73d91cf205594ce9accebfd408afb839173f"
|
|
33
48
|
}
|
|
34
49
|
],
|
|
35
|
-
"post-install": [
|
|
36
|
-
"install -Dm644 -t $FLATPAK_DEST/share/licenses/sord ../COPYING"
|
|
37
|
-
],
|
|
38
50
|
"cleanup": [
|
|
39
51
|
"/bin",
|
|
40
52
|
"/include",
|
|
@@ -43,6 +55,7 @@
|
|
|
43
55
|
"/share/man"
|
|
44
56
|
]
|
|
45
57
|
},
|
|
58
|
+
"lv2.json",
|
|
46
59
|
{
|
|
47
60
|
"name": "sratom",
|
|
48
61
|
"buildsystem": "meson",
|
|
@@ -53,25 +66,19 @@
|
|
|
53
66
|
"sources": [
|
|
54
67
|
{
|
|
55
68
|
"type": "archive",
|
|
56
|
-
"url": "https://download.drobilla.net/sratom-0.6.
|
|
57
|
-
"sha256": "
|
|
69
|
+
"url": "https://download.drobilla.net/sratom-0.6.22.tar.xz",
|
|
70
|
+
"sha256": "0209b7d0f22c96abb416722ed735b0933be47931ecff4aa4b26ded7760b4f252"
|
|
58
71
|
}
|
|
59
|
-
],
|
|
60
|
-
"post-install": [
|
|
61
|
-
"install -Dm644 -t $FLATPAK_DEST/share/licenses/sratom ../COPYING"
|
|
62
72
|
]
|
|
63
73
|
}
|
|
64
74
|
],
|
|
65
75
|
"sources": [
|
|
66
76
|
{
|
|
67
77
|
"type": "archive",
|
|
68
|
-
"url": "https://download.drobilla.net/lilv-0.
|
|
69
|
-
"sha256": "
|
|
78
|
+
"url": "https://download.drobilla.net/lilv-0.28.0.tar.xz",
|
|
79
|
+
"sha256": "8dcb70adb5cf072335115a6b091f4113710bdc73abaadaa3f9e9c1e55957b149"
|
|
70
80
|
}
|
|
71
81
|
],
|
|
72
|
-
"post-install": [
|
|
73
|
-
"install -Dm644 -t $FLATPAK_DEST/share/licenses/lilv ../COPYING"
|
|
74
|
-
],
|
|
75
82
|
"cleanup": [
|
|
76
83
|
"/bin",
|
|
77
84
|
"/etc",
|
|
@@ -1,15 +1,30 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* shared/components/AgentActivityTable.tsx
|
|
3
|
-
* Renders the "Working through task" activity drawer rows.
|
|
4
|
-
* Shared by both Desktop and Web ChatPanel — do NOT duplicate this.
|
|
5
|
-
*/
|
|
6
1
|
import { useState } from 'react'
|
|
7
2
|
import type { AgentActivityView } from '../utils/agentActivity'
|
|
3
|
+
import { materialFileIcon, materialFolderIcon, getExtension } from '../utils/fileIcons'
|
|
8
4
|
|
|
9
5
|
interface Props {
|
|
10
6
|
activityView: AgentActivityView
|
|
11
7
|
}
|
|
12
8
|
|
|
9
|
+
function getFilename(target: string): string {
|
|
10
|
+
const clean = target.split(' #')[0].trim()
|
|
11
|
+
const segments = clean.split('/')
|
|
12
|
+
return segments[segments.length - 1]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function resolveActivityIcon(kind: string, target: string): string | null {
|
|
16
|
+
if (kind === 'file') {
|
|
17
|
+
const filename = getFilename(target)
|
|
18
|
+
const ext = getExtension(filename)
|
|
19
|
+
return materialFileIcon(filename, ext)
|
|
20
|
+
}
|
|
21
|
+
if (kind === 'folder') {
|
|
22
|
+
const foldername = getFilename(target)
|
|
23
|
+
return materialFolderIcon(foldername, false)
|
|
24
|
+
}
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
|
|
13
28
|
export function AgentActivityTable({ activityView }: Props) {
|
|
14
29
|
const [expandedIndices, setExpandedIndices] = useState<Record<number, boolean>>({})
|
|
15
30
|
|
|
@@ -30,6 +45,7 @@ export function AgentActivityTable({ activityView }: Props) {
|
|
|
30
45
|
</div>
|
|
31
46
|
{activityView.items.map((activity, index) => {
|
|
32
47
|
const isExpanded = !!expandedIndices[index]
|
|
48
|
+
const icon = resolveActivityIcon(activity.kind, activity.target)
|
|
33
49
|
return (
|
|
34
50
|
<div
|
|
35
51
|
className="agent-activity-item"
|
|
@@ -40,7 +56,9 @@ export function AgentActivityTable({ activityView }: Props) {
|
|
|
40
56
|
onClick={() => toggleExpand(index)}
|
|
41
57
|
>
|
|
42
58
|
<span className="agent-activity-label">{activity.label}</span>
|
|
43
|
-
<span className="agent-activity-icon" aria-hidden="true"
|
|
59
|
+
<span className="agent-activity-icon" aria-hidden="true" data-has-img={!!icon}>
|
|
60
|
+
{icon && <img src={icon} alt="" draggable={false} />}
|
|
61
|
+
</span>
|
|
44
62
|
<span
|
|
45
63
|
className="agent-activity-text"
|
|
46
64
|
style={isExpanded ? { whiteSpace: 'normal', wordBreak: 'break-all', overflow: 'visible', textOverflow: 'clip' } : undefined}
|
|
@@ -64,3 +82,4 @@ export function AgentActivityTable({ activityView }: Props) {
|
|
|
64
82
|
</div>
|
|
65
83
|
)
|
|
66
84
|
}
|
|
85
|
+
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* shared/utils/fileIcons.ts
|
|
3
|
+
* Utility mapping and resolution functions for Material Icon Theme icons.
|
|
4
|
+
* Extracted from WorkspacePanel to be shared across multiple components (e.g. AgentActivityTable).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import folderIcon from 'material-icon-theme/icons/folder.svg?url'
|
|
8
|
+
import folderOpenIcon from 'material-icon-theme/icons/folder-open.svg?url'
|
|
9
|
+
import folderComponentsIcon from 'material-icon-theme/icons/folder-components.svg?url'
|
|
10
|
+
import folderComponentsOpenIcon from 'material-icon-theme/icons/folder-components-open.svg?url'
|
|
11
|
+
import folderConfigIcon from 'material-icon-theme/icons/folder-config.svg?url'
|
|
12
|
+
import folderConfigOpenIcon from 'material-icon-theme/icons/folder-config-open.svg?url'
|
|
13
|
+
import folderDistIcon from 'material-icon-theme/icons/folder-dist.svg?url'
|
|
14
|
+
import folderDistOpenIcon from 'material-icon-theme/icons/folder-dist-open.svg?url'
|
|
15
|
+
import folderDocsIcon from 'material-icon-theme/icons/folder-docs.svg?url'
|
|
16
|
+
import folderDocsOpenIcon from 'material-icon-theme/icons/folder-docs-open.svg?url'
|
|
17
|
+
import folderGitIcon from 'material-icon-theme/icons/folder-git.svg?url'
|
|
18
|
+
import folderGitOpenIcon from 'material-icon-theme/icons/folder-git-open.svg?url'
|
|
19
|
+
import folderGithubIcon from 'material-icon-theme/icons/folder-github.svg?url'
|
|
20
|
+
import folderGithubOpenIcon from 'material-icon-theme/icons/folder-github-open.svg?url'
|
|
21
|
+
import folderImagesIcon from 'material-icon-theme/icons/folder-images.svg?url'
|
|
22
|
+
import folderImagesOpenIcon from 'material-icon-theme/icons/folder-images-open.svg?url'
|
|
23
|
+
import folderLogIcon from 'material-icon-theme/icons/folder-log.svg?url'
|
|
24
|
+
import folderLogOpenIcon from 'material-icon-theme/icons/folder-log-open.svg?url'
|
|
25
|
+
import folderNodeIcon from 'material-icon-theme/icons/folder-node.svg?url'
|
|
26
|
+
import folderNodeOpenIcon from 'material-icon-theme/icons/folder-node-open.svg?url'
|
|
27
|
+
import folderPublicIcon from 'material-icon-theme/icons/folder-public.svg?url'
|
|
28
|
+
import folderPublicOpenIcon from 'material-icon-theme/icons/folder-public-open.svg?url'
|
|
29
|
+
import folderRustIcon from 'material-icon-theme/icons/folder-rust.svg?url'
|
|
30
|
+
import folderRustOpenIcon from 'material-icon-theme/icons/folder-rust-open.svg?url'
|
|
31
|
+
import folderScriptsIcon from 'material-icon-theme/icons/folder-scripts.svg?url'
|
|
32
|
+
import folderScriptsOpenIcon from 'material-icon-theme/icons/folder-scripts-open.svg?url'
|
|
33
|
+
import folderSrcIcon from 'material-icon-theme/icons/folder-src.svg?url'
|
|
34
|
+
import folderSrcOpenIcon from 'material-icon-theme/icons/folder-src-open.svg?url'
|
|
35
|
+
import folderSrcTauriIcon from 'material-icon-theme/icons/folder-src-tauri.svg?url'
|
|
36
|
+
import folderSrcTauriOpenIcon from 'material-icon-theme/icons/folder-src-tauri-open.svg?url'
|
|
37
|
+
import folderTestIcon from 'material-icon-theme/icons/folder-test.svg?url'
|
|
38
|
+
import folderTestOpenIcon from 'material-icon-theme/icons/folder-test-open.svg?url'
|
|
39
|
+
import folderUiIcon from 'material-icon-theme/icons/folder-ui.svg?url'
|
|
40
|
+
import folderUiOpenIcon from 'material-icon-theme/icons/folder-ui-open.svg?url'
|
|
41
|
+
import folderUtilsIcon from 'material-icon-theme/icons/folder-utils.svg?url'
|
|
42
|
+
import folderUtilsOpenIcon from 'material-icon-theme/icons/folder-utils-open.svg?url'
|
|
43
|
+
import cssIcon from 'material-icon-theme/icons/css.svg?url'
|
|
44
|
+
import documentIcon from 'material-icon-theme/icons/document.svg?url'
|
|
45
|
+
import htmlIcon from 'material-icon-theme/icons/html.svg?url'
|
|
46
|
+
import imageIcon from 'material-icon-theme/icons/image.svg?url'
|
|
47
|
+
import javascriptIcon from 'material-icon-theme/icons/javascript.svg?url'
|
|
48
|
+
import jsonIcon from 'material-icon-theme/icons/json.svg?url'
|
|
49
|
+
import lockIcon from 'material-icon-theme/icons/lock.svg?url'
|
|
50
|
+
import logIcon from 'material-icon-theme/icons/log.svg?url'
|
|
51
|
+
import markdownIcon from 'material-icon-theme/icons/markdown.svg?url'
|
|
52
|
+
import npmIcon from 'material-icon-theme/icons/npm.svg?url'
|
|
53
|
+
import reactIcon from 'material-icon-theme/icons/react.svg?url'
|
|
54
|
+
import reactTsIcon from 'material-icon-theme/icons/react_ts.svg?url'
|
|
55
|
+
import rustIcon from 'material-icon-theme/icons/rust.svg?url'
|
|
56
|
+
import settingsIcon from 'material-icon-theme/icons/settings.svg?url'
|
|
57
|
+
import tauriIcon from 'material-icon-theme/icons/tauri.svg?url'
|
|
58
|
+
import tomlIcon from 'material-icon-theme/icons/toml.svg?url'
|
|
59
|
+
import typescriptIcon from 'material-icon-theme/icons/typescript.svg?url'
|
|
60
|
+
import viteIcon from 'material-icon-theme/icons/vite.svg?url'
|
|
61
|
+
import yamlIcon from 'material-icon-theme/icons/yaml.svg?url'
|
|
62
|
+
|
|
63
|
+
export const FOLDER_ICONS: Record<string, { closed: string; open: string }> = {
|
|
64
|
+
'.cargo_home': { closed: folderRustIcon, open: folderRustOpenIcon },
|
|
65
|
+
'.github': { closed: folderGithubIcon, open: folderGithubOpenIcon },
|
|
66
|
+
'.git': { closed: folderGitIcon, open: folderGitOpenIcon },
|
|
67
|
+
'.rustup': { closed: folderRustIcon, open: folderRustOpenIcon },
|
|
68
|
+
'.rustup_copy': { closed: folderRustIcon, open: folderRustOpenIcon },
|
|
69
|
+
'.rustup_home': { closed: folderRustIcon, open: folderRustOpenIcon },
|
|
70
|
+
assets: { closed: folderImagesIcon, open: folderImagesOpenIcon },
|
|
71
|
+
components: { closed: folderComponentsIcon, open: folderComponentsOpenIcon },
|
|
72
|
+
crates: { closed: folderRustIcon, open: folderRustOpenIcon },
|
|
73
|
+
css: { closed: folderUiIcon, open: folderUiOpenIcon },
|
|
74
|
+
dist: { closed: folderDistIcon, open: folderDistOpenIcon },
|
|
75
|
+
docs: { closed: folderDocsIcon, open: folderDocsOpenIcon },
|
|
76
|
+
logs: { closed: folderLogIcon, open: folderLogOpenIcon },
|
|
77
|
+
node_modules: { closed: folderNodeIcon, open: folderNodeOpenIcon },
|
|
78
|
+
out: { closed: folderDistIcon, open: folderDistOpenIcon },
|
|
79
|
+
public: { closed: folderPublicIcon, open: folderPublicOpenIcon },
|
|
80
|
+
renderer: { closed: folderUiIcon, open: folderUiOpenIcon },
|
|
81
|
+
scripts: { closed: folderScriptsIcon, open: folderScriptsOpenIcon },
|
|
82
|
+
src: { closed: folderSrcIcon, open: folderSrcOpenIcon },
|
|
83
|
+
'src-tauri': { closed: folderSrcTauriIcon, open: folderSrcTauriOpenIcon },
|
|
84
|
+
tests: { closed: folderTestIcon, open: folderTestOpenIcon },
|
|
85
|
+
utils: { closed: folderUtilsIcon, open: folderUtilsOpenIcon },
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const FILE_ICONS_BY_EXTENSION: Record<string, string> = {
|
|
89
|
+
css: cssIcon,
|
|
90
|
+
html: htmlIcon,
|
|
91
|
+
jpeg: imageIcon,
|
|
92
|
+
jpg: imageIcon,
|
|
93
|
+
js: javascriptIcon,
|
|
94
|
+
json: jsonIcon,
|
|
95
|
+
lock: lockIcon,
|
|
96
|
+
log: logIcon,
|
|
97
|
+
md: markdownIcon,
|
|
98
|
+
png: imageIcon,
|
|
99
|
+
rs: rustIcon,
|
|
100
|
+
svg: imageIcon,
|
|
101
|
+
toml: tomlIcon,
|
|
102
|
+
ts: typescriptIcon,
|
|
103
|
+
tsx: reactTsIcon,
|
|
104
|
+
yaml: yamlIcon,
|
|
105
|
+
yml: yamlIcon,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export const FILE_ICONS_BY_NAME: Record<string, string> = {
|
|
109
|
+
'package-lock.json': npmIcon,
|
|
110
|
+
'package.json': npmIcon,
|
|
111
|
+
'tauri.conf.json': tauriIcon,
|
|
112
|
+
'vite.config.ts': viteIcon,
|
|
113
|
+
'vite.config.web.ts': viteIcon,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function getExtension(name: string): string {
|
|
117
|
+
const index = name.lastIndexOf('.')
|
|
118
|
+
return index === -1 ? '' : name.slice(index + 1).toLowerCase()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function materialFolderIcon(name: string, open: boolean): string {
|
|
122
|
+
const icon = FOLDER_ICONS[name] || FOLDER_ICONS[name.toLowerCase()]
|
|
123
|
+
if (!icon) return open ? folderOpenIcon : folderIcon
|
|
124
|
+
return open ? icon.open : icon.closed
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function materialFileIcon(name: string, fileExtension: string): string {
|
|
128
|
+
if (name.endsWith('.config.ts') || name.endsWith('.config.js')) return settingsIcon
|
|
129
|
+
if (name.endsWith('.tsx')) return reactTsIcon
|
|
130
|
+
if (name.endsWith('.jsx')) return reactIcon
|
|
131
|
+
return FILE_ICONS_BY_NAME[name.toLowerCase()] || FILE_ICONS_BY_EXTENSION[fileExtension] || documentIcon
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export { folderIcon, folderOpenIcon }
|
|
135
|
+
|
|
@@ -224,8 +224,13 @@ export default function ChatPanel({
|
|
|
224
224
|
case 'ollama':
|
|
225
225
|
return dynamicOllamaModels
|
|
226
226
|
default:
|
|
227
|
+
if (provider.startsWith('custom:')) {
|
|
228
|
+
const id = provider.replace(/^custom:/, '')
|
|
229
|
+
const cp = (settingsConfig?.customProviders ?? []).find(p => p.id === id)
|
|
230
|
+
return cp?.models.map(m => m.modelId) ?? []
|
|
231
|
+
}
|
|
227
232
|
return []
|
|
228
|
-
|
|
233
|
+
}
|
|
229
234
|
}
|
|
230
235
|
|
|
231
236
|
const activeProvider = status?.activeProvider ?? ''
|
|
@@ -251,6 +256,11 @@ export default function ChatPanel({
|
|
|
251
256
|
case 'ollama':
|
|
252
257
|
return settingsConfig.ollamaModel
|
|
253
258
|
default:
|
|
259
|
+
if (provider.startsWith('custom:')) {
|
|
260
|
+
const id = provider.replace(/^custom:/, '')
|
|
261
|
+
const cp = (settingsConfig?.customProviders ?? []).find(p => p.id === id)
|
|
262
|
+
return (settingsConfig.customModelSelections ?? {})[id] ?? cp?.models[0]?.modelId ?? ''
|
|
263
|
+
}
|
|
254
264
|
return ''
|
|
255
265
|
}
|
|
256
266
|
}
|
|
@@ -382,14 +392,19 @@ export default function ChatPanel({
|
|
|
382
392
|
}
|
|
383
393
|
}
|
|
384
394
|
if (sending) return
|
|
385
|
-
if (!
|
|
386
|
-
|
|
395
|
+
if (!latest?.aiText || latest.id === lastAutoSpokenIdRef.current) return
|
|
396
|
+
|
|
397
|
+
if (!settingsConfig?.enableVoiceReply) {
|
|
398
|
+
lastAutoSpokenIdRef.current = latest.id
|
|
399
|
+
if (voiceMode) {
|
|
400
|
+
scheduleVoiceListen(350)
|
|
401
|
+
}
|
|
387
402
|
return
|
|
388
403
|
}
|
|
389
|
-
|
|
404
|
+
|
|
390
405
|
lastAutoSpokenIdRef.current = latest.id
|
|
391
406
|
speak(latest.aiText)
|
|
392
|
-
}, [interactions, sending, settingsConfig?.enableVoiceReply])
|
|
407
|
+
}, [interactions, sending, settingsConfig?.enableVoiceReply, voiceMode])
|
|
393
408
|
useEffect(() => {
|
|
394
409
|
if (!toolMenuOpen) return
|
|
395
410
|
const closeMenu = (event: MouseEvent) => {
|
|
@@ -1055,6 +1070,11 @@ export default function ChatPanel({
|
|
|
1055
1070
|
else if (provider === 'huggingface') displayName = 'HF'
|
|
1056
1071
|
else if (provider === 'local_openai') displayName = 'Local'
|
|
1057
1072
|
else if (provider === 'ollama') displayName = 'Ollama'
|
|
1073
|
+
else if (provider.startsWith('custom:')) {
|
|
1074
|
+
const id = provider.replace(/^custom:/, '')
|
|
1075
|
+
const cp = (settingsConfig?.customProviders ?? []).find(p => p.id === id)
|
|
1076
|
+
displayName = cp?.displayName || id
|
|
1077
|
+
}
|
|
1058
1078
|
return <option key={provider} value={provider}>{displayName}</option>
|
|
1059
1079
|
})}
|
|
1060
1080
|
</select>
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
cancelChatMessage,
|
|
16
16
|
submitToolApproval,
|
|
17
17
|
listen,
|
|
18
|
+
readClipboardImage,
|
|
18
19
|
type AgentProgress,
|
|
19
20
|
type ChatResponse,
|
|
20
21
|
type ChatSession,
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
type PictureEntry,
|
|
23
24
|
type RuntimeStatus,
|
|
24
25
|
} from '../tauri'
|
|
26
|
+
|
|
25
27
|
import ChatPanel from './ChatPanel'
|
|
26
28
|
import DashboardSidebar, { type DashboardView } from './DashboardSidebar'
|
|
27
29
|
import ImageStudioPanel from './ImageStudioPanel'
|
|
@@ -138,6 +140,7 @@ export default function MintDashboard() {
|
|
|
138
140
|
const [conversationId, setConversationId] = useState(activeConversationId)
|
|
139
141
|
const [chatSessions, setChatSessions] = useState<ChatSession[]>([])
|
|
140
142
|
const chatEnd = useRef<HTMLDivElement | null>(null)
|
|
143
|
+
const lastNativePasteTimeRef = useRef(0)
|
|
141
144
|
const startupReady = (dashboardDataReady && modelReady) || startupTimedOut
|
|
142
145
|
const [proactiveSuggestion, setProactiveSuggestion] = useState<any>(null)
|
|
143
146
|
|
|
@@ -575,7 +578,28 @@ export default function MintDashboard() {
|
|
|
575
578
|
}
|
|
576
579
|
}
|
|
577
580
|
|
|
578
|
-
if (!file)
|
|
581
|
+
if (!file) {
|
|
582
|
+
// Fallback for Tauri desktop app where WebKitGTK/WebView clipboard paste events do not provide file handles
|
|
583
|
+
const now = Date.now()
|
|
584
|
+
if (now - lastNativePasteTimeRef.current > 100) {
|
|
585
|
+
lastNativePasteTimeRef.current = now
|
|
586
|
+
readClipboardImage().then((dataUri) => {
|
|
587
|
+
if (dataUri) {
|
|
588
|
+
const name = 'Pasted image'
|
|
589
|
+
createTrimmedImagePreview(dataUri)
|
|
590
|
+
.catch(() => dataUri)
|
|
591
|
+
.then((previewDataUri) => {
|
|
592
|
+
setImageAttachments((current) => [...current, { dataUri, previewDataUri, name }])
|
|
593
|
+
})
|
|
594
|
+
}
|
|
595
|
+
}).catch((err) => {
|
|
596
|
+
console.warn('Failed to read clipboard image via Tauri API:', err)
|
|
597
|
+
})
|
|
598
|
+
}
|
|
599
|
+
return false
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
|
|
579
603
|
|
|
580
604
|
readImage(file)
|
|
581
605
|
.then((dataUri) => {
|
|
@@ -591,6 +615,7 @@ export default function MintDashboard() {
|
|
|
591
615
|
}
|
|
592
616
|
|
|
593
617
|
|
|
618
|
+
|
|
594
619
|
async function selectDocument(event: ChangeEvent<HTMLInputElement>) {
|
|
595
620
|
const file = event.target.files?.[0]
|
|
596
621
|
if (!file) return
|
|
@@ -736,6 +761,11 @@ export default function MintDashboard() {
|
|
|
736
761
|
else if (provider === 'huggingface') providerName = 'HF'
|
|
737
762
|
else if (provider === 'local_openai') providerName = 'Local'
|
|
738
763
|
else if (provider === 'ollama') providerName = 'Ollama'
|
|
764
|
+
else if (provider.startsWith('custom:')) {
|
|
765
|
+
const id = provider.replace(/^custom:/, '')
|
|
766
|
+
const cp = (settingsConfig?.customProviders ?? []).find((p: any) => p.id === id)
|
|
767
|
+
providerName = cp?.displayName || id
|
|
768
|
+
}
|
|
739
769
|
|
|
740
770
|
if (providerName && providerName === provider) {
|
|
741
771
|
providerName = providerName.charAt(0).toUpperCase() + providerName.slice(1)
|
|
@@ -753,7 +783,14 @@ export default function MintDashboard() {
|
|
|
753
783
|
case 'huggingface': return config.hfModel || 'meta-llama/Meta-Llama-3-8B-Instruct'
|
|
754
784
|
case 'local_openai': return config.localModelName || 'llama3'
|
|
755
785
|
case 'ollama': return config.ollamaModel || 'llama3:latest'
|
|
756
|
-
default:
|
|
786
|
+
default: {
|
|
787
|
+
if (provider.startsWith('custom:')) {
|
|
788
|
+
const id = provider.replace(/^custom:/, '')
|
|
789
|
+
const cp = (config.customProviders ?? []).find((p: any) => p.id === id)
|
|
790
|
+
return (config.customModelSelections ?? {})[id] ?? cp?.models[0]?.modelId ?? ''
|
|
791
|
+
}
|
|
792
|
+
return ''
|
|
793
|
+
}
|
|
757
794
|
}
|
|
758
795
|
}
|
|
759
796
|
|
|
@@ -795,6 +832,12 @@ export default function MintDashboard() {
|
|
|
795
832
|
config.localModelName = modelName
|
|
796
833
|
} else if (provider === 'ollama') {
|
|
797
834
|
config.ollamaModel = modelName
|
|
835
|
+
} else if (provider.startsWith('custom:')) {
|
|
836
|
+
const id = provider.replace(/^custom:/, '')
|
|
837
|
+
config.customModelSelections = {
|
|
838
|
+
...(config.customModelSelections ?? {}),
|
|
839
|
+
[id]: modelName
|
|
840
|
+
}
|
|
798
841
|
}
|
|
799
842
|
await window.settingsApi.saveSettings(config)
|
|
800
843
|
setSettingsConfig(config)
|
|
@@ -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)
|