mulmoclaude 0.1.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/README.md +44 -0
- package/bin/mulmoclaude.js +202 -0
- package/bin/prepare-dist.js +93 -0
- package/client/assets/chunk-vKJrgz-R-C_I3GbVV.js +1 -0
- package/client/assets/html2canvas-Cx501zZr-BF5dYYkY.js +5 -0
- package/client/assets/index-D8rhwXLq.js +4906 -0
- package/client/assets/index-KNLBjwuh.css +1 -0
- package/client/assets/index.es-D4YyL_Dg-BfRHLTZV.js +5 -0
- package/client/assets/material-icons-Dr0goTwe.woff +0 -0
- package/client/assets/material-icons-kAwBdRge.woff2 +0 -0
- package/client/assets/material-icons-outlined-BpWbwl2n.woff +0 -0
- package/client/assets/material-icons-outlined-DZhiGvEA.woff2 +0 -0
- package/client/assets/material-icons-round-BDlwx-sv.woff +0 -0
- package/client/assets/material-icons-round-DrirKXBx.woff2 +0 -0
- package/client/assets/material-icons-sharp-CH1KkVu7.woff +0 -0
- package/client/assets/material-icons-sharp-gidztirS.woff2 +0 -0
- package/client/assets/material-icons-two-tone-B7wz7mED.woff +0 -0
- package/client/assets/material-icons-two-tone-DuNIpaEj.woff2 +0 -0
- package/client/assets/mulmo_bw-ERmkSv0a.png +0 -0
- package/client/assets/purify.es-Fx1Nqyry-PeS5RUhs.js +2 -0
- package/client/assets/typeof-DBp4T-Ny-BC0P-2DM.js +1 -0
- package/client/index.html +28 -0
- package/package.json +66 -0
- package/server/agent/attachmentConverter.ts +270 -0
- package/server/agent/config.ts +414 -0
- package/server/agent/index.ts +260 -0
- package/server/agent/mcp-server.ts +412 -0
- package/server/agent/mcp-tools/index.ts +63 -0
- package/server/agent/mcp-tools/x.ts +188 -0
- package/server/agent/plugin-names.ts +75 -0
- package/server/agent/prompt.ts +349 -0
- package/server/agent/resumeFailover.ts +129 -0
- package/server/agent/sandboxMounts.ts +329 -0
- package/server/agent/stream.ts +194 -0
- package/server/api/auth/bearerAuth.ts +61 -0
- package/server/api/auth/token.ts +98 -0
- package/server/api/csrfGuard.ts +85 -0
- package/server/api/routes/agent.ts +478 -0
- package/server/api/routes/chart.ts +98 -0
- package/server/api/routes/chat-index.ts +46 -0
- package/server/api/routes/config.ts +258 -0
- package/server/api/routes/dispatchResponse.ts +79 -0
- package/server/api/routes/files.ts +812 -0
- package/server/api/routes/html.ts +101 -0
- package/server/api/routes/image.ts +169 -0
- package/server/api/routes/mulmo-script.ts +712 -0
- package/server/api/routes/mulmoScriptValidate.ts +101 -0
- package/server/api/routes/notifications.ts +69 -0
- package/server/api/routes/pdf.ts +163 -0
- package/server/api/routes/plugins.ts +276 -0
- package/server/api/routes/presentHtml.ts +48 -0
- package/server/api/routes/roles.ts +125 -0
- package/server/api/routes/scheduler.ts +153 -0
- package/server/api/routes/schedulerHandlers.ts +151 -0
- package/server/api/routes/schedulerTasks.ts +163 -0
- package/server/api/routes/sessions.ts +294 -0
- package/server/api/routes/sessionsCursor.ts +59 -0
- package/server/api/routes/skills.ts +195 -0
- package/server/api/routes/sources.ts +540 -0
- package/server/api/routes/todos.ts +263 -0
- package/server/api/routes/todosColumnsHandlers.ts +347 -0
- package/server/api/routes/todosHandlers.ts +274 -0
- package/server/api/routes/todosItemsHandlers.ts +386 -0
- package/server/api/routes/wiki/pageIndex.ts +53 -0
- package/server/api/routes/wiki.ts +363 -0
- package/server/api/sandboxStatus.ts +64 -0
- package/server/events/notifications.ts +160 -0
- package/server/events/pub-sub/index.ts +45 -0
- package/server/events/relay-client.ts +288 -0
- package/server/events/scheduler-adapter.ts +302 -0
- package/server/events/session-store/index.ts +492 -0
- package/server/events/task-manager/index.ts +181 -0
- package/server/index.ts +572 -0
- package/server/system/config.ts +243 -0
- package/server/system/credentials.ts +220 -0
- package/server/system/docker.ts +97 -0
- package/server/system/env.ts +109 -0
- package/server/system/logger/config.ts +112 -0
- package/server/system/logger/formatters.ts +40 -0
- package/server/system/logger/index.ts +53 -0
- package/server/system/logger/rotation.ts +37 -0
- package/server/system/logger/sinks.ts +101 -0
- package/server/system/logger/types.ts +29 -0
- package/server/utils/date.ts +57 -0
- package/server/utils/errors.ts +7 -0
- package/server/utils/fetch.ts +27 -0
- package/server/utils/files/atomic.ts +125 -0
- package/server/utils/files/html-io.ts +20 -0
- package/server/utils/files/image-store.ts +66 -0
- package/server/utils/files/index.ts +45 -0
- package/server/utils/files/journal-io.ts +213 -0
- package/server/utils/files/json.ts +69 -0
- package/server/utils/files/markdown-store.ts +33 -0
- package/server/utils/files/naming.ts +50 -0
- package/server/utils/files/reference-dirs-io.ts +45 -0
- package/server/utils/files/roles-io.ts +45 -0
- package/server/utils/files/safe.ts +106 -0
- package/server/utils/files/scheduler-io.ts +20 -0
- package/server/utils/files/scheduler-overrides-io.ts +64 -0
- package/server/utils/files/session-io.ts +136 -0
- package/server/utils/files/spreadsheet-store.ts +63 -0
- package/server/utils/files/todos-io.ts +29 -0
- package/server/utils/files/user-tasks-io.ts +25 -0
- package/server/utils/files/workspace-io.ts +221 -0
- package/server/utils/gemini.ts +59 -0
- package/server/utils/gitignore.ts +69 -0
- package/server/utils/http.ts +15 -0
- package/server/utils/httpError.ts +61 -0
- package/server/utils/id.ts +16 -0
- package/server/utils/json.ts +83 -0
- package/server/utils/logBackgroundError.ts +22 -0
- package/server/utils/markdown.ts +82 -0
- package/server/utils/request.ts +29 -0
- package/server/utils/slug.ts +50 -0
- package/server/utils/spawn.ts +62 -0
- package/server/utils/time.ts +34 -0
- package/server/utils/types.ts +47 -0
- package/server/workspace/chat-index/index.ts +153 -0
- package/server/workspace/chat-index/indexer.ts +209 -0
- package/server/workspace/chat-index/paths.ts +34 -0
- package/server/workspace/chat-index/summarizer.ts +247 -0
- package/server/workspace/chat-index/types.ts +38 -0
- package/server/workspace/custom-dirs.ts +220 -0
- package/server/workspace/helps/business.md +104 -0
- package/server/workspace/helps/github.md +23 -0
- package/server/workspace/helps/index.md +60 -0
- package/server/workspace/helps/mulmoscript.md +249 -0
- package/server/workspace/helps/sandbox.md +90 -0
- package/server/workspace/helps/spreadsheet.md +43 -0
- package/server/workspace/helps/telegram.md +135 -0
- package/server/workspace/helps/wiki.md +131 -0
- package/server/workspace/journal/archivist.ts +386 -0
- package/server/workspace/journal/dailyPass.ts +743 -0
- package/server/workspace/journal/diff.ts +71 -0
- package/server/workspace/journal/index.ts +185 -0
- package/server/workspace/journal/indexFile.ts +136 -0
- package/server/workspace/journal/linkRewrite.ts +4 -0
- package/server/workspace/journal/memoryExtractor.ts +130 -0
- package/server/workspace/journal/optimizationPass.ts +160 -0
- package/server/workspace/journal/paths.ts +76 -0
- package/server/workspace/journal/state.ts +125 -0
- package/server/workspace/paths.ts +158 -0
- package/server/workspace/reference-dirs.ts +252 -0
- package/server/workspace/roles.ts +37 -0
- package/server/workspace/skills/discovery.ts +125 -0
- package/server/workspace/skills/index.ts +10 -0
- package/server/workspace/skills/parser.ts +144 -0
- package/server/workspace/skills/paths.ts +41 -0
- package/server/workspace/skills/scheduler.ts +149 -0
- package/server/workspace/skills/types.ts +30 -0
- package/server/workspace/skills/user-tasks.ts +257 -0
- package/server/workspace/skills/writer.ts +189 -0
- package/server/workspace/sources/arxivDiscovery.ts +182 -0
- package/server/workspace/sources/classifier.ts +268 -0
- package/server/workspace/sources/fetchers/arxiv.ts +170 -0
- package/server/workspace/sources/fetchers/github.ts +106 -0
- package/server/workspace/sources/fetchers/githubIssues.ts +208 -0
- package/server/workspace/sources/fetchers/githubReleases.ts +186 -0
- package/server/workspace/sources/fetchers/index.ts +71 -0
- package/server/workspace/sources/fetchers/registerAll.ts +15 -0
- package/server/workspace/sources/fetchers/rss.ts +141 -0
- package/server/workspace/sources/fetchers/rssParser.ts +295 -0
- package/server/workspace/sources/httpFetcher.ts +230 -0
- package/server/workspace/sources/interests.ts +120 -0
- package/server/workspace/sources/paths.ts +110 -0
- package/server/workspace/sources/pipeline/dedup.ts +60 -0
- package/server/workspace/sources/pipeline/fetch.ts +136 -0
- package/server/workspace/sources/pipeline/index.ts +249 -0
- package/server/workspace/sources/pipeline/notify.ts +72 -0
- package/server/workspace/sources/pipeline/plan.ts +66 -0
- package/server/workspace/sources/pipeline/summarize.ts +189 -0
- package/server/workspace/sources/pipeline/write.ts +185 -0
- package/server/workspace/sources/rateLimiter.ts +148 -0
- package/server/workspace/sources/registry.ts +326 -0
- package/server/workspace/sources/robots.ts +271 -0
- package/server/workspace/sources/sourceState.ts +135 -0
- package/server/workspace/sources/taxonomy.ts +74 -0
- package/server/workspace/sources/types.ts +144 -0
- package/server/workspace/sources/urls.ts +112 -0
- package/server/workspace/tool-trace/classify.ts +114 -0
- package/server/workspace/tool-trace/index.ts +250 -0
- package/server/workspace/tool-trace/writeSearch.ts +98 -0
- package/server/workspace/wiki-backlinks/index.ts +107 -0
- package/server/workspace/wiki-backlinks/sessionBacklinks.ts +144 -0
- package/server/workspace/workspace.ts +66 -0
- package/src/App.vue +720 -0
- package/src/assets/mulmo_bw.png +0 -0
- package/src/components/CanvasViewToggle.vue +27 -0
- package/src/components/ChatAttachmentPreview.vue +45 -0
- package/src/components/ChatImagePreview.vue +17 -0
- package/src/components/ChatInput.vue +208 -0
- package/src/components/FileContentHeader.vue +49 -0
- package/src/components/FileContentRenderer.vue +162 -0
- package/src/components/FileTree.vue +115 -0
- package/src/components/FileTreePane.vue +85 -0
- package/src/components/FilesView.vue +206 -0
- package/src/components/LockStatusPopup.vue +111 -0
- package/src/components/NotificationBell.vue +131 -0
- package/src/components/NotificationToast.vue +72 -0
- package/src/components/PluginLauncher.vue +138 -0
- package/src/components/RightSidebar.vue +113 -0
- package/src/components/RoleSelector.vue +64 -0
- package/src/components/SessionHistoryPanel.vue +176 -0
- package/src/components/SessionTabBar.vue +81 -0
- package/src/components/SettingsMcpTab.vue +350 -0
- package/src/components/SettingsModal.vue +275 -0
- package/src/components/SettingsReferenceDirsTab.vue +173 -0
- package/src/components/SettingsWorkspaceDirsTab.vue +174 -0
- package/src/components/SidebarHeader.vue +69 -0
- package/src/components/StackView.vue +360 -0
- package/src/components/SuggestionsPanel.vue +65 -0
- package/src/components/TodoExplorer.vue +358 -0
- package/src/components/ToolResultsPanel.vue +77 -0
- package/src/components/todo/TodoAddDialog.vue +131 -0
- package/src/components/todo/TodoEditDialog.vue +47 -0
- package/src/components/todo/TodoEditPanel.vue +113 -0
- package/src/components/todo/TodoKanbanView.vue +249 -0
- package/src/components/todo/TodoListView.vue +79 -0
- package/src/components/todo/TodoTableView.vue +177 -0
- package/src/composables/useActiveSession.ts +40 -0
- package/src/composables/useAppApi.ts +45 -0
- package/src/composables/useCanvasViewMode.ts +121 -0
- package/src/composables/useChatScroll.ts +47 -0
- package/src/composables/useClickOutside.ts +26 -0
- package/src/composables/useClipboardCopy.ts +44 -0
- package/src/composables/useContentDisplay.ts +52 -0
- package/src/composables/useDebugBeat.ts +23 -0
- package/src/composables/useDynamicFavicon.ts +115 -0
- package/src/composables/useEventListeners.ts +42 -0
- package/src/composables/useExpandedDirs.ts +64 -0
- package/src/composables/useFaviconState.ts +30 -0
- package/src/composables/useFileSelection.ts +115 -0
- package/src/composables/useFileSortMode.ts +24 -0
- package/src/composables/useFileTree.ts +85 -0
- package/src/composables/useFreshPluginData.ts +89 -0
- package/src/composables/useHealth.ts +38 -0
- package/src/composables/useImeAwareEnter.ts +57 -0
- package/src/composables/useKeyNavigation.ts +60 -0
- package/src/composables/useMarkdownLinkHandler.ts +46 -0
- package/src/composables/useMarkdownMode.ts +17 -0
- package/src/composables/useMcpTools.ts +71 -0
- package/src/composables/useMergedSessions.ts +27 -0
- package/src/composables/useNotifications.ts +90 -0
- package/src/composables/usePdfDownload.ts +60 -0
- package/src/composables/usePendingCalls.ts +77 -0
- package/src/composables/usePubSub.ts +85 -0
- package/src/composables/useRightSidebar.ts +23 -0
- package/src/composables/useRoles.ts +34 -0
- package/src/composables/useSandboxStatus.ts +67 -0
- package/src/composables/useSelectedResult.ts +49 -0
- package/src/composables/useSessionDerived.ts +51 -0
- package/src/composables/useSessionHistory.ts +81 -0
- package/src/composables/useSessionSync.ts +57 -0
- package/src/composables/useViewLayout.ts +55 -0
- package/src/config/apiRoutes.ts +173 -0
- package/src/config/pubsubChannels.ts +45 -0
- package/src/config/roles.ts +335 -0
- package/src/config/schedulerActions.ts +25 -0
- package/src/config/toolNames.ts +71 -0
- package/src/config/workspacePaths.ts +24 -0
- package/src/index.css +107 -0
- package/src/main.ts +25 -0
- package/src/plugins/canvas/Preview.vue +13 -0
- package/src/plugins/canvas/View.vue +333 -0
- package/src/plugins/canvas/definition.ts +38 -0
- package/src/plugins/canvas/index.ts +36 -0
- package/src/plugins/chart/Preview.vue +49 -0
- package/src/plugins/chart/View.vue +143 -0
- package/src/plugins/chart/definition.ts +58 -0
- package/src/plugins/chart/index.ts +52 -0
- package/src/plugins/editImage/Preview.vue +13 -0
- package/src/plugins/editImage/View.vue +13 -0
- package/src/plugins/editImage/definition.ts +27 -0
- package/src/plugins/editImage/index.ts +36 -0
- package/src/plugins/generateImage/Preview.vue +13 -0
- package/src/plugins/generateImage/View.vue +33 -0
- package/src/plugins/generateImage/definition.ts +32 -0
- package/src/plugins/generateImage/index.ts +56 -0
- package/src/plugins/manageRoles/Preview.vue +49 -0
- package/src/plugins/manageRoles/View.vue +525 -0
- package/src/plugins/manageRoles/definition.ts +43 -0
- package/src/plugins/manageRoles/index.ts +47 -0
- package/src/plugins/manageSkills/Preview.vue +21 -0
- package/src/plugins/manageSkills/View.vue +321 -0
- package/src/plugins/manageSkills/definition.ts +49 -0
- package/src/plugins/manageSkills/index.ts +49 -0
- package/src/plugins/manageSource/Preview.vue +33 -0
- package/src/plugins/manageSource/View.vue +697 -0
- package/src/plugins/manageSource/definition.ts +63 -0
- package/src/plugins/manageSource/index.ts +66 -0
- package/src/plugins/markdown/Preview.vue +77 -0
- package/src/plugins/markdown/View.vue +476 -0
- package/src/plugins/markdown/definition.ts +50 -0
- package/src/plugins/markdown/index.ts +36 -0
- package/src/plugins/presentHtml/Preview.vue +25 -0
- package/src/plugins/presentHtml/View.vue +52 -0
- package/src/plugins/presentHtml/definition.ts +27 -0
- package/src/plugins/presentHtml/helpers.ts +72 -0
- package/src/plugins/presentHtml/index.ts +41 -0
- package/src/plugins/presentMulmoScript/Preview.vue +23 -0
- package/src/plugins/presentMulmoScript/View.vue +1166 -0
- package/src/plugins/presentMulmoScript/definition.ts +95 -0
- package/src/plugins/presentMulmoScript/helpers.ts +162 -0
- package/src/plugins/presentMulmoScript/index.ts +40 -0
- package/src/plugins/scheduler/Preview.vue +67 -0
- package/src/plugins/scheduler/TasksTab.vue +205 -0
- package/src/plugins/scheduler/View.vue +565 -0
- package/src/plugins/scheduler/definition.ts +57 -0
- package/src/plugins/scheduler/index.ts +45 -0
- package/src/plugins/scheduler/viewModes.ts +26 -0
- package/src/plugins/spreadsheet/Preview.vue +29 -0
- package/src/plugins/spreadsheet/View.vue +997 -0
- package/src/plugins/spreadsheet/cellHighlights.ts +79 -0
- package/src/plugins/spreadsheet/definition.ts +121 -0
- package/src/plugins/spreadsheet/engine/calculator.ts +459 -0
- package/src/plugins/spreadsheet/engine/cellBuilder.ts +81 -0
- package/src/plugins/spreadsheet/engine/date-parser.ts +220 -0
- package/src/plugins/spreadsheet/engine/date-utils.ts +56 -0
- package/src/plugins/spreadsheet/engine/engine.ts +176 -0
- package/src/plugins/spreadsheet/engine/evaluator.ts +390 -0
- package/src/plugins/spreadsheet/engine/formatter.ts +172 -0
- package/src/plugins/spreadsheet/engine/formulaRefs.ts +101 -0
- package/src/plugins/spreadsheet/engine/functions/date.ts +299 -0
- package/src/plugins/spreadsheet/engine/functions/financial.ts +387 -0
- package/src/plugins/spreadsheet/engine/functions/index.ts +16 -0
- package/src/plugins/spreadsheet/engine/functions/logical.ts +262 -0
- package/src/plugins/spreadsheet/engine/functions/lookup.ts +400 -0
- package/src/plugins/spreadsheet/engine/functions/mathematical.ts +297 -0
- package/src/plugins/spreadsheet/engine/functions/statistical.ts +338 -0
- package/src/plugins/spreadsheet/engine/functions/text.ts +389 -0
- package/src/plugins/spreadsheet/engine/index.ts +27 -0
- package/src/plugins/spreadsheet/engine/jsonCellLocator.ts +111 -0
- package/src/plugins/spreadsheet/engine/parser.ts +143 -0
- package/src/plugins/spreadsheet/engine/registry.ts +150 -0
- package/src/plugins/spreadsheet/engine/responseDecoder.ts +67 -0
- package/src/plugins/spreadsheet/engine/types.ts +64 -0
- package/src/plugins/spreadsheet/index.ts +36 -0
- package/src/plugins/textResponse/Preview.vue +94 -0
- package/src/plugins/textResponse/View.vue +503 -0
- package/src/plugins/textResponse/definition.ts +34 -0
- package/src/plugins/textResponse/index.ts +27 -0
- package/src/plugins/textResponse/plugin.ts +29 -0
- package/src/plugins/textResponse/samples.ts +97 -0
- package/src/plugins/textResponse/types.ts +11 -0
- package/src/plugins/todo/Preview.vue +63 -0
- package/src/plugins/todo/View.vue +364 -0
- package/src/plugins/todo/composables/useTodos.ts +177 -0
- package/src/plugins/todo/definition.ts +45 -0
- package/src/plugins/todo/index.ts +61 -0
- package/src/plugins/todo/labels.ts +163 -0
- package/src/plugins/todo/priority.ts +98 -0
- package/src/plugins/todo/viewModes.ts +19 -0
- package/src/plugins/ui-image/ImagePreview.vue +23 -0
- package/src/plugins/ui-image/ImageView.vue +34 -0
- package/src/plugins/ui-image/index.ts +3 -0
- package/src/plugins/ui-image/types.ts +4 -0
- package/src/plugins/wiki/Preview.vue +65 -0
- package/src/plugins/wiki/View.vue +342 -0
- package/src/plugins/wiki/definition.ts +25 -0
- package/src/plugins/wiki/helpers.ts +59 -0
- package/src/plugins/wiki/index.ts +52 -0
- package/src/router/guards.ts +61 -0
- package/src/router/index.ts +50 -0
- package/src/tools/index.ts +52 -0
- package/src/tools/types.ts +27 -0
- package/src/types/events.ts +16 -0
- package/src/types/fileTree.ts +13 -0
- package/src/types/notification.ts +67 -0
- package/src/types/session.ts +116 -0
- package/src/types/sse.ts +90 -0
- package/src/types/toolCallHistory.ts +13 -0
- package/src/utils/agent/eventDispatch.ts +74 -0
- package/src/utils/agent/request.ts +55 -0
- package/src/utils/agent/toolCalls.ts +62 -0
- package/src/utils/api.ts +218 -0
- package/src/utils/canvas/viewMode.ts +46 -0
- package/src/utils/dom/authTokenMeta.ts +20 -0
- package/src/utils/dom/clickOutside.ts +11 -0
- package/src/utils/dom/externalLink.ts +57 -0
- package/src/utils/dom/scrollable.ts +24 -0
- package/src/utils/errors.ts +11 -0
- package/src/utils/files/expandedDirs.ts +25 -0
- package/src/utils/files/filename.ts +12 -0
- package/src/utils/files/sortChildren.ts +20 -0
- package/src/utils/filesPreview/schedulerPreview.ts +38 -0
- package/src/utils/filesPreview/todoPreview.ts +40 -0
- package/src/utils/format/date.ts +85 -0
- package/src/utils/format/frontmatter.ts +80 -0
- package/src/utils/format/jsonSyntax.ts +109 -0
- package/src/utils/html/previewCsp.ts +65 -0
- package/src/utils/image/resolve.ts +8 -0
- package/src/utils/image/rewriteMarkdownImageRefs.ts +182 -0
- package/src/utils/markdown/extractFirstH1.ts +39 -0
- package/src/utils/notification/dispatch.ts +22 -0
- package/src/utils/path/relativeLink.ts +130 -0
- package/src/utils/role/icon.ts +20 -0
- package/src/utils/role/merge.ts +10 -0
- package/src/utils/role/plugins.ts +12 -0
- package/src/utils/session/mergeSessions.ts +103 -0
- package/src/utils/session/seedRoleDefault.ts +35 -0
- package/src/utils/session/sessionEntries.ts +121 -0
- package/src/utils/session/sessionFactory.ts +22 -0
- package/src/utils/session/sessionHelpers.ts +99 -0
- package/src/utils/tools/dedup.ts +17 -0
- package/src/utils/tools/mcp.ts +33 -0
- package/src/utils/tools/pendingCalls.ts +16 -0
- package/src/utils/tools/result.ts +40 -0
- package/src/utils/types.ts +44 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
<template>
|
|
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">No results yet</div>
|
|
4
|
+
<div
|
|
5
|
+
v-for="result in toolResults"
|
|
6
|
+
:key="result.uuid"
|
|
7
|
+
:ref="(element) => setItemRef(result.uuid, element as HTMLElement | null)"
|
|
8
|
+
class="bg-white rounded-lg border transition-colors"
|
|
9
|
+
:class="result.uuid === selectedResultUuid ? 'border-blue-400 ring-2 ring-blue-200' : 'border-gray-200'"
|
|
10
|
+
>
|
|
11
|
+
<button
|
|
12
|
+
class="w-full flex items-center gap-2 px-3 py-2 border-b border-gray-100 text-left hover:bg-gray-50"
|
|
13
|
+
:title="result.title || result.toolName"
|
|
14
|
+
@click="emit('select', result.uuid)"
|
|
15
|
+
>
|
|
16
|
+
<span class="material-icons text-sm text-gray-400">{{ iconFor(result.toolName) }}</span>
|
|
17
|
+
<span class="text-sm font-medium text-gray-800 truncate">{{ result.title || result.toolName }}</span>
|
|
18
|
+
<span v-if="resultTimestamps.get(result.uuid)" class="text-[10px] text-gray-400 shrink-0">{{
|
|
19
|
+
formatSmartTime(resultTimestamps.get(result.uuid)!)
|
|
20
|
+
}}</span>
|
|
21
|
+
<span class="font-mono text-xs text-gray-400 shrink-0">{{ result.toolName }}</span>
|
|
22
|
+
</button>
|
|
23
|
+
<!-- text-response: render the message as Markdown via the
|
|
24
|
+
underlying plugin View. The .stack-text-response class below
|
|
25
|
+
collapses the plugin's own card chrome (outer p-6, inner
|
|
26
|
+
rounded/border/shadow box, role header) so only the stack
|
|
27
|
+
card's own border shows.
|
|
28
|
+
|
|
29
|
+
We render the upstream OriginalView directly rather than our
|
|
30
|
+
local TextResponseView wrapper, so we lose the wrapper's
|
|
31
|
+
"open external links in a new tab" click handler. Attach
|
|
32
|
+
the same handler here via @click.capture so cross-origin
|
|
33
|
+
links in assistant Markdown don't navigate the SPA away. -->
|
|
34
|
+
<div v-if="isTextResponse(result)" class="stack-text-response" @click.capture="handleExternalLinkClick">
|
|
35
|
+
<TextResponseOriginalView :selected-result="result" />
|
|
36
|
+
</div>
|
|
37
|
+
<!-- Document-like plugins: let the content flow at its natural
|
|
38
|
+
height by overriding the plugin's internal h-full / overflow
|
|
39
|
+
/ flex-1 via the .stack-natural scoped styles below. For
|
|
40
|
+
plugins that embed iframes (e.g. presentHtml) we also size
|
|
41
|
+
each iframe to its content after load. -->
|
|
42
|
+
<div
|
|
43
|
+
v-else-if="isStackNatural(result.toolName)"
|
|
44
|
+
:ref="(element) => setNaturalWrapperRef(result.uuid, element as HTMLElement | null)"
|
|
45
|
+
class="stack-natural"
|
|
46
|
+
>
|
|
47
|
+
<component
|
|
48
|
+
:is="getPlugin(result.toolName)?.viewComponent"
|
|
49
|
+
v-if="getPlugin(result.toolName)?.viewComponent"
|
|
50
|
+
:selected-result="result"
|
|
51
|
+
:send-text-message="sendTextMessage"
|
|
52
|
+
@update-result="(r: ToolResultComplete) => emit('updateResult', r)"
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
<!-- Other plugins: fixed height wrapper so plugins that rely on
|
|
56
|
+
h-full continue to render properly. -->
|
|
57
|
+
<div v-else :style="{ height: PLUGIN_HEIGHT }">
|
|
58
|
+
<component
|
|
59
|
+
:is="getPlugin(result.toolName)?.viewComponent"
|
|
60
|
+
v-if="getPlugin(result.toolName)?.viewComponent"
|
|
61
|
+
:selected-result="result"
|
|
62
|
+
:send-text-message="sendTextMessage"
|
|
63
|
+
@update-result="(r: ToolResultComplete) => emit('updateResult', r)"
|
|
64
|
+
/>
|
|
65
|
+
<pre v-else class="h-full overflow-auto p-4 text-xs text-gray-500 whitespace-pre-wrap">{{ JSON.stringify(result, null, 2) }}</pre>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</template>
|
|
70
|
+
|
|
71
|
+
<script setup lang="ts">
|
|
72
|
+
import { ref, computed, watch, nextTick, onMounted, onUnmounted } from "vue";
|
|
73
|
+
import { getPlugin } from "../tools";
|
|
74
|
+
import type { ToolResultComplete } from "gui-chat-protocol/vue";
|
|
75
|
+
import { View as TextResponseOriginalView } from "../plugins/textResponse/index";
|
|
76
|
+
import { handleExternalLinkClick } from "../utils/dom/externalLink";
|
|
77
|
+
import type { TextResponseData } from "../plugins/textResponse/types";
|
|
78
|
+
import { formatSmartTime } from "../utils/format/date";
|
|
79
|
+
import { isRecord } from "../utils/types";
|
|
80
|
+
|
|
81
|
+
// Most plugin viewComponents use h-full internally, so a defined parent
|
|
82
|
+
// height is required for them to render. text-response and the
|
|
83
|
+
// "stack-natural" plugins below are special-cased.
|
|
84
|
+
const PLUGIN_HEIGHT = "min(60vh, 560px)";
|
|
85
|
+
|
|
86
|
+
// How long to ignore scroll-spy after a programmatic scroll (sidebar
|
|
87
|
+
// click, auto-scroll on new result). Keeps the spy from emitting a
|
|
88
|
+
// stale uuid while the scroll is still settling.
|
|
89
|
+
const SCROLL_SPY_SUPPRESS_MS = 150;
|
|
90
|
+
|
|
91
|
+
// Plugins that look better flowing at natural height in stack view
|
|
92
|
+
// rather than being clipped to PLUGIN_HEIGHT with an inner scrollbar.
|
|
93
|
+
const STACK_NATURAL_TOOLS = new Set<string>([
|
|
94
|
+
"presentHtml",
|
|
95
|
+
"presentDocument",
|
|
96
|
+
"presentSpreadsheet",
|
|
97
|
+
"manageWiki",
|
|
98
|
+
// presentChart documents can hold multiple charts; fixed-height
|
|
99
|
+
// clipping forces an inner scrollbar per result. Letting them flow
|
|
100
|
+
// keeps everything visible in one scroll.
|
|
101
|
+
"presentChart",
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
function isStackNatural(toolName: string): boolean {
|
|
105
|
+
return STACK_NATURAL_TOOLS.has(toolName);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const props = defineProps<{
|
|
109
|
+
toolResults: ToolResultComplete[];
|
|
110
|
+
selectedResultUuid: string | null;
|
|
111
|
+
resultTimestamps: Map<string, number>;
|
|
112
|
+
sendTextMessage?: (text: string) => void;
|
|
113
|
+
}>();
|
|
114
|
+
|
|
115
|
+
const emit = defineEmits<{
|
|
116
|
+
select: [uuid: string];
|
|
117
|
+
updateResult: [result: ToolResultComplete];
|
|
118
|
+
}>();
|
|
119
|
+
|
|
120
|
+
const containerRef = ref<HTMLDivElement | null>(null);
|
|
121
|
+
const itemRefs = new Map<string, HTMLElement>();
|
|
122
|
+
const naturalWrapperRefs = new Map<string, HTMLElement>();
|
|
123
|
+
|
|
124
|
+
function setItemRef(uuid: string, element: HTMLElement | null): void {
|
|
125
|
+
if (element) itemRefs.set(uuid, element);
|
|
126
|
+
else itemRefs.delete(uuid);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function setNaturalWrapperRef(uuid: string, element: HTMLElement | null): void {
|
|
130
|
+
if (element) {
|
|
131
|
+
naturalWrapperRefs.set(uuid, element);
|
|
132
|
+
nextTick(() => sizeIframesIn(element));
|
|
133
|
+
} else {
|
|
134
|
+
naturalWrapperRefs.delete(uuid);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Sandboxed iframes inside stack-natural plugins (e.g. presentHtml)
|
|
139
|
+
// have no intrinsic content height, so CSS alone collapses them. Set
|
|
140
|
+
// each iframe's height to match its document's scrollHeight on load.
|
|
141
|
+
function sizeIframesIn(wrapper: HTMLElement): void {
|
|
142
|
+
const iframes = wrapper.querySelectorAll<HTMLIFrameElement>("iframe");
|
|
143
|
+
for (const iframe of iframes) {
|
|
144
|
+
if (iframe.dataset.stackSized === "true") continue;
|
|
145
|
+
iframe.dataset.stackSized = "true";
|
|
146
|
+
const resize = () => resizeOneIframe(iframe);
|
|
147
|
+
iframe.addEventListener("load", resize);
|
|
148
|
+
// If the iframe already finished loading before we attached the
|
|
149
|
+
// listener, size it now as well.
|
|
150
|
+
try {
|
|
151
|
+
if (iframe.contentDocument?.readyState === "complete") {
|
|
152
|
+
resize();
|
|
153
|
+
}
|
|
154
|
+
} catch {
|
|
155
|
+
// cross-origin — leave default height
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function resizeOneIframe(iframe: HTMLIFrameElement): void {
|
|
161
|
+
try {
|
|
162
|
+
const doc = iframe.contentDocument;
|
|
163
|
+
if (!doc) return;
|
|
164
|
+
const height = Math.max(doc.documentElement?.scrollHeight ?? 0, doc.body?.scrollHeight ?? 0);
|
|
165
|
+
if (height > 0) iframe.style.height = `${height}px`;
|
|
166
|
+
} catch {
|
|
167
|
+
// cross-origin sandbox — can't measure, leave default
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function isTextResponse(result: ToolResultComplete): result is ToolResultComplete<TextResponseData> {
|
|
172
|
+
if (result.toolName !== "text-response") return false;
|
|
173
|
+
const data = result.data;
|
|
174
|
+
if (!isRecord(data)) return false;
|
|
175
|
+
return typeof data.text === "string";
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function iconFor(toolName: string): string {
|
|
179
|
+
if (toolName === "text-response") return "chat";
|
|
180
|
+
return "extension";
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Scroll-spy state: as the user scrolls the stack container we emit
|
|
184
|
+
// a `select` for whichever card currently occupies the top, so the
|
|
185
|
+
// sidebar selection always tracks what's on screen.
|
|
186
|
+
//
|
|
187
|
+
// Coordination between scroll and selection:
|
|
188
|
+
// - `suppressScrollSync` is set while the component programmatically
|
|
189
|
+
// scrolls (sidebar click → scrollIntoView, auto-scroll on new
|
|
190
|
+
// result) so the spy doesn't fire on its own scroll.
|
|
191
|
+
// - `scrollSpyEmittedUuid` holds the exact uuid the spy most
|
|
192
|
+
// recently emitted. The watch on `selectedResultUuid` only skips
|
|
193
|
+
// its scrollIntoView when the incoming uuid matches, so a
|
|
194
|
+
// sidebar click that arrives right after a spy emit still gets
|
|
195
|
+
// its normal scroll behaviour.
|
|
196
|
+
let suppressScrollSync = false;
|
|
197
|
+
let suppressScrollTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
198
|
+
let scrollSpyRafId: number | null = null;
|
|
199
|
+
let scrollSpyEmittedUuid: string | null = null;
|
|
200
|
+
|
|
201
|
+
function beginSuppressScrollSync(): void {
|
|
202
|
+
suppressScrollSync = true;
|
|
203
|
+
if (suppressScrollTimeout !== null) clearTimeout(suppressScrollTimeout);
|
|
204
|
+
suppressScrollTimeout = setTimeout(() => {
|
|
205
|
+
suppressScrollSync = false;
|
|
206
|
+
suppressScrollTimeout = null;
|
|
207
|
+
}, SCROLL_SPY_SUPPRESS_MS);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function readPaddingTop(element: HTMLElement): number {
|
|
211
|
+
const value = parseFloat(getComputedStyle(element).paddingTop);
|
|
212
|
+
return Number.isFinite(value) ? value : 0;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Walk items in order and return the last one whose top edge is at or
|
|
216
|
+
// above the padded content top of the container. Accounting for the
|
|
217
|
+
// container's padding-top means the handoff happens at the visual
|
|
218
|
+
// start of the cards rather than the invisible border of the
|
|
219
|
+
// container itself. Iterating in DOM order lets us break early once
|
|
220
|
+
// an item is below the line.
|
|
221
|
+
function computeActiveUuidFromScroll(): string | null {
|
|
222
|
+
if (!containerRef.value) return null;
|
|
223
|
+
const container = containerRef.value;
|
|
224
|
+
const paddedTop = container.getBoundingClientRect().top + readPaddingTop(container);
|
|
225
|
+
let activeUuid: string | null = null;
|
|
226
|
+
for (const result of props.toolResults) {
|
|
227
|
+
const element = itemRefs.get(result.uuid);
|
|
228
|
+
if (!element) continue;
|
|
229
|
+
if (element.getBoundingClientRect().top <= paddedTop) {
|
|
230
|
+
activeUuid = result.uuid;
|
|
231
|
+
} else {
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return activeUuid;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function onContainerScroll(): void {
|
|
239
|
+
if (suppressScrollSync) return;
|
|
240
|
+
if (scrollSpyRafId !== null) return;
|
|
241
|
+
scrollSpyRafId = requestAnimationFrame(() => {
|
|
242
|
+
scrollSpyRafId = null;
|
|
243
|
+
if (suppressScrollSync) return;
|
|
244
|
+
const activeUuid = computeActiveUuidFromScroll();
|
|
245
|
+
if (activeUuid && activeUuid !== props.selectedResultUuid) {
|
|
246
|
+
scrollSpyEmittedUuid = activeUuid;
|
|
247
|
+
emit("select", activeUuid);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Scroll the selected card to the top whenever the external selection
|
|
253
|
+
// changes (sidebar click, initial load). Skip the scroll only when the
|
|
254
|
+
// incoming uuid matches the one we just emitted from the spy — that
|
|
255
|
+
// means the viewport is already in the right place. Any other change
|
|
256
|
+
// (sidebar click, new result) still gets its normal scrollIntoView.
|
|
257
|
+
watch(
|
|
258
|
+
() => props.selectedResultUuid,
|
|
259
|
+
(uuid) => {
|
|
260
|
+
if (!uuid) return;
|
|
261
|
+
if (scrollSpyEmittedUuid === uuid) {
|
|
262
|
+
scrollSpyEmittedUuid = null;
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
scrollSpyEmittedUuid = null;
|
|
266
|
+
nextTick(() => {
|
|
267
|
+
const element = itemRefs.get(uuid);
|
|
268
|
+
if (!element) return;
|
|
269
|
+
beginSuppressScrollSync();
|
|
270
|
+
element.scrollIntoView({ block: "start", behavior: "auto" });
|
|
271
|
+
});
|
|
272
|
+
},
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
// Key that changes both on new results AND on streaming updates to
|
|
276
|
+
// the last text card (which appends in place, leaving length stable).
|
|
277
|
+
const latestResultScrollKey = computed(() => {
|
|
278
|
+
const list = props.toolResults;
|
|
279
|
+
const last = list[list.length - 1];
|
|
280
|
+
return `${list.length}:${last?.uuid ?? ""}:${last?.message?.length ?? 0}`;
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
watch(latestResultScrollKey, () => {
|
|
284
|
+
nextTick(() => {
|
|
285
|
+
if (containerRef.value) {
|
|
286
|
+
beginSuppressScrollSync();
|
|
287
|
+
containerRef.value.scrollTop = containerRef.value.scrollHeight;
|
|
288
|
+
}
|
|
289
|
+
// New items may have brought in more iframes to size.
|
|
290
|
+
for (const wrapper of naturalWrapperRefs.values()) {
|
|
291
|
+
sizeIframesIn(wrapper);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
onMounted(() => {
|
|
297
|
+
containerRef.value?.addEventListener("scroll", onContainerScroll, {
|
|
298
|
+
passive: true,
|
|
299
|
+
});
|
|
300
|
+
// Align the initial scroll position with the externally selected
|
|
301
|
+
// item so the sidebar and stack start in sync on mount.
|
|
302
|
+
nextTick(() => {
|
|
303
|
+
if (!props.selectedResultUuid) return;
|
|
304
|
+
const element = itemRefs.get(props.selectedResultUuid);
|
|
305
|
+
if (!element) return;
|
|
306
|
+
beginSuppressScrollSync();
|
|
307
|
+
element.scrollIntoView({ block: "start", behavior: "auto" });
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
onUnmounted(() => {
|
|
312
|
+
containerRef.value?.removeEventListener("scroll", onContainerScroll);
|
|
313
|
+
if (scrollSpyRafId !== null) cancelAnimationFrame(scrollSpyRafId);
|
|
314
|
+
if (suppressScrollTimeout !== null) clearTimeout(suppressScrollTimeout);
|
|
315
|
+
naturalWrapperRefs.clear();
|
|
316
|
+
});
|
|
317
|
+
</script>
|
|
318
|
+
|
|
319
|
+
<style scoped>
|
|
320
|
+
/* Force document-like plugin viewComponents (presentHtml,
|
|
321
|
+
presentDocument, presentSpreadsheet) to flow at their natural
|
|
322
|
+
height inside stack view instead of clipping to the wrapper with
|
|
323
|
+
an inner scrollbar. */
|
|
324
|
+
.stack-natural :deep(.h-full),
|
|
325
|
+
.stack-natural :deep(.min-h-full) {
|
|
326
|
+
height: auto !important;
|
|
327
|
+
min-height: 0 !important;
|
|
328
|
+
}
|
|
329
|
+
.stack-natural :deep(.overflow-hidden),
|
|
330
|
+
.stack-natural :deep(.overflow-auto),
|
|
331
|
+
.stack-natural :deep(.overflow-y-auto),
|
|
332
|
+
.stack-natural :deep(.overflow-x-auto) {
|
|
333
|
+
overflow: visible !important;
|
|
334
|
+
}
|
|
335
|
+
.stack-natural :deep(.flex-1) {
|
|
336
|
+
flex: 0 0 auto !important;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/* Collapse the nested chrome that text-response draws around its
|
|
340
|
+
Markdown output so it reads like plain content inside our stack card
|
|
341
|
+
instead of creating a second border/shadow "card" inside ours. */
|
|
342
|
+
.stack-text-response :deep(.text-response-content-wrapper > .p-6) {
|
|
343
|
+
padding: 0.5rem 0.75rem;
|
|
344
|
+
}
|
|
345
|
+
.stack-text-response :deep(.text-response-container .max-w-3xl) {
|
|
346
|
+
max-width: none;
|
|
347
|
+
margin-left: 0;
|
|
348
|
+
margin-right: 0;
|
|
349
|
+
}
|
|
350
|
+
.stack-text-response :deep(.text-response-container .mb-2) {
|
|
351
|
+
display: none; /* redundant role header — stack card header shows it already */
|
|
352
|
+
}
|
|
353
|
+
.stack-text-response :deep(.text-response-container .shadow-sm) {
|
|
354
|
+
border: 0;
|
|
355
|
+
box-shadow: none;
|
|
356
|
+
padding: 0;
|
|
357
|
+
background: transparent;
|
|
358
|
+
border-radius: 0;
|
|
359
|
+
}
|
|
360
|
+
</style>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if="queries.length > 0" class="border-t border-gray-200">
|
|
3
|
+
<div v-if="expanded" ref="listRef" class="px-4 pt-2 max-h-64 overflow-y-auto flex flex-col gap-1">
|
|
4
|
+
<button
|
|
5
|
+
v-for="query in queries"
|
|
6
|
+
:key="query"
|
|
7
|
+
class="text-left text-xs bg-gray-100 hover:bg-gray-200 text-gray-700 rounded px-3 py-1.5 border border-gray-300 transition-colors"
|
|
8
|
+
@click="onClick($event, query)"
|
|
9
|
+
>
|
|
10
|
+
{{ query }}
|
|
11
|
+
</button>
|
|
12
|
+
<p class="text-center text-[10px] text-gray-400 py-0.5">click to send · shift+click to edit</p>
|
|
13
|
+
</div>
|
|
14
|
+
<button
|
|
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"
|
|
16
|
+
@click="expanded = !expanded"
|
|
17
|
+
>
|
|
18
|
+
<span class="flex items-center gap-1">
|
|
19
|
+
<span class="material-icons text-sm">lightbulb</span>
|
|
20
|
+
Suggestions
|
|
21
|
+
</span>
|
|
22
|
+
<span class="material-icons text-sm transition-transform" :class="{ 'rotate-180': !expanded }">expand_less</span>
|
|
23
|
+
</button>
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup lang="ts">
|
|
28
|
+
import { nextTick, ref, watch } from "vue";
|
|
29
|
+
|
|
30
|
+
defineProps<{
|
|
31
|
+
queries: string[];
|
|
32
|
+
}>();
|
|
33
|
+
|
|
34
|
+
const emit = defineEmits<{
|
|
35
|
+
send: [query: string];
|
|
36
|
+
edit: [query: string];
|
|
37
|
+
}>();
|
|
38
|
+
|
|
39
|
+
const expanded = ref(false);
|
|
40
|
+
const listRef = ref<HTMLDivElement | null>(null);
|
|
41
|
+
|
|
42
|
+
watch(expanded, (isExpanded) => {
|
|
43
|
+
if (!isExpanded) return;
|
|
44
|
+
nextTick(() => {
|
|
45
|
+
if (listRef.value) {
|
|
46
|
+
listRef.value.scrollTop = listRef.value.scrollHeight;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
function onClick(e: MouseEvent, query: string): void {
|
|
52
|
+
expanded.value = false;
|
|
53
|
+
if (e.shiftKey) {
|
|
54
|
+
emit("edit", query);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
emit("send", query);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function collapse(): void {
|
|
61
|
+
expanded.value = false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
defineExpose({ collapse });
|
|
65
|
+
</script>
|