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,138 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div ref="rootRef" class="flex border border-gray-300 rounded overflow-hidden text-xs" data-testid="plugin-launcher">
|
|
3
|
+
<template v-for="(target, idx) in TARGETS" :key="target.key">
|
|
4
|
+
<!-- Visual separator between data plugins and management plugins -->
|
|
5
|
+
<div v-if="idx === SEPARATOR_AFTER_INDEX" class="w-px bg-gray-300 my-0.5" />
|
|
6
|
+
<button
|
|
7
|
+
:class="[
|
|
8
|
+
'px-2.5 py-1 flex items-center gap-1 border-r border-gray-200 last:border-r-0 transition-colors',
|
|
9
|
+
isActive(target) ? 'bg-blue-50 text-blue-600 font-medium' : 'bg-white text-gray-600 hover:bg-gray-50',
|
|
10
|
+
]"
|
|
11
|
+
:title="target.title"
|
|
12
|
+
:data-testid="`plugin-launcher-${target.key}`"
|
|
13
|
+
@click="emit('navigate', target)"
|
|
14
|
+
>
|
|
15
|
+
<span class="material-icons text-sm">{{ target.icon }}</span>
|
|
16
|
+
<span v-if="!compact">{{ target.label }}</span>
|
|
17
|
+
</button>
|
|
18
|
+
</template>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script setup lang="ts">
|
|
23
|
+
import { onBeforeUnmount, onMounted, ref } from "vue";
|
|
24
|
+
|
|
25
|
+
// Quick-access toolbar sitting above the canvas. Each button
|
|
26
|
+
// switches the canvas to a dedicated view mode via URL
|
|
27
|
+
// (?view=todos, ?view=wiki, etc.). The "invoke" kind is kept in
|
|
28
|
+
// the union for future use but currently all targets use "view".
|
|
29
|
+
//
|
|
30
|
+
// First slice of issue #253. The list of targets is declared here so
|
|
31
|
+
// the launcher can be swapped for a customisable per-role palette
|
|
32
|
+
// later without touching the App.vue wiring.
|
|
33
|
+
|
|
34
|
+
const props = defineProps<{
|
|
35
|
+
/** Current canvas view mode — the matching button lights up. */
|
|
36
|
+
activeViewMode?: string | null;
|
|
37
|
+
}>();
|
|
38
|
+
|
|
39
|
+
export type PluginLauncherKind = "view"; // Switch the canvas to a dedicated view mode
|
|
40
|
+
|
|
41
|
+
export interface PluginLauncherTarget {
|
|
42
|
+
/** Stable key for testid + dispatch in App.vue. */
|
|
43
|
+
key: "todos" | "scheduler" | "skills" | "wiki" | "roles" | "files";
|
|
44
|
+
kind: PluginLauncherKind;
|
|
45
|
+
/** Material-icons glyph. */
|
|
46
|
+
icon: string;
|
|
47
|
+
/** Visible label next to the icon. */
|
|
48
|
+
label: string;
|
|
49
|
+
/** Tooltip on hover. */
|
|
50
|
+
title: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const TARGETS: PluginLauncherTarget[] = [
|
|
54
|
+
// ─── Data plugins ───
|
|
55
|
+
{
|
|
56
|
+
key: "todos",
|
|
57
|
+
kind: "view",
|
|
58
|
+
icon: "checklist",
|
|
59
|
+
label: "Todos",
|
|
60
|
+
title: "Open todos (⌘4)",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
key: "scheduler",
|
|
64
|
+
kind: "view",
|
|
65
|
+
icon: "event",
|
|
66
|
+
label: "Schedule",
|
|
67
|
+
title: "Open schedule (⌘5)",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
key: "wiki",
|
|
71
|
+
kind: "view",
|
|
72
|
+
icon: "menu_book",
|
|
73
|
+
label: "Wiki",
|
|
74
|
+
title: "Open wiki (⌘6)",
|
|
75
|
+
},
|
|
76
|
+
// ─── Management / navigation ───
|
|
77
|
+
{
|
|
78
|
+
key: "skills",
|
|
79
|
+
kind: "view",
|
|
80
|
+
icon: "psychology",
|
|
81
|
+
label: "Skills",
|
|
82
|
+
title: "Open skills (⌘7)",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
key: "roles",
|
|
86
|
+
kind: "view",
|
|
87
|
+
icon: "manage_accounts",
|
|
88
|
+
label: "Roles",
|
|
89
|
+
title: "Open roles (⌘8)",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
key: "files",
|
|
93
|
+
kind: "view",
|
|
94
|
+
icon: "folder",
|
|
95
|
+
label: "Files",
|
|
96
|
+
title: "Open workspace files (⌘3)",
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
// Index AFTER which the visual separator is inserted (between wiki
|
|
101
|
+
// and skills — data plugins on the left, management on the right).
|
|
102
|
+
const SEPARATOR_AFTER_INDEX = 3;
|
|
103
|
+
|
|
104
|
+
function isActive(target: PluginLauncherTarget): boolean {
|
|
105
|
+
return props.activeViewMode === target.key;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const emit = defineEmits<{
|
|
109
|
+
navigate: [target: PluginLauncherTarget];
|
|
110
|
+
}>();
|
|
111
|
+
|
|
112
|
+
// Compact mode (icons only) kicks in when the toolbar's parent row
|
|
113
|
+
// is narrower than this threshold. Tuned against the six labelled
|
|
114
|
+
// buttons + the canvas-view toggle sharing one row.
|
|
115
|
+
const COMPACT_BREAKPOINT_PX = 640;
|
|
116
|
+
|
|
117
|
+
const rootRef = ref<HTMLElement | null>(null);
|
|
118
|
+
const compact = ref(false);
|
|
119
|
+
let observer: ResizeObserver | null = null;
|
|
120
|
+
|
|
121
|
+
onMounted(() => {
|
|
122
|
+
const parent = rootRef.value?.parentElement;
|
|
123
|
+
if (!parent) return;
|
|
124
|
+
const update = (width: number) => {
|
|
125
|
+
compact.value = width < COMPACT_BREAKPOINT_PX;
|
|
126
|
+
};
|
|
127
|
+
update(parent.clientWidth);
|
|
128
|
+
observer = new ResizeObserver((entries) => {
|
|
129
|
+
for (const entry of entries) update(entry.contentRect.width);
|
|
130
|
+
});
|
|
131
|
+
observer.observe(parent);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
onBeforeUnmount(() => {
|
|
135
|
+
observer?.disconnect();
|
|
136
|
+
observer = null;
|
|
137
|
+
});
|
|
138
|
+
</script>
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="w-80 flex-shrink-0 border-l border-gray-200 flex flex-col bg-white text-gray-900">
|
|
3
|
+
<div ref="historyContainer" class="flex-1 overflow-y-auto min-h-0">
|
|
4
|
+
<div class="bg-white border-b border-gray-200">
|
|
5
|
+
<button
|
|
6
|
+
class="w-full flex items-center justify-between p-4 text-left hover:bg-gray-50"
|
|
7
|
+
title="Toggle system prompt"
|
|
8
|
+
@click="showSystemPrompt = !showSystemPrompt"
|
|
9
|
+
>
|
|
10
|
+
<span class="text-xs font-semibold text-gray-500 uppercase tracking-wide">System Prompt</span>
|
|
11
|
+
<span class="text-gray-400 text-xs">{{ showSystemPrompt ? "▲" : "▼" }}</span>
|
|
12
|
+
</button>
|
|
13
|
+
<div v-if="showSystemPrompt" class="px-4 pb-4">
|
|
14
|
+
<div class="text-xs text-gray-600 leading-relaxed whitespace-pre-wrap">
|
|
15
|
+
{{ rolePrompt }}
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div class="bg-white border-b border-gray-200">
|
|
21
|
+
<div class="p-4 pb-0">
|
|
22
|
+
<span class="text-xs font-semibold text-gray-500 uppercase tracking-wide">Available Tools</span>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="px-4 py-3 space-y-1">
|
|
25
|
+
<div v-for="tool in availableTools" :key="tool" class="text-xs">
|
|
26
|
+
<button class="flex items-center gap-1 w-full text-left" title="Toggle tool description" @click="toggleTool(tool)">
|
|
27
|
+
<span class="bg-gray-100 text-gray-700 rounded px-2 py-0.5 border border-gray-200 font-mono">{{ tool }}</span>
|
|
28
|
+
<span v-if="toolDescriptions[tool]" class="text-gray-400">{{ expandedTools.has(tool) ? "▲" : "▼" }}</span>
|
|
29
|
+
</button>
|
|
30
|
+
<div v-if="toolDescriptions[tool] && expandedTools.has(tool)" class="text-gray-500 mt-0.5 pl-1 leading-snug whitespace-pre-wrap">
|
|
31
|
+
{{ toolDescriptions[tool] }}
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div class="p-4 border-b border-gray-200 bg-white">
|
|
38
|
+
<h2 class="text-lg font-semibold">Tool Call History</h2>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div class="p-2 space-y-2 bg-gray-100">
|
|
42
|
+
<div v-if="toolCallHistory.length === 0" class="text-gray-400 text-sm text-center py-4">No tool calls yet</div>
|
|
43
|
+
<div v-for="(call, index) in toolCallHistory" :key="index" class="border border-gray-300 rounded p-3 bg-white text-xs space-y-1">
|
|
44
|
+
<div class="flex justify-between items-start gap-2">
|
|
45
|
+
<span class="font-semibold text-blue-600 break-all">{{ call.toolName }}</span>
|
|
46
|
+
<span class="text-gray-400 flex-shrink-0">{{ formatTime(call.timestamp) }}</span>
|
|
47
|
+
</div>
|
|
48
|
+
<div>
|
|
49
|
+
<div class="font-medium text-gray-500 mb-1">Arguments</div>
|
|
50
|
+
<pre class="bg-gray-50 p-2 rounded overflow-x-auto text-gray-700">{{ formatJson(call.args) }}</pre>
|
|
51
|
+
</div>
|
|
52
|
+
<div v-if="call.error">
|
|
53
|
+
<div class="font-medium text-gray-500 mb-1">Error</div>
|
|
54
|
+
<div class="bg-red-50 p-2 rounded text-red-700">
|
|
55
|
+
{{ call.error }}
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
<div v-else-if="call.result !== undefined">
|
|
59
|
+
<div class="font-medium text-gray-500 mb-1">Result</div>
|
|
60
|
+
<pre class="bg-green-50 p-2 rounded overflow-x-auto text-gray-700">{{ call.result }}</pre>
|
|
61
|
+
</div>
|
|
62
|
+
<div v-else>
|
|
63
|
+
<div class="text-gray-400 italic">Running...</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</template>
|
|
70
|
+
|
|
71
|
+
<script setup lang="ts">
|
|
72
|
+
import { ref, nextTick } from "vue";
|
|
73
|
+
import type { ToolCallHistoryItem } from "../types/toolCallHistory";
|
|
74
|
+
import { formatTime } from "../utils/format/date";
|
|
75
|
+
|
|
76
|
+
defineProps<{
|
|
77
|
+
toolCallHistory: ToolCallHistoryItem[];
|
|
78
|
+
availableTools: string[];
|
|
79
|
+
rolePrompt: string;
|
|
80
|
+
toolDescriptions: Record<string, string>;
|
|
81
|
+
}>();
|
|
82
|
+
|
|
83
|
+
const showSystemPrompt = ref(false);
|
|
84
|
+
const expandedTools = ref(new Set<string>());
|
|
85
|
+
const historyContainer = ref<HTMLDivElement | null>(null);
|
|
86
|
+
|
|
87
|
+
function toggleTool(tool: string): void {
|
|
88
|
+
if (expandedTools.value.has(tool)) {
|
|
89
|
+
expandedTools.value.delete(tool);
|
|
90
|
+
} else {
|
|
91
|
+
expandedTools.value.add(tool);
|
|
92
|
+
}
|
|
93
|
+
expandedTools.value = new Set(expandedTools.value);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function formatJson(obj: unknown): string {
|
|
97
|
+
try {
|
|
98
|
+
return JSON.stringify(obj, null, 2);
|
|
99
|
+
} catch {
|
|
100
|
+
return String(obj);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function scrollToBottom(): void {
|
|
105
|
+
nextTick(() => {
|
|
106
|
+
if (historyContainer.value) {
|
|
107
|
+
historyContainer.value.scrollTop = historyContainer.value.scrollHeight;
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
defineExpose({ scrollToBottom });
|
|
113
|
+
</script>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex items-center gap-2 relative w-56 shrink-0">
|
|
3
|
+
<button
|
|
4
|
+
ref="button"
|
|
5
|
+
class="flex-1 flex items-center gap-2 bg-white border border-gray-300 rounded px-2 py-1 text-sm text-gray-900 hover:bg-gray-50 text-left"
|
|
6
|
+
data-testid="role-selector-btn"
|
|
7
|
+
@click="open = !open"
|
|
8
|
+
>
|
|
9
|
+
<span class="material-icons text-base text-gray-500">{{ roleIcon(roles, currentRoleId) }}</span>
|
|
10
|
+
<span class="flex-1 truncate">{{ currentRoleName }}</span>
|
|
11
|
+
<span class="material-icons text-sm text-gray-400">expand_more</span>
|
|
12
|
+
</button>
|
|
13
|
+
<div v-if="open" ref="dropdown" class="absolute left-0 right-0 top-full z-50 bg-white border border-gray-200 rounded-lg shadow-lg overflow-hidden">
|
|
14
|
+
<button
|
|
15
|
+
v-for="role in roles"
|
|
16
|
+
:key="role.id"
|
|
17
|
+
:data-testid="`role-option-${role.id}`"
|
|
18
|
+
class="w-full flex items-center gap-1.5 px-3 py-1 text-sm text-gray-900 hover:bg-gray-50 text-left"
|
|
19
|
+
@click="selectRole(role.id)"
|
|
20
|
+
>
|
|
21
|
+
<span class="material-icons text-base text-gray-400">{{ roleIcon(roles, role.id) }}</span>
|
|
22
|
+
{{ role.name }}
|
|
23
|
+
</button>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script setup lang="ts">
|
|
29
|
+
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
|
|
30
|
+
import type { Role } from "../config/roles";
|
|
31
|
+
import { roleIcon, roleName } from "../utils/role/icon";
|
|
32
|
+
import { useClickOutside } from "../composables/useClickOutside";
|
|
33
|
+
|
|
34
|
+
const props = defineProps<{
|
|
35
|
+
roles: Role[];
|
|
36
|
+
currentRoleId: string;
|
|
37
|
+
}>();
|
|
38
|
+
|
|
39
|
+
const emit = defineEmits<{
|
|
40
|
+
"update:currentRoleId": [id: string];
|
|
41
|
+
change: [];
|
|
42
|
+
}>();
|
|
43
|
+
|
|
44
|
+
const open = ref(false);
|
|
45
|
+
const button = ref<HTMLButtonElement | null>(null);
|
|
46
|
+
const dropdown = ref<HTMLDivElement | null>(null);
|
|
47
|
+
|
|
48
|
+
const currentRoleName = computed(() => roleName(props.roles, props.currentRoleId));
|
|
49
|
+
|
|
50
|
+
function selectRole(id: string): void {
|
|
51
|
+
emit("update:currentRoleId", id);
|
|
52
|
+
open.value = false;
|
|
53
|
+
emit("change");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const { handler } = useClickOutside({
|
|
57
|
+
isOpen: open,
|
|
58
|
+
buttonRef: button,
|
|
59
|
+
popupRef: dropdown,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
onMounted(() => document.addEventListener("mousedown", handler));
|
|
63
|
+
onBeforeUnmount(() => document.removeEventListener("mousedown", handler));
|
|
64
|
+
</script>
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
ref="root"
|
|
4
|
+
class="absolute left-0 right-0 bottom-0 bg-white border-b border-gray-200 shadow-lg z-50 overflow-y-auto"
|
|
5
|
+
:style="{ top: topOffset != null ? topOffset + 'px' : '4rem' }"
|
|
6
|
+
>
|
|
7
|
+
<div class="p-2 space-y-1">
|
|
8
|
+
<!-- Origin filter bar -->
|
|
9
|
+
<div class="flex gap-1 mb-1 flex-wrap" data-testid="session-filter-bar">
|
|
10
|
+
<button
|
|
11
|
+
v-for="f in FILTERS"
|
|
12
|
+
:key="f.value"
|
|
13
|
+
class="px-2 py-0.5 text-[10px] rounded-full border transition-colors"
|
|
14
|
+
:class="activeFilter === f.value ? 'bg-blue-500 text-white border-blue-500' : 'bg-white text-gray-500 border-gray-300 hover:bg-gray-50'"
|
|
15
|
+
:data-testid="`session-filter-${f.value}`"
|
|
16
|
+
@click="activeFilter = f.value"
|
|
17
|
+
>
|
|
18
|
+
{{ f.label }}
|
|
19
|
+
<span v-if="f.value !== 'all'" class="ml-0.5 opacity-70">{{ countByOrigin(f.value) }}</span>
|
|
20
|
+
</button>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div
|
|
24
|
+
v-if="errorMessage"
|
|
25
|
+
class="text-xs text-red-700 bg-red-50 border border-red-200 rounded px-2 py-1 mb-1"
|
|
26
|
+
role="alert"
|
|
27
|
+
data-testid="session-history-error"
|
|
28
|
+
>
|
|
29
|
+
⚠ Failed to refresh: {{ errorMessage }}
|
|
30
|
+
<span v-if="sessions.length > 0"> — showing last known list.</span>
|
|
31
|
+
</div>
|
|
32
|
+
<p v-if="filteredSessions.length === 0" class="text-xs text-gray-400 p-2">
|
|
33
|
+
{{ activeFilter === "all" ? "No sessions yet." : "No matching sessions." }}
|
|
34
|
+
</p>
|
|
35
|
+
<div
|
|
36
|
+
v-for="session in filteredSessions"
|
|
37
|
+
:key="session.id"
|
|
38
|
+
class="cursor-pointer rounded border p-2 text-sm transition-colors"
|
|
39
|
+
:class="rowClasses(session)"
|
|
40
|
+
:data-testid="`session-item-${session.id}`"
|
|
41
|
+
@click="emit('loadSession', session.id)"
|
|
42
|
+
>
|
|
43
|
+
<div class="flex items-center gap-1 text-xs text-gray-500 mb-1">
|
|
44
|
+
<span class="material-icons text-xs">{{ roleIconFor(session.roleId) }}</span>
|
|
45
|
+
<span>{{ roleNameFor(session.roleId) }}</span>
|
|
46
|
+
<span
|
|
47
|
+
v-if="originOf(session) !== 'human'"
|
|
48
|
+
class="material-icons text-[10px] ml-0.5"
|
|
49
|
+
:class="originColor(originOf(session))"
|
|
50
|
+
:title="originOf(session)"
|
|
51
|
+
>{{ originIcon(originOf(session)) }}</span
|
|
52
|
+
>
|
|
53
|
+
<span class="ml-auto flex items-center gap-1.5">
|
|
54
|
+
<span v-if="isSessionRunning(session)" class="flex items-center gap-0.5 text-yellow-600 font-medium">
|
|
55
|
+
<span class="w-1.5 h-1.5 rounded-full bg-yellow-400 animate-pulse" />
|
|
56
|
+
Running
|
|
57
|
+
</span>
|
|
58
|
+
<span v-else-if="isSessionUnread(session)" class="flex items-center gap-0.5 text-gray-900 font-bold"> Unread </span>
|
|
59
|
+
<span v-else>{{ formatDate(session.updatedAt) }}</span>
|
|
60
|
+
</span>
|
|
61
|
+
</div>
|
|
62
|
+
<p class="truncate" :class="previewClasses(session)">
|
|
63
|
+
{{ session.preview || "(no messages)" }}
|
|
64
|
+
</p>
|
|
65
|
+
<!-- Optional second line: AI-generated summary of the
|
|
66
|
+
session, populated by the chat indexer (#123). -->
|
|
67
|
+
<p v-if="session.summary" class="text-xs text-gray-500 truncate mt-0.5">
|
|
68
|
+
{{ session.summary }}
|
|
69
|
+
</p>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
</template>
|
|
74
|
+
|
|
75
|
+
<script setup lang="ts">
|
|
76
|
+
import { ref, computed } from "vue";
|
|
77
|
+
import type { Role } from "../config/roles";
|
|
78
|
+
import type { SessionSummary, SessionOrigin } from "../types/session";
|
|
79
|
+
import { SESSION_ORIGINS } from "../types/session";
|
|
80
|
+
import { formatDate } from "../utils/format/date";
|
|
81
|
+
import { roleIcon, roleName } from "../utils/role/icon";
|
|
82
|
+
|
|
83
|
+
const FILTERS = [
|
|
84
|
+
{ value: "all" as const, label: "All" },
|
|
85
|
+
{ value: SESSION_ORIGINS.human, label: "Human" },
|
|
86
|
+
{ value: SESSION_ORIGINS.scheduler, label: "Scheduler" },
|
|
87
|
+
{ value: SESSION_ORIGINS.skill, label: "Skill" },
|
|
88
|
+
{ value: SESSION_ORIGINS.bridge, label: "Bridge" },
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
const ORIGIN_ICONS: Record<string, string> = {
|
|
92
|
+
human: "person",
|
|
93
|
+
scheduler: "event",
|
|
94
|
+
skill: "auto_fix_high",
|
|
95
|
+
bridge: "chat",
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const ORIGIN_COLORS: Record<string, string> = {
|
|
99
|
+
human: "text-gray-400",
|
|
100
|
+
scheduler: "text-blue-500",
|
|
101
|
+
skill: "text-purple-500",
|
|
102
|
+
bridge: "text-green-500",
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const props = defineProps<{
|
|
106
|
+
sessions: SessionSummary[];
|
|
107
|
+
currentSessionId: string;
|
|
108
|
+
roles: Role[];
|
|
109
|
+
topOffset?: number;
|
|
110
|
+
// Latest fetch error from useSessionHistory, or null when healthy.
|
|
111
|
+
errorMessage?: string | null;
|
|
112
|
+
}>();
|
|
113
|
+
|
|
114
|
+
const emit = defineEmits<{
|
|
115
|
+
loadSession: [id: string];
|
|
116
|
+
}>();
|
|
117
|
+
|
|
118
|
+
const root = ref<HTMLDivElement | null>(null);
|
|
119
|
+
defineExpose({ root });
|
|
120
|
+
|
|
121
|
+
// ── Filter ──────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
const activeFilter = ref<SessionOrigin | "all">("all");
|
|
124
|
+
|
|
125
|
+
function originOf(session: SessionSummary): SessionOrigin {
|
|
126
|
+
return session.origin ?? SESSION_ORIGINS.human;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const filteredSessions = computed(() => {
|
|
130
|
+
if (activeFilter.value === "all") return props.sessions;
|
|
131
|
+
return props.sessions.filter((s) => originOf(s) === activeFilter.value);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
function countByOrigin(origin: string): number {
|
|
135
|
+
if (origin === "all") return props.sessions.length;
|
|
136
|
+
return props.sessions.filter((s) => originOf(s) === origin).length;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function originIcon(origin: string): string {
|
|
140
|
+
return ORIGIN_ICONS[origin] ?? "person";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function originColor(origin: string): string {
|
|
144
|
+
return ORIGIN_COLORS[origin] ?? "text-gray-400";
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── Role helpers ────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
function roleIconFor(id: string): string {
|
|
150
|
+
return roleIcon(props.roles, id);
|
|
151
|
+
}
|
|
152
|
+
function roleNameFor(id: string): string {
|
|
153
|
+
return roleName(props.roles, id);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function isSessionRunning(session: SessionSummary): boolean {
|
|
157
|
+
return session.isRunning ?? false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function isSessionUnread(session: SessionSummary): boolean {
|
|
161
|
+
return session.hasUnread ?? false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function rowClasses(session: SessionSummary): string {
|
|
165
|
+
if (isSessionRunning(session)) return "border-yellow-400 bg-yellow-50 hover:bg-yellow-100";
|
|
166
|
+
if (isSessionUnread(session)) return "border-sky-300 bg-sky-50 hover:bg-sky-100";
|
|
167
|
+
if (session.id === props.currentSessionId) return "border-blue-400 bg-blue-50 hover:bg-blue-100";
|
|
168
|
+
return "border-gray-200 hover:bg-gray-50";
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function previewClasses(session: SessionSummary): string {
|
|
172
|
+
if (isSessionRunning(session)) return "text-yellow-800";
|
|
173
|
+
if (isSessionUnread(session)) return "text-gray-900 font-bold";
|
|
174
|
+
return "text-gray-700";
|
|
175
|
+
}
|
|
176
|
+
</script>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex-1 flex gap-1 items-center min-w-0">
|
|
3
|
+
<button
|
|
4
|
+
class="flex-shrink-0 flex items-center justify-center w-7 py-1 rounded border border-dashed border-gray-300 text-gray-400 hover:border-blue-400 hover:text-blue-500 hover:bg-blue-50 transition-colors"
|
|
5
|
+
data-testid="new-session-btn"
|
|
6
|
+
title="New session"
|
|
7
|
+
aria-label="New session"
|
|
8
|
+
@click="emit('newSession')"
|
|
9
|
+
>
|
|
10
|
+
<span class="material-icons text-sm">add</span>
|
|
11
|
+
</button>
|
|
12
|
+
<template v-for="i in 6" :key="i">
|
|
13
|
+
<button
|
|
14
|
+
v-if="sessions[i - 1]"
|
|
15
|
+
class="flex-1 flex items-center justify-center py-1 rounded transition-colors"
|
|
16
|
+
:class="sessions[i - 1].id === currentSessionId ? 'border border-gray-300 bg-white shadow-sm' : 'hover:bg-gray-100'"
|
|
17
|
+
:title="sessions[i - 1].preview || roleName(roles, sessions[i - 1].roleId)"
|
|
18
|
+
:data-testid="`session-tab-${sessions[i - 1].id}`"
|
|
19
|
+
@click="emit('loadSession', sessions[i - 1].id)"
|
|
20
|
+
>
|
|
21
|
+
<span class="material-icons text-base" :class="[tabColor(sessions[i - 1]), sessions[i - 1].isRunning ? 'animate-spin [animation-duration:3s]' : '']">{{
|
|
22
|
+
roleIcon(roles, sessions[i - 1].roleId)
|
|
23
|
+
}}</span>
|
|
24
|
+
</button>
|
|
25
|
+
<div v-else class="flex-1" />
|
|
26
|
+
</template>
|
|
27
|
+
<button
|
|
28
|
+
ref="historyButton"
|
|
29
|
+
data-testid="history-btn"
|
|
30
|
+
class="relative flex-shrink-0 flex items-center justify-center w-7 py-1 rounded text-gray-400 hover:text-gray-700 hover:bg-gray-100 transition-colors"
|
|
31
|
+
:class="{ 'text-blue-500': historyOpen }"
|
|
32
|
+
title="Session history"
|
|
33
|
+
@click="emit('toggleHistory')"
|
|
34
|
+
>
|
|
35
|
+
<span class="material-icons text-base">expand_more</span>
|
|
36
|
+
<span
|
|
37
|
+
v-if="activeSessionCount > 0"
|
|
38
|
+
class="absolute -top-0.5 -left-0.5 min-w-[1rem] h-4 px-0.5 bg-yellow-400 text-white text-[10px] font-bold rounded-full flex items-center justify-center leading-none cursor-help"
|
|
39
|
+
:title="`${activeSessionCount} active session${activeSessionCount > 1 ? 's' : ''} (agent running)`"
|
|
40
|
+
>{{ activeSessionCount }}</span
|
|
41
|
+
>
|
|
42
|
+
<span
|
|
43
|
+
v-if="unreadCount > 0"
|
|
44
|
+
class="absolute -top-0.5 -right-0.5 min-w-[1rem] h-4 px-0.5 bg-red-500 text-white text-[10px] font-bold rounded-full flex items-center justify-center leading-none cursor-help"
|
|
45
|
+
:title="`${unreadCount} unread repl${unreadCount > 1 ? 'ies' : 'y'}`"
|
|
46
|
+
>{{ unreadCount }}</span
|
|
47
|
+
>
|
|
48
|
+
</button>
|
|
49
|
+
</div>
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<script setup lang="ts">
|
|
53
|
+
import { ref } from "vue";
|
|
54
|
+
import type { Role } from "../config/roles";
|
|
55
|
+
import type { SessionSummary } from "../types/session";
|
|
56
|
+
import { roleIcon, roleName } from "../utils/role/icon";
|
|
57
|
+
|
|
58
|
+
defineProps<{
|
|
59
|
+
sessions: SessionSummary[];
|
|
60
|
+
currentSessionId: string;
|
|
61
|
+
roles: Role[];
|
|
62
|
+
activeSessionCount: number;
|
|
63
|
+
unreadCount: number;
|
|
64
|
+
historyOpen: boolean;
|
|
65
|
+
}>();
|
|
66
|
+
|
|
67
|
+
const emit = defineEmits<{
|
|
68
|
+
newSession: [];
|
|
69
|
+
loadSession: [id: string];
|
|
70
|
+
toggleHistory: [];
|
|
71
|
+
}>();
|
|
72
|
+
|
|
73
|
+
const historyButton = ref<HTMLButtonElement | null>(null);
|
|
74
|
+
defineExpose({ historyButton });
|
|
75
|
+
|
|
76
|
+
function tabColor(session: SessionSummary): string {
|
|
77
|
+
if (session.isRunning) return "text-yellow-400";
|
|
78
|
+
if (session.hasUnread) return "text-gray-900";
|
|
79
|
+
return "text-gray-400";
|
|
80
|
+
}
|
|
81
|
+
</script>
|