mulmoclaude 0.1.2 → 0.4.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-Cv5snK9D.js +5 -0
- package/client/assets/index-CubzmCVK.css +2 -0
- package/client/assets/{index-D8rhwXLq.js → index-DtcyExH9.js} +80 -61
- package/client/assets/{index.es-D4YyL_Dg-BfRHLTZV.js → index.es-D4YyL_Dg-DnizuhIY.js} +5 -5
- package/client/index.html +2 -4
- package/package.json +13 -13
- package/server/agent/attachmentConverter.ts +2 -2
- package/server/agent/config.ts +12 -12
- package/server/agent/index.ts +9 -3
- package/server/agent/mcp-server.ts +19 -19
- package/server/agent/mcp-tools/index.ts +6 -6
- package/server/agent/mcp-tools/x.ts +7 -6
- package/server/agent/prompt.ts +195 -29
- package/server/agent/resumeFailover.ts +5 -5
- package/server/agent/sandboxMounts.ts +10 -10
- package/server/agent/stream.ts +4 -4
- package/server/api/auth/bearerAuth.ts +3 -3
- package/server/api/auth/token.ts +2 -2
- package/server/api/routes/agent.ts +21 -3
- package/server/api/routes/config.ts +1 -1
- package/server/api/routes/files.ts +22 -21
- package/server/api/routes/html.ts +2 -2
- package/server/api/routes/image.ts +7 -7
- package/server/api/routes/mulmo-script.ts +33 -31
- package/server/api/routes/pdf.ts +2 -2
- package/server/api/routes/plugins.ts +16 -6
- package/server/api/routes/roles.ts +2 -2
- package/server/api/routes/scheduler.ts +14 -12
- package/server/api/routes/schedulerHandlers.ts +12 -12
- package/server/api/routes/schedulerTasks.ts +19 -17
- package/server/api/routes/sessions.ts +26 -26
- package/server/api/routes/sessionsCursor.ts +4 -4
- package/server/api/routes/skills.ts +5 -5
- package/server/api/routes/sources.ts +3 -3
- package/server/api/routes/todosColumnsHandlers.ts +30 -30
- package/server/api/routes/todosHandlers.ts +1 -1
- package/server/api/routes/todosItemsHandlers.ts +14 -14
- package/server/api/routes/wiki.ts +36 -22
- package/server/api/sandboxStatus.ts +1 -1
- package/server/events/notifications.ts +6 -6
- package/server/events/pub-sub/index.ts +3 -3
- package/server/events/relay-client.ts +17 -16
- package/server/events/scheduler-adapter.ts +20 -20
- package/server/events/session-store/index.ts +10 -10
- package/server/events/task-manager/index.ts +7 -7
- package/server/index.ts +59 -65
- package/server/system/config.ts +5 -5
- package/server/system/credentials.ts +7 -5
- package/server/system/env.ts +5 -5
- package/server/utils/date.ts +18 -18
- package/server/utils/files/atomic.ts +16 -16
- package/server/utils/files/html-io.ts +5 -5
- package/server/utils/files/image-store.ts +19 -8
- package/server/utils/files/journal-io.ts +4 -4
- package/server/utils/files/json.ts +5 -5
- package/server/utils/files/markdown-store.ts +4 -4
- package/server/utils/files/naming.ts +2 -2
- package/server/utils/files/reference-dirs-io.ts +3 -3
- package/server/utils/files/roles-io.ts +12 -12
- package/server/utils/files/safe.ts +14 -14
- package/server/utils/files/scheduler-io.ts +5 -5
- package/server/utils/files/scheduler-overrides-io.ts +2 -2
- package/server/utils/files/session-io.ts +35 -35
- package/server/utils/files/spreadsheet-store.ts +7 -7
- package/server/utils/files/todos-io.ts +9 -9
- package/server/utils/files/user-tasks-io.ts +5 -5
- package/server/utils/files/workspace-io.ts +12 -12
- package/server/utils/gemini.ts +2 -2
- package/server/utils/gitignore.ts +9 -9
- package/server/utils/json.ts +5 -5
- package/server/utils/logBackgroundError.ts +12 -3
- 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/request.ts +12 -6
- package/server/utils/spawn.ts +1 -1
- package/server/utils/types.ts +2 -2
- package/server/workspace/chat-index/indexer.ts +15 -15
- package/server/workspace/chat-index/summarizer.ts +4 -4
- package/server/workspace/custom-dirs.ts +16 -16
- package/server/workspace/journal/archivist.ts +35 -35
- package/server/workspace/journal/dailyPass.ts +31 -28
- package/server/workspace/journal/diff.ts +2 -2
- package/server/workspace/journal/index.ts +4 -4
- package/server/workspace/journal/indexFile.ts +29 -25
- package/server/workspace/journal/optimizationPass.ts +2 -2
- package/server/workspace/journal/state.ts +6 -6
- package/server/workspace/paths.ts +3 -3
- package/server/workspace/reference-dirs.ts +20 -20
- package/server/workspace/roles.ts +6 -6
- package/server/workspace/skills/discovery.ts +4 -4
- package/server/workspace/skills/parser.ts +6 -6
- package/server/workspace/skills/scheduler.ts +3 -3
- package/server/workspace/skills/user-tasks.ts +34 -34
- package/server/workspace/skills/writer.ts +3 -3
- package/server/workspace/sources/arxivDiscovery.ts +10 -10
- package/server/workspace/sources/classifier.ts +7 -7
- package/server/workspace/sources/fetchers/arxiv.ts +7 -7
- package/server/workspace/sources/fetchers/githubIssues.ts +7 -7
- package/server/workspace/sources/fetchers/githubReleases.ts +7 -7
- package/server/workspace/sources/fetchers/rss.ts +5 -5
- package/server/workspace/sources/fetchers/rssParser.ts +4 -4
- package/server/workspace/sources/interests.ts +12 -12
- package/server/workspace/sources/paths.ts +6 -6
- package/server/workspace/sources/pipeline/fetch.ts +36 -13
- package/server/workspace/sources/pipeline/index.ts +8 -13
- package/server/workspace/sources/pipeline/notify.ts +3 -3
- package/server/workspace/sources/pipeline/plan.ts +15 -13
- package/server/workspace/sources/pipeline/write.ts +5 -5
- package/server/workspace/sources/rateLimiter.ts +1 -1
- package/server/workspace/sources/registry.ts +16 -16
- package/server/workspace/sources/robots.ts +14 -14
- package/server/workspace/sources/sourceState.ts +17 -10
- 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/tool-trace/index.ts +1 -1
- package/server/workspace/tool-trace/writeSearch.ts +26 -16
- package/server/workspace/wiki-backlinks/index.ts +8 -8
- package/server/workspace/wiki-backlinks/sessionBacklinks.ts +15 -15
- package/server/workspace/workspace.ts +7 -7
- package/src/App.vue +315 -141
- package/src/components/CanvasViewToggle.vue +10 -7
- package/src/components/ChatInput.vue +67 -33
- package/src/components/FileContentHeader.vue +7 -4
- package/src/components/FileContentRenderer.vue +20 -6
- package/src/components/FileTree.vue +6 -3
- package/src/components/FileTreePane.vue +11 -8
- package/src/components/FilesView.vue +5 -3
- package/src/components/LockStatusPopup.vue +17 -14
- package/src/components/NotificationBell.vue +14 -5
- package/src/components/NotificationToast.vue +6 -3
- package/src/components/PluginLauncher.vue +19 -56
- package/src/components/RightSidebar.vue +13 -10
- package/src/components/RoleSelector.vue +2 -2
- package/src/components/SessionHistoryPanel.vue +38 -34
- package/src/components/SessionTabBar.vue +8 -10
- package/src/components/SettingsMcpTab.vue +49 -36
- package/src/components/SettingsModal.vue +24 -22
- package/src/components/SettingsReferenceDirsTab.vue +39 -34
- package/src/components/SettingsWorkspaceDirsTab.vue +37 -27
- package/src/components/SidebarHeader.vue +25 -4
- package/src/components/StackView.vue +4 -1
- package/src/components/SuggestionsPanel.vue +7 -4
- package/src/components/TodoExplorer.vue +26 -15
- package/src/components/ToolResultsPanel.vue +27 -13
- package/src/components/todo/TodoAddDialog.vue +19 -14
- package/src/components/todo/TodoEditDialog.vue +7 -2
- package/src/components/todo/TodoEditPanel.vue +17 -12
- package/src/components/todo/TodoKanbanView.vue +10 -5
- package/src/components/todo/TodoListView.vue +10 -7
- package/src/components/todo/TodoTableView.vue +5 -2
- package/src/composables/useAppApi.ts +9 -0
- package/src/composables/useClickOutside.ts +2 -2
- package/src/composables/useDynamicFavicon.ts +172 -37
- package/src/composables/useEventListeners.ts +7 -8
- package/src/composables/useFaviconState.ts +13 -2
- package/src/composables/useFileSelection.ts +24 -6
- package/src/composables/useFreshPluginData.ts +3 -3
- package/src/composables/useKeyNavigation.ts +11 -11
- package/src/composables/useLayoutMode.ts +32 -0
- package/src/composables/useMcpTools.ts +2 -2
- package/src/composables/useNotifications.ts +3 -3
- package/src/composables/usePdfDownload.ts +4 -4
- package/src/composables/usePendingCalls.ts +1 -1
- package/src/composables/usePubSub.ts +10 -10
- package/src/composables/useRoles.ts +1 -1
- package/src/composables/useSandboxStatus.ts +1 -1
- package/src/composables/useSessionDerived.ts +3 -3
- package/src/composables/useSessionHistory.ts +7 -17
- package/src/composables/useSessionSync.ts +8 -8
- package/src/composables/useViewLayout.ts +20 -34
- package/src/config/roles.ts +2 -2
- package/src/lang/de.ts +536 -0
- package/src/lang/en.ts +558 -0
- package/src/lang/es.ts +543 -0
- package/src/lang/fr.ts +536 -0
- package/src/lang/ja.ts +536 -0
- package/src/lang/ko.ts +540 -0
- package/src/lang/pt-BR.ts +534 -0
- package/src/lang/zh.ts +537 -0
- package/src/lib/vue-i18n.ts +97 -0
- package/src/main.ts +2 -0
- package/src/plugins/canvas/View.vue +102 -186
- package/src/plugins/canvas/definition.ts +0 -8
- package/src/plugins/chart/Preview.vue +5 -5
- package/src/plugins/chart/View.vue +9 -4
- package/src/plugins/manageRoles/Preview.vue +4 -1
- package/src/plugins/manageRoles/View.vue +59 -43
- package/src/plugins/manageSkills/Preview.vue +8 -3
- package/src/plugins/manageSkills/View.vue +29 -25
- package/src/plugins/manageSource/Preview.vue +2 -2
- package/src/plugins/manageSource/View.vue +73 -52
- package/src/plugins/markdown/Preview.vue +1 -1
- package/src/plugins/markdown/View.vue +26 -36
- package/src/plugins/presentHtml/Preview.vue +1 -1
- package/src/plugins/presentHtml/View.vue +7 -4
- package/src/plugins/presentHtml/helpers.ts +8 -8
- package/src/plugins/presentMulmoScript/Preview.vue +1 -1
- package/src/plugins/presentMulmoScript/View.vue +40 -30
- package/src/plugins/presentMulmoScript/helpers.ts +1 -1
- package/src/plugins/scheduler/Preview.vue +13 -10
- package/src/plugins/scheduler/TasksTab.vue +57 -28
- package/src/plugins/scheduler/View.vue +28 -19
- package/src/plugins/scheduler/formatSchedule.ts +93 -0
- package/src/plugins/spreadsheet/Preview.vue +8 -3
- package/src/plugins/spreadsheet/View.vue +21 -12
- package/src/plugins/textResponse/Preview.vue +15 -58
- package/src/plugins/textResponse/View.vue +29 -9
- package/src/plugins/todo/Preview.vue +13 -8
- package/src/plugins/todo/View.vue +38 -24
- package/src/plugins/todo/composables/useTodos.ts +5 -5
- package/src/plugins/ui-image/ImagePreview.vue +6 -3
- package/src/plugins/ui-image/ImageView.vue +7 -4
- package/src/plugins/wiki/Preview.vue +10 -7
- package/src/plugins/wiki/View.vue +202 -81
- package/src/plugins/wiki/helpers.ts +4 -4
- package/src/plugins/wiki/route.ts +112 -0
- package/src/router/guards.ts +46 -28
- package/src/router/index.ts +41 -26
- package/src/types/session.ts +4 -3
- package/src/types/vue-i18n.d.ts +20 -0
- package/src/utils/agent/request.ts +22 -3
- package/src/utils/canvas/layoutMode.ts +26 -0
- package/src/utils/dom/scrollable.ts +2 -2
- package/src/utils/files/expandedDirs.ts +1 -1
- package/src/utils/files/sortChildren.ts +6 -6
- package/src/utils/format/frontmatter.ts +6 -6
- package/src/utils/image/cacheBust.ts +16 -0
- package/src/utils/image/resolve.ts +16 -0
- package/src/utils/image/rewriteMarkdownImageRefs.ts +5 -5
- package/src/utils/markdown/extractFirstH1.ts +2 -2
- package/src/utils/path/relativeLink.ts +15 -15
- package/src/utils/path/workspaceLinkRouter.ts +81 -0
- package/src/utils/role/icon.ts +2 -2
- package/src/utils/role/merge.ts +2 -2
- package/src/utils/role/plugins.ts +1 -1
- package/src/utils/session/sessionFactory.ts +2 -2
- package/src/utils/session/sessionHelpers.ts +2 -2
- package/src/utils/tools/dedup.ts +4 -4
- package/src/utils/tools/result.ts +3 -3
- package/src/utils/types.ts +2 -2
- 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-KNLBjwuh.css +0 -1
- package/client/assets/typeof-DBp4T-Ny-BC0P-2DM.js +0 -1
- package/src/composables/useCanvasViewMode.ts +0 -121
- package/src/utils/canvas/viewMode.ts +0 -46
- /package/client/assets/{purify.es-Fx1Nqyry-PeS5RUhs.js → purify.es-Fx1Nqyry-BwJECkqS.js} +0 -0
|
@@ -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);
|
|
@@ -431,7 +441,7 @@ const charErrors = reactive<Record<string, string>>({});
|
|
|
431
441
|
const charDragOver = reactive<Record<string, boolean>>({});
|
|
432
442
|
const beatDragOver = reactive<Record<number, boolean>>({});
|
|
433
443
|
|
|
434
|
-
const anyBeatRendering = computed(() => Object.values(renderState).some((
|
|
444
|
+
const anyBeatRendering = computed(() => Object.values(renderState).some((state) => state === "rendering"));
|
|
435
445
|
|
|
436
446
|
const characterKeys = computed(() => {
|
|
437
447
|
const imgs = script.value.imageParams?.images ?? {};
|
|
@@ -447,10 +457,10 @@ const chatSessionId = computed(() => activeSessionRef?.value?.id);
|
|
|
447
457
|
const pendingForThisScript = computed(() => {
|
|
448
458
|
const out: Record<string, PendingGeneration> = {};
|
|
449
459
|
const pending = activeSessionRef?.value?.pendingGenerations ?? {};
|
|
450
|
-
const
|
|
451
|
-
if (!
|
|
460
|
+
const currentPath = filePath.value;
|
|
461
|
+
if (!currentPath) return out;
|
|
452
462
|
for (const [mapKey, entry] of Object.entries(pending)) {
|
|
453
|
-
if (entry.filePath ===
|
|
463
|
+
if (entry.filePath === currentPath) out[mapKey] = entry;
|
|
454
464
|
}
|
|
455
465
|
return out;
|
|
456
466
|
});
|
|
@@ -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
|
|
|
@@ -63,7 +63,7 @@ export function shouldAutoRenderBeat(beat: { image?: { type?: string } | undefin
|
|
|
63
63
|
* what's missing after a movie-generation event arrives.
|
|
64
64
|
*/
|
|
65
65
|
export function getMissingCharacterKeys(keys: readonly string[], images: Record<string, unknown>, renderState: Record<string, string | undefined>): string[] {
|
|
66
|
-
return keys.filter((
|
|
66
|
+
return keys.filter((charKey) => !images[charKey] && renderState[charKey] !== "rendering");
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
/**
|
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="text-sm">
|
|
2
|
+
<div class="p-2 text-sm">
|
|
3
3
|
<div class="flex items-center gap-1 font-medium text-gray-700 mb-1">
|
|
4
|
-
<span
|
|
5
|
-
<span>{{ upcomingItems.length }}
|
|
4
|
+
<span aria-hidden="true">{{ t("pluginScheduler.previewIcon") }}</span>
|
|
5
|
+
<span>{{ t("pluginScheduler.previewUpcoming", { count: upcomingItems.length }) }}</span>
|
|
6
6
|
</div>
|
|
7
7
|
<div v-for="item in preview" :key="item.id" class="text-xs truncate text-gray-600">
|
|
8
8
|
<span v-if="item.props.date" class="text-gray-400 mr-1">{{ item.props.date }}</span>
|
|
9
9
|
{{ item.title }}
|
|
10
10
|
</div>
|
|
11
|
-
<div v-if="more > 0" class="text-xs text-gray-400"
|
|
11
|
+
<div v-if="more > 0" class="text-xs text-gray-400">{{ t("pluginScheduler.previewMore", { count: more }) }}</div>
|
|
12
12
|
</div>
|
|
13
13
|
</template>
|
|
14
14
|
|
|
15
15
|
<script setup lang="ts">
|
|
16
16
|
import { computed, ref, watch } from "vue";
|
|
17
|
+
import { useI18n } from "vue-i18n";
|
|
17
18
|
import type { ToolResultComplete } from "gui-chat-protocol/vue";
|
|
18
19
|
import type { SchedulerData, ScheduledItem } from "./index";
|
|
19
20
|
import { useFreshPluginData } from "../../composables/useFreshPluginData";
|
|
20
21
|
import { API_ROUTES } from "../../config/apiRoutes";
|
|
21
22
|
|
|
23
|
+
const { t } = useI18n();
|
|
24
|
+
|
|
22
25
|
const props = defineProps<{ result: ToolResultComplete<SchedulerData> }>();
|
|
23
26
|
|
|
24
27
|
const items = ref<ScheduledItem[]>(props.result.data?.items ?? []);
|
|
@@ -26,8 +29,8 @@ const items = ref<ScheduledItem[]>(props.result.data?.items ?? []);
|
|
|
26
29
|
const { refresh } = useFreshPluginData<ScheduledItem[]>({
|
|
27
30
|
endpoint: () => API_ROUTES.scheduler.base,
|
|
28
31
|
extract: (json) => {
|
|
29
|
-
const
|
|
30
|
-
return Array.isArray(
|
|
32
|
+
const extracted = (json as { data?: { items?: ScheduledItem[] } }).data?.items;
|
|
33
|
+
return Array.isArray(extracted) ? extracted : null;
|
|
31
34
|
},
|
|
32
35
|
apply: (data) => {
|
|
33
36
|
items.value = data;
|
|
@@ -49,15 +52,15 @@ const upcomingItems = computed(() => {
|
|
|
49
52
|
const noDate: ScheduledItem[] = [];
|
|
50
53
|
|
|
51
54
|
for (const item of items.value) {
|
|
52
|
-
const
|
|
53
|
-
if (typeof
|
|
54
|
-
if (
|
|
55
|
+
const dateVal = item.props.date;
|
|
56
|
+
if (typeof dateVal === "string") {
|
|
57
|
+
if (dateVal >= today) withDate.push(item);
|
|
55
58
|
} else {
|
|
56
59
|
noDate.push(item);
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
62
|
|
|
60
|
-
withDate.sort((
|
|
63
|
+
withDate.sort((itemA, itemB) => (String(itemA.props.date) < String(itemB.props.date) ? -1 : 1));
|
|
61
64
|
|
|
62
65
|
return [...withDate, ...noDate];
|
|
63
66
|
});
|
|
@@ -6,16 +6,37 @@
|
|
|
6
6
|
</div>
|
|
7
7
|
|
|
8
8
|
<!-- Loading -->
|
|
9
|
-
<div v-if="loading" class="flex items-center justify-center h-32 text-gray-400">
|
|
9
|
+
<div v-if="loading" class="flex items-center justify-center h-32 text-gray-400">{{ t("common.loading") }}</div>
|
|
10
10
|
|
|
11
11
|
<!-- Error -->
|
|
12
12
|
<div v-else-if="error" class="px-4 py-2 bg-red-50 text-red-700 rounded text-sm">
|
|
13
13
|
{{ error }}
|
|
14
14
|
</div>
|
|
15
15
|
|
|
16
|
-
<!-- Task list -->
|
|
16
|
+
<!-- Task list + frequency hints -->
|
|
17
17
|
<div v-else>
|
|
18
|
-
|
|
18
|
+
<!-- Frequency hints reference -->
|
|
19
|
+
<details class="mb-4 border border-gray-200 rounded-lg text-sm" data-testid="scheduler-frequency-hints">
|
|
20
|
+
<summary class="px-3 py-2 cursor-pointer text-gray-600 font-medium select-none hover:bg-gray-50 rounded-lg">
|
|
21
|
+
{{ t("pluginSchedulerTasks.recommendedFrequencies") }}
|
|
22
|
+
</summary>
|
|
23
|
+
<table class="w-full mt-1 mb-2 text-xs text-gray-500">
|
|
24
|
+
<thead>
|
|
25
|
+
<tr class="border-b border-gray-100">
|
|
26
|
+
<th class="px-3 py-1 text-left font-medium text-gray-600">{{ t("pluginSchedulerTasks.tableTaskType") }}</th>
|
|
27
|
+
<th class="px-3 py-1 text-left font-medium text-gray-600">{{ t("pluginSchedulerTasks.tableSuggestedSchedule") }}</th>
|
|
28
|
+
</tr>
|
|
29
|
+
</thead>
|
|
30
|
+
<tbody>
|
|
31
|
+
<tr v-for="hint in FREQUENCY_HINTS" :key="hint.label" class="border-b border-gray-50 last:border-0">
|
|
32
|
+
<td class="px-3 py-1">{{ hint.label }}</td>
|
|
33
|
+
<td class="px-3 py-1 font-mono text-gray-700">{{ formatSchedule(hint.schedule) }}</td>
|
|
34
|
+
</tr>
|
|
35
|
+
</tbody>
|
|
36
|
+
</table>
|
|
37
|
+
</details>
|
|
38
|
+
|
|
39
|
+
<div v-if="tasks.length === 0" class="flex items-center justify-center h-32 text-gray-400">{{ t("pluginSchedulerTasks.noTasks") }}</div>
|
|
19
40
|
|
|
20
41
|
<div v-else class="space-y-2">
|
|
21
42
|
<div
|
|
@@ -40,8 +61,8 @@
|
|
|
40
61
|
<button
|
|
41
62
|
v-if="task.origin === 'user'"
|
|
42
63
|
class="px-2 py-1 text-xs text-blue-600 hover:bg-blue-50 rounded"
|
|
43
|
-
title="
|
|
44
|
-
aria-label="
|
|
64
|
+
:title="t('pluginSchedulerTasks.runNow')"
|
|
65
|
+
:aria-label="t('pluginSchedulerTasks.runNow')"
|
|
45
66
|
data-testid="scheduler-task-run"
|
|
46
67
|
@click="runTask(task.id)"
|
|
47
68
|
>
|
|
@@ -52,7 +73,7 @@
|
|
|
52
73
|
v-if="task.origin === 'user'"
|
|
53
74
|
class="px-2 py-1 text-xs rounded"
|
|
54
75
|
:class="task.enabled !== false ? 'text-green-600 hover:bg-green-50' : 'text-gray-400 hover:bg-gray-100'"
|
|
55
|
-
:title="task.enabled !== false ? '
|
|
76
|
+
:title="task.enabled !== false ? t('pluginSchedulerTasks.disable') : t('pluginSchedulerTasks.enable')"
|
|
56
77
|
@click="toggleEnabled(task)"
|
|
57
78
|
>
|
|
58
79
|
<span class="material-icons text-sm">
|
|
@@ -63,8 +84,8 @@
|
|
|
63
84
|
<button
|
|
64
85
|
v-if="task.origin === 'user'"
|
|
65
86
|
class="px-2 py-1 text-xs text-red-500 hover:bg-red-50 rounded"
|
|
66
|
-
title="
|
|
67
|
-
aria-label="
|
|
87
|
+
:title="t('pluginSchedulerTasks.delete')"
|
|
88
|
+
:aria-label="t('pluginSchedulerTasks.delete')"
|
|
68
89
|
data-testid="scheduler-task-delete"
|
|
69
90
|
@click="deleteTask(task.id)"
|
|
70
91
|
>
|
|
@@ -80,7 +101,7 @@
|
|
|
80
101
|
<span class="inline-block w-2 h-2 rounded-full" :class="resultDotClass(task.state.lastRunResult)"></span>
|
|
81
102
|
{{ task.state.lastRunResult }}
|
|
82
103
|
</span>
|
|
83
|
-
<span v-if="task.state?.nextScheduledAt">
|
|
104
|
+
<span v-if="task.state?.nextScheduledAt">{{ t("pluginSchedulerTasks.nextRun", { time: formatShortTime(task.state.nextScheduledAt) }) }}</span>
|
|
84
105
|
</div>
|
|
85
106
|
|
|
86
107
|
<!-- Description -->
|
|
@@ -95,9 +116,13 @@
|
|
|
95
116
|
|
|
96
117
|
<script setup lang="ts">
|
|
97
118
|
import { ref, onMounted } from "vue";
|
|
119
|
+
import { useI18n } from "vue-i18n";
|
|
98
120
|
import { apiGet, apiPost, apiPut, apiDelete } from "../../utils/api";
|
|
99
121
|
import { API_ROUTES } from "../../config/apiRoutes";
|
|
100
122
|
import { formatShortTime } from "../../utils/format/date";
|
|
123
|
+
import { formatSchedule as formatTaskSchedule, type TaskSchedule as FormatterTaskSchedule } from "./formatSchedule";
|
|
124
|
+
|
|
125
|
+
const { t } = useI18n();
|
|
101
126
|
|
|
102
127
|
interface TaskSchedule {
|
|
103
128
|
type: string;
|
|
@@ -121,6 +146,18 @@ interface SchedulerTask {
|
|
|
121
146
|
state?: TaskState;
|
|
122
147
|
}
|
|
123
148
|
|
|
149
|
+
// Hints showing common task cadences. Stored as structured schedules
|
|
150
|
+
// (not pre-rendered strings) so the display routes through
|
|
151
|
+
// formatTaskSchedule() and picks up the viewer's local timezone for
|
|
152
|
+
// daily rows — the same conversion applied to real tasks below.
|
|
153
|
+
const FREQUENCY_HINTS: Array<{ label: string; schedule: FormatterTaskSchedule }> = [
|
|
154
|
+
{ label: "News / RSS fetch", schedule: { type: "interval", intervalMs: 3_600_000 } },
|
|
155
|
+
{ label: "Journal daily pass", schedule: { type: "daily", time: "23:00" } },
|
|
156
|
+
{ label: "Wiki maintenance", schedule: { type: "daily", time: "02:00" } },
|
|
157
|
+
{ label: "Memory extraction", schedule: { type: "daily", time: "00:00" } },
|
|
158
|
+
{ label: "Calendar / contact sync", schedule: { type: "interval", intervalMs: 14_400_000 } },
|
|
159
|
+
];
|
|
160
|
+
|
|
124
161
|
const tasks = ref<SchedulerTask[]>([]);
|
|
125
162
|
const loading = ref(true);
|
|
126
163
|
const error = ref("");
|
|
@@ -139,9 +176,9 @@ async function fetchTasks(): Promise<void> {
|
|
|
139
176
|
}
|
|
140
177
|
|
|
141
178
|
function originLabel(origin: string): string {
|
|
142
|
-
if (origin === "system") return "
|
|
143
|
-
if (origin === "user") return "
|
|
144
|
-
return "
|
|
179
|
+
if (origin === "system") return t("pluginSchedulerTasks.originSystem");
|
|
180
|
+
if (origin === "user") return t("pluginSchedulerTasks.originUser");
|
|
181
|
+
return t("pluginSchedulerTasks.originSkill");
|
|
145
182
|
}
|
|
146
183
|
|
|
147
184
|
function originClass(origin: string): string {
|
|
@@ -157,23 +194,15 @@ function resultDotClass(result: string): string {
|
|
|
157
194
|
}
|
|
158
195
|
|
|
159
196
|
function formatSchedule(schedule: TaskSchedule): string {
|
|
160
|
-
|
|
161
|
-
const mins = Math.round(schedule.intervalMs / 60000);
|
|
162
|
-
if (mins >= 60) return `Every ${Math.round(mins / 60)}h`;
|
|
163
|
-
return `Every ${mins}m`;
|
|
164
|
-
}
|
|
165
|
-
if (schedule.type === "daily" && schedule.time) {
|
|
166
|
-
return `Daily ${schedule.time} UTC`;
|
|
167
|
-
}
|
|
168
|
-
return JSON.stringify(schedule);
|
|
197
|
+
return formatTaskSchedule(schedule as FormatterTaskSchedule);
|
|
169
198
|
}
|
|
170
199
|
|
|
171
|
-
async function runTask(
|
|
200
|
+
async function runTask(taskId: string): Promise<void> {
|
|
172
201
|
mutationError.value = "";
|
|
173
|
-
const url = API_ROUTES.scheduler.taskRun.replace(":id",
|
|
202
|
+
const url = API_ROUTES.scheduler.taskRun.replace(":id", taskId);
|
|
174
203
|
const result = await apiPost(url, {});
|
|
175
204
|
if (!result.ok) {
|
|
176
|
-
mutationError.value =
|
|
205
|
+
mutationError.value = t("pluginSchedulerTasks.runFailed", { error: result.error });
|
|
177
206
|
return;
|
|
178
207
|
}
|
|
179
208
|
await fetchTasks();
|
|
@@ -184,18 +213,18 @@ async function toggleEnabled(task: SchedulerTask): Promise<void> {
|
|
|
184
213
|
const url = API_ROUTES.scheduler.task.replace(":id", task.id);
|
|
185
214
|
const result = await apiPut(url, { enabled: task.enabled === false });
|
|
186
215
|
if (!result.ok) {
|
|
187
|
-
mutationError.value =
|
|
216
|
+
mutationError.value = t("pluginSchedulerTasks.toggleFailed", { error: result.error });
|
|
188
217
|
return;
|
|
189
218
|
}
|
|
190
219
|
await fetchTasks();
|
|
191
220
|
}
|
|
192
221
|
|
|
193
|
-
async function deleteTask(
|
|
222
|
+
async function deleteTask(taskId: string): Promise<void> {
|
|
194
223
|
mutationError.value = "";
|
|
195
|
-
const url = API_ROUTES.scheduler.task.replace(":id",
|
|
224
|
+
const url = API_ROUTES.scheduler.task.replace(":id", taskId);
|
|
196
225
|
const result = await apiDelete(url);
|
|
197
226
|
if (!result.ok) {
|
|
198
|
-
mutationError.value =
|
|
227
|
+
mutationError.value = t("pluginSchedulerTasks.deleteFailed", { error: result.error });
|
|
199
228
|
return;
|
|
200
229
|
}
|
|
201
230
|
await fetchTasks();
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<!-- API error banner — surfaces POST /api/scheduler failures so a
|
|
4
4
|
delete/add/replace that silently no-ops becomes diagnosable. -->
|
|
5
5
|
<div v-if="apiError" class="px-4 py-2 bg-red-50 border-b border-red-200 text-sm text-red-700" role="alert" data-testid="scheduler-api-error">
|
|
6
|
-
|
|
6
|
+
{{ t("pluginScheduler.apiError", { error: apiError }) }}
|
|
7
7
|
</div>
|
|
8
8
|
<!-- Top-level tab bar: Calendar / Tasks -->
|
|
9
9
|
<div class="flex border-b border-gray-200 px-6">
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
data-testid="scheduler-tab-calendar"
|
|
14
14
|
@click="activeTab = SCHEDULER_TAB.calendar"
|
|
15
15
|
>
|
|
16
|
-
|
|
16
|
+
{{ t("pluginScheduler.tabCalendar") }}
|
|
17
17
|
</button>
|
|
18
18
|
<button
|
|
19
19
|
class="px-4 py-2 text-sm font-medium border-b-2 -mb-px"
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
data-testid="scheduler-tab-tasks"
|
|
22
22
|
@click="activeTab = SCHEDULER_TAB.tasks"
|
|
23
23
|
>
|
|
24
|
-
|
|
24
|
+
{{ t("pluginScheduler.tabTasks") }}
|
|
25
25
|
</button>
|
|
26
26
|
</div>
|
|
27
27
|
|
|
@@ -33,17 +33,19 @@
|
|
|
33
33
|
<!-- Header -->
|
|
34
34
|
<div class="flex items-center justify-between px-6 py-3 border-b border-gray-100">
|
|
35
35
|
<div class="flex items-center gap-3">
|
|
36
|
-
<h2 class="text-lg font-semibold text-gray-800">
|
|
37
|
-
<span class="text-sm text-gray-500">{{ items.length
|
|
36
|
+
<h2 class="text-lg font-semibold text-gray-800">{{ t("pluginScheduler.heading") }}</h2>
|
|
37
|
+
<span class="text-sm text-gray-500">{{ t("pluginScheduler.itemCount", items.length, { named: { count: items.length } }) }}</span>
|
|
38
38
|
</div>
|
|
39
39
|
<div class="flex items-center gap-2">
|
|
40
40
|
<!-- Navigation (calendar modes only) -->
|
|
41
41
|
<template v-if="viewMode !== SCHEDULER_VIEW.list">
|
|
42
|
-
<button class="px-2 py-1 text-sm text-gray-500 hover:text-gray-800 hover:bg-gray-100 rounded" title="
|
|
42
|
+
<button class="px-2 py-1 text-sm text-gray-500 hover:text-gray-800 hover:bg-gray-100 rounded" :title="t('pluginScheduler.prev')" @click="goPrev">
|
|
43
43
|
<span class="material-icons text-sm">chevron_left</span>
|
|
44
44
|
</button>
|
|
45
|
-
<button class="px-2 py-1 text-xs text-gray-600 hover:bg-gray-100 rounded" title="
|
|
46
|
-
|
|
45
|
+
<button class="px-2 py-1 text-xs text-gray-600 hover:bg-gray-100 rounded" :title="t('pluginScheduler.goToday')" @click="goToday">
|
|
46
|
+
{{ t("pluginScheduler.today") }}
|
|
47
|
+
</button>
|
|
48
|
+
<button class="px-2 py-1 text-sm text-gray-500 hover:text-gray-800 hover:bg-gray-100 rounded" :title="t('pluginScheduler.next')" @click="goNext">
|
|
47
49
|
<span class="material-icons text-sm">chevron_right</span>
|
|
48
50
|
</button>
|
|
49
51
|
<span class="text-sm text-gray-600 min-w-[140px] text-center">{{ headerLabel }}</span>
|
|
@@ -66,7 +68,7 @@
|
|
|
66
68
|
|
|
67
69
|
<!-- List view -->
|
|
68
70
|
<div v-if="viewMode === SCHEDULER_VIEW.list" class="flex-1 overflow-y-auto min-h-0">
|
|
69
|
-
<div v-if="items.length === 0" class="flex items-center justify-center h-full text-gray-400">
|
|
71
|
+
<div v-if="items.length === 0" class="flex items-center justify-center h-full text-gray-400">{{ t("pluginScheduler.noScheduled") }}</div>
|
|
70
72
|
|
|
71
73
|
<ul v-else class="p-4 space-y-2">
|
|
72
74
|
<li
|
|
@@ -86,14 +88,14 @@
|
|
|
86
88
|
:key="key"
|
|
87
89
|
class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-gray-100 text-xs text-gray-600"
|
|
88
90
|
>
|
|
89
|
-
<span class="text-gray-400">{{ key }}
|
|
91
|
+
<span class="text-gray-400">{{ t("pluginScheduler.propLabel", { key }) }}</span>
|
|
90
92
|
<span>{{ val }}</span>
|
|
91
93
|
</span>
|
|
92
94
|
</div>
|
|
93
95
|
</div>
|
|
94
96
|
<button
|
|
95
97
|
class="opacity-0 group-hover:opacity-100 text-gray-400 hover:text-red-500 text-xs px-1 mt-0.5 shrink-0"
|
|
96
|
-
title="
|
|
98
|
+
:title="t('pluginScheduler.deleteItem')"
|
|
97
99
|
@click.stop="remove(item)"
|
|
98
100
|
>
|
|
99
101
|
✕
|
|
@@ -133,7 +135,7 @@
|
|
|
133
135
|
</div>
|
|
134
136
|
<!-- Unscheduled -->
|
|
135
137
|
<div v-if="unscheduledItems.length > 0" class="p-3 border-t border-gray-200">
|
|
136
|
-
<div class="text-xs text-gray-400 mb-1.5">
|
|
138
|
+
<div class="text-xs text-gray-400 mb-1.5">{{ t("pluginScheduler.unscheduled") }}</div>
|
|
137
139
|
<div class="flex flex-wrap gap-1">
|
|
138
140
|
<div
|
|
139
141
|
v-for="item in unscheduledItems"
|
|
@@ -179,14 +181,14 @@
|
|
|
179
181
|
{{ item.title }}
|
|
180
182
|
</div>
|
|
181
183
|
<div v-if="itemsForDay(day).length > MAX_MONTH_ITEMS" class="text-[10px] text-gray-400 px-1">
|
|
182
|
-
|
|
184
|
+
{{ t("pluginScheduler.moreCount", { count: itemsForDay(day).length - MAX_MONTH_ITEMS }) }}
|
|
183
185
|
</div>
|
|
184
186
|
</div>
|
|
185
187
|
</div>
|
|
186
188
|
</div>
|
|
187
189
|
<!-- Unscheduled -->
|
|
188
190
|
<div v-if="unscheduledItems.length > 0" class="p-3 border-t border-gray-200">
|
|
189
|
-
<div class="text-xs text-gray-400 mb-1.5">
|
|
191
|
+
<div class="text-xs text-gray-400 mb-1.5">{{ t("pluginScheduler.unscheduled") }}</div>
|
|
190
192
|
<div class="flex flex-wrap gap-1">
|
|
191
193
|
<div
|
|
192
194
|
v-for="item in unscheduledItems"
|
|
@@ -204,8 +206,8 @@
|
|
|
204
206
|
<!-- Item YAML editor -->
|
|
205
207
|
<div v-if="selectedId" class="border-t border-blue-200 bg-blue-50 shrink-0">
|
|
206
208
|
<div class="flex items-center justify-between px-4 py-2 text-sm font-medium text-blue-700">
|
|
207
|
-
<span>
|
|
208
|
-
<button class="text-blue-400 hover:text-blue-600 text-xs" title="
|
|
209
|
+
<span>{{ t("pluginScheduler.editItem") }}</span>
|
|
210
|
+
<button class="text-blue-400 hover:text-blue-600 text-xs" :title="t('pluginScheduler.closeEditor')" @click="selectedId = null">✕</button>
|
|
209
211
|
</div>
|
|
210
212
|
<div class="px-3 pb-3">
|
|
211
213
|
<textarea
|
|
@@ -214,7 +216,9 @@
|
|
|
214
216
|
spellcheck="false"
|
|
215
217
|
/>
|
|
216
218
|
<div class="flex items-center gap-2 mt-2">
|
|
217
|
-
<button class="px-3 py-1.5 text-sm rounded bg-blue-500 text-white hover:bg-blue-600" @click="applyItemEdit">
|
|
219
|
+
<button class="px-3 py-1.5 text-sm rounded bg-blue-500 text-white hover:bg-blue-600" @click="applyItemEdit">
|
|
220
|
+
{{ t("pluginScheduler.update") }}
|
|
221
|
+
</button>
|
|
218
222
|
<span v-if="yamlError" class="text-xs text-red-500">{{ yamlError }}</span>
|
|
219
223
|
</div>
|
|
220
224
|
</div>
|
|
@@ -222,7 +226,9 @@
|
|
|
222
226
|
|
|
223
227
|
<!-- JSON source editor -->
|
|
224
228
|
<details class="border-t border-gray-200 bg-gray-50 shrink-0">
|
|
225
|
-
<summary class="cursor-pointer select-none px-4 py-2 text-sm font-medium text-gray-600 hover:bg-gray-100">
|
|
229
|
+
<summary class="cursor-pointer select-none px-4 py-2 text-sm font-medium text-gray-600 hover:bg-gray-100">
|
|
230
|
+
{{ t("pluginScheduler.editSource") }}
|
|
231
|
+
</summary>
|
|
226
232
|
<div class="p-3">
|
|
227
233
|
<textarea
|
|
228
234
|
v-model="editorText"
|
|
@@ -235,7 +241,7 @@
|
|
|
235
241
|
class="px-3 py-1.5 text-sm rounded bg-blue-500 text-white hover:bg-blue-600 disabled:bg-gray-200 disabled:text-gray-400 disabled:cursor-not-allowed"
|
|
236
242
|
@click="applyChanges"
|
|
237
243
|
>
|
|
238
|
-
|
|
244
|
+
{{ t("pluginScheduler.applyChanges") }}
|
|
239
245
|
</button>
|
|
240
246
|
<span v-if="parseError" class="text-xs text-red-500">{{ parseError }}</span>
|
|
241
247
|
</div>
|
|
@@ -247,6 +253,7 @@
|
|
|
247
253
|
|
|
248
254
|
<script setup lang="ts">
|
|
249
255
|
import { computed, ref, watch } from "vue";
|
|
256
|
+
import { useI18n } from "vue-i18n";
|
|
250
257
|
import type { ToolResultComplete } from "gui-chat-protocol/vue";
|
|
251
258
|
import type { SchedulerData, ScheduledItem } from "./index";
|
|
252
259
|
import { useFreshPluginData } from "../../composables/useFreshPluginData";
|
|
@@ -255,6 +262,8 @@ import { API_ROUTES } from "../../config/apiRoutes";
|
|
|
255
262
|
import TasksTab from "./TasksTab.vue";
|
|
256
263
|
import { isToday } from "../../utils/format/date";
|
|
257
264
|
|
|
265
|
+
const { t } = useI18n();
|
|
266
|
+
|
|
258
267
|
type YamlScalar = string | number | boolean | null;
|
|
259
268
|
|
|
260
269
|
const props = defineProps<{
|