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,358 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="h-full bg-white flex flex-col">
|
|
3
|
+
<!-- Header -->
|
|
4
|
+
<div class="flex items-center justify-between px-4 py-2 border-b border-gray-100 shrink-0 gap-3">
|
|
5
|
+
<div class="flex items-center gap-3 min-w-0">
|
|
6
|
+
<h2 class="text-base font-semibold text-gray-800 shrink-0">Todo</h2>
|
|
7
|
+
<span class="text-xs text-gray-500 shrink-0">{{ completedCount }}/{{ items.length }} done</span>
|
|
8
|
+
<input
|
|
9
|
+
v-model="search"
|
|
10
|
+
data-testid="todo-search"
|
|
11
|
+
type="text"
|
|
12
|
+
placeholder="Search..."
|
|
13
|
+
class="px-2 py-1 text-xs border border-gray-200 rounded w-44 focus:outline-none focus:border-blue-400"
|
|
14
|
+
/>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="flex items-center gap-2">
|
|
17
|
+
<!-- Add button -->
|
|
18
|
+
<button data-testid="todo-add-btn" class="px-2 py-1 text-xs rounded bg-blue-500 text-white hover:bg-blue-600" @click="addOpen = true">+ Add</button>
|
|
19
|
+
<!-- Add column button (kanban only) -->
|
|
20
|
+
<button
|
|
21
|
+
v-if="viewMode === TODO_VIEW.kanban"
|
|
22
|
+
data-testid="todo-column-add-btn"
|
|
23
|
+
class="px-2 py-1 text-xs rounded border border-gray-300 text-gray-600 hover:bg-gray-50"
|
|
24
|
+
@click="addColumnOpen = true"
|
|
25
|
+
>
|
|
26
|
+
+ Column
|
|
27
|
+
</button>
|
|
28
|
+
<!-- View mode toggle -->
|
|
29
|
+
<div class="flex border border-gray-300 rounded overflow-hidden text-xs">
|
|
30
|
+
<button
|
|
31
|
+
v-for="mode in VIEW_MODES"
|
|
32
|
+
:key="mode.key"
|
|
33
|
+
class="px-2.5 py-1"
|
|
34
|
+
:class="viewMode === mode.key ? 'bg-blue-500 text-white' : 'bg-white text-gray-600 hover:bg-gray-50'"
|
|
35
|
+
:data-testid="`todo-view-${mode.key}`"
|
|
36
|
+
:title="mode.label"
|
|
37
|
+
@click="setViewMode(mode.key)"
|
|
38
|
+
>
|
|
39
|
+
<span class="material-icons text-sm">{{ mode.icon }}</span>
|
|
40
|
+
</button>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<!-- Label filter chips -->
|
|
46
|
+
<div v-if="labelInventory.length > 0" class="flex flex-wrap items-center gap-1.5 px-4 py-1.5 border-b border-gray-100 bg-gray-50 shrink-0">
|
|
47
|
+
<span class="text-[11px] text-gray-500 mr-1">Labels:</span>
|
|
48
|
+
<button
|
|
49
|
+
v-for="entry in labelInventory"
|
|
50
|
+
:key="entry.label"
|
|
51
|
+
class="px-2 py-0.5 rounded-full text-[10px] font-medium transition-all"
|
|
52
|
+
:class="
|
|
53
|
+
activeFilters.has(entry.label.toLowerCase())
|
|
54
|
+
? 'ring-2 ring-blue-400 ' + colorForLabel(entry.label)
|
|
55
|
+
: colorForLabel(entry.label) + ' opacity-70 hover:opacity-100'
|
|
56
|
+
"
|
|
57
|
+
@click="toggleFilter(entry.label)"
|
|
58
|
+
>
|
|
59
|
+
{{ entry.label }}
|
|
60
|
+
<span class="opacity-60">{{ entry.count }}</span>
|
|
61
|
+
</button>
|
|
62
|
+
<button v-if="activeFilters.size > 0" class="ml-auto text-[11px] text-gray-500 hover:text-gray-700" title="Clear label filters" @click="clearFilters">
|
|
63
|
+
Clear ✕
|
|
64
|
+
</button>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<!-- Error banner -->
|
|
68
|
+
<div v-if="error" class="px-4 py-2 text-xs text-red-600 bg-red-50 border-b border-red-100 shrink-0">
|
|
69
|
+
{{ error }}
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<!-- Body -->
|
|
73
|
+
<div class="flex-1 min-h-0">
|
|
74
|
+
<div v-if="items.length === 0" class="h-full flex items-center justify-center text-gray-400 text-sm">No todo items yet. Click "+ Add" to create one.</div>
|
|
75
|
+
<template v-else>
|
|
76
|
+
<TodoKanbanView
|
|
77
|
+
v-if="viewMode === TODO_VIEW.kanban"
|
|
78
|
+
:filtered-items="filteredItems"
|
|
79
|
+
:columns="columns"
|
|
80
|
+
@move="onMove"
|
|
81
|
+
@open="onOpenItem"
|
|
82
|
+
@toggle-complete="onToggleComplete"
|
|
83
|
+
@quick-add="quickAddInColumn"
|
|
84
|
+
@rename-column="onRenameColumn"
|
|
85
|
+
@delete-column="onDeleteColumn"
|
|
86
|
+
@mark-done="onMarkDone"
|
|
87
|
+
@reorder-columns="onReorderColumns"
|
|
88
|
+
/>
|
|
89
|
+
<TodoTableView
|
|
90
|
+
v-else-if="viewMode === TODO_VIEW.table"
|
|
91
|
+
:filtered-items="filteredItems"
|
|
92
|
+
:columns="columns"
|
|
93
|
+
@patch="onPatchItem"
|
|
94
|
+
@delete="onDeleteItem"
|
|
95
|
+
@toggle-complete="onToggleComplete"
|
|
96
|
+
/>
|
|
97
|
+
<TodoListView
|
|
98
|
+
v-else
|
|
99
|
+
:filtered-items="filteredItems"
|
|
100
|
+
:columns="columns"
|
|
101
|
+
@patch="onPatchItem"
|
|
102
|
+
@delete="onDeleteItem"
|
|
103
|
+
@toggle-complete="onToggleComplete"
|
|
104
|
+
/>
|
|
105
|
+
</template>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<!-- Add item dialog -->
|
|
109
|
+
<TodoAddDialog v-if="addOpen" :columns="columns" :default-status="addDefaultStatus" @cancel="addOpen = false" @create="onCreateItem" />
|
|
110
|
+
|
|
111
|
+
<!-- Edit item dialog (used by kanban click; list/table use the
|
|
112
|
+
inline edit panel and don't need to open this) -->
|
|
113
|
+
<TodoEditDialog
|
|
114
|
+
v-if="editingItem"
|
|
115
|
+
:item="editingItem"
|
|
116
|
+
:columns="columns"
|
|
117
|
+
@cancel="editingItem = null"
|
|
118
|
+
@save="onEditDialogSave"
|
|
119
|
+
@delete="onEditDialogDelete"
|
|
120
|
+
/>
|
|
121
|
+
|
|
122
|
+
<!-- Add column dialog -->
|
|
123
|
+
<div v-if="addColumnOpen" class="fixed inset-0 z-50 bg-black/30 flex items-center justify-center" @click="addColumnOpen = false">
|
|
124
|
+
<div class="bg-white rounded-lg shadow-xl w-80 p-5 space-y-3" role="dialog" aria-modal="true" aria-labelledby="todo-add-column-title" @click.stop>
|
|
125
|
+
<h3 id="todo-add-column-title" class="text-base font-semibold text-gray-800">Add Column</h3>
|
|
126
|
+
<label class="block text-xs text-gray-600">
|
|
127
|
+
Label
|
|
128
|
+
<input
|
|
129
|
+
v-model="newColumnLabel"
|
|
130
|
+
type="text"
|
|
131
|
+
placeholder="Review"
|
|
132
|
+
class="mt-1 w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:outline-none focus:border-blue-400"
|
|
133
|
+
@keydown.enter="commitNewColumn"
|
|
134
|
+
/>
|
|
135
|
+
</label>
|
|
136
|
+
<div class="flex justify-end gap-2 pt-1">
|
|
137
|
+
<button class="px-3 py-1.5 text-sm rounded border border-gray-300 text-gray-600 hover:bg-gray-50" @click="addColumnOpen = false">Cancel</button>
|
|
138
|
+
<button class="px-3 py-1.5 text-sm rounded bg-blue-500 text-white hover:bg-blue-600" @click="commitNewColumn">Add</button>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
</template>
|
|
144
|
+
|
|
145
|
+
<script setup lang="ts">
|
|
146
|
+
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
|
|
147
|
+
import type { ToolResultComplete } from "gui-chat-protocol/vue";
|
|
148
|
+
import type { TodoData, TodoItem } from "../plugins/todo/index";
|
|
149
|
+
import { colorForLabel, filterByLabels, listLabelsWithCount } from "../plugins/todo/labels";
|
|
150
|
+
import { useTodos, type CreateItemInput, type PatchItemInput } from "../plugins/todo/composables/useTodos";
|
|
151
|
+
import TodoKanbanView from "./todo/TodoKanbanView.vue";
|
|
152
|
+
import TodoTableView from "./todo/TodoTableView.vue";
|
|
153
|
+
import TodoListView from "./todo/TodoListView.vue";
|
|
154
|
+
import TodoAddDialog from "./todo/TodoAddDialog.vue";
|
|
155
|
+
import TodoEditDialog from "./todo/TodoEditDialog.vue";
|
|
156
|
+
|
|
157
|
+
import { TODO_VIEW, TODO_VIEW_MODES as VIEW_MODES, type TodoViewMode as ViewMode } from "../plugins/todo/viewModes";
|
|
158
|
+
|
|
159
|
+
const VIEW_MODE_KEY = "todo_explorer_view_mode";
|
|
160
|
+
|
|
161
|
+
const props = defineProps<{
|
|
162
|
+
selectedResult?: ToolResultComplete<TodoData>;
|
|
163
|
+
}>();
|
|
164
|
+
|
|
165
|
+
const { items, columns, error, refresh, createItem, patchItem, moveItem, deleteItem, addColumn, patchColumn, deleteColumn, reorderColumns } = useTodos(
|
|
166
|
+
props.selectedResult?.data?.items ?? [],
|
|
167
|
+
props.selectedResult?.data?.columns ?? [],
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// When the parent swaps in a different tool result, reseed the local
|
|
171
|
+
// state and re-fetch from the server. Watching the uuid (not items)
|
|
172
|
+
// so empty-result swaps still trigger.
|
|
173
|
+
watch(
|
|
174
|
+
() => props.selectedResult?.uuid,
|
|
175
|
+
() => {
|
|
176
|
+
items.value = props.selectedResult?.data?.items ?? [];
|
|
177
|
+
columns.value = props.selectedResult?.data?.columns ?? [];
|
|
178
|
+
void refresh();
|
|
179
|
+
},
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
// ── View mode (persisted in localStorage) ───────────────────────
|
|
183
|
+
|
|
184
|
+
const VALID_VIEW_MODES: ReadonlySet<string> = new Set(Object.values(TODO_VIEW));
|
|
185
|
+
|
|
186
|
+
function loadViewMode(): ViewMode {
|
|
187
|
+
const stored = localStorage.getItem(VIEW_MODE_KEY);
|
|
188
|
+
if (stored && VALID_VIEW_MODES.has(stored)) {
|
|
189
|
+
return stored as ViewMode;
|
|
190
|
+
}
|
|
191
|
+
return TODO_VIEW.kanban;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const viewMode = ref<ViewMode>(loadViewMode());
|
|
195
|
+
|
|
196
|
+
function setViewMode(next: ViewMode): void {
|
|
197
|
+
viewMode.value = next;
|
|
198
|
+
localStorage.setItem(VIEW_MODE_KEY, next);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ── Filtering ──────────────────────────────────────────────────
|
|
202
|
+
|
|
203
|
+
const search = ref("");
|
|
204
|
+
const activeFilters = ref<Set<string>>(new Set());
|
|
205
|
+
|
|
206
|
+
const labelInventory = computed(() => listLabelsWithCount(items.value));
|
|
207
|
+
|
|
208
|
+
function toggleFilter(label: string): void {
|
|
209
|
+
const key = label.toLowerCase();
|
|
210
|
+
const next = new Set(activeFilters.value);
|
|
211
|
+
if (next.has(key)) next.delete(key);
|
|
212
|
+
else next.add(key);
|
|
213
|
+
activeFilters.value = next;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function clearFilters(): void {
|
|
217
|
+
activeFilters.value = new Set();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const filteredItems = computed(() => {
|
|
221
|
+
const byLabels = filterByLabels(items.value, [...activeFilters.value]);
|
|
222
|
+
const query = search.value.trim().toLowerCase();
|
|
223
|
+
if (query.length === 0) return byLabels;
|
|
224
|
+
return byLabels.filter((item) => {
|
|
225
|
+
if (item.text.toLowerCase().includes(query)) return true;
|
|
226
|
+
if (item.note?.toLowerCase().includes(query)) return true;
|
|
227
|
+
return false;
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const completedCount = computed(() => items.value.filter((i) => i.completed).length);
|
|
232
|
+
|
|
233
|
+
// ── Add dialog ─────────────────────────────────────────────────
|
|
234
|
+
|
|
235
|
+
const addOpen = ref(false);
|
|
236
|
+
const addDefaultStatus = ref<string | undefined>(undefined);
|
|
237
|
+
|
|
238
|
+
function quickAddInColumn(statusId: string): void {
|
|
239
|
+
addDefaultStatus.value = statusId;
|
|
240
|
+
addOpen.value = true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function onCreateItem(input: CreateItemInput): Promise<void> {
|
|
244
|
+
const created = await createItem(input);
|
|
245
|
+
if (created) {
|
|
246
|
+
addOpen.value = false;
|
|
247
|
+
addDefaultStatus.value = undefined;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ── Add column dialog ──────────────────────────────────────────
|
|
252
|
+
|
|
253
|
+
const addColumnOpen = ref(false);
|
|
254
|
+
const newColumnLabel = ref("");
|
|
255
|
+
|
|
256
|
+
async function commitNewColumn(): Promise<void> {
|
|
257
|
+
const label = newColumnLabel.value.trim();
|
|
258
|
+
if (label.length === 0) return;
|
|
259
|
+
const added = await addColumn({ label });
|
|
260
|
+
if (added) {
|
|
261
|
+
addColumnOpen.value = false;
|
|
262
|
+
newColumnLabel.value = "";
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Escape closes the inline add-column dialog. The Add and Edit
|
|
267
|
+
// dialogs handle their own Escape via document listeners; this one
|
|
268
|
+
// is owned by the explorer template directly so it lives here.
|
|
269
|
+
function onExplorerKeydown(event: KeyboardEvent): void {
|
|
270
|
+
if (event.key !== "Escape") return;
|
|
271
|
+
if (addColumnOpen.value) {
|
|
272
|
+
addColumnOpen.value = false;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
onMounted(() => document.addEventListener("keydown", onExplorerKeydown));
|
|
276
|
+
onUnmounted(() => document.removeEventListener("keydown", onExplorerKeydown));
|
|
277
|
+
|
|
278
|
+
// ── Item handlers ──────────────────────────────────────────────
|
|
279
|
+
|
|
280
|
+
function onPatchItem(itemId: string, input: PatchItemInput): void {
|
|
281
|
+
void patchItem(itemId, input);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Single confirm gate for every item deletion path: row "✕" buttons
|
|
285
|
+
// in list/table, the kanban edit dialog's delete button, anything
|
|
286
|
+
// else that wants to remove an item. Centralised so we never
|
|
287
|
+
// accidentally bypass the confirm in a future caller.
|
|
288
|
+
function confirmAndDelete(itemId: string): boolean {
|
|
289
|
+
const item = items.value.find((i) => i.id === itemId);
|
|
290
|
+
if (!item) return false;
|
|
291
|
+
const confirmed = window.confirm(`Delete "${item.text}"?`);
|
|
292
|
+
if (!confirmed) return false;
|
|
293
|
+
void deleteItem(itemId);
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function onDeleteItem(itemId: string): void {
|
|
298
|
+
confirmAndDelete(itemId);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function onToggleComplete(item: TodoItem): void {
|
|
302
|
+
void patchItem(item.id, { completed: !item.completed });
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function onMove(itemId: string, statusId: string, position: number): void {
|
|
306
|
+
void moveItem(itemId, { status: statusId, position });
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ── Edit dialog (kanban click) ─────────────────────────────────
|
|
310
|
+
|
|
311
|
+
const editingItem = ref<TodoItem | null>(null);
|
|
312
|
+
|
|
313
|
+
function onOpenItem(item: TodoItem): void {
|
|
314
|
+
// Kanban cards open the modal edit dialog. List and Table views
|
|
315
|
+
// have their own inline edit panels and don't go through here.
|
|
316
|
+
editingItem.value = item;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async function onEditDialogSave(input: PatchItemInput): Promise<void> {
|
|
320
|
+
const target = editingItem.value;
|
|
321
|
+
if (!target) return;
|
|
322
|
+
const saved = await patchItem(target.id, input);
|
|
323
|
+
if (saved) editingItem.value = null;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function onEditDialogDelete(itemId: string): void {
|
|
327
|
+
// Funnel through the same confirm gate as the inline ✕ buttons.
|
|
328
|
+
// The dialog only closes if the user confirmed; if they cancelled
|
|
329
|
+
// the confirm, the dialog stays open so they can keep editing.
|
|
330
|
+
if (confirmAndDelete(itemId)) editingItem.value = null;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ── Column handlers ────────────────────────────────────────────
|
|
334
|
+
|
|
335
|
+
function onRenameColumn(columnId: string, label: string): void {
|
|
336
|
+
void patchColumn(columnId, { label });
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function onDeleteColumn(columnId: string): void {
|
|
340
|
+
// Use a native confirm dialog: deleting a column reassigns its
|
|
341
|
+
// items, which is reversible but worth a beat. The other column
|
|
342
|
+
// operations (rename, mark-done) are inexpensive enough not to need
|
|
343
|
+
// confirmation.
|
|
344
|
+
const col = columns.value.find((column) => column.id === columnId);
|
|
345
|
+
if (!col) return;
|
|
346
|
+
const confirmed = window.confirm(`Delete column "${col.label}"? Items in this column will be moved to another column.`);
|
|
347
|
+
if (!confirmed) return;
|
|
348
|
+
void deleteColumn(columnId);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function onMarkDone(columnId: string): void {
|
|
352
|
+
void patchColumn(columnId, { isDone: true });
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function onReorderColumns(ids: string[]): void {
|
|
356
|
+
void reorderColumns(ids);
|
|
357
|
+
}
|
|
358
|
+
</script>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
ref="root"
|
|
4
|
+
class="flex-1 min-h-0 overflow-y-auto p-2 space-y-2 bg-gray-100 outline-none"
|
|
5
|
+
tabindex="0"
|
|
6
|
+
data-testid="tool-results-scroll"
|
|
7
|
+
@mousedown="emit('activate')"
|
|
8
|
+
>
|
|
9
|
+
<div
|
|
10
|
+
v-for="result in results"
|
|
11
|
+
:key="result.uuid"
|
|
12
|
+
class="cursor-pointer rounded border border-gray-300 p-2 text-sm text-gray-900 hover:opacity-75 transition-opacity"
|
|
13
|
+
:class="result.uuid === selectedUuid ? 'ring-2 ring-blue-500' : ''"
|
|
14
|
+
@click="emit('select', result.uuid)"
|
|
15
|
+
>
|
|
16
|
+
<div class="flex items-center gap-1">
|
|
17
|
+
<component
|
|
18
|
+
:is="getPlugin(result.toolName)?.previewComponent"
|
|
19
|
+
v-if="getPlugin(result.toolName)?.previewComponent"
|
|
20
|
+
:result="result"
|
|
21
|
+
class="flex-1 min-w-0"
|
|
22
|
+
/>
|
|
23
|
+
<span v-else class="flex-1 min-w-0 truncate">{{ result.title || result.toolName }}</span>
|
|
24
|
+
<span v-if="resultTimestamps.get(result.uuid)" class="text-[10px] text-gray-400 shrink-0">{{
|
|
25
|
+
formatSmartTime(resultTimestamps.get(result.uuid)!)
|
|
26
|
+
}}</span>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<!-- Thinking indicator -->
|
|
31
|
+
<div v-if="isRunning" class="px-2 py-1 text-sm">
|
|
32
|
+
<div class="flex items-center gap-2 text-gray-500">
|
|
33
|
+
<span class="text-xs">{{ statusMessage }}</span>
|
|
34
|
+
<span class="flex gap-1">
|
|
35
|
+
<span class="w-1.5 h-1.5 rounded-full bg-gray-400 animate-bounce" style="animation-delay: 0ms" />
|
|
36
|
+
<span class="w-1.5 h-1.5 rounded-full bg-gray-400 animate-bounce" style="animation-delay: 150ms" />
|
|
37
|
+
<span class="w-1.5 h-1.5 rounded-full bg-gray-400 animate-bounce" style="animation-delay: 300ms" />
|
|
38
|
+
</span>
|
|
39
|
+
</div>
|
|
40
|
+
<div v-if="pendingCalls.length > 0" class="mt-1 space-y-0.5">
|
|
41
|
+
<div v-for="call in pendingCalls" :key="call.toolUseId" class="flex items-center gap-1.5 text-xs text-gray-400">
|
|
42
|
+
<span class="w-1.5 h-1.5 rounded-full bg-blue-300 shrink-0 animate-pulse" />
|
|
43
|
+
<span class="font-mono truncate">{{ call.toolName }}</span>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</template>
|
|
49
|
+
|
|
50
|
+
<script setup lang="ts">
|
|
51
|
+
import { ref } from "vue";
|
|
52
|
+
import type { ToolResultComplete } from "gui-chat-protocol/vue";
|
|
53
|
+
import { getPlugin } from "../tools";
|
|
54
|
+
import { formatSmartTime } from "../utils/format/date";
|
|
55
|
+
|
|
56
|
+
interface PendingCall {
|
|
57
|
+
toolUseId: string;
|
|
58
|
+
toolName: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
defineProps<{
|
|
62
|
+
results: ToolResultComplete[];
|
|
63
|
+
selectedUuid: string | null;
|
|
64
|
+
resultTimestamps: Map<string, number>;
|
|
65
|
+
isRunning: boolean;
|
|
66
|
+
statusMessage: string;
|
|
67
|
+
pendingCalls: PendingCall[];
|
|
68
|
+
}>();
|
|
69
|
+
|
|
70
|
+
const emit = defineEmits<{
|
|
71
|
+
select: [uuid: string];
|
|
72
|
+
activate: [];
|
|
73
|
+
}>();
|
|
74
|
+
|
|
75
|
+
const root = ref<HTMLDivElement | null>(null);
|
|
76
|
+
defineExpose({ root });
|
|
77
|
+
</script>
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="fixed inset-0 z-50 bg-black/30 flex items-center justify-center" @click="emit('cancel')">
|
|
3
|
+
<div
|
|
4
|
+
class="bg-white rounded-lg shadow-xl w-96 max-w-[90vw] p-5 space-y-3"
|
|
5
|
+
role="dialog"
|
|
6
|
+
aria-modal="true"
|
|
7
|
+
aria-labelledby="todo-add-dialog-title"
|
|
8
|
+
@click.stop
|
|
9
|
+
>
|
|
10
|
+
<h3 id="todo-add-dialog-title" class="text-base font-semibold text-gray-800">Add Todo</h3>
|
|
11
|
+
<label class="block text-xs text-gray-600">
|
|
12
|
+
Text
|
|
13
|
+
<input
|
|
14
|
+
ref="textInput"
|
|
15
|
+
v-model="text"
|
|
16
|
+
type="text"
|
|
17
|
+
placeholder="What needs doing?"
|
|
18
|
+
class="mt-1 w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:outline-none focus:border-blue-400"
|
|
19
|
+
@keydown.enter="submit"
|
|
20
|
+
/>
|
|
21
|
+
</label>
|
|
22
|
+
<label class="block text-xs text-gray-600">
|
|
23
|
+
Note
|
|
24
|
+
<textarea
|
|
25
|
+
v-model="note"
|
|
26
|
+
rows="2"
|
|
27
|
+
class="mt-1 w-full px-2 py-1.5 text-sm border border-gray-300 rounded resize-y focus:outline-none focus:border-blue-400"
|
|
28
|
+
/>
|
|
29
|
+
</label>
|
|
30
|
+
<div class="grid grid-cols-2 gap-3">
|
|
31
|
+
<label class="block text-xs text-gray-600">
|
|
32
|
+
Status
|
|
33
|
+
<select v-model="status" class="mt-1 w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:outline-none focus:border-blue-400">
|
|
34
|
+
<option v-for="col in columns" :key="col.id" :value="col.id">
|
|
35
|
+
{{ col.label }}
|
|
36
|
+
</option>
|
|
37
|
+
</select>
|
|
38
|
+
</label>
|
|
39
|
+
<label class="block text-xs text-gray-600">
|
|
40
|
+
Priority
|
|
41
|
+
<select v-model="priority" class="mt-1 w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:outline-none focus:border-blue-400">
|
|
42
|
+
<option value="">— None —</option>
|
|
43
|
+
<option v-for="p in PRIORITIES" :key="p" :value="p">
|
|
44
|
+
{{ PRIORITY_LABELS[p] }}
|
|
45
|
+
</option>
|
|
46
|
+
</select>
|
|
47
|
+
</label>
|
|
48
|
+
<label class="block text-xs text-gray-600">
|
|
49
|
+
Due date
|
|
50
|
+
<input
|
|
51
|
+
v-model="dueDate"
|
|
52
|
+
type="date"
|
|
53
|
+
class="mt-1 w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:outline-none focus:border-blue-400"
|
|
54
|
+
/>
|
|
55
|
+
</label>
|
|
56
|
+
<label class="block text-xs text-gray-600">
|
|
57
|
+
Labels
|
|
58
|
+
<input
|
|
59
|
+
v-model="labelsText"
|
|
60
|
+
type="text"
|
|
61
|
+
placeholder="work, urgent"
|
|
62
|
+
class="mt-1 w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:outline-none focus:border-blue-400"
|
|
63
|
+
/>
|
|
64
|
+
</label>
|
|
65
|
+
</div>
|
|
66
|
+
<div class="flex justify-end gap-2 pt-1">
|
|
67
|
+
<button class="px-3 py-1.5 text-sm rounded border border-gray-300 text-gray-600 hover:bg-gray-50" @click="emit('cancel')">Cancel</button>
|
|
68
|
+
<button class="px-3 py-1.5 text-sm rounded bg-blue-500 text-white hover:bg-blue-600" @click="submit">Add</button>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</template>
|
|
73
|
+
|
|
74
|
+
<script setup lang="ts">
|
|
75
|
+
import { onMounted, onUnmounted, ref } from "vue";
|
|
76
|
+
import type { StatusColumn } from "../../plugins/todo/index";
|
|
77
|
+
import { PRIORITIES, PRIORITY_LABELS } from "../../plugins/todo/priority";
|
|
78
|
+
import type { CreateItemInput } from "../../plugins/todo/composables/useTodos";
|
|
79
|
+
|
|
80
|
+
const props = defineProps<{
|
|
81
|
+
columns: StatusColumn[];
|
|
82
|
+
defaultStatus?: string;
|
|
83
|
+
}>();
|
|
84
|
+
|
|
85
|
+
const emit = defineEmits<{
|
|
86
|
+
cancel: [];
|
|
87
|
+
create: [input: CreateItemInput];
|
|
88
|
+
}>();
|
|
89
|
+
|
|
90
|
+
const text = ref("");
|
|
91
|
+
const note = ref("");
|
|
92
|
+
const status = ref<string>(props.defaultStatus ?? props.columns[0]?.id ?? "");
|
|
93
|
+
const priority = ref<string>("");
|
|
94
|
+
const dueDate = ref("");
|
|
95
|
+
const labelsText = ref("");
|
|
96
|
+
|
|
97
|
+
const textInput = ref<HTMLInputElement | null>(null);
|
|
98
|
+
|
|
99
|
+
// Escape closes the dialog. Bound at the document level rather than
|
|
100
|
+
// on the modal div so it works no matter where focus is — Vue's
|
|
101
|
+
// `@keydown.esc` only fires when the modal owns focus, which it
|
|
102
|
+
// loses as soon as the user tabs into one of the form inputs.
|
|
103
|
+
function onKeydown(event: KeyboardEvent): void {
|
|
104
|
+
if (event.key === "Escape") emit("cancel");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
onMounted(() => {
|
|
108
|
+
textInput.value?.focus();
|
|
109
|
+
document.addEventListener("keydown", onKeydown);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
onUnmounted(() => {
|
|
113
|
+
document.removeEventListener("keydown", onKeydown);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
function submit(): void {
|
|
117
|
+
const trimmed = text.value.trim();
|
|
118
|
+
if (trimmed.length === 0) return;
|
|
119
|
+
const input: CreateItemInput = { text: trimmed };
|
|
120
|
+
if (note.value !== "") input.note = note.value;
|
|
121
|
+
if (status.value !== "") input.status = status.value;
|
|
122
|
+
if (priority.value !== "") input.priority = priority.value;
|
|
123
|
+
if (dueDate.value !== "") input.dueDate = dueDate.value;
|
|
124
|
+
const labels = labelsText.value
|
|
125
|
+
.split(",")
|
|
126
|
+
.map((s) => s.trim())
|
|
127
|
+
.filter((s) => s.length > 0);
|
|
128
|
+
if (labels.length > 0) input.labels = labels;
|
|
129
|
+
emit("create", input);
|
|
130
|
+
}
|
|
131
|
+
</script>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="fixed inset-0 z-50 bg-black/30 flex items-center justify-center" @click="emit('cancel')">
|
|
3
|
+
<div
|
|
4
|
+
class="bg-white rounded-lg shadow-xl w-[28rem] max-w-[92vw] overflow-hidden"
|
|
5
|
+
role="dialog"
|
|
6
|
+
aria-modal="true"
|
|
7
|
+
aria-labelledby="todo-edit-dialog-title"
|
|
8
|
+
@click.stop
|
|
9
|
+
>
|
|
10
|
+
<div class="flex items-center justify-between px-4 py-2 border-b border-gray-100">
|
|
11
|
+
<h3 id="todo-edit-dialog-title" class="text-base font-semibold text-gray-800">Edit Todo</h3>
|
|
12
|
+
<button class="text-gray-400 hover:text-red-500 text-xs px-2 py-0.5" title="Delete this item" @click="emit('delete', item.id)">Delete</button>
|
|
13
|
+
</div>
|
|
14
|
+
<TodoEditPanel :item="item" :columns="columns" @save="(input) => emit('save', input)" @cancel="emit('cancel')" />
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script setup lang="ts">
|
|
20
|
+
import { onMounted, onUnmounted } from "vue";
|
|
21
|
+
import type { StatusColumn, TodoItem } from "../../plugins/todo/index";
|
|
22
|
+
import type { PatchItemInput } from "../../plugins/todo/composables/useTodos";
|
|
23
|
+
import TodoEditPanel from "./TodoEditPanel.vue";
|
|
24
|
+
|
|
25
|
+
defineProps<{
|
|
26
|
+
item: TodoItem;
|
|
27
|
+
columns: StatusColumn[];
|
|
28
|
+
}>();
|
|
29
|
+
|
|
30
|
+
const emit = defineEmits<{
|
|
31
|
+
save: [input: PatchItemInput];
|
|
32
|
+
cancel: [];
|
|
33
|
+
delete: [id: string];
|
|
34
|
+
}>();
|
|
35
|
+
// The parent (TodoExplorer) gates deletion behind a single confirm
|
|
36
|
+
// helper, so this dialog just emits the delete intent and lets the
|
|
37
|
+
// caller decide whether to actually remove the item.
|
|
38
|
+
|
|
39
|
+
// Escape closes the dialog. Document-level listener so it works even
|
|
40
|
+
// when focus is inside the form (Vue's @keydown.esc only fires on
|
|
41
|
+
// the element that owns focus).
|
|
42
|
+
function onKeydown(event: KeyboardEvent): void {
|
|
43
|
+
if (event.key === "Escape") emit("cancel");
|
|
44
|
+
}
|
|
45
|
+
onMounted(() => document.addEventListener("keydown", onKeydown));
|
|
46
|
+
onUnmounted(() => document.removeEventListener("keydown", onKeydown));
|
|
47
|
+
</script>
|