mulmoclaude 0.1.2 → 0.4.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/bin/mulmoclaude.js +7 -24
- package/client/assets/html2canvas-Cx501zZr-Cv5snK9D.js +5 -0
- package/client/assets/index-CubzmCVK.css +2 -0
- package/client/assets/{index-D8rhwXLq.js → index-DtcyExH9.js} +80 -61
- package/client/assets/{index.es-D4YyL_Dg-BfRHLTZV.js → index.es-D4YyL_Dg-DnizuhIY.js} +5 -5
- package/client/index.html +2 -4
- package/package.json +13 -13
- package/server/agent/attachmentConverter.ts +2 -2
- package/server/agent/config.ts +12 -12
- package/server/agent/index.ts +9 -3
- package/server/agent/mcp-server.ts +19 -19
- package/server/agent/mcp-tools/index.ts +6 -6
- package/server/agent/mcp-tools/x.ts +7 -6
- package/server/agent/prompt.ts +195 -29
- package/server/agent/resumeFailover.ts +5 -5
- package/server/agent/sandboxMounts.ts +10 -10
- package/server/agent/stream.ts +4 -4
- package/server/api/auth/bearerAuth.ts +3 -3
- package/server/api/auth/token.ts +2 -2
- package/server/api/routes/agent.ts +21 -3
- package/server/api/routes/config.ts +1 -1
- package/server/api/routes/files.ts +22 -21
- package/server/api/routes/html.ts +2 -2
- package/server/api/routes/image.ts +7 -7
- package/server/api/routes/mulmo-script.ts +33 -31
- package/server/api/routes/pdf.ts +2 -2
- package/server/api/routes/plugins.ts +16 -6
- package/server/api/routes/roles.ts +2 -2
- package/server/api/routes/scheduler.ts +14 -12
- package/server/api/routes/schedulerHandlers.ts +12 -12
- package/server/api/routes/schedulerTasks.ts +19 -17
- package/server/api/routes/sessions.ts +26 -26
- package/server/api/routes/sessionsCursor.ts +4 -4
- package/server/api/routes/skills.ts +5 -5
- package/server/api/routes/sources.ts +3 -3
- package/server/api/routes/todosColumnsHandlers.ts +30 -30
- package/server/api/routes/todosHandlers.ts +1 -1
- package/server/api/routes/todosItemsHandlers.ts +14 -14
- package/server/api/routes/wiki.ts +36 -22
- package/server/api/sandboxStatus.ts +1 -1
- package/server/events/notifications.ts +6 -6
- package/server/events/pub-sub/index.ts +3 -3
- package/server/events/relay-client.ts +17 -16
- package/server/events/scheduler-adapter.ts +20 -20
- package/server/events/session-store/index.ts +10 -10
- package/server/events/task-manager/index.ts +7 -7
- package/server/index.ts +59 -65
- package/server/system/config.ts +5 -5
- package/server/system/credentials.ts +7 -5
- package/server/system/env.ts +5 -5
- package/server/utils/date.ts +18 -18
- package/server/utils/files/atomic.ts +16 -16
- package/server/utils/files/html-io.ts +5 -5
- package/server/utils/files/image-store.ts +19 -8
- package/server/utils/files/journal-io.ts +4 -4
- package/server/utils/files/json.ts +5 -5
- package/server/utils/files/markdown-store.ts +4 -4
- package/server/utils/files/naming.ts +2 -2
- package/server/utils/files/reference-dirs-io.ts +3 -3
- package/server/utils/files/roles-io.ts +12 -12
- package/server/utils/files/safe.ts +14 -14
- package/server/utils/files/scheduler-io.ts +5 -5
- package/server/utils/files/scheduler-overrides-io.ts +2 -2
- package/server/utils/files/session-io.ts +35 -35
- package/server/utils/files/spreadsheet-store.ts +7 -7
- package/server/utils/files/todos-io.ts +9 -9
- package/server/utils/files/user-tasks-io.ts +5 -5
- package/server/utils/files/workspace-io.ts +12 -12
- package/server/utils/gemini.ts +2 -2
- package/server/utils/gitignore.ts +9 -9
- package/server/utils/json.ts +5 -5
- package/server/utils/logBackgroundError.ts +12 -3
- package/server/utils/markdown.ts +5 -5
- package/server/utils/port.d.mts +6 -0
- package/server/utils/port.mjs +48 -0
- package/server/utils/request.ts +12 -6
- package/server/utils/spawn.ts +1 -1
- package/server/utils/types.ts +2 -2
- package/server/workspace/chat-index/indexer.ts +15 -15
- package/server/workspace/chat-index/summarizer.ts +4 -4
- package/server/workspace/custom-dirs.ts +16 -16
- package/server/workspace/journal/archivist.ts +35 -35
- package/server/workspace/journal/dailyPass.ts +31 -28
- package/server/workspace/journal/diff.ts +2 -2
- package/server/workspace/journal/index.ts +4 -4
- package/server/workspace/journal/indexFile.ts +29 -25
- package/server/workspace/journal/optimizationPass.ts +2 -2
- package/server/workspace/journal/state.ts +6 -6
- package/server/workspace/paths.ts +3 -3
- package/server/workspace/reference-dirs.ts +20 -20
- package/server/workspace/roles.ts +6 -6
- package/server/workspace/skills/discovery.ts +4 -4
- package/server/workspace/skills/parser.ts +6 -6
- package/server/workspace/skills/scheduler.ts +3 -3
- package/server/workspace/skills/user-tasks.ts +34 -34
- package/server/workspace/skills/writer.ts +3 -3
- package/server/workspace/sources/arxivDiscovery.ts +10 -10
- package/server/workspace/sources/classifier.ts +7 -7
- package/server/workspace/sources/fetchers/arxiv.ts +7 -7
- package/server/workspace/sources/fetchers/githubIssues.ts +7 -7
- package/server/workspace/sources/fetchers/githubReleases.ts +7 -7
- package/server/workspace/sources/fetchers/rss.ts +5 -5
- package/server/workspace/sources/fetchers/rssParser.ts +4 -4
- package/server/workspace/sources/interests.ts +12 -12
- package/server/workspace/sources/paths.ts +6 -6
- package/server/workspace/sources/pipeline/fetch.ts +36 -13
- package/server/workspace/sources/pipeline/index.ts +8 -13
- package/server/workspace/sources/pipeline/notify.ts +3 -3
- package/server/workspace/sources/pipeline/plan.ts +15 -13
- package/server/workspace/sources/pipeline/write.ts +5 -5
- package/server/workspace/sources/rateLimiter.ts +1 -1
- package/server/workspace/sources/registry.ts +16 -16
- package/server/workspace/sources/robots.ts +14 -14
- package/server/workspace/sources/sourceState.ts +17 -10
- package/server/workspace/sources/types.ts +9 -0
- package/server/workspace/sources/urls.ts +1 -1
- package/server/workspace/tool-trace/classify.ts +4 -4
- package/server/workspace/tool-trace/index.ts +1 -1
- package/server/workspace/tool-trace/writeSearch.ts +26 -16
- package/server/workspace/wiki-backlinks/index.ts +8 -8
- package/server/workspace/wiki-backlinks/sessionBacklinks.ts +15 -15
- package/server/workspace/workspace.ts +7 -7
- package/src/App.vue +315 -141
- package/src/components/CanvasViewToggle.vue +10 -7
- package/src/components/ChatInput.vue +67 -33
- package/src/components/FileContentHeader.vue +7 -4
- package/src/components/FileContentRenderer.vue +20 -6
- package/src/components/FileTree.vue +6 -3
- package/src/components/FileTreePane.vue +11 -8
- package/src/components/FilesView.vue +5 -3
- package/src/components/LockStatusPopup.vue +17 -14
- package/src/components/NotificationBell.vue +14 -5
- package/src/components/NotificationToast.vue +6 -3
- package/src/components/PluginLauncher.vue +19 -56
- package/src/components/RightSidebar.vue +13 -10
- package/src/components/RoleSelector.vue +2 -2
- package/src/components/SessionHistoryPanel.vue +38 -34
- package/src/components/SessionTabBar.vue +8 -10
- package/src/components/SettingsMcpTab.vue +49 -36
- package/src/components/SettingsModal.vue +24 -22
- package/src/components/SettingsReferenceDirsTab.vue +39 -34
- package/src/components/SettingsWorkspaceDirsTab.vue +37 -27
- package/src/components/SidebarHeader.vue +25 -4
- package/src/components/StackView.vue +4 -1
- package/src/components/SuggestionsPanel.vue +7 -4
- package/src/components/TodoExplorer.vue +26 -15
- package/src/components/ToolResultsPanel.vue +27 -13
- package/src/components/todo/TodoAddDialog.vue +19 -14
- package/src/components/todo/TodoEditDialog.vue +7 -2
- package/src/components/todo/TodoEditPanel.vue +17 -12
- package/src/components/todo/TodoKanbanView.vue +10 -5
- package/src/components/todo/TodoListView.vue +10 -7
- package/src/components/todo/TodoTableView.vue +5 -2
- package/src/composables/useAppApi.ts +9 -0
- package/src/composables/useClickOutside.ts +2 -2
- package/src/composables/useDynamicFavicon.ts +172 -37
- package/src/composables/useEventListeners.ts +7 -8
- package/src/composables/useFaviconState.ts +13 -2
- package/src/composables/useFileSelection.ts +24 -6
- package/src/composables/useFreshPluginData.ts +3 -3
- package/src/composables/useKeyNavigation.ts +11 -11
- package/src/composables/useLayoutMode.ts +32 -0
- package/src/composables/useMcpTools.ts +2 -2
- package/src/composables/useNotifications.ts +3 -3
- package/src/composables/usePdfDownload.ts +4 -4
- package/src/composables/usePendingCalls.ts +1 -1
- package/src/composables/usePubSub.ts +10 -10
- package/src/composables/useRoles.ts +1 -1
- package/src/composables/useSandboxStatus.ts +1 -1
- package/src/composables/useSessionDerived.ts +3 -3
- package/src/composables/useSessionHistory.ts +7 -17
- package/src/composables/useSessionSync.ts +8 -8
- package/src/composables/useViewLayout.ts +20 -34
- package/src/config/roles.ts +2 -2
- package/src/lang/de.ts +536 -0
- package/src/lang/en.ts +558 -0
- package/src/lang/es.ts +543 -0
- package/src/lang/fr.ts +536 -0
- package/src/lang/ja.ts +536 -0
- package/src/lang/ko.ts +540 -0
- package/src/lang/pt-BR.ts +534 -0
- package/src/lang/zh.ts +537 -0
- package/src/lib/vue-i18n.ts +97 -0
- package/src/main.ts +2 -0
- package/src/plugins/canvas/View.vue +102 -186
- package/src/plugins/canvas/definition.ts +0 -8
- package/src/plugins/chart/Preview.vue +5 -5
- package/src/plugins/chart/View.vue +9 -4
- package/src/plugins/manageRoles/Preview.vue +4 -1
- package/src/plugins/manageRoles/View.vue +59 -43
- package/src/plugins/manageSkills/Preview.vue +8 -3
- package/src/plugins/manageSkills/View.vue +29 -25
- package/src/plugins/manageSource/Preview.vue +2 -2
- package/src/plugins/manageSource/View.vue +73 -52
- package/src/plugins/markdown/Preview.vue +1 -1
- package/src/plugins/markdown/View.vue +26 -36
- package/src/plugins/presentHtml/Preview.vue +1 -1
- package/src/plugins/presentHtml/View.vue +7 -4
- package/src/plugins/presentHtml/helpers.ts +8 -8
- package/src/plugins/presentMulmoScript/Preview.vue +1 -1
- package/src/plugins/presentMulmoScript/View.vue +40 -30
- package/src/plugins/presentMulmoScript/helpers.ts +1 -1
- package/src/plugins/scheduler/Preview.vue +13 -10
- package/src/plugins/scheduler/TasksTab.vue +57 -28
- package/src/plugins/scheduler/View.vue +28 -19
- package/src/plugins/scheduler/formatSchedule.ts +93 -0
- package/src/plugins/spreadsheet/Preview.vue +8 -3
- package/src/plugins/spreadsheet/View.vue +21 -12
- package/src/plugins/textResponse/Preview.vue +15 -58
- package/src/plugins/textResponse/View.vue +29 -9
- package/src/plugins/todo/Preview.vue +13 -8
- package/src/plugins/todo/View.vue +38 -24
- package/src/plugins/todo/composables/useTodos.ts +5 -5
- package/src/plugins/ui-image/ImagePreview.vue +6 -3
- package/src/plugins/ui-image/ImageView.vue +7 -4
- package/src/plugins/wiki/Preview.vue +10 -7
- package/src/plugins/wiki/View.vue +202 -81
- package/src/plugins/wiki/helpers.ts +4 -4
- package/src/plugins/wiki/route.ts +112 -0
- package/src/router/guards.ts +46 -28
- package/src/router/index.ts +41 -26
- package/src/types/session.ts +4 -3
- package/src/types/vue-i18n.d.ts +20 -0
- package/src/utils/agent/request.ts +22 -3
- package/src/utils/canvas/layoutMode.ts +26 -0
- package/src/utils/dom/scrollable.ts +2 -2
- package/src/utils/files/expandedDirs.ts +1 -1
- package/src/utils/files/sortChildren.ts +6 -6
- package/src/utils/format/frontmatter.ts +6 -6
- package/src/utils/image/cacheBust.ts +16 -0
- package/src/utils/image/resolve.ts +16 -0
- package/src/utils/image/rewriteMarkdownImageRefs.ts +5 -5
- package/src/utils/markdown/extractFirstH1.ts +2 -2
- package/src/utils/path/relativeLink.ts +15 -15
- package/src/utils/path/workspaceLinkRouter.ts +81 -0
- package/src/utils/role/icon.ts +2 -2
- package/src/utils/role/merge.ts +2 -2
- package/src/utils/role/plugins.ts +1 -1
- package/src/utils/session/sessionFactory.ts +2 -2
- package/src/utils/session/sessionHelpers.ts +2 -2
- package/src/utils/tools/dedup.ts +4 -4
- package/src/utils/tools/result.ts +3 -3
- package/src/utils/types.ts +2 -2
- package/src/vite-env.d.ts +9 -0
- package/client/assets/chunk-vKJrgz-R-C_I3GbVV.js +0 -1
- package/client/assets/html2canvas-Cx501zZr-BF5dYYkY.js +0 -5
- package/client/assets/index-KNLBjwuh.css +0 -1
- package/client/assets/typeof-DBp4T-Ny-BC0P-2DM.js +0 -1
- package/src/composables/useCanvasViewMode.ts +0 -121
- package/src/utils/canvas/viewMode.ts +0 -46
- /package/client/assets/{purify.es-Fx1Nqyry-PeS5RUhs.js → purify.es-Fx1Nqyry-BwJECkqS.js} +0 -0
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
@click.stop
|
|
10
10
|
>
|
|
11
11
|
<div class="px-5 py-4 border-b border-gray-200 flex items-center justify-between">
|
|
12
|
-
<h2 id="settings-modal-title" class="text-base font-semibold text-gray-900">
|
|
13
|
-
<button class="text-gray-400 hover:text-gray-700" title="
|
|
12
|
+
<h2 id="settings-modal-title" class="text-base font-semibold text-gray-900">{{ t("settingsModal.title") }}</h2>
|
|
13
|
+
<button class="text-gray-400 hover:text-gray-700" :title="t('common.close')" data-testid="settings-close-btn" @click="close">
|
|
14
14
|
<span class="material-icons">close</span>
|
|
15
15
|
</button>
|
|
16
16
|
</div>
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
data-testid="settings-tab-tools"
|
|
23
23
|
@click="activeTab = 'tools'"
|
|
24
24
|
>
|
|
25
|
-
|
|
25
|
+
{{ t("settingsModal.tabs.tools") }}
|
|
26
26
|
</button>
|
|
27
27
|
<button
|
|
28
28
|
class="px-3 py-2 text-sm border-b-2"
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
data-testid="settings-tab-mcp"
|
|
31
31
|
@click="activeTab = 'mcp'"
|
|
32
32
|
>
|
|
33
|
-
|
|
33
|
+
{{ t("settingsModal.tabs.mcp") }}
|
|
34
34
|
</button>
|
|
35
35
|
<button
|
|
36
36
|
class="px-3 py-2 text-sm border-b-2"
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
data-testid="settings-tab-dirs"
|
|
39
39
|
@click="activeTab = 'dirs'"
|
|
40
40
|
>
|
|
41
|
-
|
|
41
|
+
{{ t("settingsModal.tabs.dirs") }}
|
|
42
42
|
</button>
|
|
43
43
|
<button
|
|
44
44
|
class="px-3 py-2 text-sm border-b-2"
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
data-testid="settings-tab-refs"
|
|
47
47
|
@click="activeTab = 'refs'"
|
|
48
48
|
>
|
|
49
|
-
|
|
49
|
+
{{ t("settingsModal.tabs.refs") }}
|
|
50
50
|
</button>
|
|
51
51
|
</div>
|
|
52
52
|
|
|
@@ -56,13 +56,12 @@
|
|
|
56
56
|
</div>
|
|
57
57
|
|
|
58
58
|
<div v-if="activeTab === 'tools'" class="space-y-3">
|
|
59
|
-
<p class="text-xs text-gray-600 leading-relaxed">
|
|
60
|
-
|
|
61
|
-
<code class="bg-gray-100 px-1 rounded"
|
|
62
|
-
|
|
63
|
-
</p>
|
|
59
|
+
<i18n-t keypath="settingsToolsTab.explanation" tag="p" class="text-xs text-gray-600 leading-relaxed">
|
|
60
|
+
<template #allowedTools><code class="bg-gray-100 px-1 rounded">--allowedTools</code></template>
|
|
61
|
+
<template #claudeMcp><code class="bg-gray-100 px-1 rounded">claude mcp</code></template>
|
|
62
|
+
</i18n-t>
|
|
64
63
|
<label class="block">
|
|
65
|
-
<span class="text-xs font-semibold text-gray-700">
|
|
64
|
+
<span class="text-xs font-semibold text-gray-700">{{ t("settingsModal.toolNamesLabel") }}</span>
|
|
66
65
|
<textarea
|
|
67
66
|
v-model="toolsText"
|
|
68
67
|
class="mt-1 w-full h-48 px-2 py-1.5 text-sm font-mono border border-gray-300 rounded focus:outline-none focus:border-blue-400"
|
|
@@ -72,8 +71,8 @@
|
|
|
72
71
|
></textarea>
|
|
73
72
|
</label>
|
|
74
73
|
<p v-if="invalidToolNames.length > 0" class="text-xs text-amber-700">
|
|
75
|
-
|
|
76
|
-
<code class="bg-gray-100 px-1 rounded">mcp__</code>)
|
|
74
|
+
{{ t("settingsModal.invalidToolNamesPrefix") }}
|
|
75
|
+
<code class="bg-gray-100 px-1 rounded">mcp__</code>{{ t("settingsModal.invalidToolNamesSuffix") }}
|
|
77
76
|
{{ invalidToolNames.join(", ") }}
|
|
78
77
|
</p>
|
|
79
78
|
</div>
|
|
@@ -85,7 +84,7 @@
|
|
|
85
84
|
role="alert"
|
|
86
85
|
data-testid="mcp-tools-error"
|
|
87
86
|
>
|
|
88
|
-
|
|
87
|
+
{{ t("settingsModal.mcpToolsError", { error: mcpToolsError }) }}
|
|
89
88
|
</div>
|
|
90
89
|
<SettingsMcpTab
|
|
91
90
|
ref="mcpTabRef"
|
|
@@ -106,19 +105,19 @@
|
|
|
106
105
|
<span v-if="statusMessage" class="text-xs" :class="statusError ? 'text-red-600' : 'text-green-600'" data-testid="settings-status">
|
|
107
106
|
{{ statusMessage }}
|
|
108
107
|
</span>
|
|
109
|
-
<span v-else class="text-xs text-gray-500">
|
|
108
|
+
<span v-else class="text-xs text-gray-500"> {{ t("settingsModal.changesHint") }} </span>
|
|
110
109
|
<div class="flex gap-2">
|
|
111
110
|
<button class="px-3 py-1.5 text-sm rounded border border-gray-300 text-gray-600 hover:bg-gray-50" data-testid="settings-cancel-btn" @click="close">
|
|
112
|
-
|
|
111
|
+
{{ t("common.cancel") }}
|
|
113
112
|
</button>
|
|
114
113
|
<button
|
|
115
114
|
class="px-3 py-1.5 text-sm rounded bg-blue-500 text-white hover:bg-blue-600 disabled:bg-gray-300 disabled:cursor-not-allowed"
|
|
116
115
|
:disabled="saving || loading || !!loadError"
|
|
117
|
-
:title="loadError ? '
|
|
116
|
+
:title="loadError ? t('settingsModal.cannotSaveTooltip') : undefined"
|
|
118
117
|
data-testid="settings-save-btn"
|
|
119
118
|
@click="save"
|
|
120
119
|
>
|
|
121
|
-
{{ saving ? "
|
|
120
|
+
{{ saving ? t("settingsModal.saving") : loading ? t("settingsModal.loadingLabel") : t("common.save") }}
|
|
122
121
|
</button>
|
|
123
122
|
</div>
|
|
124
123
|
</div>
|
|
@@ -128,6 +127,7 @@
|
|
|
128
127
|
|
|
129
128
|
<script setup lang="ts">
|
|
130
129
|
import { computed, ref, watch } from "vue";
|
|
130
|
+
import { useI18n } from "vue-i18n";
|
|
131
131
|
import SettingsMcpTab from "./SettingsMcpTab.vue";
|
|
132
132
|
import SettingsWorkspaceDirsTab from "./SettingsWorkspaceDirsTab.vue";
|
|
133
133
|
import SettingsReferenceDirsTab from "./SettingsReferenceDirsTab.vue";
|
|
@@ -135,6 +135,8 @@ import type { McpServerEntry } from "./SettingsMcpTab.vue";
|
|
|
135
135
|
import { apiGet, apiPut } from "../utils/api";
|
|
136
136
|
import { API_ROUTES } from "../config/apiRoutes";
|
|
137
137
|
|
|
138
|
+
const { t } = useI18n();
|
|
139
|
+
|
|
138
140
|
interface Props {
|
|
139
141
|
open: boolean;
|
|
140
142
|
dockerMode?: boolean;
|
|
@@ -177,11 +179,11 @@ let loadToken = 0;
|
|
|
177
179
|
const parsedToolNames = computed(() =>
|
|
178
180
|
toolsText.value
|
|
179
181
|
.split("\n")
|
|
180
|
-
.map((
|
|
181
|
-
.filter((
|
|
182
|
+
.map((line) => line.trim())
|
|
183
|
+
.filter((line) => line.length > 0),
|
|
182
184
|
);
|
|
183
185
|
|
|
184
|
-
const invalidToolNames = computed(() => parsedToolNames.value.filter((
|
|
186
|
+
const invalidToolNames = computed(() => parsedToolNames.value.filter((name) => !name.startsWith("mcp__") && !isBuiltIn(name)));
|
|
185
187
|
|
|
186
188
|
function isBuiltIn(name: string): boolean {
|
|
187
189
|
return ["Bash", "Read", "Write", "Edit", "Glob", "Grep", "WebFetch", "WebSearch"].includes(name);
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ref, onMounted } from "vue";
|
|
3
|
+
import { useI18n } from "vue-i18n";
|
|
3
4
|
import { apiGet, apiPut } from "../utils/api";
|
|
4
5
|
import { API_ROUTES } from "../config/apiRoutes";
|
|
5
6
|
|
|
7
|
+
const { t } = useI18n();
|
|
8
|
+
|
|
6
9
|
interface RefDirEntry {
|
|
7
10
|
hostPath: string;
|
|
8
11
|
label: string;
|
|
9
12
|
}
|
|
10
13
|
|
|
14
|
+
// See note in SettingsWorkspaceDirsTab — mirrored here.
|
|
15
|
+
type SaveStatus = { kind: "ok" } | { kind: "error"; message: string };
|
|
16
|
+
|
|
11
17
|
const dirs = ref<RefDirEntry[]>([]);
|
|
12
18
|
const loading = ref(true);
|
|
13
19
|
const error = ref("");
|
|
14
20
|
const saving = ref(false);
|
|
15
|
-
const saveStatus = ref(
|
|
21
|
+
const saveStatus = ref<SaveStatus | null>(null);
|
|
16
22
|
|
|
17
23
|
const draftPath = ref("");
|
|
18
24
|
const draftLabel = ref("");
|
|
@@ -32,50 +38,50 @@ async function load(): Promise<void> {
|
|
|
32
38
|
|
|
33
39
|
async function save(): Promise<void> {
|
|
34
40
|
saving.value = true;
|
|
35
|
-
saveStatus.value =
|
|
41
|
+
saveStatus.value = null;
|
|
36
42
|
const result = await apiPut<{ dirs: RefDirEntry[] }>(API_ROUTES.config.referenceDirs, { dirs: dirs.value });
|
|
37
43
|
saving.value = false;
|
|
38
44
|
if (!result.ok) {
|
|
39
|
-
saveStatus.value = result.error;
|
|
45
|
+
saveStatus.value = { kind: "error", message: result.error };
|
|
40
46
|
return;
|
|
41
47
|
}
|
|
42
48
|
dirs.value = result.data.dirs;
|
|
43
|
-
saveStatus.value = "
|
|
49
|
+
saveStatus.value = { kind: "ok" };
|
|
44
50
|
setTimeout(() => {
|
|
45
|
-
saveStatus.value =
|
|
51
|
+
saveStatus.value = null;
|
|
46
52
|
}, 2000);
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
function addEntry(): void {
|
|
50
56
|
draftError.value = "";
|
|
51
|
-
const
|
|
52
|
-
if (!
|
|
53
|
-
draftError.value = "
|
|
57
|
+
const path = draftPath.value.trim();
|
|
58
|
+
if (!path) {
|
|
59
|
+
draftError.value = t("settingsReferenceDirs.errPathRequired");
|
|
54
60
|
return;
|
|
55
61
|
}
|
|
56
|
-
if (!
|
|
57
|
-
draftError.value = "
|
|
62
|
+
if (!path.startsWith("/") && !path.startsWith("~/")) {
|
|
63
|
+
draftError.value = t("settingsReferenceDirs.errMustBeAbsolute");
|
|
58
64
|
return;
|
|
59
65
|
}
|
|
60
66
|
// Normalize: trim trailing slashes for consistent comparison
|
|
61
|
-
let normalized =
|
|
67
|
+
let normalized = path;
|
|
62
68
|
while (normalized.length > 1 && normalized.endsWith("/")) {
|
|
63
69
|
normalized = normalized.slice(0, -1);
|
|
64
70
|
}
|
|
65
|
-
const stripSlash = (
|
|
66
|
-
let
|
|
67
|
-
while (
|
|
68
|
-
return
|
|
71
|
+
const stripSlash = (str: string): string => {
|
|
72
|
+
let cleaned = str;
|
|
73
|
+
while (cleaned.length > 1 && cleaned.endsWith("/")) cleaned = cleaned.slice(0, -1);
|
|
74
|
+
return cleaned;
|
|
69
75
|
};
|
|
70
|
-
if (dirs.value.some((
|
|
71
|
-
draftError.value = "
|
|
76
|
+
if (dirs.value.some((dir) => stripSlash(dir.hostPath) === normalized)) {
|
|
77
|
+
draftError.value = t("settingsReferenceDirs.errAlreadyExists");
|
|
72
78
|
return;
|
|
73
79
|
}
|
|
74
80
|
const lastSeg = normalized.split("/").pop();
|
|
75
81
|
const label = draftLabel.value.trim() || lastSeg || normalized;
|
|
76
82
|
// Reject duplicate labels — @ref/<label> routing requires uniqueness
|
|
77
|
-
if (dirs.value.some((
|
|
78
|
-
draftError.value =
|
|
83
|
+
if (dirs.value.some((dir) => dir.label === label)) {
|
|
84
|
+
draftError.value = t("settingsReferenceDirs.errLabelConflict", { label });
|
|
79
85
|
return;
|
|
80
86
|
}
|
|
81
87
|
dirs.value.push({ hostPath: normalized, label });
|
|
@@ -92,20 +98,17 @@ onMounted(load);
|
|
|
92
98
|
|
|
93
99
|
<template>
|
|
94
100
|
<div class="space-y-3">
|
|
95
|
-
<p class="text-xs text-gray-600 leading-relaxed">
|
|
96
|
-
External directories Claude can read but not modify. In Docker mode, these are mounted read-only. Useful for referencing Obsidian vaults, project code, or
|
|
97
|
-
document folders.
|
|
98
|
-
</p>
|
|
101
|
+
<p class="text-xs text-gray-600 leading-relaxed">{{ t("settingsReferenceDirs.explanation") }}</p>
|
|
99
102
|
|
|
100
103
|
<!-- Loading -->
|
|
101
|
-
<div v-if="loading" class="text-sm text-gray-400">
|
|
104
|
+
<div v-if="loading" class="text-sm text-gray-400">{{ t("common.loading") }}</div>
|
|
102
105
|
<div v-else-if="error" class="text-sm text-red-600 bg-red-50 rounded px-3 py-2">
|
|
103
106
|
{{ error }}
|
|
104
107
|
</div>
|
|
105
108
|
|
|
106
109
|
<template v-else>
|
|
107
110
|
<!-- Existing entries -->
|
|
108
|
-
<div v-if="dirs.length === 0" class="text-sm text-gray-400">
|
|
111
|
+
<div v-if="dirs.length === 0" class="text-sm text-gray-400">{{ t("settingsReferenceDirs.noEntries") }}</div>
|
|
109
112
|
<div v-else class="space-y-1.5">
|
|
110
113
|
<div
|
|
111
114
|
v-for="(dir, i) in dirs"
|
|
@@ -122,8 +125,8 @@ onMounted(load);
|
|
|
122
125
|
{{ dir.label }}
|
|
123
126
|
</div>
|
|
124
127
|
</div>
|
|
125
|
-
<span class="text-[10px] px-1.5 py-0.5 rounded bg-blue-100 text-blue-600 shrink-0">
|
|
126
|
-
<button class="text-gray-300 hover:text-red-500 shrink-0" title="
|
|
128
|
+
<span class="text-[10px] px-1.5 py-0.5 rounded bg-blue-100 text-blue-600 shrink-0">{{ t("settingsReferenceDirs.readOnlyBadge") }}</span>
|
|
129
|
+
<button class="text-gray-300 hover:text-red-500 shrink-0" :title="t('common.remove')" data-testid="reference-dir-remove-btn" @click="removeEntry(i)">
|
|
127
130
|
<span class="material-icons text-sm">close</span>
|
|
128
131
|
</button>
|
|
129
132
|
</div>
|
|
@@ -131,11 +134,11 @@ onMounted(load);
|
|
|
131
134
|
|
|
132
135
|
<!-- Add new -->
|
|
133
136
|
<div class="border border-gray-200 rounded p-2 space-y-2">
|
|
134
|
-
<div class="text-xs font-semibold text-gray-600">
|
|
137
|
+
<div class="text-xs font-semibold text-gray-600">{{ t("settingsReferenceDirs.addDirTitle") }}</div>
|
|
135
138
|
<input
|
|
136
139
|
v-model="draftPath"
|
|
137
140
|
class="w-full px-2 py-1 text-xs font-mono border border-gray-300 rounded focus:outline-none focus:border-blue-400"
|
|
138
|
-
placeholder="
|
|
141
|
+
:placeholder="t('settingsReferenceDirs.pathPlaceholder')"
|
|
139
142
|
data-testid="reference-dir-path-input"
|
|
140
143
|
@keydown.enter="addEntry"
|
|
141
144
|
@keydown.stop
|
|
@@ -143,13 +146,15 @@ onMounted(load);
|
|
|
143
146
|
<input
|
|
144
147
|
v-model="draftLabel"
|
|
145
148
|
class="w-full px-2 py-1 text-xs border border-gray-300 rounded focus:outline-none focus:border-blue-400"
|
|
146
|
-
placeholder="
|
|
149
|
+
:placeholder="t('settingsReferenceDirs.labelPlaceholder')"
|
|
147
150
|
data-testid="reference-dir-label-input"
|
|
148
151
|
@keydown.enter="addEntry"
|
|
149
152
|
@keydown.stop
|
|
150
153
|
/>
|
|
151
154
|
<div class="flex items-center gap-2">
|
|
152
|
-
<button class="px-2 py-1 text-xs rounded bg-blue-500 text-white hover:bg-blue-600" data-testid="reference-dir-add-btn" @click="addEntry">
|
|
155
|
+
<button class="px-2 py-1 text-xs rounded bg-blue-500 text-white hover:bg-blue-600" data-testid="reference-dir-add-btn" @click="addEntry">
|
|
156
|
+
{{ t("common.add") }}
|
|
157
|
+
</button>
|
|
153
158
|
<span v-if="draftError" class="text-xs text-red-500">{{ draftError }}</span>
|
|
154
159
|
</div>
|
|
155
160
|
</div>
|
|
@@ -162,10 +167,10 @@ onMounted(load);
|
|
|
162
167
|
data-testid="reference-dirs-save-btn"
|
|
163
168
|
@click="save"
|
|
164
169
|
>
|
|
165
|
-
{{ saving ? "
|
|
170
|
+
{{ saving ? t("common.saving") : t("common.save") }}
|
|
166
171
|
</button>
|
|
167
|
-
<span v-if="saveStatus" class="text-xs" :class="saveStatus === '
|
|
168
|
-
{{ saveStatus }}
|
|
172
|
+
<span v-if="saveStatus" class="text-xs" :class="saveStatus.kind === 'ok' ? 'text-green-600' : 'text-red-600'">
|
|
173
|
+
{{ saveStatus.kind === "ok" ? t("common.saved") : saveStatus.message }}
|
|
169
174
|
</span>
|
|
170
175
|
</div>
|
|
171
176
|
</template>
|
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ref, onMounted } from "vue";
|
|
3
|
+
import { useI18n } from "vue-i18n";
|
|
3
4
|
import { apiGet, apiPut } from "../utils/api";
|
|
4
5
|
import { API_ROUTES } from "../config/apiRoutes";
|
|
5
6
|
|
|
7
|
+
const { t } = useI18n();
|
|
8
|
+
|
|
6
9
|
interface DirEntry {
|
|
7
10
|
path: string;
|
|
8
11
|
description: string;
|
|
9
12
|
structure: "flat" | "by-name" | "by-date";
|
|
10
13
|
}
|
|
11
14
|
|
|
15
|
+
// Typed save status so the template can drive colour off `kind`
|
|
16
|
+
// instead of string-comparing a localised "Saved" — previously the
|
|
17
|
+
// green-on-success styling was coupled to the English literal.
|
|
18
|
+
type SaveStatus = { kind: "ok" } | { kind: "error"; message: string };
|
|
19
|
+
|
|
12
20
|
const dirs = ref<DirEntry[]>([]);
|
|
13
21
|
const loading = ref(true);
|
|
14
22
|
const error = ref("");
|
|
15
23
|
const saving = ref(false);
|
|
16
|
-
const saveStatus = ref(
|
|
24
|
+
const saveStatus = ref<SaveStatus | null>(null);
|
|
17
25
|
|
|
18
26
|
// Draft for new entry
|
|
19
27
|
const draftPath = ref("");
|
|
@@ -35,37 +43,37 @@ async function load(): Promise<void> {
|
|
|
35
43
|
|
|
36
44
|
async function save(): Promise<void> {
|
|
37
45
|
saving.value = true;
|
|
38
|
-
saveStatus.value =
|
|
46
|
+
saveStatus.value = null;
|
|
39
47
|
const result = await apiPut<{ dirs: DirEntry[] }>(API_ROUTES.config.workspaceDirs, { dirs: dirs.value });
|
|
40
48
|
saving.value = false;
|
|
41
49
|
if (!result.ok) {
|
|
42
|
-
saveStatus.value = result.error;
|
|
50
|
+
saveStatus.value = { kind: "error", message: result.error };
|
|
43
51
|
return;
|
|
44
52
|
}
|
|
45
53
|
dirs.value = result.data.dirs;
|
|
46
|
-
saveStatus.value = "
|
|
54
|
+
saveStatus.value = { kind: "ok" };
|
|
47
55
|
setTimeout(() => {
|
|
48
|
-
saveStatus.value =
|
|
56
|
+
saveStatus.value = null;
|
|
49
57
|
}, 2000);
|
|
50
58
|
}
|
|
51
59
|
|
|
52
60
|
function addEntry(): void {
|
|
53
61
|
draftError.value = "";
|
|
54
|
-
const
|
|
55
|
-
if (!
|
|
56
|
-
draftError.value = "
|
|
62
|
+
const path = draftPath.value.trim();
|
|
63
|
+
if (!path) {
|
|
64
|
+
draftError.value = t("settingsWorkspaceDirs.errPathRequired");
|
|
57
65
|
return;
|
|
58
66
|
}
|
|
59
|
-
if (!
|
|
60
|
-
draftError.value = "
|
|
67
|
+
if (!path.startsWith("data/") && !path.startsWith("artifacts/")) {
|
|
68
|
+
draftError.value = t("settingsWorkspaceDirs.errMustStartWith");
|
|
61
69
|
return;
|
|
62
70
|
}
|
|
63
|
-
if (dirs.value.some((
|
|
64
|
-
draftError.value = "
|
|
71
|
+
if (dirs.value.some((dir) => dir.path === path)) {
|
|
72
|
+
draftError.value = t("settingsWorkspaceDirs.errAlreadyExists");
|
|
65
73
|
return;
|
|
66
74
|
}
|
|
67
75
|
dirs.value.push({
|
|
68
|
-
path
|
|
76
|
+
path,
|
|
69
77
|
description: draftDescription.value.trim(),
|
|
70
78
|
structure: draftStructure.value,
|
|
71
79
|
});
|
|
@@ -83,20 +91,20 @@ onMounted(load);
|
|
|
83
91
|
|
|
84
92
|
<template>
|
|
85
93
|
<div class="space-y-3">
|
|
86
|
-
<p class="text-xs text-gray-600 leading-relaxed">
|
|
87
|
-
|
|
88
|
-
<
|
|
89
|
-
</
|
|
94
|
+
<i18n-t keypath="settingsWorkspaceDirs.explanation" tag="p" class="text-xs text-gray-600 leading-relaxed">
|
|
95
|
+
<template #dataDir><code class="bg-gray-100 px-1 rounded">data/</code></template>
|
|
96
|
+
<template #artifactsDir><code class="bg-gray-100 px-1 rounded">artifacts/</code></template>
|
|
97
|
+
</i18n-t>
|
|
90
98
|
|
|
91
99
|
<!-- Loading -->
|
|
92
|
-
<div v-if="loading" class="text-sm text-gray-400">
|
|
100
|
+
<div v-if="loading" class="text-sm text-gray-400">{{ t("common.loading") }}</div>
|
|
93
101
|
<div v-else-if="error" class="text-sm text-red-600 bg-red-50 rounded px-3 py-2">
|
|
94
102
|
{{ error }}
|
|
95
103
|
</div>
|
|
96
104
|
|
|
97
105
|
<template v-else>
|
|
98
106
|
<!-- Existing entries -->
|
|
99
|
-
<div v-if="dirs.length === 0" class="text-sm text-gray-400">
|
|
107
|
+
<div v-if="dirs.length === 0" class="text-sm text-gray-400">{{ t("settingsWorkspaceDirs.noEntries") }}</div>
|
|
100
108
|
<div v-else class="space-y-1.5">
|
|
101
109
|
<div
|
|
102
110
|
v-for="(dir, i) in dirs"
|
|
@@ -113,7 +121,7 @@ onMounted(load);
|
|
|
113
121
|
<span class="text-[10px] px-1.5 py-0.5 rounded bg-gray-200 text-gray-600 shrink-0">
|
|
114
122
|
{{ dir.structure }}
|
|
115
123
|
</span>
|
|
116
|
-
<button class="text-gray-300 hover:text-red-500 shrink-0" title="
|
|
124
|
+
<button class="text-gray-300 hover:text-red-500 shrink-0" :title="t('common.remove')" @click="removeEntry(i)">
|
|
117
125
|
<span class="material-icons text-sm">close</span>
|
|
118
126
|
</button>
|
|
119
127
|
</div>
|
|
@@ -121,12 +129,12 @@ onMounted(load);
|
|
|
121
129
|
|
|
122
130
|
<!-- Add new -->
|
|
123
131
|
<div class="border border-gray-200 rounded p-2 space-y-2">
|
|
124
|
-
<div class="text-xs font-semibold text-gray-600">
|
|
132
|
+
<div class="text-xs font-semibold text-gray-600">{{ t("settingsWorkspaceDirs.addDirTitle") }}</div>
|
|
125
133
|
<div class="flex gap-2">
|
|
126
134
|
<input
|
|
127
135
|
v-model="draftPath"
|
|
128
136
|
class="flex-1 px-2 py-1 text-xs font-mono border border-gray-300 rounded focus:outline-none focus:border-blue-400"
|
|
129
|
-
placeholder="
|
|
137
|
+
:placeholder="t('settingsWorkspaceDirs.pathPlaceholder')"
|
|
130
138
|
data-testid="workspace-dir-path-input"
|
|
131
139
|
@keydown.enter="addEntry"
|
|
132
140
|
@keydown.stop
|
|
@@ -144,13 +152,15 @@ onMounted(load);
|
|
|
144
152
|
<input
|
|
145
153
|
v-model="draftDescription"
|
|
146
154
|
class="w-full px-2 py-1 text-xs border border-gray-300 rounded focus:outline-none focus:border-blue-400"
|
|
147
|
-
placeholder="
|
|
155
|
+
:placeholder="t('settingsWorkspaceDirs.descPlaceholder')"
|
|
148
156
|
data-testid="workspace-dir-desc-input"
|
|
149
157
|
@keydown.enter="addEntry"
|
|
150
158
|
@keydown.stop
|
|
151
159
|
/>
|
|
152
160
|
<div class="flex items-center gap-2">
|
|
153
|
-
<button class="px-2 py-1 text-xs rounded bg-blue-500 text-white hover:bg-blue-600" data-testid="workspace-dir-add-btn" @click="addEntry">
|
|
161
|
+
<button class="px-2 py-1 text-xs rounded bg-blue-500 text-white hover:bg-blue-600" data-testid="workspace-dir-add-btn" @click="addEntry">
|
|
162
|
+
{{ t("common.add") }}
|
|
163
|
+
</button>
|
|
154
164
|
<span v-if="draftError" class="text-xs text-red-500">{{ draftError }}</span>
|
|
155
165
|
</div>
|
|
156
166
|
</div>
|
|
@@ -163,10 +173,10 @@ onMounted(load);
|
|
|
163
173
|
data-testid="workspace-dirs-save-btn"
|
|
164
174
|
@click="save"
|
|
165
175
|
>
|
|
166
|
-
{{ saving ? "
|
|
176
|
+
{{ saving ? t("common.saving") : t("common.save") }}
|
|
167
177
|
</button>
|
|
168
|
-
<span v-if="saveStatus" class="text-xs" :class="saveStatus === '
|
|
169
|
-
{{ saveStatus }}
|
|
178
|
+
<span v-if="saveStatus" class="text-xs" :class="saveStatus.kind === 'ok' ? 'text-green-600' : 'text-red-600'">
|
|
179
|
+
{{ saveStatus.kind === "ok" ? t("common.saved") : saveStatus.message }}
|
|
170
180
|
</span>
|
|
171
181
|
</div>
|
|
172
182
|
</template>
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="flex items-center gap-2">
|
|
3
|
-
<
|
|
4
|
-
|
|
3
|
+
<button
|
|
4
|
+
type="button"
|
|
5
|
+
class="flex items-center gap-2 -my-1 -ml-1 py-1 pl-1 pr-2 rounded hover:bg-gray-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400"
|
|
6
|
+
data-testid="app-home-btn"
|
|
7
|
+
:title="t('sidebarHeader.home')"
|
|
8
|
+
:aria-label="t('sidebarHeader.home')"
|
|
9
|
+
@click="emit('home')"
|
|
10
|
+
>
|
|
11
|
+
<img :src="logoUrl" alt="" class="h-[50px] w-auto -my-3.5 -ml-3 rounded object-contain shrink-0" />
|
|
12
|
+
<h1 data-testid="app-title" class="text-sm font-semibold text-gray-800 mr-1" :style="titleStyle">MulmoClaude</h1>
|
|
13
|
+
</button>
|
|
5
14
|
<div class="flex gap-2">
|
|
6
15
|
<LockStatusPopup
|
|
7
16
|
ref="lockPopup"
|
|
@@ -12,14 +21,21 @@
|
|
|
12
21
|
/>
|
|
13
22
|
<NotificationBell :force-close="lockPopupOpen" @navigate="(action) => emit('notificationNavigate', action)" @update:open="onNotificationOpen" />
|
|
14
23
|
<button
|
|
24
|
+
v-if="isChatPage"
|
|
15
25
|
class="text-gray-400 hover:text-gray-700"
|
|
16
26
|
:class="{ 'text-blue-500': showRightSidebar }"
|
|
17
|
-
title="
|
|
27
|
+
:title="t('sidebarHeader.toolCallHistory')"
|
|
18
28
|
@click="emit('toggleRightSidebar')"
|
|
19
29
|
>
|
|
20
30
|
<span class="material-icons">build</span>
|
|
21
31
|
</button>
|
|
22
|
-
<button
|
|
32
|
+
<button
|
|
33
|
+
class="text-gray-400 hover:text-gray-700"
|
|
34
|
+
data-testid="settings-btn"
|
|
35
|
+
:title="t('sidebarHeader.settings')"
|
|
36
|
+
:aria-label="t('sidebarHeader.settings')"
|
|
37
|
+
@click="emit('openSettings')"
|
|
38
|
+
>
|
|
23
39
|
<span class="material-icons">settings</span>
|
|
24
40
|
</button>
|
|
25
41
|
</div>
|
|
@@ -28,15 +44,19 @@
|
|
|
28
44
|
|
|
29
45
|
<script setup lang="ts">
|
|
30
46
|
import { computed, onBeforeUnmount, onMounted, ref, type CSSProperties } from "vue";
|
|
47
|
+
import { useI18n } from "vue-i18n";
|
|
31
48
|
import LockStatusPopup from "./LockStatusPopup.vue";
|
|
32
49
|
import NotificationBell from "./NotificationBell.vue";
|
|
33
50
|
import { useClickOutside } from "../composables/useClickOutside";
|
|
34
51
|
import type { NotificationPayload } from "../types/notification";
|
|
35
52
|
import logoUrl from "../assets/mulmo_bw.png";
|
|
36
53
|
|
|
54
|
+
const { t } = useI18n();
|
|
55
|
+
|
|
37
56
|
defineProps<{
|
|
38
57
|
sandboxEnabled: boolean;
|
|
39
58
|
showRightSidebar: boolean;
|
|
59
|
+
isChatPage: boolean;
|
|
40
60
|
titleStyle?: CSSProperties;
|
|
41
61
|
}>();
|
|
42
62
|
|
|
@@ -45,6 +65,7 @@ const emit = defineEmits<{
|
|
|
45
65
|
notificationNavigate: [action: NotificationPayload["action"]];
|
|
46
66
|
toggleRightSidebar: [];
|
|
47
67
|
openSettings: [];
|
|
68
|
+
home: [];
|
|
48
69
|
}>();
|
|
49
70
|
|
|
50
71
|
const lockPopupOpen = ref(false);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div ref="containerRef" class="h-full overflow-y-auto bg-gray-50 p-4 space-y-3" data-testid="stack-scroll">
|
|
3
|
-
<div v-if="toolResults.length === 0" class="flex items-center justify-center h-full text-gray-400 text-sm">
|
|
3
|
+
<div v-if="toolResults.length === 0" class="flex items-center justify-center h-full text-gray-400 text-sm">{{ t("common.noResultsYet") }}</div>
|
|
4
4
|
<div
|
|
5
5
|
v-for="result in toolResults"
|
|
6
6
|
:key="result.uuid"
|
|
@@ -70,7 +70,10 @@
|
|
|
70
70
|
|
|
71
71
|
<script setup lang="ts">
|
|
72
72
|
import { ref, computed, watch, nextTick, onMounted, onUnmounted } from "vue";
|
|
73
|
+
import { useI18n } from "vue-i18n";
|
|
73
74
|
import { getPlugin } from "../tools";
|
|
75
|
+
|
|
76
|
+
const { t } = useI18n();
|
|
74
77
|
import type { ToolResultComplete } from "gui-chat-protocol/vue";
|
|
75
78
|
import { View as TextResponseOriginalView } from "../plugins/textResponse/index";
|
|
76
79
|
import { handleExternalLinkClick } from "../utils/dom/externalLink";
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
>
|
|
10
10
|
{{ query }}
|
|
11
11
|
</button>
|
|
12
|
-
<p class="text-center text-[10px] text-gray-400 py-0.5">
|
|
12
|
+
<p class="text-center text-[10px] text-gray-400 py-0.5">{{ t("suggestionsPanel.sendEditHint") }}</p>
|
|
13
13
|
</div>
|
|
14
14
|
<button
|
|
15
15
|
class="w-full flex items-center justify-between px-4 py-1 text-xs text-gray-500 hover:text-gray-700 hover:bg-gray-50 transition-colors"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
>
|
|
18
18
|
<span class="flex items-center gap-1">
|
|
19
19
|
<span class="material-icons text-sm">lightbulb</span>
|
|
20
|
-
|
|
20
|
+
{{ t("suggestionsPanel.suggestions") }}
|
|
21
21
|
</span>
|
|
22
22
|
<span class="material-icons text-sm transition-transform" :class="{ 'rotate-180': !expanded }">expand_less</span>
|
|
23
23
|
</button>
|
|
@@ -26,6 +26,9 @@
|
|
|
26
26
|
|
|
27
27
|
<script setup lang="ts">
|
|
28
28
|
import { nextTick, ref, watch } from "vue";
|
|
29
|
+
import { useI18n } from "vue-i18n";
|
|
30
|
+
|
|
31
|
+
const { t } = useI18n();
|
|
29
32
|
|
|
30
33
|
defineProps<{
|
|
31
34
|
queries: string[];
|
|
@@ -48,9 +51,9 @@ watch(expanded, (isExpanded) => {
|
|
|
48
51
|
});
|
|
49
52
|
});
|
|
50
53
|
|
|
51
|
-
function onClick(
|
|
54
|
+
function onClick(event: MouseEvent, query: string): void {
|
|
52
55
|
expanded.value = false;
|
|
53
|
-
if (
|
|
56
|
+
if (event.shiftKey) {
|
|
54
57
|
emit("edit", query);
|
|
55
58
|
return;
|
|
56
59
|
}
|