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
|
@@ -31,24 +31,36 @@
|
|
|
31
31
|
</div>
|
|
32
32
|
|
|
33
33
|
<div class="flex items-center gap-1">
|
|
34
|
-
<button
|
|
34
|
+
<button
|
|
35
|
+
class="w-8 h-8 flex items-center justify-center rounded border-2 border-gray-300 bg-white hover:bg-gray-50"
|
|
36
|
+
:title="t('pluginCanvas.undo')"
|
|
37
|
+
@click="undo"
|
|
38
|
+
>
|
|
35
39
|
<span class="material-icons text-sm">undo</span>
|
|
36
40
|
</button>
|
|
37
|
-
<button
|
|
41
|
+
<button
|
|
42
|
+
class="w-8 h-8 flex items-center justify-center rounded border-2 border-gray-300 bg-white hover:bg-gray-50"
|
|
43
|
+
:title="t('pluginCanvas.redo')"
|
|
44
|
+
@click="redo"
|
|
45
|
+
>
|
|
38
46
|
<span class="material-icons text-sm">redo</span>
|
|
39
47
|
</button>
|
|
40
|
-
<button
|
|
48
|
+
<button
|
|
49
|
+
class="w-8 h-8 flex items-center justify-center rounded border-2 border-red-300 bg-white hover:bg-red-50"
|
|
50
|
+
:title="t('pluginCanvas.clear')"
|
|
51
|
+
@click="clear"
|
|
52
|
+
>
|
|
41
53
|
<span class="material-icons text-sm">delete</span>
|
|
42
54
|
</button>
|
|
43
55
|
</div>
|
|
44
56
|
</div>
|
|
45
57
|
</div>
|
|
46
58
|
|
|
47
|
-
<div class="flex-1 p-4 overflow-hidden">
|
|
59
|
+
<div ref="containerRef" class="flex-1 p-4 overflow-hidden">
|
|
48
60
|
<VueDrawingCanvas
|
|
61
|
+
v-if="canvasWidth > 0"
|
|
49
62
|
ref="canvasRef"
|
|
50
63
|
:key="`${selectedResult?.uuid || 'default'}-${canvasRenderKey}`"
|
|
51
|
-
v-model:image="canvasImage"
|
|
52
64
|
:width="canvasWidth"
|
|
53
65
|
:height="canvasHeight"
|
|
54
66
|
:stroke-type="'dash'"
|
|
@@ -59,20 +71,19 @@
|
|
|
59
71
|
:line-width="brushSize"
|
|
60
72
|
:color="brushColor"
|
|
61
73
|
:background-color="'#FFFFFF'"
|
|
62
|
-
:background-image="
|
|
74
|
+
:background-image="backgroundImage"
|
|
63
75
|
:watermark="undefined"
|
|
64
|
-
:initial-image="initialStrokes"
|
|
65
76
|
save-as="png"
|
|
66
77
|
:styles="{
|
|
67
78
|
border: '1px solid #ddd',
|
|
68
79
|
borderRadius: '8px',
|
|
69
80
|
}"
|
|
70
81
|
:lock="false"
|
|
71
|
-
@mouseup="
|
|
72
|
-
@touchend="
|
|
82
|
+
@mouseup="saveDrawing"
|
|
83
|
+
@touchend="saveDrawing"
|
|
73
84
|
/>
|
|
74
85
|
<div class="flex items-center gap-2 flex-wrap mt-3">
|
|
75
|
-
<span class="text-xs text-gray-500 mr-1">
|
|
86
|
+
<span class="text-xs text-gray-500 mr-1">{{ t("pluginCanvas.styleLabel") }}</span>
|
|
76
87
|
<button
|
|
77
88
|
v-for="style in artStyles"
|
|
78
89
|
:key="style.id"
|
|
@@ -87,22 +98,23 @@
|
|
|
87
98
|
</template>
|
|
88
99
|
|
|
89
100
|
<script setup lang="ts">
|
|
90
|
-
import { ref, onMounted, onUnmounted, nextTick
|
|
101
|
+
import { ref, computed, onMounted, onUnmounted, nextTick } from "vue";
|
|
102
|
+
import { useI18n } from "vue-i18n";
|
|
91
103
|
import VueDrawingCanvas from "vue-drawing-canvas";
|
|
92
104
|
import type { ToolResult } from "gui-chat-protocol/vue";
|
|
93
|
-
import type { ImageToolData
|
|
94
|
-
import {
|
|
105
|
+
import type { ImageToolData } from "./definition";
|
|
106
|
+
import { apiPut } from "../../utils/api";
|
|
95
107
|
import { API_ROUTES } from "../../config/apiRoutes";
|
|
108
|
+
import { resolveImageSrc } from "../../utils/image/resolve";
|
|
109
|
+
import { bumpImage } from "../../utils/image/cacheBust";
|
|
110
|
+
|
|
111
|
+
const { t } = useI18n();
|
|
96
112
|
|
|
97
113
|
const props = defineProps<{
|
|
98
114
|
selectedResult: ToolResult<ImageToolData> | null;
|
|
99
115
|
sendTextMessage?: (text: string) => void;
|
|
100
116
|
}>();
|
|
101
117
|
|
|
102
|
-
const emit = defineEmits<{
|
|
103
|
-
updateResult: [result: ToolResult<ImageToolData>];
|
|
104
|
-
}>();
|
|
105
|
-
|
|
106
118
|
const artStyles = [
|
|
107
119
|
{ id: "ghibli", label: "Ghibli" },
|
|
108
120
|
{ id: "ukiyoe", label: "Ukiyoe" },
|
|
@@ -118,215 +130,119 @@ const artStyles = [
|
|
|
118
130
|
{ id: "pixelart", label: "Pixel Art" },
|
|
119
131
|
];
|
|
120
132
|
|
|
121
|
-
const applyStyle =
|
|
122
|
-
|
|
123
|
-
if (!saved) return;
|
|
124
|
-
if (props.sendTextMessage) {
|
|
125
|
-
props.sendTextMessage(`Turn my drawing on the canvas into a ${style.label} style image.`);
|
|
126
|
-
}
|
|
133
|
+
const applyStyle = (style: { id: string; label: string }) => {
|
|
134
|
+
props.sendTextMessage?.(`Turn my drawing on the canvas into a ${style.label} style image.`);
|
|
127
135
|
};
|
|
128
136
|
|
|
129
137
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
130
138
|
const canvasRef = ref<any>(null);
|
|
131
|
-
const
|
|
139
|
+
const containerRef = ref<HTMLDivElement | null>(null);
|
|
132
140
|
const brushSize = ref(5);
|
|
133
141
|
const brushColor = ref("#000000");
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
142
|
+
// `canvasWidth === 0` doubles as "not measured yet" — the child has a
|
|
143
|
+
// v-if on `canvasWidth > 0` so it mounts exactly once, with the
|
|
144
|
+
// correct dimensions. Mounting at a default 800×600 and resizing a
|
|
145
|
+
// tick later races the in-flight background fetch and blanks the
|
|
146
|
+
// canvas on reload.
|
|
147
|
+
const canvasWidth = ref(0);
|
|
148
|
+
const canvasHeight = ref(0);
|
|
137
149
|
const canvasRenderKey = ref(0);
|
|
138
150
|
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (props.selectedResult?.viewState?.drawingState) {
|
|
149
|
-
const state = props.selectedResult.viewState.drawingState as CanvasDrawingState;
|
|
151
|
+
// The PNG on disk is the source of truth. The path is baked into
|
|
152
|
+
// the tool result at openCanvas time (server-allocated), so reload
|
|
153
|
+
// finds the file with zero client→server sync. Every stroke PUTs
|
|
154
|
+
// back to this same path.
|
|
155
|
+
const imagePath = computed(() => {
|
|
156
|
+
const stored = props.selectedResult?.data?.imageData;
|
|
157
|
+
if (!stored || stored.startsWith("data:")) return "";
|
|
158
|
+
return stored;
|
|
159
|
+
});
|
|
150
160
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
161
|
+
// Per-mount cache buster for the VueDrawingCanvas child. The URL
|
|
162
|
+
// must be stable for the lifetime of one canvas instance — if it
|
|
163
|
+
// changes while the child is alive (e.g. from a post-save bump),
|
|
164
|
+
// the library nulls its cached `loadedImage` and the next redraw
|
|
165
|
+
// races a fresh re-fetch against stroke painting, blanking the
|
|
166
|
+
// canvas. Tying the token to `canvasRenderKey` (which increments
|
|
167
|
+
// when we explicitly remount via :key on resize) gives us fresh
|
|
168
|
+
// bytes on page reload and on resize, without mid-session churn.
|
|
169
|
+
const setupTime = Date.now();
|
|
170
|
+
const backgroundImage = computed(() => {
|
|
171
|
+
if (!imagePath.value) return undefined;
|
|
172
|
+
const base = resolveImageSrc(imagePath.value);
|
|
173
|
+
const sep = base.includes("?") ? "&" : "?";
|
|
174
|
+
return `${base}${sep}mt=${setupTime}-${canvasRenderKey.value}`;
|
|
175
|
+
});
|
|
155
176
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
} else {
|
|
159
|
-
initialStrokes.value = [];
|
|
160
|
-
}
|
|
161
|
-
} else {
|
|
162
|
-
initialStrokes.value = [];
|
|
163
|
-
}
|
|
164
|
-
// Reinitialise imagePath from the incoming result so autosaves
|
|
165
|
-
// after a selectedResult change don't upload against the old path.
|
|
166
|
-
imagePath.value =
|
|
167
|
-
props.selectedResult?.data?.imageData && !props.selectedResult.data.imageData.startsWith("data:") ? props.selectedResult.data.imageData : "";
|
|
168
|
-
};
|
|
169
|
-
restoreDrawingState();
|
|
177
|
+
let uploadInFlight = false;
|
|
178
|
+
let pendingSave = false;
|
|
170
179
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
setTimeout(saveDrawingState, 50);
|
|
177
|
-
} catch (error) {
|
|
178
|
-
console.warn("Undo operation failed:", error);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
180
|
+
// Undo/redo kick off the library's async redraw; give it a tick to
|
|
181
|
+
// composite before we snapshot-and-upload.
|
|
182
|
+
const undo = () => {
|
|
183
|
+
canvasRef.value?.undo();
|
|
184
|
+
setTimeout(saveDrawing, 50);
|
|
181
185
|
};
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
try {
|
|
186
|
-
canvasRef.value.redo();
|
|
187
|
-
// Wait for the canvas to update, then save state
|
|
188
|
-
setTimeout(saveDrawingState, 50);
|
|
189
|
-
} catch (error) {
|
|
190
|
-
console.warn("Redo operation failed:", error);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
186
|
+
const redo = () => {
|
|
187
|
+
canvasRef.value?.redo();
|
|
188
|
+
setTimeout(saveDrawing, 50);
|
|
193
189
|
};
|
|
194
|
-
|
|
195
190
|
const clear = () => {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
canvasRef.value.reset();
|
|
199
|
-
saveDrawingState();
|
|
200
|
-
} catch (error) {
|
|
201
|
-
console.warn("Clear operation failed:", error);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
const handleDrawingEnd = () => {
|
|
207
|
-
saveDrawingState();
|
|
191
|
+
canvasRef.value?.reset();
|
|
192
|
+
saveDrawing();
|
|
208
193
|
};
|
|
209
194
|
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
// the start so an async upload never accidentally writes against a
|
|
216
|
-
// different result that was swapped in mid-flight.
|
|
217
|
-
const saveDrawingState = async (): Promise<boolean> => {
|
|
218
|
-
if (!canvasRef.value || !props.selectedResult) return false;
|
|
195
|
+
// Snapshot the current bitmap and PUT it back to the pre-allocated
|
|
196
|
+
// file. No result mutation — the path is fixed from canvas creation,
|
|
197
|
+
// so nothing upstream needs to know about saves.
|
|
198
|
+
const saveDrawing = async (): Promise<void> => {
|
|
199
|
+
if (!canvasRef.value || !imagePath.value) return;
|
|
219
200
|
if (uploadInFlight) {
|
|
220
201
|
pendingSave = true;
|
|
221
|
-
return
|
|
202
|
+
return;
|
|
222
203
|
}
|
|
223
|
-
// Snapshot the current result and path *before* any await so a
|
|
224
|
-
// concurrent selectedResult swap doesn't silently redirect the
|
|
225
|
-
// upload.
|
|
226
|
-
const boundResult = props.selectedResult;
|
|
227
|
-
const boundImagePath = imagePath.value;
|
|
228
204
|
uploadInFlight = true;
|
|
229
205
|
try {
|
|
230
206
|
const imageDataUri: string = await canvasRef.value.save();
|
|
231
|
-
const
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
brushColor: brushColor.value,
|
|
236
|
-
canvasWidth: canvasWidth.value,
|
|
237
|
-
canvasHeight: canvasHeight.value,
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
// If selectedResult changed while we were saving the canvas
|
|
241
|
-
// bitmap, abort — the upload would go to the wrong session.
|
|
242
|
-
if (boundResult !== props.selectedResult) return false;
|
|
243
|
-
|
|
244
|
-
const savedPath = boundImagePath
|
|
245
|
-
? await (async () => {
|
|
246
|
-
const filename = boundImagePath.replace(/^images\//, "");
|
|
247
|
-
const result = await apiPut<{ path: string }>(API_ROUTES.image.update.replace(":filename", filename), { imageData: imageDataUri });
|
|
248
|
-
if (!result.ok) throw new Error(`PUT failed: ${result.error}`);
|
|
249
|
-
return result.data.path;
|
|
250
|
-
})()
|
|
251
|
-
: await (async () => {
|
|
252
|
-
const result = await apiPost<{ path: string }>(API_ROUTES.image.upload, {
|
|
253
|
-
imageData: imageDataUri,
|
|
254
|
-
});
|
|
255
|
-
if (!result.ok) throw new Error(`POST failed: ${result.error}`);
|
|
256
|
-
return result.data.path;
|
|
257
|
-
})();
|
|
258
|
-
|
|
259
|
-
imagePath.value = savedPath;
|
|
260
|
-
|
|
261
|
-
const updatedResult: ToolResult<ImageToolData> = {
|
|
262
|
-
...boundResult,
|
|
263
|
-
data: {
|
|
264
|
-
prompt: boundResult.data?.prompt || "",
|
|
265
|
-
imageData: savedPath,
|
|
266
|
-
},
|
|
267
|
-
viewState: { drawingState },
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
emit("updateResult", updatedResult);
|
|
271
|
-
return true;
|
|
207
|
+
const filename = imagePath.value.split("/").pop() ?? "";
|
|
208
|
+
const result = await apiPut<{ path: string }>(API_ROUTES.image.update.replace(":filename", filename), { imageData: imageDataUri });
|
|
209
|
+
if (!result.ok) throw new Error(`PUT failed: ${result.error}`);
|
|
210
|
+
bumpImage(imagePath.value);
|
|
272
211
|
} catch (error) {
|
|
273
|
-
console.error("Failed to save drawing
|
|
274
|
-
return false;
|
|
212
|
+
console.error("Failed to save drawing:", error);
|
|
275
213
|
} finally {
|
|
276
214
|
uploadInFlight = false;
|
|
277
215
|
if (pendingSave) {
|
|
278
216
|
pendingSave = false;
|
|
279
|
-
void
|
|
217
|
+
void saveDrawing();
|
|
280
218
|
}
|
|
281
219
|
}
|
|
282
220
|
};
|
|
283
221
|
|
|
284
|
-
// Watch for selectedResult changes to restore drawing state
|
|
285
|
-
watch(
|
|
286
|
-
() => props.selectedResult,
|
|
287
|
-
() => {
|
|
288
|
-
restoreDrawingState();
|
|
289
|
-
},
|
|
290
|
-
{ immediate: false },
|
|
291
|
-
);
|
|
292
|
-
|
|
293
|
-
// Watch for changes to automatically save drawing state
|
|
294
|
-
watch([brushSize, brushColor], () => {
|
|
295
|
-
saveDrawingState();
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
// Watch for canvas size changes and force re-mount
|
|
299
|
-
watch([canvasWidth, canvasHeight], () => {
|
|
300
|
-
// Force canvas to re-mount with new dimensions by changing the key
|
|
301
|
-
canvasRenderKey.value++;
|
|
302
|
-
});
|
|
303
222
|
const updateCanvasSize = () => {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
223
|
+
const container = containerRef.value;
|
|
224
|
+
if (!container) return;
|
|
225
|
+
const containerRect = container.getBoundingClientRect();
|
|
226
|
+
const padding = 32;
|
|
227
|
+
const newWidth = Math.floor(containerRect.width - padding);
|
|
228
|
+
const newHeight = Math.floor((newWidth * 9) / 16);
|
|
229
|
+
if (newWidth <= 0) return;
|
|
230
|
+
if (newWidth === canvasWidth.value && newHeight === canvasHeight.value) return;
|
|
231
|
+
const firstPaint = canvasWidth.value === 0;
|
|
232
|
+
canvasWidth.value = newWidth;
|
|
233
|
+
canvasHeight.value = newHeight;
|
|
234
|
+
// Remount the child on every size change *after* the first paint so
|
|
235
|
+
// it re-reads the background image at the new dimensions. The first
|
|
236
|
+
// paint doesn't need this — v-if gates the mount on `canvasWidth > 0`.
|
|
237
|
+
if (!firstPaint) canvasRenderKey.value++;
|
|
319
238
|
};
|
|
320
239
|
|
|
321
240
|
onMounted(async () => {
|
|
322
241
|
await nextTick();
|
|
323
242
|
updateCanvasSize();
|
|
324
|
-
|
|
325
|
-
// Listen for window resize to update canvas size
|
|
326
243
|
window.addEventListener("resize", updateCanvasSize);
|
|
327
244
|
});
|
|
328
245
|
|
|
329
|
-
// Clean up resize listener
|
|
330
246
|
onUnmounted(() => {
|
|
331
247
|
window.removeEventListener("resize", updateCanvasSize);
|
|
332
248
|
});
|
|
@@ -7,14 +7,6 @@ export interface ImageToolData {
|
|
|
7
7
|
prompt: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export interface CanvasDrawingState {
|
|
11
|
-
brushSize?: number;
|
|
12
|
-
brushColor?: string;
|
|
13
|
-
canvasWidth?: number;
|
|
14
|
-
canvasHeight?: number;
|
|
15
|
-
strokes?: unknown[];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
10
|
const toolDefinition: ToolDefinition = {
|
|
19
11
|
type: "function",
|
|
20
12
|
name: TOOL_NAME,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="text-sm">
|
|
2
|
+
<div class="p-2 text-sm">
|
|
3
3
|
<div class="font-medium text-gray-700 truncate mb-1">
|
|
4
4
|
{{ title }}
|
|
5
5
|
</div>
|
|
@@ -25,8 +25,8 @@ const hint = computed(() => {
|
|
|
25
25
|
const charts = data.value?.document?.charts ?? [];
|
|
26
26
|
if (charts.length === 0) return "";
|
|
27
27
|
const types = charts
|
|
28
|
-
.map((
|
|
29
|
-
.filter((
|
|
28
|
+
.map((chart) => chart.type ?? inferTypeFromOption(chart.option))
|
|
29
|
+
.filter((chartType): chartType is string => Boolean(chartType))
|
|
30
30
|
.slice(0, 3);
|
|
31
31
|
const suffix = charts.length > types.length ? ", …" : "";
|
|
32
32
|
const typeList = types.join(", ");
|
|
@@ -41,8 +41,8 @@ function inferTypeFromOption(option: Record<string, unknown>): string | null {
|
|
|
41
41
|
const first = series[0] as { type?: unknown };
|
|
42
42
|
if (typeof first.type === "string") return first.type;
|
|
43
43
|
} else if (series && typeof series === "object") {
|
|
44
|
-
const
|
|
45
|
-
if (typeof
|
|
44
|
+
const seriesType = (series as { type?: unknown }).type;
|
|
45
|
+
if (typeof seriesType === "string") return seriesType;
|
|
46
46
|
}
|
|
47
47
|
return null;
|
|
48
48
|
}
|
|
@@ -2,16 +2,18 @@
|
|
|
2
2
|
<div class="h-full flex flex-col overflow-hidden">
|
|
3
3
|
<div class="px-4 py-2 border-b border-gray-100 shrink-0 flex items-center justify-between">
|
|
4
4
|
<span class="text-sm font-medium text-gray-700 truncate">
|
|
5
|
-
{{ title ?? "
|
|
5
|
+
{{ title ?? t("pluginChart.untitled") }}
|
|
6
|
+
</span>
|
|
7
|
+
<span class="text-xs text-gray-500 shrink-0">
|
|
8
|
+
{{ t("pluginChart.chartCount", charts.length, { named: { count: charts.length } }) }}
|
|
6
9
|
</span>
|
|
7
|
-
<span class="text-xs text-gray-500 shrink-0"> {{ charts.length }} chart{{ charts.length === 1 ? "" : "s" }} </span>
|
|
8
10
|
</div>
|
|
9
11
|
<div class="flex-1 overflow-y-auto p-4 space-y-4">
|
|
10
12
|
<div v-for="(chart, idx) in charts" :key="idx" class="border border-gray-200 rounded-lg bg-white" :data-testid="`chart-card-${idx}`">
|
|
11
13
|
<div class="px-3 py-2 border-b border-gray-100 flex items-center justify-between gap-2">
|
|
12
14
|
<div class="flex items-center gap-2 min-w-0">
|
|
13
15
|
<span class="text-sm font-medium text-gray-800 truncate">
|
|
14
|
-
{{ chart.title ??
|
|
16
|
+
{{ chart.title ?? t("pluginChart.chartTitle", { num: idx + 1 }) }}
|
|
15
17
|
</span>
|
|
16
18
|
<span v-if="chart.type" class="text-[10px] uppercase tracking-wide rounded px-1.5 py-0.5 bg-blue-50 text-blue-700 shrink-0">
|
|
17
19
|
{{ chart.type }}
|
|
@@ -23,7 +25,7 @@
|
|
|
23
25
|
@click="exportPng(idx, chart.title)"
|
|
24
26
|
>
|
|
25
27
|
<span class="material-icons text-sm align-middle">download</span>
|
|
26
|
-
|
|
28
|
+
{{ t("pluginChart.png") }}
|
|
27
29
|
</button>
|
|
28
30
|
</div>
|
|
29
31
|
<div :ref="(el) => setChartRef(idx, el as HTMLDivElement | null)" class="w-full h-[400px]" :data-testid="`chart-canvas-${idx}`" />
|
|
@@ -34,11 +36,14 @@
|
|
|
34
36
|
|
|
35
37
|
<script setup lang="ts">
|
|
36
38
|
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
|
39
|
+
import { useI18n } from "vue-i18n";
|
|
37
40
|
import * as echarts from "echarts";
|
|
38
41
|
import type { ToolResultComplete } from "gui-chat-protocol/vue";
|
|
39
42
|
import type { ChartEntry, PresentChartData } from "./index";
|
|
40
43
|
import { isRecord } from "../../utils/types";
|
|
41
44
|
|
|
45
|
+
const { t } = useI18n();
|
|
46
|
+
|
|
42
47
|
const props = defineProps<{
|
|
43
48
|
selectedResult: ToolResultComplete<PresentChartData>;
|
|
44
49
|
}>();
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div class="text-sm">
|
|
3
3
|
<div class="flex items-center gap-1 font-medium text-gray-700 mb-1">
|
|
4
4
|
<span class="material-icons" style="font-size: 14px">manage_accounts</span>
|
|
5
|
-
<span>{{ customRoles.length
|
|
5
|
+
<span>{{ t("pluginManageRoles.previewCount", customRoles.length, { named: { count: customRoles.length } }) }}</span>
|
|
6
6
|
</div>
|
|
7
7
|
<div v-for="role in customRoles" :key="role.id" class="text-xs text-gray-600 flex items-center gap-1">
|
|
8
8
|
<span class="material-icons" style="font-size: 12px">{{ role.icon }}</span>
|
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
|
|
14
14
|
<script setup lang="ts">
|
|
15
15
|
import { ref, watch } from "vue";
|
|
16
|
+
import { useI18n } from "vue-i18n";
|
|
16
17
|
import type { ToolResultComplete } from "gui-chat-protocol/vue";
|
|
18
|
+
|
|
19
|
+
const { t } = useI18n();
|
|
17
20
|
import type { ManageRolesData, CustomRole } from "./index";
|
|
18
21
|
import { useFreshPluginData } from "../../composables/useFreshPluginData";
|
|
19
22
|
import { API_ROUTES } from "../../config/apiRoutes";
|