mulmoclaude 0.3.0 → 0.5.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-DiKaqnKs.js +5 -0
- package/client/assets/{index-eHWB79u5.js → index-C94GcmNa.js} +203 -198
- package/client/assets/index-CY-WpQUm.css +2 -0
- package/client/assets/{index.es-D4YyL_Dg-BfRHLTZV.js → index.es-D4YyL_Dg-5ipqh8Pe.js} +5 -5
- package/client/assets/material-symbols-outlined-NzYEeyps.woff2 +0 -0
- package/client/index.html +2 -4
- package/package.json +17 -15
- package/server/agent/attachmentConverter.ts +2 -2
- package/server/agent/backend/claude-code.ts +170 -0
- package/server/agent/backend/index.ts +14 -0
- package/server/agent/backend/types.ts +65 -0
- package/server/agent/index.ts +31 -159
- package/server/agent/mcp-server.ts +88 -10
- package/server/agent/mcp-tools/index.ts +8 -7
- package/server/agent/mcp-tools/notify.ts +76 -0
- package/server/agent/mcp-tools/x.ts +12 -2
- package/server/agent/plugin-names.ts +10 -4
- package/server/agent/prompt.ts +187 -26
- package/server/agent/resumeFailover.ts +5 -5
- package/server/agent/sandboxMounts.ts +3 -3
- package/server/api/auth/bearerAuth.ts +3 -3
- package/server/api/auth/token.ts +2 -2
- package/server/api/routes/agent.ts +99 -4
- package/server/api/routes/chart.ts +13 -0
- package/server/api/routes/chat-index.ts +2 -1
- package/server/api/routes/config.ts +35 -8
- package/server/api/routes/files.ts +75 -24
- package/server/api/routes/html.ts +15 -2
- package/server/api/routes/image.ts +75 -20
- package/server/api/routes/mulmo-script.ts +33 -31
- package/server/api/routes/news.ts +146 -0
- package/server/api/routes/notifications.ts +58 -2
- package/server/api/routes/pdf.ts +2 -2
- package/server/api/routes/plugins.ts +73 -91
- package/server/api/routes/presentHtml.ts +9 -0
- package/server/api/routes/roles.ts +12 -2
- package/server/api/routes/scheduler.ts +20 -11
- package/server/api/routes/schedulerTasks.ts +58 -21
- package/server/api/routes/sessions.ts +15 -4
- package/server/api/routes/sessionsCursor.ts +4 -4
- package/server/api/routes/skills.ts +26 -5
- package/server/api/routes/sources.ts +8 -7
- package/server/api/routes/todos.ts +30 -0
- package/server/api/routes/todosColumnsHandlers.ts +13 -27
- package/server/api/routes/todosHandlers.ts +1 -1
- package/server/api/routes/todosItemsHandlers.ts +14 -14
- package/server/api/routes/wiki/frontmatter.ts +86 -0
- package/server/api/routes/wiki.ts +335 -75
- package/server/api/sandboxStatus.ts +1 -1
- package/server/events/notifications.ts +32 -8
- package/server/events/pub-sub/index.ts +3 -3
- package/server/events/relay-client.ts +26 -16
- package/server/events/resolveRelayBridgeOptions.ts +125 -0
- package/server/index.ts +72 -49
- package/server/system/config.ts +5 -5
- package/server/system/credentials.ts +7 -5
- package/server/system/env.ts +15 -5
- package/server/system/macosNotify.ts +152 -0
- package/server/utils/errors.ts +11 -2
- package/server/utils/fetch.ts +54 -0
- package/server/utils/files/atomic.ts +18 -17
- package/server/utils/files/image-store.ts +19 -13
- package/server/utils/files/journal-io.ts +2 -2
- package/server/utils/files/json.ts +5 -5
- package/server/utils/files/markdown-image-fill.ts +131 -0
- package/server/utils/files/markdown-store.ts +22 -6
- package/server/utils/files/naming.ts +20 -10
- package/server/utils/files/reference-dirs-io.ts +3 -3
- package/server/utils/files/roles-io.ts +4 -4
- package/server/utils/files/safe.ts +14 -14
- package/server/utils/files/scheduler-overrides-io.ts +2 -2
- package/server/utils/files/spreadsheet-store.ts +15 -10
- package/server/utils/files/workspace-io.ts +12 -12
- package/server/utils/gemini.ts +30 -4
- package/server/utils/gitignore.ts +9 -9
- package/server/utils/id.ts +40 -8
- package/server/utils/json.ts +5 -5
- package/server/utils/logBackgroundError.ts +12 -3
- package/server/utils/logPreview.ts +24 -0
- 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/promptMeta.ts +32 -0
- package/server/utils/request.ts +12 -6
- package/server/utils/slug.ts +65 -4
- package/server/utils/spawn.ts +1 -1
- package/server/utils/types.ts +2 -2
- package/server/workspace/chat-index/index.ts +1 -1
- package/server/workspace/chat-index/summarizer.ts +5 -5
- package/server/workspace/custom-dirs.ts +5 -5
- package/server/workspace/helps/gemini.md +57 -0
- package/server/workspace/helps/index.md +2 -1
- package/server/workspace/helps/sources.md +42 -0
- package/server/workspace/helps/wiki.md +40 -5
- package/server/workspace/journal/archivist-cli.ts +121 -0
- package/server/workspace/journal/{archivist.ts → archivist-schemas.ts} +12 -120
- package/server/workspace/journal/dailyPass.ts +78 -38
- package/server/workspace/journal/diff.ts +2 -2
- package/server/workspace/journal/index.ts +56 -5
- package/server/workspace/journal/memoryExtractor.ts +1 -1
- package/server/workspace/journal/optimizationPass.ts +4 -5
- package/server/workspace/journal/paths.ts +8 -24
- package/server/workspace/journal/state.ts +18 -8
- package/server/workspace/news/reader.ts +248 -0
- package/server/workspace/paths.ts +4 -3
- package/server/workspace/reference-dirs.ts +3 -3
- package/server/workspace/skills/parser.ts +6 -6
- package/server/workspace/skills/scheduler.ts +5 -4
- package/server/workspace/skills/user-tasks.ts +3 -2
- package/server/workspace/skills/writer.ts +3 -3
- package/server/workspace/sources/arxivDiscovery.ts +2 -2
- package/server/workspace/sources/classifier.ts +1 -1
- package/server/workspace/sources/fetchers/rss.ts +5 -5
- package/server/workspace/sources/fetchers/rssParser.ts +4 -4
- package/server/workspace/sources/interests.ts +3 -3
- package/server/workspace/sources/paths.ts +6 -6
- package/server/workspace/sources/pipeline/fetch.ts +59 -13
- package/server/workspace/sources/pipeline/index.ts +59 -7
- package/server/workspace/sources/pipeline/notify.ts +13 -5
- package/server/workspace/sources/pipeline/plan.ts +11 -9
- package/server/workspace/sources/pipeline/summarize.ts +1 -1
- package/server/workspace/sources/pipeline/write.ts +5 -5
- package/server/workspace/sources/rateLimiter.ts +1 -1
- package/server/workspace/sources/sourceState.ts +9 -4
- 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/workspace.ts +7 -7
- package/src/App.vue +477 -251
- package/src/components/CanvasViewToggle.vue +12 -10
- package/src/components/ChatInput.vue +112 -105
- package/src/components/FileContentHeader.vue +10 -7
- package/src/components/FileContentRenderer.vue +37 -10
- package/src/components/FileTree.vue +34 -4
- package/src/components/FileTreePane.vue +32 -27
- package/src/components/FilesView.vue +5 -3
- package/src/components/FilterChip.vue +22 -0
- package/src/components/LockStatusPopup.vue +19 -13
- package/src/components/NewsView.vue +252 -0
- package/src/components/NotificationBell.vue +35 -9
- package/src/components/NotificationToast.vue +4 -1
- package/src/components/PageChatComposer.vue +101 -0
- package/src/components/PluginLauncher.vue +36 -62
- package/src/components/RightSidebar.vue +13 -10
- package/src/components/RoleSelector.vue +3 -2
- package/src/components/SessionHeaderControls.vue +63 -0
- package/src/components/SessionHistoryExpandButton.vue +30 -0
- package/src/components/SessionHistoryPanel.vue +64 -93
- package/src/components/SessionHistoryToggleButton.vue +40 -0
- package/src/components/SessionRoleIcon.vue +72 -0
- package/src/components/SessionSidebar.vue +96 -0
- package/src/components/SessionTabBar.vue +44 -51
- package/src/components/SettingsMcpTab.vue +361 -52
- package/src/components/SettingsModal.vue +203 -72
- package/src/components/SettingsReferenceDirsTab.vue +72 -51
- package/src/components/SettingsWorkspaceDirsTab.vue +74 -51
- package/src/components/SidebarHeader.vue +50 -16
- package/src/components/SourcesManager.vue +900 -0
- package/src/components/SourcesView.vue +45 -0
- package/src/components/StackView.vue +84 -48
- package/src/components/SuggestionsPanel.vue +25 -36
- package/src/components/SystemFileBanner.vue +106 -0
- package/src/components/ThinkingIndicator.vue +41 -0
- package/src/components/TodoExplorer.vue +72 -22
- package/src/components/todo/TodoAddDialog.vue +17 -12
- package/src/components/todo/TodoEditDialog.vue +7 -2
- package/src/components/todo/TodoEditPanel.vue +15 -10
- package/src/components/todo/TodoKanbanView.vue +16 -6
- package/src/components/todo/TodoListView.vue +14 -3
- package/src/components/todo/TodoTableView.vue +36 -5
- package/src/composables/favicon/conditions.ts +76 -0
- package/src/composables/favicon/resolveColor.ts +93 -0
- package/src/composables/favicon/types.ts +61 -0
- package/src/composables/useAppApi.ts +23 -0
- package/src/composables/useChatScroll.ts +5 -5
- package/src/composables/useCurrentRole.ts +32 -0
- package/src/composables/useDynamicFavicon.ts +174 -58
- package/src/composables/useEventListeners.ts +7 -12
- package/src/composables/useFaviconState.ts +93 -12
- package/src/composables/useFileSelection.ts +25 -6
- package/src/composables/useHealth.ts +76 -7
- package/src/composables/useLayoutMode.ts +27 -0
- package/src/composables/useNewsItems.ts +38 -0
- package/src/composables/useNewsReadState.ts +75 -0
- package/src/composables/useNotifications.ts +76 -13
- package/src/composables/usePendingCalls.ts +11 -1
- package/src/composables/useRoles.ts +6 -10
- package/src/composables/useRunElapsed.ts +80 -0
- package/src/composables/useSessionDerived.ts +21 -5
- package/src/composables/useSessionHistory.ts +7 -17
- package/src/composables/useSidePanelVisible.ts +25 -0
- package/src/composables/useViewLayout.ts +16 -37
- package/src/config/apiRoutes.ts +19 -6
- package/src/config/historyFilters.ts +30 -0
- package/src/config/mcpCatalog.ts +285 -0
- package/src/config/mcpTypes.ts +26 -0
- package/src/config/roles.ts +19 -51
- package/src/config/systemFileDescriptors.ts +170 -0
- package/src/config/toolNames.ts +6 -1
- package/src/config/workspacePaths.ts +1 -0
- package/src/index.css +14 -0
- package/src/lang/de.ts +706 -0
- package/src/lang/en.ts +726 -0
- package/src/lang/es.ts +712 -0
- package/src/lang/fr.ts +704 -0
- package/src/lang/ja.ts +707 -0
- package/src/lang/ko.ts +709 -0
- package/src/lang/pt-BR.ts +702 -0
- package/src/lang/zh.ts +705 -0
- package/src/lib/vue-i18n.ts +97 -0
- package/src/main.ts +3 -0
- package/src/plugins/canvas/View.vue +104 -186
- package/src/plugins/canvas/definition.ts +0 -8
- package/src/plugins/canvas/index.ts +3 -2
- package/src/plugins/chart/Preview.vue +1 -1
- package/src/plugins/chart/View.vue +9 -4
- package/src/plugins/chart/index.ts +3 -2
- package/src/plugins/editImage/index.ts +3 -2
- package/src/plugins/generateImage/index.ts +3 -2
- package/src/plugins/manageRoles/Preview.vue +4 -1
- package/src/plugins/manageRoles/View.vue +67 -46
- package/src/plugins/manageRoles/index.ts +3 -2
- package/src/plugins/manageSkills/Preview.vue +8 -3
- package/src/plugins/manageSkills/View.vue +39 -34
- package/src/plugins/manageSkills/index.ts +3 -2
- package/src/plugins/manageSource/Preview.vue +1 -1
- package/src/plugins/manageSource/View.vue +3 -687
- package/src/plugins/manageSource/index.ts +3 -2
- package/src/plugins/markdown/Preview.vue +1 -1
- package/src/plugins/markdown/View.vue +164 -73
- package/src/plugins/markdown/definition.ts +6 -4
- package/src/plugins/markdown/index.ts +3 -2
- package/src/plugins/presentForm/Preview.vue +99 -0
- package/src/plugins/presentForm/View.vue +675 -0
- package/src/plugins/presentForm/definition.ts +127 -0
- package/src/plugins/presentForm/index.ts +18 -0
- package/src/plugins/presentForm/plugin.ts +94 -0
- package/src/plugins/presentForm/types.ts +109 -0
- package/src/plugins/presentHtml/Preview.vue +1 -1
- package/src/plugins/presentHtml/View.vue +7 -4
- package/src/plugins/presentHtml/index.ts +3 -2
- package/src/plugins/presentMulmoScript/Preview.vue +1 -1
- package/src/plugins/presentMulmoScript/View.vue +36 -26
- package/src/plugins/presentMulmoScript/index.ts +3 -2
- package/src/plugins/scheduler/AutomationsPreview.vue +37 -0
- package/src/plugins/scheduler/AutomationsView.vue +23 -0
- package/src/plugins/scheduler/CalendarView.vue +23 -0
- package/src/plugins/scheduler/LegacySchedulerView.vue +32 -0
- package/src/plugins/scheduler/Preview.vue +7 -4
- package/src/plugins/scheduler/TasksTab.vue +119 -28
- package/src/plugins/scheduler/View.vue +75 -32
- package/src/plugins/scheduler/automationsDefinition.ts +58 -0
- package/src/plugins/scheduler/calendarDefinition.ts +46 -0
- package/src/plugins/scheduler/formatSchedule.ts +93 -0
- package/src/plugins/scheduler/index.ts +68 -14
- package/src/plugins/scheduler/legacyShape.ts +34 -0
- package/src/plugins/spreadsheet/Preview.vue +9 -5
- package/src/plugins/spreadsheet/View.vue +43 -57
- package/src/plugins/spreadsheet/engine/responseDecoder.ts +2 -1
- package/src/plugins/spreadsheet/index.ts +3 -2
- package/src/plugins/textResponse/Preview.vue +15 -58
- package/src/plugins/textResponse/View.vue +42 -45
- package/src/plugins/textResponse/utils.ts +25 -0
- package/src/plugins/todo/Preview.vue +11 -6
- package/src/plugins/todo/View.vue +27 -13
- package/src/plugins/todo/composables/useTodos.ts +3 -1
- package/src/plugins/todo/index.ts +3 -2
- package/src/plugins/ui-image/ImagePreview.vue +6 -3
- package/src/plugins/ui-image/ImageView.vue +7 -4
- package/src/plugins/wiki/Preview.vue +5 -2
- package/src/plugins/wiki/View.vue +539 -92
- package/src/plugins/wiki/index.ts +5 -2
- package/src/plugins/wiki/route.ts +121 -0
- package/src/router/guards.ts +43 -24
- package/src/router/index.ts +53 -26
- package/src/router/pageRoutes.ts +23 -0
- package/src/tools/index.ts +12 -5
- package/src/tools/legacyPluginNames.ts +13 -0
- package/src/types/notification.ts +31 -6
- package/src/types/vue-i18n.d.ts +20 -0
- package/src/utils/agent/eventDispatch.ts +3 -6
- package/src/utils/agent/formatElapsed.ts +37 -0
- package/src/utils/agent/request.ts +22 -1
- package/src/utils/canvas/layoutMode.ts +26 -0
- package/src/utils/canvas/sidePanelVisible.ts +19 -0
- package/src/utils/dom/scrollIntoViewByTestId.ts +38 -0
- package/src/utils/errors.ts +9 -2
- package/src/utils/files/filename.ts +24 -0
- package/src/utils/filesPreview/schedulerPreview.ts +9 -3
- package/src/utils/id.ts +18 -0
- package/src/utils/image/cacheBust.ts +16 -0
- package/src/utils/image/resolve.ts +16 -0
- package/src/utils/markdown/taskList.ts +175 -0
- package/src/utils/mcp/interpolateSpec.ts +97 -0
- package/src/utils/notification/dispatch.ts +51 -15
- package/src/utils/path/workspaceLinkRouter.ts +99 -0
- package/src/utils/session/mergeSessions.ts +5 -0
- package/src/utils/sources/filter.ts +69 -0
- 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-Bm70FDU2.css +0 -1
- package/client/assets/typeof-DBp4T-Ny-BC0P-2DM.js +0 -1
- package/server/workspace/journal/linkRewrite.ts +0 -4
- package/src/components/ToolResultsPanel.vue +0 -77
- package/src/composables/useCanvasViewMode.ts +0 -121
- package/src/plugins/scheduler/definition.ts +0 -57
- package/src/utils/canvas/viewMode.ts +0 -46
- package/src/utils/role/plugins.ts +0 -12
- package/src/utils/session/seedRoleDefault.ts +0 -35
- /package/client/assets/{purify.es-Fx1Nqyry-PeS5RUhs.js → purify.es-Fx1Nqyry-BwJECkqS.js} +0 -0
|
@@ -5,6 +5,7 @@ import View from "./View.vue";
|
|
|
5
5
|
import Preview from "./Preview.vue";
|
|
6
6
|
import { apiPost } from "../../utils/api";
|
|
7
7
|
import { API_ROUTES } from "../../config/apiRoutes";
|
|
8
|
+
import { makeUuid } from "../../utils/id";
|
|
8
9
|
|
|
9
10
|
// Mirrors server/sources/types.ts#Source. Re-declared here so the
|
|
10
11
|
// frontend doesn't have to import a server package.
|
|
@@ -46,14 +47,14 @@ const manageSourcePlugin: ToolPlugin<ManageSourceData> = {
|
|
|
46
47
|
if (!result.ok) {
|
|
47
48
|
return {
|
|
48
49
|
toolName: TOOL_NAME,
|
|
49
|
-
uuid:
|
|
50
|
+
uuid: makeUuid(),
|
|
50
51
|
message: result.error,
|
|
51
52
|
};
|
|
52
53
|
}
|
|
53
54
|
return {
|
|
54
55
|
...result.data,
|
|
55
56
|
toolName: TOOL_NAME,
|
|
56
|
-
uuid:
|
|
57
|
+
uuid: makeUuid(),
|
|
57
58
|
};
|
|
58
59
|
},
|
|
59
60
|
isEnabled: () => true,
|
|
@@ -1,49 +1,52 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="markdown-container">
|
|
3
3
|
<div v-if="loading" class="min-h-full p-8 flex items-center justify-center">
|
|
4
|
-
<div class="text-gray-500">
|
|
4
|
+
<div class="text-gray-500">{{ t("pluginMarkdown.loading") }}</div>
|
|
5
5
|
</div>
|
|
6
6
|
<div v-else-if="loadError && !markdownContent" class="min-h-full p-8 flex items-center justify-center">
|
|
7
|
-
<div class="load-error-banner" role="alert"
|
|
7
|
+
<div class="load-error-banner" role="alert">{{ t("pluginMarkdown.loadFailed", { error: loadError }) }}</div>
|
|
8
8
|
</div>
|
|
9
9
|
<div v-else-if="!markdownContent" class="min-h-full p-8 flex items-center justify-center">
|
|
10
|
-
<div class="text-gray-500">
|
|
10
|
+
<div class="text-gray-500">{{ t("pluginMarkdown.noContent") }}</div>
|
|
11
11
|
</div>
|
|
12
12
|
<template v-else>
|
|
13
|
+
<div class="flex items-center justify-end gap-2 px-3 py-2 border-b border-gray-100 shrink-0">
|
|
14
|
+
<button
|
|
15
|
+
class="h-8 px-2.5 flex items-center gap-1 rounded bg-green-600 hover:bg-green-700 text-white text-sm disabled:opacity-60 disabled:cursor-not-allowed transition-colors"
|
|
16
|
+
:disabled="pdfDownloading"
|
|
17
|
+
@click="downloadPdf"
|
|
18
|
+
>
|
|
19
|
+
<span class="material-icons text-base">{{ pdfDownloading ? "hourglass_empty" : "download" }}</span>
|
|
20
|
+
{{ t("pluginMarkdown.pdf") }}
|
|
21
|
+
</button>
|
|
22
|
+
<span v-if="pdfError" class="text-xs text-red-500" :title="pdfError">{{ t("pluginMarkdown.pdfFailedShort") }}</span>
|
|
23
|
+
</div>
|
|
13
24
|
<div v-if="loadError" class="load-error-banner" role="alert">
|
|
14
|
-
|
|
25
|
+
{{ t("pluginMarkdown.refreshFailed", { error: loadError }) }}
|
|
15
26
|
</div>
|
|
16
27
|
<div class="markdown-content-wrapper">
|
|
17
28
|
<div class="p-4">
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
<button class="download-btn download-btn-green" :disabled="pdfDownloading" @click="downloadPdf">
|
|
24
|
-
<span class="material-icons">{{ pdfDownloading ? "hourglass_empty" : "download" }}</span>
|
|
25
|
-
PDF
|
|
26
|
-
</button>
|
|
27
|
-
</div>
|
|
28
|
-
<span v-if="pdfError" class="text-xs text-red-500 self-center ml-2" :title="pdfError">⚠ PDF failed</span>
|
|
29
|
-
</div>
|
|
30
|
-
<div class="markdown-content prose prose-slate max-w-none" v-html="renderedHtml"></div>
|
|
29
|
+
<!-- Click delegation: a single listener on the wrapper picks
|
|
30
|
+
up every interactive checkbox inserted by v-html. We
|
|
31
|
+
cannot bind @click directly on each `<input>` because
|
|
32
|
+
v-html bypasses Vue's template compiler. -->
|
|
33
|
+
<div class="markdown-content prose prose-slate max-w-none" @click="onMarkdownClick" v-html="renderedHtml"></div>
|
|
31
34
|
</div>
|
|
32
35
|
</div>
|
|
33
36
|
|
|
34
37
|
<div class="bottom-bar-wrapper">
|
|
35
38
|
<details ref="sourceDetails" class="markdown-source" @toggle="onDetailsToggle">
|
|
36
|
-
<summary>
|
|
39
|
+
<summary>{{ t("pluginMarkdown.editSource") }}</summary>
|
|
37
40
|
<textarea v-model="editableMarkdown" class="markdown-editor" spellcheck="false"></textarea>
|
|
38
41
|
<div class="editor-actions">
|
|
39
42
|
<button class="apply-btn" :disabled="!hasChanges || saving" @click="applyMarkdown">
|
|
40
|
-
{{ saving ? "
|
|
43
|
+
{{ saving ? t("pluginMarkdown.saving") : t("pluginMarkdown.applyChanges") }}
|
|
41
44
|
</button>
|
|
42
|
-
<button class="cancel-btn" @click="cancelEdit">
|
|
45
|
+
<button class="cancel-btn" @click="cancelEdit">{{ t("pluginMarkdown.cancel") }}</button>
|
|
43
46
|
</div>
|
|
44
|
-
<p v-if="saveError" class="save-error" role="alert"
|
|
47
|
+
<p v-if="saveError" class="save-error" role="alert">{{ t("pluginMarkdown.saveError", { error: saveError }) }}</p>
|
|
45
48
|
</details>
|
|
46
|
-
<button v-show="!editing" class="copy-btn" :title="copied ? '
|
|
49
|
+
<button v-show="!editing" class="copy-btn" :title="copied ? t('pluginMarkdown.copiedLabel') : t('pluginMarkdown.copyLabel')" @click="copyText">
|
|
47
50
|
<span class="material-icons">{{ copied ? "check" : "content_copy" }}</span>
|
|
48
51
|
</button>
|
|
49
52
|
</div>
|
|
@@ -53,15 +56,20 @@
|
|
|
53
56
|
|
|
54
57
|
<script setup lang="ts">
|
|
55
58
|
import { computed, ref, watch, nextTick } from "vue";
|
|
59
|
+
import { useI18n } from "vue-i18n";
|
|
56
60
|
import { marked } from "marked";
|
|
61
|
+
|
|
62
|
+
const { t } = useI18n();
|
|
57
63
|
import type { ToolResult } from "gui-chat-protocol";
|
|
58
64
|
import { isFilePath, type MarkdownToolData } from "./definition";
|
|
59
65
|
import { rewriteMarkdownImageRefs } from "../../utils/image/rewriteMarkdownImageRefs";
|
|
66
|
+
import { findTaskLines, makeTasksInteractive, toggleTaskAt } from "../../utils/markdown/taskList";
|
|
60
67
|
import { usePdfDownload } from "../../composables/usePdfDownload";
|
|
61
68
|
import { apiGet, apiPut } from "../../utils/api";
|
|
62
69
|
import { API_ROUTES } from "../../config/apiRoutes";
|
|
63
70
|
import { useClipboardCopy } from "../../composables/useClipboardCopy";
|
|
64
|
-
import {
|
|
71
|
+
import { buildPdfFilename } from "../../utils/files/filename";
|
|
72
|
+
import { useAppApi } from "../../composables/useAppApi";
|
|
65
73
|
|
|
66
74
|
const props = defineProps<{
|
|
67
75
|
selectedResult: ToolResult<MarkdownToolData>;
|
|
@@ -71,6 +79,8 @@ const emit = defineEmits<{
|
|
|
71
79
|
updateResult: [result: ToolResult<MarkdownToolData>];
|
|
72
80
|
}>();
|
|
73
81
|
|
|
82
|
+
const appApi = useAppApi();
|
|
83
|
+
|
|
74
84
|
const loading = ref(false);
|
|
75
85
|
const saving = ref(false);
|
|
76
86
|
// Human-readable message shown next to the Save button when a PUT
|
|
@@ -133,7 +143,12 @@ const renderedHtml = computed(() => {
|
|
|
133
143
|
const raw = props.selectedResult.data?.markdown;
|
|
134
144
|
const basePath = typeof raw === "string" && isFilePath(raw) ? raw.slice(0, raw.lastIndexOf("/")) : "";
|
|
135
145
|
const withImages = rewriteMarkdownImageRefs(markdownContent.value, basePath);
|
|
136
|
-
|
|
146
|
+
// Strip the `disabled=""` attribute marked puts on GFM task
|
|
147
|
+
// checkboxes and tag them so `onMarkdownClick` can find them
|
|
148
|
+
// (#775). Inline content (no file backing) gets the same
|
|
149
|
+
// treatment so non-file-backed sessions still feel responsive,
|
|
150
|
+
// even though clicks there only update local state.
|
|
151
|
+
return makeTasksInteractive(marked(withImages) as string);
|
|
137
152
|
});
|
|
138
153
|
|
|
139
154
|
// Watch for scroll requests from viewState
|
|
@@ -178,9 +193,14 @@ const { pdfDownloading, pdfError, downloadPdf: rawDownloadPdf } = usePdfDownload
|
|
|
178
193
|
async function downloadPdf() {
|
|
179
194
|
if (!markdownContent.value) return;
|
|
180
195
|
const prefix = props.selectedResult.data?.filenamePrefix;
|
|
181
|
-
const rawName = prefix || props.selectedResult.title || "
|
|
182
|
-
const
|
|
183
|
-
|
|
196
|
+
const rawName = prefix || props.selectedResult.title || "";
|
|
197
|
+
const uuid = props.selectedResult.uuid;
|
|
198
|
+
const filename = buildPdfFilename({
|
|
199
|
+
name: rawName,
|
|
200
|
+
fallback: "document",
|
|
201
|
+
timestampMs: uuid ? appApi.getResultTimestamp(uuid) : undefined,
|
|
202
|
+
});
|
|
203
|
+
await rawDownloadPdf(markdownContent.value, filename);
|
|
184
204
|
}
|
|
185
205
|
|
|
186
206
|
async function applyMarkdown() {
|
|
@@ -189,16 +209,21 @@ async function applyMarkdown() {
|
|
|
189
209
|
|
|
190
210
|
saveError.value = null;
|
|
191
211
|
|
|
192
|
-
// If file-based, save to server
|
|
212
|
+
// If file-based, save to server. The `raw` value is the
|
|
213
|
+
// workspace-relative path returned by the server, so we send it
|
|
214
|
+
// verbatim — the route accepts any depth under `artifacts/documents/`
|
|
215
|
+
// (e.g. the YYYY/MM partitions added in #764).
|
|
193
216
|
if (isFilePath(raw)) {
|
|
194
217
|
saving.value = true;
|
|
195
|
-
const
|
|
196
|
-
|
|
218
|
+
const result = await apiPut<unknown>(API_ROUTES.plugins.updateMarkdown, {
|
|
219
|
+
relativePath: raw,
|
|
197
220
|
markdown: editableMarkdown.value,
|
|
198
221
|
});
|
|
199
222
|
saving.value = false;
|
|
200
223
|
if (!result.ok) {
|
|
201
|
-
|
|
224
|
+
// Store the raw error; the template formats it via t() so locale
|
|
225
|
+
// switches re-render without double-translating.
|
|
226
|
+
saveError.value = result.error;
|
|
202
227
|
return;
|
|
203
228
|
}
|
|
204
229
|
}
|
|
@@ -221,6 +246,114 @@ async function applyMarkdown() {
|
|
|
221
246
|
if (sourceDetails.value) sourceDetails.value.open = false;
|
|
222
247
|
}
|
|
223
248
|
|
|
249
|
+
// ── Inline task-list checkbox toggle (#775) ──────────────────────
|
|
250
|
+
//
|
|
251
|
+
// Click delegation handler bound to the rendered viewer. When the
|
|
252
|
+
// user clicks a GFM task checkbox we:
|
|
253
|
+
// 1. compute the new source via `toggleTaskAt`
|
|
254
|
+
// 2. update local state optimistically (v-html re-renders to match)
|
|
255
|
+
// 3. for file-backed docs, queue a PUT through the existing
|
|
256
|
+
// `/api/markdowns/update` route
|
|
257
|
+
//
|
|
258
|
+
// Skipped while the source editor is open — the textarea has its own
|
|
259
|
+
// edit/apply flow and a checkbox click would race with whatever the
|
|
260
|
+
// user is typing.
|
|
261
|
+
|
|
262
|
+
let taskPersistChain: Promise<unknown> = Promise.resolve();
|
|
263
|
+
|
|
264
|
+
async function persistTaskMarkdown(relativePath: string, markdown: string): Promise<void> {
|
|
265
|
+
// Bail if the user navigated to a different result while this PUT
|
|
266
|
+
// was queued — the snapshot belongs to a document that's no longer
|
|
267
|
+
// on screen, and persisting it would clobber unrelated state.
|
|
268
|
+
if (props.selectedResult.data?.markdown !== relativePath) return;
|
|
269
|
+
|
|
270
|
+
const result = await apiPut<unknown>(API_ROUTES.plugins.updateMarkdown, {
|
|
271
|
+
relativePath,
|
|
272
|
+
markdown,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// The user may have switched results during the round-trip. Skip
|
|
276
|
+
// every state mutation past this point — the watcher on
|
|
277
|
+
// `selectedResult.data?.markdown` already loads the new document,
|
|
278
|
+
// and writing `saveError` / triggering a refetch here would touch
|
|
279
|
+
// unrelated state (or refetch the *new* doc, masking edits the
|
|
280
|
+
// user just made there).
|
|
281
|
+
if (props.selectedResult.data?.markdown !== relativePath) return;
|
|
282
|
+
|
|
283
|
+
if (!result.ok) {
|
|
284
|
+
saveError.value = result.error;
|
|
285
|
+
// Refetch synchronously inside the chain so subsequent queued
|
|
286
|
+
// clicks observe the canonical (server-side) markdown before
|
|
287
|
+
// computing their own toggle. Detaching this with `void` could
|
|
288
|
+
// let the refetch land after a newer click already wrote.
|
|
289
|
+
await fetchMarkdownContent();
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
// Clear any stale error from a prior failed click.
|
|
293
|
+
saveError.value = null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function onMarkdownClick(event: MouseEvent): void {
|
|
297
|
+
const target = event.target;
|
|
298
|
+
if (!(target instanceof HTMLInputElement)) return;
|
|
299
|
+
if (target.type !== "checkbox") return;
|
|
300
|
+
if (!target.classList.contains("md-task")) return;
|
|
301
|
+
if (editing.value) {
|
|
302
|
+
// Edit panel open — let the textarea own the source. Reverting
|
|
303
|
+
// here keeps the visual in sync with the still-untouched markdown.
|
|
304
|
+
target.checked = !target.checked;
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const root = event.currentTarget as HTMLElement;
|
|
309
|
+
const taskInputs = root.querySelectorAll<HTMLInputElement>("input.md-task");
|
|
310
|
+
const taskIndex = Array.from(taskInputs).indexOf(target);
|
|
311
|
+
if (taskIndex < 0) return;
|
|
312
|
+
|
|
313
|
+
// Cross-check: if the source-side walker sees a different number
|
|
314
|
+
// of tasks than `marked` rendered into the DOM, the index map
|
|
315
|
+
// can't be trusted. The most common cause is a `- [ ]`-shaped line
|
|
316
|
+
// inside a 4-space indented code block (the source walker treats
|
|
317
|
+
// it as a task; marked treats it as code) — toggling source by
|
|
318
|
+
// index would corrupt the file. Refuse all clicks when this
|
|
319
|
+
// happens.
|
|
320
|
+
const sourceTasks = findTaskLines(markdownContent.value);
|
|
321
|
+
if (sourceTasks.length !== taskInputs.length) {
|
|
322
|
+
target.checked = !target.checked;
|
|
323
|
+
saveError.value = t("pluginMarkdown.taskCountMismatch");
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const updated = toggleTaskAt(markdownContent.value, taskIndex);
|
|
328
|
+
if (updated === null) {
|
|
329
|
+
// Source/DOM drift — refuse to write something we can't trace.
|
|
330
|
+
target.checked = !target.checked;
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Optimistic local update — v-html will re-render and the
|
|
335
|
+
// textarea (if anyone opens it next) sees the same content.
|
|
336
|
+
markdownContent.value = updated;
|
|
337
|
+
editableMarkdown.value = updated;
|
|
338
|
+
|
|
339
|
+
const raw = props.selectedResult.data?.markdown;
|
|
340
|
+
if (typeof raw === "string" && isFilePath(raw)) {
|
|
341
|
+
// Serialize PUTs so quick successive clicks don't race each
|
|
342
|
+
// other on the wire — the chain captures `updated` per click.
|
|
343
|
+
taskPersistChain = taskPersistChain.then(() => persistTaskMarkdown(raw, updated));
|
|
344
|
+
} else {
|
|
345
|
+
// Inline content — emit so the parent stores the edit.
|
|
346
|
+
emit("updateResult", {
|
|
347
|
+
...props.selectedResult,
|
|
348
|
+
data: {
|
|
349
|
+
...props.selectedResult.data,
|
|
350
|
+
markdown: updated,
|
|
351
|
+
pdfPath: undefined,
|
|
352
|
+
},
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
224
357
|
// Watch for external changes to selectedResult (when user clicks different result)
|
|
225
358
|
watch(
|
|
226
359
|
() => props.selectedResult.data?.markdown,
|
|
@@ -245,48 +378,6 @@ watch(
|
|
|
245
378
|
min-height: 0;
|
|
246
379
|
}
|
|
247
380
|
|
|
248
|
-
.header-row {
|
|
249
|
-
display: flex;
|
|
250
|
-
align-items: center;
|
|
251
|
-
justify-content: space-between;
|
|
252
|
-
margin-bottom: 1em;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
.document-title {
|
|
256
|
-
font-size: 2em;
|
|
257
|
-
margin: 0;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
.button-group {
|
|
261
|
-
display: flex;
|
|
262
|
-
gap: 0.5em;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
.download-btn {
|
|
266
|
-
padding: 0.5em 1em;
|
|
267
|
-
color: white;
|
|
268
|
-
border: none;
|
|
269
|
-
border-radius: 4px;
|
|
270
|
-
cursor: pointer;
|
|
271
|
-
font-size: 0.9em;
|
|
272
|
-
display: flex;
|
|
273
|
-
align-items: center;
|
|
274
|
-
gap: 0.5em;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
.download-btn-green {
|
|
278
|
-
background-color: #4caf50;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
.download-btn .material-icons {
|
|
282
|
-
font-size: 1.2em;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
.download-btn:disabled {
|
|
286
|
-
opacity: 0.6;
|
|
287
|
-
cursor: not-allowed;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
381
|
.markdown-content :deep(h1) {
|
|
291
382
|
font-size: 2rem;
|
|
292
383
|
font-weight: bold;
|
|
@@ -9,12 +9,14 @@ export interface MarkdownToolData {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
/** True when the `markdown` field is a workspace-relative file path
|
|
12
|
-
* rather than inline content.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* rather than inline content. Accepts only the canonical
|
|
13
|
+
* `artifacts/documents/*.md` prefix now that server-side
|
|
14
|
+
* `isMarkdownPath` agrees. Any legacy `markdowns/*.md` references
|
|
15
|
+
* in old session JSONL must be migrated via
|
|
16
|
+
* `scripts/migrate-legacy-artifact-paths.ts` (#773). */
|
|
15
17
|
export function isFilePath(value: string): boolean {
|
|
16
18
|
if (!value.endsWith(".md")) return false;
|
|
17
|
-
return value.startsWith("artifacts/documents/")
|
|
19
|
+
return value.startsWith("artifacts/documents/");
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
const toolDefinition: ToolDefinition = {
|
|
@@ -6,6 +6,7 @@ import View from "./View.vue";
|
|
|
6
6
|
import Preview from "./Preview.vue";
|
|
7
7
|
import { apiPost } from "../../utils/api";
|
|
8
8
|
import { API_ROUTES } from "../../config/apiRoutes";
|
|
9
|
+
import { makeUuid } from "../../utils/id";
|
|
9
10
|
|
|
10
11
|
const markdownPlugin: ToolPlugin<MarkdownToolData> = {
|
|
11
12
|
toolDefinition,
|
|
@@ -15,14 +16,14 @@ const markdownPlugin: ToolPlugin<MarkdownToolData> = {
|
|
|
15
16
|
if (!result.ok) {
|
|
16
17
|
return {
|
|
17
18
|
toolName: TOOL_NAME,
|
|
18
|
-
uuid:
|
|
19
|
+
uuid: makeUuid(),
|
|
19
20
|
message: result.error,
|
|
20
21
|
};
|
|
21
22
|
}
|
|
22
23
|
return {
|
|
23
24
|
...result.data,
|
|
24
25
|
toolName: TOOL_NAME,
|
|
25
|
-
uuid:
|
|
26
|
+
uuid: makeUuid(),
|
|
26
27
|
};
|
|
27
28
|
},
|
|
28
29
|
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="w-full h-full flex flex-col items-center justify-center p-4 bg-gradient-to-br from-blue-50 to-indigo-50 rounded-lg border-2 border-gray-200">
|
|
3
|
+
<div class="text-center">
|
|
4
|
+
<!-- Form Icon -->
|
|
5
|
+
<svg class="w-12 h-12 mx-auto mb-3 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
6
|
+
<path
|
|
7
|
+
stroke-linecap="round"
|
|
8
|
+
stroke-linejoin="round"
|
|
9
|
+
stroke-width="2"
|
|
10
|
+
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
|
11
|
+
/>
|
|
12
|
+
</svg>
|
|
13
|
+
|
|
14
|
+
<!-- Form Title or Default -->
|
|
15
|
+
<h3 class="text-gray-900 font-bold text-lg mb-1 line-clamp-2">
|
|
16
|
+
{{ formData?.title || t("pluginPresentForm.fallbackTitle") }}
|
|
17
|
+
</h3>
|
|
18
|
+
|
|
19
|
+
<!-- Field Count -->
|
|
20
|
+
<p class="text-gray-600 text-sm mb-2">{{ t("pluginPresentForm.fieldCount", { count: fieldCount }, fieldCount) }}</p>
|
|
21
|
+
|
|
22
|
+
<!-- Completion Status -->
|
|
23
|
+
<div v-if="!isSubmitted" class="flex items-center justify-center gap-2">
|
|
24
|
+
<div class="w-32 h-2 bg-gray-200 rounded-full overflow-hidden">
|
|
25
|
+
<div class="h-full bg-blue-600 transition-all duration-300" :style="{ width: `${completionPercentage}%` }" />
|
|
26
|
+
</div>
|
|
27
|
+
<span class="text-xs text-gray-500">{{ `${completionPercentage}%` }}</span>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<!-- Submitted Badge -->
|
|
31
|
+
<div v-else class="inline-flex items-center gap-1 px-3 py-1 bg-green-100 text-green-700 rounded-full text-xs font-semibold">
|
|
32
|
+
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
|
|
33
|
+
<path
|
|
34
|
+
fill-rule="evenodd"
|
|
35
|
+
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
|
36
|
+
clip-rule="evenodd"
|
|
37
|
+
/>
|
|
38
|
+
</svg>
|
|
39
|
+
{{ t("pluginPresentForm.submitted") }}
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</template>
|
|
44
|
+
|
|
45
|
+
<script setup lang="ts">
|
|
46
|
+
import { computed } from "vue";
|
|
47
|
+
import { useI18n } from "vue-i18n";
|
|
48
|
+
import type { ToolResult } from "gui-chat-protocol";
|
|
49
|
+
import type { FormData } from "./types";
|
|
50
|
+
|
|
51
|
+
const { t } = useI18n();
|
|
52
|
+
|
|
53
|
+
interface FormViewState {
|
|
54
|
+
userResponses: Record<string, unknown>;
|
|
55
|
+
touched: string[];
|
|
56
|
+
submitted?: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const props = defineProps<{
|
|
60
|
+
result: ToolResult;
|
|
61
|
+
}>();
|
|
62
|
+
|
|
63
|
+
const formData = computed<FormData | null>(() => {
|
|
64
|
+
if (props.result?.toolName === "presentForm") {
|
|
65
|
+
return props.result.jsonData as FormData;
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const viewState = computed<FormViewState | null>(() => {
|
|
71
|
+
return (props.result?.viewState as unknown as FormViewState) || null;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const fieldCount = computed(() => {
|
|
75
|
+
return formData.value?.fields.length || 0;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const isSubmitted = computed(() => {
|
|
79
|
+
return viewState.value?.submitted || false;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const completionPercentage = computed(() => {
|
|
83
|
+
if (!formData.value || isSubmitted.value) return 100;
|
|
84
|
+
|
|
85
|
+
const requiredFields = formData.value.fields.filter((field) => field.required);
|
|
86
|
+
if (requiredFields.length === 0) return 0;
|
|
87
|
+
|
|
88
|
+
const responses = viewState.value?.userResponses || {};
|
|
89
|
+
const filledRequired = requiredFields.filter((field) => {
|
|
90
|
+
const value = responses[field.id];
|
|
91
|
+
if (value === null || value === undefined) return false;
|
|
92
|
+
if (typeof value === "string") return value.trim() !== "";
|
|
93
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
94
|
+
return true;
|
|
95
|
+
}).length;
|
|
96
|
+
|
|
97
|
+
return Math.round((filledRequired / requiredFields.length) * 100);
|
|
98
|
+
});
|
|
99
|
+
</script>
|