mulmoclaude 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/mulmoclaude.js +7 -24
- package/client/assets/html2canvas-Cx501zZr-DiKaqnKs.js +5 -0
- package/client/assets/{index-eHWB79u5.js → index-C94GcmNa.js} +203 -198
- package/client/assets/index-CY-WpQUm.css +2 -0
- package/client/assets/{index.es-D4YyL_Dg-BfRHLTZV.js → index.es-D4YyL_Dg-5ipqh8Pe.js} +5 -5
- package/client/assets/material-symbols-outlined-NzYEeyps.woff2 +0 -0
- package/client/index.html +2 -4
- package/package.json +17 -15
- package/server/agent/attachmentConverter.ts +2 -2
- package/server/agent/backend/claude-code.ts +170 -0
- package/server/agent/backend/index.ts +14 -0
- package/server/agent/backend/types.ts +65 -0
- package/server/agent/index.ts +31 -159
- package/server/agent/mcp-server.ts +88 -10
- package/server/agent/mcp-tools/index.ts +8 -7
- package/server/agent/mcp-tools/notify.ts +76 -0
- package/server/agent/mcp-tools/x.ts +12 -2
- package/server/agent/plugin-names.ts +10 -4
- package/server/agent/prompt.ts +187 -26
- package/server/agent/resumeFailover.ts +5 -5
- package/server/agent/sandboxMounts.ts +3 -3
- package/server/api/auth/bearerAuth.ts +3 -3
- package/server/api/auth/token.ts +2 -2
- package/server/api/routes/agent.ts +99 -4
- package/server/api/routes/chart.ts +13 -0
- package/server/api/routes/chat-index.ts +2 -1
- package/server/api/routes/config.ts +35 -8
- package/server/api/routes/files.ts +75 -24
- package/server/api/routes/html.ts +15 -2
- package/server/api/routes/image.ts +75 -20
- package/server/api/routes/mulmo-script.ts +33 -31
- package/server/api/routes/news.ts +146 -0
- package/server/api/routes/notifications.ts +58 -2
- package/server/api/routes/pdf.ts +2 -2
- package/server/api/routes/plugins.ts +73 -91
- package/server/api/routes/presentHtml.ts +9 -0
- package/server/api/routes/roles.ts +12 -2
- package/server/api/routes/scheduler.ts +20 -11
- package/server/api/routes/schedulerTasks.ts +58 -21
- package/server/api/routes/sessions.ts +15 -4
- package/server/api/routes/sessionsCursor.ts +4 -4
- package/server/api/routes/skills.ts +26 -5
- package/server/api/routes/sources.ts +8 -7
- package/server/api/routes/todos.ts +30 -0
- package/server/api/routes/todosColumnsHandlers.ts +13 -27
- package/server/api/routes/todosHandlers.ts +1 -1
- package/server/api/routes/todosItemsHandlers.ts +14 -14
- package/server/api/routes/wiki/frontmatter.ts +86 -0
- package/server/api/routes/wiki.ts +335 -75
- package/server/api/sandboxStatus.ts +1 -1
- package/server/events/notifications.ts +32 -8
- package/server/events/pub-sub/index.ts +3 -3
- package/server/events/relay-client.ts +26 -16
- package/server/events/resolveRelayBridgeOptions.ts +125 -0
- package/server/index.ts +72 -49
- package/server/system/config.ts +5 -5
- package/server/system/credentials.ts +7 -5
- package/server/system/env.ts +15 -5
- package/server/system/macosNotify.ts +152 -0
- package/server/utils/errors.ts +11 -2
- package/server/utils/fetch.ts +54 -0
- package/server/utils/files/atomic.ts +18 -17
- package/server/utils/files/image-store.ts +19 -13
- package/server/utils/files/journal-io.ts +2 -2
- package/server/utils/files/json.ts +5 -5
- package/server/utils/files/markdown-image-fill.ts +131 -0
- package/server/utils/files/markdown-store.ts +22 -6
- package/server/utils/files/naming.ts +20 -10
- package/server/utils/files/reference-dirs-io.ts +3 -3
- package/server/utils/files/roles-io.ts +4 -4
- package/server/utils/files/safe.ts +14 -14
- package/server/utils/files/scheduler-overrides-io.ts +2 -2
- package/server/utils/files/spreadsheet-store.ts +15 -10
- package/server/utils/files/workspace-io.ts +12 -12
- package/server/utils/gemini.ts +30 -4
- package/server/utils/gitignore.ts +9 -9
- package/server/utils/id.ts +40 -8
- package/server/utils/json.ts +5 -5
- package/server/utils/logBackgroundError.ts +12 -3
- package/server/utils/logPreview.ts +24 -0
- package/server/utils/markdown.ts +5 -5
- package/server/utils/port.d.mts +6 -0
- package/server/utils/port.mjs +48 -0
- package/server/utils/promptMeta.ts +32 -0
- package/server/utils/request.ts +12 -6
- package/server/utils/slug.ts +65 -4
- package/server/utils/spawn.ts +1 -1
- package/server/utils/types.ts +2 -2
- package/server/workspace/chat-index/index.ts +1 -1
- package/server/workspace/chat-index/summarizer.ts +5 -5
- package/server/workspace/custom-dirs.ts +5 -5
- package/server/workspace/helps/gemini.md +57 -0
- package/server/workspace/helps/index.md +2 -1
- package/server/workspace/helps/sources.md +42 -0
- package/server/workspace/helps/wiki.md +40 -5
- package/server/workspace/journal/archivist-cli.ts +121 -0
- package/server/workspace/journal/{archivist.ts → archivist-schemas.ts} +12 -120
- package/server/workspace/journal/dailyPass.ts +78 -38
- package/server/workspace/journal/diff.ts +2 -2
- package/server/workspace/journal/index.ts +56 -5
- package/server/workspace/journal/memoryExtractor.ts +1 -1
- package/server/workspace/journal/optimizationPass.ts +4 -5
- package/server/workspace/journal/paths.ts +8 -24
- package/server/workspace/journal/state.ts +18 -8
- package/server/workspace/news/reader.ts +248 -0
- package/server/workspace/paths.ts +4 -3
- package/server/workspace/reference-dirs.ts +3 -3
- package/server/workspace/skills/parser.ts +6 -6
- package/server/workspace/skills/scheduler.ts +5 -4
- package/server/workspace/skills/user-tasks.ts +3 -2
- package/server/workspace/skills/writer.ts +3 -3
- package/server/workspace/sources/arxivDiscovery.ts +2 -2
- package/server/workspace/sources/classifier.ts +1 -1
- package/server/workspace/sources/fetchers/rss.ts +5 -5
- package/server/workspace/sources/fetchers/rssParser.ts +4 -4
- package/server/workspace/sources/interests.ts +3 -3
- package/server/workspace/sources/paths.ts +6 -6
- package/server/workspace/sources/pipeline/fetch.ts +59 -13
- package/server/workspace/sources/pipeline/index.ts +59 -7
- package/server/workspace/sources/pipeline/notify.ts +13 -5
- package/server/workspace/sources/pipeline/plan.ts +11 -9
- package/server/workspace/sources/pipeline/summarize.ts +1 -1
- package/server/workspace/sources/pipeline/write.ts +5 -5
- package/server/workspace/sources/rateLimiter.ts +1 -1
- package/server/workspace/sources/sourceState.ts +9 -4
- package/server/workspace/sources/types.ts +9 -0
- package/server/workspace/sources/urls.ts +1 -1
- package/server/workspace/tool-trace/classify.ts +4 -4
- package/server/workspace/workspace.ts +7 -7
- package/src/App.vue +477 -251
- package/src/components/CanvasViewToggle.vue +12 -10
- package/src/components/ChatInput.vue +112 -105
- package/src/components/FileContentHeader.vue +10 -7
- package/src/components/FileContentRenderer.vue +37 -10
- package/src/components/FileTree.vue +34 -4
- package/src/components/FileTreePane.vue +32 -27
- package/src/components/FilesView.vue +5 -3
- package/src/components/FilterChip.vue +22 -0
- package/src/components/LockStatusPopup.vue +19 -13
- package/src/components/NewsView.vue +252 -0
- package/src/components/NotificationBell.vue +35 -9
- package/src/components/NotificationToast.vue +4 -1
- package/src/components/PageChatComposer.vue +101 -0
- package/src/components/PluginLauncher.vue +36 -62
- package/src/components/RightSidebar.vue +13 -10
- package/src/components/RoleSelector.vue +3 -2
- package/src/components/SessionHeaderControls.vue +63 -0
- package/src/components/SessionHistoryExpandButton.vue +30 -0
- package/src/components/SessionHistoryPanel.vue +64 -93
- package/src/components/SessionHistoryToggleButton.vue +40 -0
- package/src/components/SessionRoleIcon.vue +72 -0
- package/src/components/SessionSidebar.vue +96 -0
- package/src/components/SessionTabBar.vue +44 -51
- package/src/components/SettingsMcpTab.vue +361 -52
- package/src/components/SettingsModal.vue +203 -72
- package/src/components/SettingsReferenceDirsTab.vue +72 -51
- package/src/components/SettingsWorkspaceDirsTab.vue +74 -51
- package/src/components/SidebarHeader.vue +50 -16
- package/src/components/SourcesManager.vue +900 -0
- package/src/components/SourcesView.vue +45 -0
- package/src/components/StackView.vue +84 -48
- package/src/components/SuggestionsPanel.vue +25 -36
- package/src/components/SystemFileBanner.vue +106 -0
- package/src/components/ThinkingIndicator.vue +41 -0
- package/src/components/TodoExplorer.vue +72 -22
- package/src/components/todo/TodoAddDialog.vue +17 -12
- package/src/components/todo/TodoEditDialog.vue +7 -2
- package/src/components/todo/TodoEditPanel.vue +15 -10
- package/src/components/todo/TodoKanbanView.vue +16 -6
- package/src/components/todo/TodoListView.vue +14 -3
- package/src/components/todo/TodoTableView.vue +36 -5
- package/src/composables/favicon/conditions.ts +76 -0
- package/src/composables/favicon/resolveColor.ts +93 -0
- package/src/composables/favicon/types.ts +61 -0
- package/src/composables/useAppApi.ts +23 -0
- package/src/composables/useChatScroll.ts +5 -5
- package/src/composables/useCurrentRole.ts +32 -0
- package/src/composables/useDynamicFavicon.ts +174 -58
- package/src/composables/useEventListeners.ts +7 -12
- package/src/composables/useFaviconState.ts +93 -12
- package/src/composables/useFileSelection.ts +25 -6
- package/src/composables/useHealth.ts +76 -7
- package/src/composables/useLayoutMode.ts +27 -0
- package/src/composables/useNewsItems.ts +38 -0
- package/src/composables/useNewsReadState.ts +75 -0
- package/src/composables/useNotifications.ts +76 -13
- package/src/composables/usePendingCalls.ts +11 -1
- package/src/composables/useRoles.ts +6 -10
- package/src/composables/useRunElapsed.ts +80 -0
- package/src/composables/useSessionDerived.ts +21 -5
- package/src/composables/useSessionHistory.ts +7 -17
- package/src/composables/useSidePanelVisible.ts +25 -0
- package/src/composables/useViewLayout.ts +16 -37
- package/src/config/apiRoutes.ts +19 -6
- package/src/config/historyFilters.ts +30 -0
- package/src/config/mcpCatalog.ts +285 -0
- package/src/config/mcpTypes.ts +26 -0
- package/src/config/roles.ts +19 -51
- package/src/config/systemFileDescriptors.ts +170 -0
- package/src/config/toolNames.ts +6 -1
- package/src/config/workspacePaths.ts +1 -0
- package/src/index.css +14 -0
- package/src/lang/de.ts +706 -0
- package/src/lang/en.ts +726 -0
- package/src/lang/es.ts +712 -0
- package/src/lang/fr.ts +704 -0
- package/src/lang/ja.ts +707 -0
- package/src/lang/ko.ts +709 -0
- package/src/lang/pt-BR.ts +702 -0
- package/src/lang/zh.ts +705 -0
- package/src/lib/vue-i18n.ts +97 -0
- package/src/main.ts +3 -0
- package/src/plugins/canvas/View.vue +104 -186
- package/src/plugins/canvas/definition.ts +0 -8
- package/src/plugins/canvas/index.ts +3 -2
- package/src/plugins/chart/Preview.vue +1 -1
- package/src/plugins/chart/View.vue +9 -4
- package/src/plugins/chart/index.ts +3 -2
- package/src/plugins/editImage/index.ts +3 -2
- package/src/plugins/generateImage/index.ts +3 -2
- package/src/plugins/manageRoles/Preview.vue +4 -1
- package/src/plugins/manageRoles/View.vue +67 -46
- package/src/plugins/manageRoles/index.ts +3 -2
- package/src/plugins/manageSkills/Preview.vue +8 -3
- package/src/plugins/manageSkills/View.vue +39 -34
- package/src/plugins/manageSkills/index.ts +3 -2
- package/src/plugins/manageSource/Preview.vue +1 -1
- package/src/plugins/manageSource/View.vue +3 -687
- package/src/plugins/manageSource/index.ts +3 -2
- package/src/plugins/markdown/Preview.vue +1 -1
- package/src/plugins/markdown/View.vue +164 -73
- package/src/plugins/markdown/definition.ts +6 -4
- package/src/plugins/markdown/index.ts +3 -2
- package/src/plugins/presentForm/Preview.vue +99 -0
- package/src/plugins/presentForm/View.vue +675 -0
- package/src/plugins/presentForm/definition.ts +127 -0
- package/src/plugins/presentForm/index.ts +18 -0
- package/src/plugins/presentForm/plugin.ts +94 -0
- package/src/plugins/presentForm/types.ts +109 -0
- package/src/plugins/presentHtml/Preview.vue +1 -1
- package/src/plugins/presentHtml/View.vue +7 -4
- package/src/plugins/presentHtml/index.ts +3 -2
- package/src/plugins/presentMulmoScript/Preview.vue +1 -1
- package/src/plugins/presentMulmoScript/View.vue +36 -26
- package/src/plugins/presentMulmoScript/index.ts +3 -2
- package/src/plugins/scheduler/AutomationsPreview.vue +37 -0
- package/src/plugins/scheduler/AutomationsView.vue +23 -0
- package/src/plugins/scheduler/CalendarView.vue +23 -0
- package/src/plugins/scheduler/LegacySchedulerView.vue +32 -0
- package/src/plugins/scheduler/Preview.vue +7 -4
- package/src/plugins/scheduler/TasksTab.vue +119 -28
- package/src/plugins/scheduler/View.vue +75 -32
- package/src/plugins/scheduler/automationsDefinition.ts +58 -0
- package/src/plugins/scheduler/calendarDefinition.ts +46 -0
- package/src/plugins/scheduler/formatSchedule.ts +93 -0
- package/src/plugins/scheduler/index.ts +68 -14
- package/src/plugins/scheduler/legacyShape.ts +34 -0
- package/src/plugins/spreadsheet/Preview.vue +9 -5
- package/src/plugins/spreadsheet/View.vue +43 -57
- package/src/plugins/spreadsheet/engine/responseDecoder.ts +2 -1
- package/src/plugins/spreadsheet/index.ts +3 -2
- package/src/plugins/textResponse/Preview.vue +15 -58
- package/src/plugins/textResponse/View.vue +42 -45
- package/src/plugins/textResponse/utils.ts +25 -0
- package/src/plugins/todo/Preview.vue +11 -6
- package/src/plugins/todo/View.vue +27 -13
- package/src/plugins/todo/composables/useTodos.ts +3 -1
- package/src/plugins/todo/index.ts +3 -2
- package/src/plugins/ui-image/ImagePreview.vue +6 -3
- package/src/plugins/ui-image/ImageView.vue +7 -4
- package/src/plugins/wiki/Preview.vue +5 -2
- package/src/plugins/wiki/View.vue +539 -92
- package/src/plugins/wiki/index.ts +5 -2
- package/src/plugins/wiki/route.ts +121 -0
- package/src/router/guards.ts +43 -24
- package/src/router/index.ts +53 -26
- package/src/router/pageRoutes.ts +23 -0
- package/src/tools/index.ts +12 -5
- package/src/tools/legacyPluginNames.ts +13 -0
- package/src/types/notification.ts +31 -6
- package/src/types/vue-i18n.d.ts +20 -0
- package/src/utils/agent/eventDispatch.ts +3 -6
- package/src/utils/agent/formatElapsed.ts +37 -0
- package/src/utils/agent/request.ts +22 -1
- package/src/utils/canvas/layoutMode.ts +26 -0
- package/src/utils/canvas/sidePanelVisible.ts +19 -0
- package/src/utils/dom/scrollIntoViewByTestId.ts +38 -0
- package/src/utils/errors.ts +9 -2
- package/src/utils/files/filename.ts +24 -0
- package/src/utils/filesPreview/schedulerPreview.ts +9 -3
- package/src/utils/id.ts +18 -0
- package/src/utils/image/cacheBust.ts +16 -0
- package/src/utils/image/resolve.ts +16 -0
- package/src/utils/markdown/taskList.ts +175 -0
- package/src/utils/mcp/interpolateSpec.ts +97 -0
- package/src/utils/notification/dispatch.ts +51 -15
- package/src/utils/path/workspaceLinkRouter.ts +99 -0
- package/src/utils/session/mergeSessions.ts +5 -0
- package/src/utils/sources/filter.ts +69 -0
- package/src/vite-env.d.ts +9 -0
- package/client/assets/chunk-vKJrgz-R-C_I3GbVV.js +0 -1
- package/client/assets/html2canvas-Cx501zZr-BF5dYYkY.js +0 -5
- package/client/assets/index-Bm70FDU2.css +0 -1
- package/client/assets/typeof-DBp4T-Ny-BC0P-2DM.js +0 -1
- package/server/workspace/journal/linkRewrite.ts +0 -4
- package/src/components/ToolResultsPanel.vue +0 -77
- package/src/composables/useCanvasViewMode.ts +0 -121
- package/src/plugins/scheduler/definition.ts +0 -57
- package/src/utils/canvas/viewMode.ts +0 -46
- package/src/utils/role/plugins.ts +0 -12
- package/src/utils/session/seedRoleDefault.ts +0 -35
- /package/client/assets/{purify.es-Fx1Nqyry-PeS5RUhs.js → purify.es-Fx1Nqyry-BwJECkqS.js} +0 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form Tool Definition (Schema)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ToolDefinition } from "gui-chat-protocol";
|
|
6
|
+
|
|
7
|
+
export const TOOL_NAME = "presentForm";
|
|
8
|
+
|
|
9
|
+
export const TOOL_DEFINITION: ToolDefinition = {
|
|
10
|
+
type: "function",
|
|
11
|
+
name: TOOL_NAME,
|
|
12
|
+
description:
|
|
13
|
+
"Create a structured form to collect information from the user. Supports various field types including text input, textarea, multiple choice (radio), dropdown menus, checkboxes, date/time pickers, and number inputs. Each field can have validation rules and help text.",
|
|
14
|
+
parameters: {
|
|
15
|
+
type: "object",
|
|
16
|
+
properties: {
|
|
17
|
+
title: {
|
|
18
|
+
type: "string",
|
|
19
|
+
description: "Optional title for the form (e.g., 'User Registration')",
|
|
20
|
+
},
|
|
21
|
+
description: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "Optional description explaining the purpose of the form",
|
|
24
|
+
},
|
|
25
|
+
fields: {
|
|
26
|
+
type: "array",
|
|
27
|
+
description: "Array of form fields with various types and configurations",
|
|
28
|
+
items: {
|
|
29
|
+
type: "object",
|
|
30
|
+
properties: {
|
|
31
|
+
id: {
|
|
32
|
+
type: "string",
|
|
33
|
+
description:
|
|
34
|
+
"Unique identifier for the field (e.g., 'email', 'birthdate'). This will be the key in the JSON response. Use descriptive camelCase or snake_case names.",
|
|
35
|
+
},
|
|
36
|
+
type: {
|
|
37
|
+
type: "string",
|
|
38
|
+
enum: ["text", "textarea", "radio", "dropdown", "checkbox", "date", "time", "number"],
|
|
39
|
+
description:
|
|
40
|
+
"Field type: 'text' for short text, 'textarea' for long text, 'radio' for 2-6 choices, 'dropdown' for many choices, 'checkbox' for multiple selections, 'date' for date picker, 'time' for time picker, 'number' for numeric input",
|
|
41
|
+
},
|
|
42
|
+
label: {
|
|
43
|
+
type: "string",
|
|
44
|
+
description: "Field label shown to the user",
|
|
45
|
+
},
|
|
46
|
+
description: {
|
|
47
|
+
type: "string",
|
|
48
|
+
description: "Optional help text explaining the field",
|
|
49
|
+
},
|
|
50
|
+
required: {
|
|
51
|
+
type: "boolean",
|
|
52
|
+
description: "Whether the field is required (default: false)",
|
|
53
|
+
},
|
|
54
|
+
placeholder: {
|
|
55
|
+
type: "string",
|
|
56
|
+
description: "Placeholder text for text/textarea fields",
|
|
57
|
+
},
|
|
58
|
+
validation: {
|
|
59
|
+
type: "string",
|
|
60
|
+
description: "For text fields: 'email', 'url', 'phone', or a regex pattern",
|
|
61
|
+
},
|
|
62
|
+
minLength: {
|
|
63
|
+
type: "number",
|
|
64
|
+
description: "Minimum character length for textarea fields",
|
|
65
|
+
},
|
|
66
|
+
maxLength: {
|
|
67
|
+
type: "number",
|
|
68
|
+
description: "Maximum character length for textarea fields",
|
|
69
|
+
},
|
|
70
|
+
rows: {
|
|
71
|
+
type: "number",
|
|
72
|
+
description: "Number of visible rows for textarea (default: 4)",
|
|
73
|
+
},
|
|
74
|
+
choices: {
|
|
75
|
+
type: "array",
|
|
76
|
+
items: { type: "string" },
|
|
77
|
+
description: "Array of choices for radio/dropdown/checkbox fields. Radio should have 2-6 choices, dropdown for 7+ choices.",
|
|
78
|
+
},
|
|
79
|
+
searchable: {
|
|
80
|
+
type: "boolean",
|
|
81
|
+
description: "Make dropdown searchable (for large lists)",
|
|
82
|
+
},
|
|
83
|
+
minSelections: {
|
|
84
|
+
type: "number",
|
|
85
|
+
description: "Minimum number of selections for checkbox fields",
|
|
86
|
+
},
|
|
87
|
+
maxSelections: {
|
|
88
|
+
type: "number",
|
|
89
|
+
description: "Maximum number of selections for checkbox fields",
|
|
90
|
+
},
|
|
91
|
+
minDate: {
|
|
92
|
+
type: "string",
|
|
93
|
+
description: "Minimum date (ISO format: YYYY-MM-DD)",
|
|
94
|
+
},
|
|
95
|
+
maxDate: {
|
|
96
|
+
type: "string",
|
|
97
|
+
description: "Maximum date (ISO format: YYYY-MM-DD)",
|
|
98
|
+
},
|
|
99
|
+
format: {
|
|
100
|
+
type: "string",
|
|
101
|
+
description: "Format for time fields: '12hr' or '24hr'",
|
|
102
|
+
},
|
|
103
|
+
min: {
|
|
104
|
+
type: "number",
|
|
105
|
+
description: "Minimum value for number fields",
|
|
106
|
+
},
|
|
107
|
+
max: {
|
|
108
|
+
type: "number",
|
|
109
|
+
description: "Maximum value for number fields",
|
|
110
|
+
},
|
|
111
|
+
step: {
|
|
112
|
+
type: "number",
|
|
113
|
+
description: "Step increment for number fields",
|
|
114
|
+
},
|
|
115
|
+
defaultValue: {
|
|
116
|
+
description:
|
|
117
|
+
"Optional default/pre-filled value for the field. Type varies by field: string for text/textarea/radio/dropdown/date/time, number for number fields, array of strings for checkbox fields. For radio/dropdown, must be one of the choices. For checkbox, must be a subset of choices.",
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
required: ["id", "type", "label"],
|
|
121
|
+
},
|
|
122
|
+
minItems: 1,
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
required: ["fields"],
|
|
126
|
+
},
|
|
127
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ToolPlugin } from "gui-chat-protocol/vue";
|
|
2
|
+
import type { FormData, FormArgs } from "./types";
|
|
3
|
+
import { TOOL_DEFINITION } from "./definition";
|
|
4
|
+
import { executeForm } from "./plugin";
|
|
5
|
+
import View from "./View.vue";
|
|
6
|
+
import Preview from "./Preview.vue";
|
|
7
|
+
|
|
8
|
+
const presentFormPlugin: ToolPlugin<never, FormData, FormArgs> = {
|
|
9
|
+
toolDefinition: TOOL_DEFINITION,
|
|
10
|
+
execute: executeForm,
|
|
11
|
+
generatingMessage: "Preparing form...",
|
|
12
|
+
isEnabled: () => true,
|
|
13
|
+
viewComponent: View,
|
|
14
|
+
previewComponent: Preview,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default presentFormPlugin;
|
|
18
|
+
export { TOOL_NAME } from "./definition";
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Local fork of @mulmochat-plugin/form (originally
|
|
2
|
+
// ~/ss/llm/GUIChatPlugins/MulmoChatPluginForm). Forked so the form
|
|
3
|
+
// submission payload (see View.vue:handleSubmit) renders as readable
|
|
4
|
+
// markdown bullets in the chat history instead of a JSON-wrapped
|
|
5
|
+
// object.
|
|
6
|
+
|
|
7
|
+
import type { ToolContext, ToolResult } from "gui-chat-protocol";
|
|
8
|
+
import type { FormData, FormArgs, FormField } from "./types";
|
|
9
|
+
|
|
10
|
+
export { TOOL_NAME, TOOL_DEFINITION } from "./definition";
|
|
11
|
+
|
|
12
|
+
function validateChoiceField(field: FormField): void {
|
|
13
|
+
if (field.type === "radio") {
|
|
14
|
+
if (!Array.isArray(field.choices) || field.choices.length < 2) {
|
|
15
|
+
throw new Error(`Field '${field.id}': radio fields must have at least 2 choices`);
|
|
16
|
+
}
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (field.type === "dropdown" || field.type === "checkbox") {
|
|
20
|
+
if (!Array.isArray(field.choices) || field.choices.length < 1) {
|
|
21
|
+
throw new Error(`Field '${field.id}': ${field.type} fields must have at least 1 choice`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function validateCheckboxRange(field: FormField & { type: "checkbox" }): void {
|
|
27
|
+
const { minSelections, maxSelections, choices, id } = field;
|
|
28
|
+
if (minSelections !== undefined && maxSelections !== undefined && minSelections > maxSelections) {
|
|
29
|
+
throw new Error(`Field '${id}': minSelections cannot be greater than maxSelections`);
|
|
30
|
+
}
|
|
31
|
+
if (maxSelections !== undefined && maxSelections > choices.length) {
|
|
32
|
+
throw new Error(`Field '${id}': maxSelections cannot exceed number of choices`);
|
|
33
|
+
}
|
|
34
|
+
// Without this lower-bound check, a form with minSelections > choices.length
|
|
35
|
+
// would render but be impossible to submit — the user can never satisfy the
|
|
36
|
+
// minimum because there aren't enough options to pick.
|
|
37
|
+
if (minSelections !== undefined && minSelections > choices.length) {
|
|
38
|
+
throw new Error(`Field '${id}': minSelections cannot exceed number of choices`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function validateRangeField(field: FormField): void {
|
|
43
|
+
if (
|
|
44
|
+
(field.type === "text" || field.type === "textarea") &&
|
|
45
|
+
field.minLength !== undefined &&
|
|
46
|
+
field.maxLength !== undefined &&
|
|
47
|
+
field.minLength > field.maxLength
|
|
48
|
+
) {
|
|
49
|
+
throw new Error(`Field '${field.id}': minLength cannot be greater than maxLength`);
|
|
50
|
+
}
|
|
51
|
+
if (field.type === "number" && field.min !== undefined && field.max !== undefined && field.min > field.max) {
|
|
52
|
+
throw new Error(`Field '${field.id}': min cannot be greater than max`);
|
|
53
|
+
}
|
|
54
|
+
if (field.type === "date" && field.minDate && field.maxDate && field.minDate > field.maxDate) {
|
|
55
|
+
throw new Error(`Field '${field.id}': minDate cannot be after maxDate`);
|
|
56
|
+
}
|
|
57
|
+
if (field.type === "checkbox") validateCheckboxRange(field);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function validateField(field: FormField, index: number, seenIds: Set<string>): void {
|
|
61
|
+
if (!field.id || typeof field.id !== "string") throw new Error(`Field ${index + 1} must have a valid 'id' property`);
|
|
62
|
+
if (!field.type || typeof field.type !== "string") throw new Error(`Field ${index + 1} must have a valid 'type' property`);
|
|
63
|
+
if (!field.label || typeof field.label !== "string") throw new Error(`Field ${index + 1} must have a valid 'label' property`);
|
|
64
|
+
if (seenIds.has(field.id)) throw new Error(`Duplicate field ID: '${field.id}'`);
|
|
65
|
+
seenIds.add(field.id);
|
|
66
|
+
validateChoiceField(field);
|
|
67
|
+
validateRangeField(field);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const executeForm = async (_context: ToolContext, args: FormArgs): Promise<ToolResult<never, FormData>> => {
|
|
71
|
+
try {
|
|
72
|
+
const { title, description, fields } = args;
|
|
73
|
+
if (!fields || !Array.isArray(fields) || fields.length === 0) {
|
|
74
|
+
throw new Error("At least one field is required");
|
|
75
|
+
}
|
|
76
|
+
const seen = new Set<string>();
|
|
77
|
+
fields.forEach((field, i) => validateField(field, i, seen));
|
|
78
|
+
|
|
79
|
+
const formData: FormData = { title, description, fields };
|
|
80
|
+
const fieldCount = `${fields.length} field${fields.length > 1 ? "s" : ""}`;
|
|
81
|
+
const titleSuffix = title ? `: ${title}` : "";
|
|
82
|
+
return {
|
|
83
|
+
message: `Form created with ${fieldCount}${titleSuffix}`,
|
|
84
|
+
jsonData: formData,
|
|
85
|
+
instructions:
|
|
86
|
+
"The form has been presented to the user. Wait for the user to fill out and submit it. They will reply with a markdown bullet list of `- {label}: {value}` lines.",
|
|
87
|
+
};
|
|
88
|
+
} catch (error) {
|
|
89
|
+
return {
|
|
90
|
+
message: `Form error: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
91
|
+
instructions: "Acknowledge that there was an error creating the form and suggest trying again with corrected field definitions.",
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form Plugin Types
|
|
3
|
+
*
|
|
4
|
+
* Form-specific type definitions only.
|
|
5
|
+
* Common types should be imported directly from gui-chat-protocol.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Form-specific Types
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
/** Field type discriminator */
|
|
13
|
+
export type FieldType = "text" | "textarea" | "radio" | "dropdown" | "checkbox" | "date" | "time" | "number";
|
|
14
|
+
|
|
15
|
+
/** Base field interface */
|
|
16
|
+
export interface BaseField {
|
|
17
|
+
id: string;
|
|
18
|
+
type: FieldType;
|
|
19
|
+
label: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
required?: boolean;
|
|
22
|
+
maxLength?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Text field */
|
|
26
|
+
export interface TextField extends BaseField {
|
|
27
|
+
type: "text";
|
|
28
|
+
placeholder?: string;
|
|
29
|
+
validation?: "email" | "url" | "phone" | string;
|
|
30
|
+
defaultValue?: string;
|
|
31
|
+
minLength?: number;
|
|
32
|
+
maxLength?: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Textarea field */
|
|
36
|
+
export interface TextareaField extends BaseField {
|
|
37
|
+
type: "textarea";
|
|
38
|
+
placeholder?: string;
|
|
39
|
+
minLength?: number;
|
|
40
|
+
maxLength?: number;
|
|
41
|
+
rows?: number;
|
|
42
|
+
defaultValue?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Radio field */
|
|
46
|
+
export interface RadioField extends BaseField {
|
|
47
|
+
type: "radio";
|
|
48
|
+
choices: string[];
|
|
49
|
+
defaultValue?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Dropdown field */
|
|
53
|
+
export interface DropdownField extends BaseField {
|
|
54
|
+
type: "dropdown";
|
|
55
|
+
choices: string[];
|
|
56
|
+
searchable?: boolean;
|
|
57
|
+
defaultValue?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Checkbox field */
|
|
61
|
+
export interface CheckboxField extends BaseField {
|
|
62
|
+
type: "checkbox";
|
|
63
|
+
choices: string[];
|
|
64
|
+
minSelections?: number;
|
|
65
|
+
maxSelections?: number;
|
|
66
|
+
defaultValue?: string[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Date field */
|
|
70
|
+
export interface DateField extends BaseField {
|
|
71
|
+
type: "date";
|
|
72
|
+
minDate?: string;
|
|
73
|
+
maxDate?: string;
|
|
74
|
+
format?: string;
|
|
75
|
+
defaultValue?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Time field */
|
|
79
|
+
export interface TimeField extends BaseField {
|
|
80
|
+
type: "time";
|
|
81
|
+
format?: "12hr" | "24hr";
|
|
82
|
+
defaultValue?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Number field */
|
|
86
|
+
export interface NumberField extends BaseField {
|
|
87
|
+
type: "number";
|
|
88
|
+
min?: number;
|
|
89
|
+
max?: number;
|
|
90
|
+
step?: number;
|
|
91
|
+
defaultValue?: number;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Union type for all fields */
|
|
95
|
+
export type FormField = TextField | TextareaField | RadioField | DropdownField | CheckboxField | DateField | TimeField | NumberField;
|
|
96
|
+
|
|
97
|
+
/** Form data stored in result.jsonData */
|
|
98
|
+
export interface FormData {
|
|
99
|
+
title?: string;
|
|
100
|
+
description?: string;
|
|
101
|
+
fields: FormField[];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Arguments passed to the form tool */
|
|
105
|
+
export interface FormArgs {
|
|
106
|
+
title?: string;
|
|
107
|
+
description?: string;
|
|
108
|
+
fields: FormField[];
|
|
109
|
+
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="h-full flex flex-col overflow-hidden">
|
|
3
3
|
<div class="px-4 py-2 border-b border-gray-100 shrink-0 flex items-center justify-between">
|
|
4
|
-
<span class="text-sm font-medium text-gray-700 truncate">{{ title ?? "
|
|
4
|
+
<span class="text-sm font-medium text-gray-700 truncate">{{ title ?? t("pluginPresentHtml.untitled") }}</span>
|
|
5
5
|
<div class="flex items-center gap-2">
|
|
6
6
|
<button
|
|
7
7
|
class="px-2 py-1 text-xs rounded border border-gray-300 text-gray-500 hover:bg-gray-50 shrink-0"
|
|
8
|
-
title="
|
|
8
|
+
:title="t('pluginPresentHtml.saveAsPdf')"
|
|
9
9
|
@click="printToPdf"
|
|
10
10
|
>
|
|
11
11
|
<span class="material-icons text-sm align-middle">picture_as_pdf</span>
|
|
12
|
-
|
|
12
|
+
{{ t("pluginPresentHtml.pdf") }}
|
|
13
13
|
</button>
|
|
14
14
|
<button class="px-2 py-1 text-xs rounded border border-gray-300 text-gray-500 hover:bg-gray-50 shrink-0" @click="sourceOpen = !sourceOpen">
|
|
15
|
-
{{ sourceOpen ? "
|
|
15
|
+
{{ sourceOpen ? t("pluginPresentHtml.hideSource") : t("pluginPresentHtml.showSource") }}
|
|
16
16
|
</button>
|
|
17
17
|
</div>
|
|
18
18
|
</div>
|
|
@@ -25,9 +25,12 @@
|
|
|
25
25
|
|
|
26
26
|
<script setup lang="ts">
|
|
27
27
|
import { computed, ref } from "vue";
|
|
28
|
+
import { useI18n } from "vue-i18n";
|
|
28
29
|
import type { ToolResultComplete } from "gui-chat-protocol/vue";
|
|
29
30
|
import type { PresentHtmlData } from "./index";
|
|
30
31
|
|
|
32
|
+
const { t } = useI18n();
|
|
33
|
+
|
|
31
34
|
const props = defineProps<{
|
|
32
35
|
selectedResult: ToolResultComplete<PresentHtmlData>;
|
|
33
36
|
}>();
|
|
@@ -5,6 +5,7 @@ import View from "./View.vue";
|
|
|
5
5
|
import Preview from "./Preview.vue";
|
|
6
6
|
import { apiPost } from "../../utils/api";
|
|
7
7
|
import { API_ROUTES } from "../../config/apiRoutes";
|
|
8
|
+
import { makeUuid } from "../../utils/id";
|
|
8
9
|
|
|
9
10
|
export interface PresentHtmlData {
|
|
10
11
|
html: string;
|
|
@@ -20,14 +21,14 @@ const presentHtmlPlugin: ToolPlugin<PresentHtmlData> = {
|
|
|
20
21
|
if (!result.ok) {
|
|
21
22
|
return {
|
|
22
23
|
toolName: TOOL_NAME,
|
|
23
|
-
uuid:
|
|
24
|
+
uuid: makeUuid(),
|
|
24
25
|
message: result.error,
|
|
25
26
|
};
|
|
26
27
|
}
|
|
27
28
|
return {
|
|
28
29
|
...result.data,
|
|
29
30
|
toolName: TOOL_NAME,
|
|
30
|
-
uuid:
|
|
31
|
+
uuid: makeUuid(),
|
|
31
32
|
};
|
|
32
33
|
},
|
|
33
34
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
{{ script.description }}
|
|
11
11
|
</p>
|
|
12
12
|
<div class="flex items-center gap-3 mt-1 text-xs text-gray-400">
|
|
13
|
-
<span>{{ beats.length
|
|
13
|
+
<span>{{ t("pluginMulmoScript.beatCount", beats.length, { named: { count: beats.length } }) }}</span>
|
|
14
14
|
<span v-if="script.lang">{{ script.lang }}</span>
|
|
15
15
|
<span v-if="filePath" class="truncate">{{ filePath }}</span>
|
|
16
16
|
</div>
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
class="px-3 py-1 text-xs rounded-full border transition-colors border-gray-200 text-gray-500 hover:bg-gray-50 flex items-center justify-center gap-1"
|
|
25
25
|
>
|
|
26
26
|
<span class="material-icons text-sm leading-none">download</span>
|
|
27
|
-
<span>
|
|
27
|
+
<span>{{ t("pluginMulmoScript.movie") }}</span>
|
|
28
28
|
</a>
|
|
29
29
|
<!-- Generate / Regenerate Movie -->
|
|
30
30
|
<button
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
|
37
37
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z" />
|
|
38
38
|
</svg>
|
|
39
|
-
<span v-if="movieGenerating">
|
|
39
|
+
<span v-if="movieGenerating">{{ t("pluginMulmoScript.generating") }}</span>
|
|
40
40
|
<template v-else>
|
|
41
41
|
<span class="material-icons text-sm leading-none">refresh</span>
|
|
42
|
-
<span>
|
|
42
|
+
<span>{{ t("pluginMulmoScript.movie") }}</span>
|
|
43
43
|
</template>
|
|
44
44
|
</button>
|
|
45
45
|
</div>
|
|
@@ -48,13 +48,13 @@
|
|
|
48
48
|
<!-- Characters section -->
|
|
49
49
|
<div v-if="characterKeys.length > 0" class="border-b border-gray-100 shrink-0 px-4 py-3">
|
|
50
50
|
<div class="flex items-center justify-between mb-2">
|
|
51
|
-
<span class="text-xs font-semibold text-gray-500 uppercase tracking-wide">
|
|
51
|
+
<span class="text-xs font-semibold text-gray-500 uppercase tracking-wide">{{ t("pluginMulmoScript.characters") }}</span>
|
|
52
52
|
<button
|
|
53
53
|
class="px-2 py-0.5 text-xs rounded border border-gray-300 text-gray-500 hover:bg-gray-50 disabled:opacity-50"
|
|
54
54
|
:disabled="movieGenerating || anyBeatRendering || characterKeys.every((key) => charRenderState[key] === 'rendering')"
|
|
55
55
|
@click="generateAllCharacters"
|
|
56
56
|
>
|
|
57
|
-
|
|
57
|
+
{{ t("pluginMulmoScript.generateAll") }}
|
|
58
58
|
</button>
|
|
59
59
|
</div>
|
|
60
60
|
<div class="flex gap-3 flex-wrap">
|
|
@@ -88,11 +88,11 @@
|
|
|
88
88
|
</template>
|
|
89
89
|
<!-- Permanent drop hint -->
|
|
90
90
|
<div v-if="!charDragOver[key]" class="absolute bottom-0 inset-x-0 text-center text-xs text-gray-400 bg-white/70 py-0.5 pointer-events-none">
|
|
91
|
-
|
|
91
|
+
{{ t("pluginMulmoScript.orDropImage") }}
|
|
92
92
|
</div>
|
|
93
93
|
<!-- Drop overlay -->
|
|
94
94
|
<div v-if="charDragOver[key]" class="absolute inset-0 flex items-center justify-center bg-blue-50/80 pointer-events-none">
|
|
95
|
-
<span class="text-xs text-blue-500 font-medium">
|
|
95
|
+
<span class="text-xs text-blue-500 font-medium">{{ t("pluginMulmoScript.drop") }}</span>
|
|
96
96
|
</div>
|
|
97
97
|
<!-- Regenerate button -->
|
|
98
98
|
<button
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
|
122
122
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z" />
|
|
123
123
|
</svg>
|
|
124
|
-
<span v-else>
|
|
124
|
+
<span v-else>{{ t("pluginMulmoScript.gen") }}</span>
|
|
125
125
|
</button>
|
|
126
126
|
</div>
|
|
127
127
|
<span class="text-xs text-gray-600 text-center truncate w-full">{{ key }}</span>
|
|
@@ -163,7 +163,7 @@
|
|
|
163
163
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
|
164
164
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z" />
|
|
165
165
|
</svg>
|
|
166
|
-
<span class="text-xs text-green-500">
|
|
166
|
+
<span class="text-xs text-green-500">{{ t("pluginMulmoScript.rendering") }}</span>
|
|
167
167
|
</template>
|
|
168
168
|
<template v-else-if="renderState[index] === 'error'">
|
|
169
169
|
<span class="text-xs text-red-400 text-center">{{ renderErrors[index] }}</span>
|
|
@@ -177,13 +177,13 @@
|
|
|
177
177
|
</div>
|
|
178
178
|
<!-- Beat drop hint / overlay -->
|
|
179
179
|
<div v-if="beatDragOver[index]" class="absolute inset-0 flex items-center justify-center bg-blue-50/80 pointer-events-none">
|
|
180
|
-
<span class="text-xs text-blue-500 font-medium">
|
|
180
|
+
<span class="text-xs text-blue-500 font-medium">{{ t("pluginMulmoScript.drop") }}</span>
|
|
181
181
|
</div>
|
|
182
182
|
<div
|
|
183
183
|
v-else-if="!renderedImages[index] && renderState[index] !== 'rendering'"
|
|
184
184
|
class="absolute bottom-0 inset-x-0 text-center text-xs text-gray-400 bg-white/70 py-0.5 pointer-events-none"
|
|
185
185
|
>
|
|
186
|
-
|
|
186
|
+
{{ t("pluginMulmoScript.orDropImage") }}
|
|
187
187
|
</div>
|
|
188
188
|
<!-- Generate button for imagePrompt beats -->
|
|
189
189
|
<button
|
|
@@ -191,7 +191,7 @@
|
|
|
191
191
|
class="absolute top-1.5 right-1.5 flex items-center gap-1 px-2 py-0.5 text-xs rounded border border-blue-400 text-blue-600 bg-white hover:bg-blue-50"
|
|
192
192
|
@click="renderBeat(index)"
|
|
193
193
|
>
|
|
194
|
-
|
|
194
|
+
{{ t("pluginMulmoScript.generate") }}
|
|
195
195
|
</button>
|
|
196
196
|
</div>
|
|
197
197
|
|
|
@@ -213,10 +213,10 @@
|
|
|
213
213
|
:class="playingAudio?.index === index ? 'border-red-400 text-red-600 hover:bg-red-50' : 'border-green-400 text-green-600 hover:bg-green-50'"
|
|
214
214
|
@click="playAudio(index)"
|
|
215
215
|
>
|
|
216
|
-
{{ playingAudio?.index === index ? "
|
|
216
|
+
{{ playingAudio?.index === index ? t("pluginMulmoScript.stop") : t("pluginMulmoScript.play") }}
|
|
217
217
|
</button>
|
|
218
218
|
<template v-else-if="audioErrors[index]">
|
|
219
|
-
<span class="text-xs text-red-400" :title="audioErrors[index]"
|
|
219
|
+
<span class="text-xs text-red-400" :title="audioErrors[index]">{{ t("pluginMulmoScript.errPrefix") }}</span>
|
|
220
220
|
<button
|
|
221
221
|
v-if="effectiveBeat(index).text"
|
|
222
222
|
class="text-xs px-2 py-0.5 rounded border border-gray-300 text-gray-500 hover:bg-gray-50 disabled:opacity-50"
|
|
@@ -231,7 +231,7 @@
|
|
|
231
231
|
class="text-xs px-2 py-0.5 rounded border border-gray-300 text-gray-500 hover:bg-gray-50"
|
|
232
232
|
@click="generateAudio(index)"
|
|
233
233
|
>
|
|
234
|
-
|
|
234
|
+
{{ t("pluginMulmoScript.generateAudio") }}
|
|
235
235
|
</button>
|
|
236
236
|
</div>
|
|
237
237
|
<button class="text-gray-400 hover:text-gray-600" :title="sourceOpen[index] ? 'Hide source' : 'Show source'" @click="toggleSource(index)">
|
|
@@ -263,7 +263,11 @@
|
|
|
263
263
|
spellcheck="false"
|
|
264
264
|
/>
|
|
265
265
|
<div class="flex items-center justify-end gap-2 px-2 pb-2">
|
|
266
|
-
<span v-if="beatSaveErrors[index]" class="text-xs text-red-600" role="alert"
|
|
266
|
+
<span v-if="beatSaveErrors[index]" class="text-xs text-red-600" role="alert">{{
|
|
267
|
+
t(beatSaveErrors[index].kind === "invalidJson" ? "pluginMulmoScript.saveErrorInvalidJson" : "pluginMulmoScript.saveErrorSaveFailed", {
|
|
268
|
+
error: beatSaveErrors[index].error,
|
|
269
|
+
})
|
|
270
|
+
}}</span>
|
|
267
271
|
<button
|
|
268
272
|
class="px-2 py-1 text-xs rounded border"
|
|
269
273
|
:class="
|
|
@@ -274,19 +278,19 @@
|
|
|
274
278
|
:disabled="!isValidBeat(index) || !!beatSaving[index]"
|
|
275
279
|
@click="updateBeat(index)"
|
|
276
280
|
>
|
|
277
|
-
{{ beatSaving[index] ? "
|
|
281
|
+
{{ beatSaving[index] ? t("pluginMulmoScript.saving") : t("pluginMulmoScript.update") }}
|
|
278
282
|
</button>
|
|
279
283
|
</div>
|
|
280
284
|
</div>
|
|
281
285
|
</div>
|
|
282
286
|
|
|
283
|
-
<div v-if="beats.length === 0" class="flex items-center justify-center h-32 text-gray-400 text-sm">
|
|
287
|
+
<div v-if="beats.length === 0" class="flex items-center justify-center h-32 text-gray-400 text-sm">{{ t("pluginMulmoScript.noBeats") }}</div>
|
|
284
288
|
</div>
|
|
285
289
|
|
|
286
290
|
<!-- Bottom bar: Edit Script Source + Copy -->
|
|
287
291
|
<div class="bottom-bar-wrapper">
|
|
288
292
|
<details ref="sourceDetails" class="script-source" @toggle="onSourceToggle(($event.target as HTMLDetailsElement).open)">
|
|
289
|
-
<summary>
|
|
293
|
+
<summary>{{ t("pluginMulmoScript.editSource") }}</summary>
|
|
290
294
|
<textarea
|
|
291
295
|
v-model="editableSource"
|
|
292
296
|
class="script-editor"
|
|
@@ -294,8 +298,8 @@
|
|
|
294
298
|
spellcheck="false"
|
|
295
299
|
></textarea>
|
|
296
300
|
<div class="editor-actions">
|
|
297
|
-
<button class="apply-btn" :disabled="!sourceChanged || !sourceValid" @click="applySource">
|
|
298
|
-
<button class="cancel-btn" @click="cancelSourceEdit">
|
|
301
|
+
<button class="apply-btn" :disabled="!sourceChanged || !sourceValid" @click="applySource">{{ t("pluginMulmoScript.applyChanges") }}</button>
|
|
302
|
+
<button class="cancel-btn" @click="cancelSourceEdit">{{ t("common.cancel") }}</button>
|
|
299
303
|
</div>
|
|
300
304
|
</details>
|
|
301
305
|
<button v-show="!editing" class="copy-btn" :title="copied ? 'Copied!' : 'Copy'" @click="copyText">
|
|
@@ -328,7 +332,7 @@
|
|
|
328
332
|
"
|
|
329
333
|
@click="playAudio(lightbox.index)"
|
|
330
334
|
>
|
|
331
|
-
{{ playingAudio?.index === lightbox.index ? "
|
|
335
|
+
{{ playingAudio?.index === lightbox.index ? t("pluginMulmoScript.stop") : t("pluginMulmoScript.play") }}
|
|
332
336
|
</button>
|
|
333
337
|
</div>
|
|
334
338
|
</div>
|
|
@@ -347,7 +351,10 @@
|
|
|
347
351
|
|
|
348
352
|
<script setup lang="ts">
|
|
349
353
|
import { computed, onMounted, reactive, ref, watch } from "vue";
|
|
354
|
+
import { useI18n } from "vue-i18n";
|
|
350
355
|
import type { ToolResultComplete } from "gui-chat-protocol/vue";
|
|
356
|
+
|
|
357
|
+
const { t } = useI18n();
|
|
351
358
|
import type { MulmoScriptData } from "./index";
|
|
352
359
|
import { mulmoBeatSchema, mulmoScriptSchema } from "@mulmocast/types";
|
|
353
360
|
import { extractErrorMessage, getMissingCharacterKeys, shouldAutoRenderBeat, streamMovieEvents, validateBeatJSON } from "./helpers";
|
|
@@ -407,7 +414,10 @@ const sourceOpen = reactive<Record<number, boolean>>({});
|
|
|
407
414
|
const sourceText = reactive<Record<number, string>>({});
|
|
408
415
|
// Surface POST /api/mulmo-script/update-beat failures inline next to
|
|
409
416
|
// the Update button. Cleared on next successful save or editor close.
|
|
410
|
-
|
|
417
|
+
// Store raw error + kind tag so the template picks a localized key,
|
|
418
|
+
// instead of pre-composing an English-prefixed string here.
|
|
419
|
+
type BeatSaveError = { kind: "invalidJson" | "saveFailed"; error: string };
|
|
420
|
+
const beatSaveErrors = reactive<Record<number, BeatSaveError>>({});
|
|
411
421
|
const beatSaving = reactive<Record<number, boolean>>({});
|
|
412
422
|
const localOverrides = reactive<Record<number, Beat>>({});
|
|
413
423
|
const movieGenerating = ref(false);
|
|
@@ -606,7 +616,7 @@ async function updateBeat(index: number) {
|
|
|
606
616
|
try {
|
|
607
617
|
beat = JSON.parse(sourceText[index]);
|
|
608
618
|
} catch (err) {
|
|
609
|
-
beatSaveErrors[index] =
|
|
619
|
+
beatSaveErrors[index] = { kind: "invalidJson", error: errorMessage(err) };
|
|
610
620
|
return;
|
|
611
621
|
}
|
|
612
622
|
const prevImage = JSON.stringify(effectiveBeat(index).image);
|
|
@@ -620,7 +630,7 @@ async function updateBeat(index: number) {
|
|
|
620
630
|
});
|
|
621
631
|
delete beatSaving[index];
|
|
622
632
|
if (!response.ok) {
|
|
623
|
-
beatSaveErrors[index] =
|
|
633
|
+
beatSaveErrors[index] = { kind: "saveFailed", error: response.error };
|
|
624
634
|
return;
|
|
625
635
|
}
|
|
626
636
|
|