slidev-addon-agent 0.0.1
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/LICENSE +21 -0
- package/README.md +54 -0
- package/agent/constants.ts +119 -0
- package/agent/deck-context.ts +67 -0
- package/agent/index.ts +201 -0
- package/agent/middleware.ts +163 -0
- package/agent/skills/slidev/README.md +61 -0
- package/agent/skills/slidev/SKILL.md +189 -0
- package/agent/skills/slidev/references/animation-click-marker.md +37 -0
- package/agent/skills/slidev/references/animation-drawing.md +68 -0
- package/agent/skills/slidev/references/animation-rough-marker.md +53 -0
- package/agent/skills/slidev/references/api-slide-hooks.md +37 -0
- package/agent/skills/slidev/references/build-og-image.md +36 -0
- package/agent/skills/slidev/references/build-pdf.md +40 -0
- package/agent/skills/slidev/references/build-remote-assets.md +34 -0
- package/agent/skills/slidev/references/build-seo-meta.md +43 -0
- package/agent/skills/slidev/references/code-groups.md +64 -0
- package/agent/skills/slidev/references/code-import-snippet.md +55 -0
- package/agent/skills/slidev/references/code-line-highlighting.md +50 -0
- package/agent/skills/slidev/references/code-line-numbers.md +46 -0
- package/agent/skills/slidev/references/code-magic-move.md +57 -0
- package/agent/skills/slidev/references/code-max-height.md +37 -0
- package/agent/skills/slidev/references/code-twoslash.md +42 -0
- package/agent/skills/slidev/references/core-animations.md +196 -0
- package/agent/skills/slidev/references/core-cli.md +140 -0
- package/agent/skills/slidev/references/core-components.md +197 -0
- package/agent/skills/slidev/references/core-exporting.md +148 -0
- package/agent/skills/slidev/references/core-frontmatter.md +195 -0
- package/agent/skills/slidev/references/core-global-context.md +155 -0
- package/agent/skills/slidev/references/core-headmatter.md +188 -0
- package/agent/skills/slidev/references/core-hosting.md +152 -0
- package/agent/skills/slidev/references/core-layouts.md +286 -0
- package/agent/skills/slidev/references/core-syntax.md +155 -0
- package/agent/skills/slidev/references/diagram-latex.md +55 -0
- package/agent/skills/slidev/references/diagram-mermaid.md +44 -0
- package/agent/skills/slidev/references/diagram-plantuml.md +45 -0
- package/agent/skills/slidev/references/editor-monaco-run.md +44 -0
- package/agent/skills/slidev/references/editor-monaco-write.md +24 -0
- package/agent/skills/slidev/references/editor-monaco.md +50 -0
- package/agent/skills/slidev/references/editor-prettier.md +40 -0
- package/agent/skills/slidev/references/editor-side.md +23 -0
- package/agent/skills/slidev/references/editor-vscode.md +55 -0
- package/agent/skills/slidev/references/layout-canvas-size.md +25 -0
- package/agent/skills/slidev/references/layout-draggable.md +57 -0
- package/agent/skills/slidev/references/layout-global-layers.md +50 -0
- package/agent/skills/slidev/references/layout-slots.md +75 -0
- package/agent/skills/slidev/references/layout-transform.md +33 -0
- package/agent/skills/slidev/references/layout-zoom.md +39 -0
- package/agent/skills/slidev/references/presenter-notes-ruby.md +35 -0
- package/agent/skills/slidev/references/presenter-recording.md +30 -0
- package/agent/skills/slidev/references/presenter-remote.md +40 -0
- package/agent/skills/slidev/references/presenter-timer.md +34 -0
- package/agent/skills/slidev/references/style-direction.md +34 -0
- package/agent/skills/slidev/references/style-icons.md +46 -0
- package/agent/skills/slidev/references/style-scoped.md +50 -0
- package/agent/skills/slidev/references/syntax-block-frontmatter.md +39 -0
- package/agent/skills/slidev/references/syntax-frontmatter-merging.md +49 -0
- package/agent/skills/slidev/references/syntax-importing-slides.md +60 -0
- package/agent/skills/slidev/references/syntax-mdc.md +51 -0
- package/agent/skills/slidev/references/tool-eject-theme.md +27 -0
- package/agent/tools/export-tool.ts +216 -0
- package/agent/tools/review-tool.ts +136 -0
- package/app/index.ts +124 -0
- package/components/MessageItem.vue +231 -0
- package/components/SlidevAgentNavButton.vue +48 -0
- package/components/SlidevAgentSidebar.vue +766 -0
- package/components/SubagentCard.vue +184 -0
- package/components/TypingDots.vue +62 -0
- package/dist/agent/constants.js +117 -0
- package/dist/agent/deck-context.js +47 -0
- package/dist/agent/index.js +167 -0
- package/dist/agent/middleware.js +134 -0
- package/dist/agent/slide-preview-tool.js +257 -0
- package/dist/agent/tools/export-tool.js +167 -0
- package/dist/agent/tools/review-tool.js +111 -0
- package/dist/app/index.js +101 -0
- package/dist/bin/slidev-agent.js +155 -0
- package/dist/lib/bridge.js +151 -0
- package/dist/lib/env.js +17 -0
- package/dist/lib/headless-tools.js +10 -0
- package/dist/lib/langgraph-init.js +59 -0
- package/dist/lib/review-tool.js +98 -0
- package/lib/bridge.ts +212 -0
- package/lib/config.ts +79 -0
- package/lib/env.ts +38 -0
- package/lib/headless-tool-impl.ts +26 -0
- package/lib/headless-tools.ts +11 -0
- package/lib/langgraph-init.ts +79 -0
- package/lib/messages.ts +169 -0
- package/lib/render-chat-markdown.ts +19 -0
- package/lib/sidebar.ts +573 -0
- package/lib/state.ts +44 -0
- package/package.json +65 -0
- package/public/deepagents.svg +12 -0
|
@@ -0,0 +1,766 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useNav } from "@slidev/client"
|
|
3
|
+
import { useStream } from "@langchain/vue"
|
|
4
|
+
import { computed, nextTick, onBeforeUnmount, onMounted, ref, toValue, unref, watch } from "vue"
|
|
5
|
+
|
|
6
|
+
import MessageItem from "./MessageItem.vue"
|
|
7
|
+
import TypingDots from "./TypingDots.vue"
|
|
8
|
+
import {
|
|
9
|
+
clearSlidevAgentThreadId,
|
|
10
|
+
persistSlidevAgentThreadId,
|
|
11
|
+
resolveSlidevAgentRuntimeConfig,
|
|
12
|
+
} from "../lib/config"
|
|
13
|
+
import { createSlidevHeadlessTools } from "../lib/headless-tool-impl"
|
|
14
|
+
import {
|
|
15
|
+
buildSidebarMessages,
|
|
16
|
+
buildToolCallLookup,
|
|
17
|
+
collectActiveSubagentIds,
|
|
18
|
+
createSubagentActivityMessage,
|
|
19
|
+
filterVisibleSidebarMessages,
|
|
20
|
+
getMappedSubagentIds,
|
|
21
|
+
normalizeMessages,
|
|
22
|
+
summarizeSubagentActivities,
|
|
23
|
+
type StreamSubagent,
|
|
24
|
+
} from "../lib/sidebar"
|
|
25
|
+
import { setSlidevAgentOpen, slidevAgentUiState } from "../lib/state"
|
|
26
|
+
|
|
27
|
+
const runtimeConfig = resolveSlidevAgentRuntimeConfig()
|
|
28
|
+
const nav = useNav()
|
|
29
|
+
const headlessTools = createSlidevHeadlessTools(nav)
|
|
30
|
+
|
|
31
|
+
const draft = ref("")
|
|
32
|
+
const isSubmitting = ref(false)
|
|
33
|
+
const isSwitchingThread = ref(false)
|
|
34
|
+
const didResetMissingThread = ref(false)
|
|
35
|
+
const runtimeErrorMessage = ref("")
|
|
36
|
+
const messagesContainer = ref<HTMLElement | null>(null)
|
|
37
|
+
const shouldAutoScroll = ref(true)
|
|
38
|
+
|
|
39
|
+
const sidebarReservedWidth = "min(26rem, 85vw)"
|
|
40
|
+
const fallbackMessages = ref([
|
|
41
|
+
{
|
|
42
|
+
type: "assistant",
|
|
43
|
+
content: "Start your LangGraph server on http://localhost:2024 and ensure the assistant ID from your langgraph.json graph key is available to the frontend.",
|
|
44
|
+
},
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
function getConfigurationErrorMessage(message: string) {
|
|
48
|
+
const normalized = message.toLowerCase()
|
|
49
|
+
|
|
50
|
+
if (normalized.includes("api key") || normalized.includes("authentication") || normalized.includes("unauthorized")) {
|
|
51
|
+
return "The LangGraph server is missing a provider API key. Set `SLIDEV_AGENT_MODEL` and the matching provider key in your server environment before sending prompts."
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (normalized.includes("model") && (normalized.includes("not found") || normalized.includes("not configured") || normalized.includes("invalid"))) {
|
|
55
|
+
return "The LangGraph server does not have a usable model configured. Set `SLIDEV_AGENT_MODEL` to a provider-prefixed model like `anthropic:claude-sonnet-4-6` and install the matching provider package in the project."
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (normalized.includes("@langchain/")) {
|
|
59
|
+
return "The selected model provider package is not installed. Add the matching integration package, for example `@langchain/anthropic` or `@langchain/openai`, to your project dependencies."
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return ""
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function clearStoredThread() {
|
|
66
|
+
clearSlidevAgentThreadId(runtimeConfig.assistantId, runtimeConfig.deckId)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function buildOptimisticValues(previous: unknown, nextMessage: { type: string, content: string }) {
|
|
70
|
+
const previousState = previous && typeof previous === "object"
|
|
71
|
+
? previous as Record<string, unknown>
|
|
72
|
+
: {}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
...previousState,
|
|
76
|
+
messages: [
|
|
77
|
+
...normalizeMessages(Reflect.get(previousState, "messages")),
|
|
78
|
+
nextMessage,
|
|
79
|
+
],
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const stream = runtimeConfig.enabled
|
|
84
|
+
? useStream({
|
|
85
|
+
apiUrl: runtimeConfig.apiUrl,
|
|
86
|
+
assistantId: runtimeConfig.assistantId,
|
|
87
|
+
threadId: runtimeConfig.threadId,
|
|
88
|
+
fetchStateHistory: false,
|
|
89
|
+
filterSubagentMessages: true,
|
|
90
|
+
reconnectOnMount: true,
|
|
91
|
+
tools: headlessTools,
|
|
92
|
+
onThreadId: (threadId) => {
|
|
93
|
+
persistSlidevAgentThreadId(runtimeConfig.assistantId, runtimeConfig.deckId, threadId)
|
|
94
|
+
},
|
|
95
|
+
onError: (error) => {
|
|
96
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
97
|
+
const configMessage = getConfigurationErrorMessage(message)
|
|
98
|
+
|
|
99
|
+
if (configMessage) {
|
|
100
|
+
runtimeErrorMessage.value = configMessage
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (
|
|
105
|
+
!didResetMissingThread.value
|
|
106
|
+
&& message.includes("Thread with ID")
|
|
107
|
+
&& message.includes("not found")
|
|
108
|
+
) {
|
|
109
|
+
didResetMissingThread.value = true
|
|
110
|
+
clearStoredThread()
|
|
111
|
+
void stream?.switchThread(null)
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
})
|
|
115
|
+
: null
|
|
116
|
+
|
|
117
|
+
const rawMessages = computed(() => {
|
|
118
|
+
if (!stream)
|
|
119
|
+
return fallbackMessages.value
|
|
120
|
+
|
|
121
|
+
return normalizeMessages(stream.messages)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
const activeSubagentIds = computed(() => {
|
|
125
|
+
return collectActiveSubagentIds(stream?.activeSubagents)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
/** Sync useStream() fields into plain refs — nested refs on a plain object do not always trigger template/computed updates. */
|
|
129
|
+
const trackedActiveSubagentCount = ref(0)
|
|
130
|
+
const trackedQueueSize = ref(0)
|
|
131
|
+
const trackedStreamLoading = ref(false)
|
|
132
|
+
const trackedThreadLoading = ref(false)
|
|
133
|
+
|
|
134
|
+
watch(
|
|
135
|
+
() => (stream ? stream.activeSubagents.length : 0),
|
|
136
|
+
value => {
|
|
137
|
+
trackedActiveSubagentCount.value = value
|
|
138
|
+
},
|
|
139
|
+
{ immediate: true },
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
watch(
|
|
143
|
+
() => {
|
|
144
|
+
if (!stream)
|
|
145
|
+
return 0
|
|
146
|
+
const size = stream.queue.size
|
|
147
|
+
return typeof size === "number" ? size : 0
|
|
148
|
+
},
|
|
149
|
+
value => {
|
|
150
|
+
trackedQueueSize.value = value
|
|
151
|
+
},
|
|
152
|
+
{ immediate: true },
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
watch(
|
|
156
|
+
() => (stream ? toValue(stream.isLoading) : false),
|
|
157
|
+
value => {
|
|
158
|
+
trackedStreamLoading.value = Boolean(value)
|
|
159
|
+
},
|
|
160
|
+
{ immediate: true },
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
watch(
|
|
164
|
+
() => (stream ? toValue(stream.isThreadLoading) : false),
|
|
165
|
+
value => {
|
|
166
|
+
trackedThreadLoading.value = Boolean(value)
|
|
167
|
+
},
|
|
168
|
+
{ immediate: true },
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
/** True while sending, streaming, loading thread history, queued work, or subagents running. */
|
|
172
|
+
const isStreamActive = computed(() => {
|
|
173
|
+
return (
|
|
174
|
+
isSubmitting.value
|
|
175
|
+
|| trackedStreamLoading.value
|
|
176
|
+
|| trackedThreadLoading.value
|
|
177
|
+
|| trackedQueueSize.value > 0
|
|
178
|
+
|| isSwitchingThread.value
|
|
179
|
+
|| trackedActiveSubagentCount.value > 0
|
|
180
|
+
)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
const connectionState = computed(() => {
|
|
184
|
+
if (!runtimeConfig.enabled)
|
|
185
|
+
return "fallback"
|
|
186
|
+
|
|
187
|
+
if (runtimeErrorMessage.value)
|
|
188
|
+
return "error"
|
|
189
|
+
|
|
190
|
+
return "connected"
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
const connectionLabel = computed(() => {
|
|
194
|
+
switch (connectionState.value) {
|
|
195
|
+
case "fallback":
|
|
196
|
+
return "Local fallback"
|
|
197
|
+
case "error":
|
|
198
|
+
return "Needs attention"
|
|
199
|
+
default:
|
|
200
|
+
return "Connected"
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
const toolCallLookup = computed(() => {
|
|
205
|
+
return buildToolCallLookup(stream?.toolCalls, rawMessages.value)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
const subagentActivities = computed(() => {
|
|
209
|
+
if (!stream)
|
|
210
|
+
return []
|
|
211
|
+
|
|
212
|
+
const subagents = Array.from(stream.subagents.values() as Iterable<StreamSubagent>)
|
|
213
|
+
return summarizeSubagentActivities(subagents, activeSubagentIds.value)
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
const knownSubagentIds = computed(() => {
|
|
217
|
+
return new Set(subagentActivities.value.map(subagent => subagent.id))
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
function getSubagentsByMessage(messageId: string): StreamSubagent[] {
|
|
221
|
+
if (!stream || !messageId)
|
|
222
|
+
return []
|
|
223
|
+
|
|
224
|
+
const subagents = stream.getSubagentsByMessage(messageId)
|
|
225
|
+
return Array.isArray(subagents) ? subagents as StreamSubagent[] : []
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const messages = computed(() => {
|
|
229
|
+
return filterVisibleSidebarMessages(buildSidebarMessages({
|
|
230
|
+
rawMessages: rawMessages.value,
|
|
231
|
+
toolCallLookup: toolCallLookup.value,
|
|
232
|
+
getSubagentsByMessage,
|
|
233
|
+
activeSubagentIds: activeSubagentIds.value,
|
|
234
|
+
knownSubagentIds: knownSubagentIds.value,
|
|
235
|
+
}))
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
const orphanedSubagentMessages = computed(() => {
|
|
239
|
+
const mappedSubagentIds = getMappedSubagentIds(messages.value)
|
|
240
|
+
return subagentActivities.value
|
|
241
|
+
.filter(subagent => !mappedSubagentIds.has(subagent.id))
|
|
242
|
+
.map(createSubagentActivityMessage)
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
const visibleMessages = computed(() => {
|
|
246
|
+
return [...messages.value, ...orphanedSubagentMessages.value]
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
/** Reserve space under the transcript so the typing bubble can fade out without shifting the composer. */
|
|
250
|
+
const showTypingSlot = computed(() => {
|
|
251
|
+
return visibleMessages.value.length > 0 || isStreamActive.value
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
const currentSlideContext = computed(() => {
|
|
255
|
+
const route = unref(nav.currentSlideRoute)
|
|
256
|
+
const slideMeta = route && typeof route === "object" ? Reflect.get(route, "meta") : undefined
|
|
257
|
+
const slide = slideMeta && typeof slideMeta === "object" ? Reflect.get(slideMeta, "slide") : undefined
|
|
258
|
+
const frontmatter = slide && typeof slide === "object" ? Reflect.get(slide, "frontmatter") : undefined
|
|
259
|
+
const title = frontmatter && typeof frontmatter === "object" ? Reflect.get(frontmatter, "title") : undefined
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
page: unref(nav.currentPage),
|
|
263
|
+
layout: unref(nav.currentLayout) || undefined,
|
|
264
|
+
route: route && typeof route === "object" ? String(Reflect.get(route, "path") || "") || undefined : undefined,
|
|
265
|
+
title: typeof title === "string" && title.trim() ? title.trim() : undefined,
|
|
266
|
+
}
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
const sendShortcutLabel = computed(() => {
|
|
270
|
+
if (typeof navigator === "undefined")
|
|
271
|
+
return "Cmd/Ctrl+Enter to send"
|
|
272
|
+
|
|
273
|
+
return /Mac|iPhone|iPad|iPod/.test(navigator.platform)
|
|
274
|
+
? "Cmd+Enter to send"
|
|
275
|
+
: "Ctrl+Enter to send"
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
function isNearBottom(element: HTMLElement) {
|
|
279
|
+
const threshold = 24
|
|
280
|
+
const distanceFromBottom = element.scrollHeight - element.scrollTop - element.clientHeight
|
|
281
|
+
return distanceFromBottom <= threshold
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function handleMessagesScroll() {
|
|
285
|
+
const element = messagesContainer.value
|
|
286
|
+
if (!element)
|
|
287
|
+
return
|
|
288
|
+
|
|
289
|
+
shouldAutoScroll.value = isNearBottom(element)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async function scrollMessagesToBottom(force = false) {
|
|
293
|
+
await nextTick()
|
|
294
|
+
|
|
295
|
+
const element = messagesContainer.value
|
|
296
|
+
if (!element)
|
|
297
|
+
return
|
|
298
|
+
|
|
299
|
+
if (!force && !shouldAutoScroll.value)
|
|
300
|
+
return
|
|
301
|
+
|
|
302
|
+
element.scrollTop = element.scrollHeight
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function handleComposerKeydown(event: KeyboardEvent) {
|
|
306
|
+
if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
|
|
307
|
+
event.preventDefault()
|
|
308
|
+
void sendMessage()
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function startNewThread() {
|
|
313
|
+
if (!stream || isSwitchingThread.value)
|
|
314
|
+
return
|
|
315
|
+
|
|
316
|
+
isSwitchingThread.value = true
|
|
317
|
+
runtimeErrorMessage.value = ""
|
|
318
|
+
didResetMissingThread.value = false
|
|
319
|
+
clearStoredThread()
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
await stream.switchThread(null)
|
|
323
|
+
}
|
|
324
|
+
finally {
|
|
325
|
+
isSwitchingThread.value = false
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async function sendMessage() {
|
|
330
|
+
const content = draft.value.trim()
|
|
331
|
+
if (!content)
|
|
332
|
+
return
|
|
333
|
+
|
|
334
|
+
runtimeErrorMessage.value = ""
|
|
335
|
+
draft.value = ""
|
|
336
|
+
|
|
337
|
+
if (!stream) {
|
|
338
|
+
fallbackMessages.value = [
|
|
339
|
+
...fallbackMessages.value,
|
|
340
|
+
{ type: "human", content },
|
|
341
|
+
{
|
|
342
|
+
type: "assistant",
|
|
343
|
+
content: "No LangGraph endpoint is configured yet, so this local fallback cannot edit slides. Add the env vars from `.env.example` to connect a real deep agent.",
|
|
344
|
+
},
|
|
345
|
+
]
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
isSubmitting.value = true
|
|
350
|
+
|
|
351
|
+
const nextMessage = {
|
|
352
|
+
type: "human",
|
|
353
|
+
content,
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
await stream.submit(
|
|
358
|
+
{ messages: [nextMessage] },
|
|
359
|
+
{
|
|
360
|
+
context: {
|
|
361
|
+
currentSlide: currentSlideContext.value,
|
|
362
|
+
},
|
|
363
|
+
multitaskStrategy: "enqueue",
|
|
364
|
+
optimisticValues: previous => buildOptimisticValues(previous, nextMessage),
|
|
365
|
+
streamSubgraphs: true,
|
|
366
|
+
},
|
|
367
|
+
)
|
|
368
|
+
}
|
|
369
|
+
catch (error) {
|
|
370
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
371
|
+
|
|
372
|
+
if (message.includes("Unreachable state when creating run")) {
|
|
373
|
+
clearStoredThread()
|
|
374
|
+
void stream.switchThread(null)
|
|
375
|
+
fallbackMessages.value = [
|
|
376
|
+
{
|
|
377
|
+
type: "assistant",
|
|
378
|
+
content: "The previous thread became invalid after a local server restart. The sidebar reset to a fresh thread, so please send your prompt again.",
|
|
379
|
+
},
|
|
380
|
+
]
|
|
381
|
+
return
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const configMessage = getConfigurationErrorMessage(message)
|
|
385
|
+
runtimeErrorMessage.value = configMessage || message
|
|
386
|
+
}
|
|
387
|
+
finally {
|
|
388
|
+
isSubmitting.value = false
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function updateViewportReservation() {
|
|
393
|
+
if (typeof document === "undefined")
|
|
394
|
+
return
|
|
395
|
+
|
|
396
|
+
document.documentElement.style.setProperty(
|
|
397
|
+
"--slidev-agent-reserved-width",
|
|
398
|
+
slidevAgentUiState.isOpen ? sidebarReservedWidth : "0px",
|
|
399
|
+
)
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
onMounted(() => {
|
|
403
|
+
updateViewportReservation()
|
|
404
|
+
void scrollMessagesToBottom(true)
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
watch(() => slidevAgentUiState.isOpen, (isOpen) => {
|
|
408
|
+
updateViewportReservation()
|
|
409
|
+
|
|
410
|
+
if (isOpen)
|
|
411
|
+
void scrollMessagesToBottom(true)
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
onBeforeUnmount(() => {
|
|
415
|
+
if (typeof document === "undefined")
|
|
416
|
+
return
|
|
417
|
+
|
|
418
|
+
document.documentElement.style.setProperty("--slidev-agent-reserved-width", "0px")
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
watch(visibleMessages, () => {
|
|
422
|
+
void scrollMessagesToBottom()
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
watch(trackedActiveSubagentCount, () => {
|
|
426
|
+
void scrollMessagesToBottom()
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
watch(isStreamActive, (active) => {
|
|
430
|
+
if (active)
|
|
431
|
+
void scrollMessagesToBottom()
|
|
432
|
+
})
|
|
433
|
+
</script>
|
|
434
|
+
|
|
435
|
+
<template>
|
|
436
|
+
<Teleport to="body">
|
|
437
|
+
<aside
|
|
438
|
+
class="slidev-agent-sidebar"
|
|
439
|
+
:class="{ 'slidev-agent-sidebar--open': slidevAgentUiState.isOpen }"
|
|
440
|
+
aria-label="LangChain slide authoring agent"
|
|
441
|
+
>
|
|
442
|
+
<div class="slidev-agent-sidebar__header">
|
|
443
|
+
<div class="slidev-agent-sidebar__header-copy">
|
|
444
|
+
<p class="slidev-agent-sidebar__eyebrow">Slidev Agent</p>
|
|
445
|
+
</div>
|
|
446
|
+
|
|
447
|
+
<div class="slidev-agent-sidebar__header-actions">
|
|
448
|
+
<span
|
|
449
|
+
class="slidev-agent-sidebar__connection-dot"
|
|
450
|
+
:class="`slidev-agent-sidebar__connection-dot--${connectionState}`"
|
|
451
|
+
:title="connectionLabel"
|
|
452
|
+
:aria-label="connectionLabel"
|
|
453
|
+
/>
|
|
454
|
+
<button
|
|
455
|
+
class="slidev-agent-sidebar__close"
|
|
456
|
+
type="button"
|
|
457
|
+
aria-label="Close agent sidebar"
|
|
458
|
+
@click="setSlidevAgentOpen(false)"
|
|
459
|
+
>
|
|
460
|
+
×
|
|
461
|
+
</button>
|
|
462
|
+
</div>
|
|
463
|
+
</div>
|
|
464
|
+
|
|
465
|
+
<p v-if="runtimeErrorMessage" class="slidev-agent-sidebar__error-banner">
|
|
466
|
+
{{ runtimeErrorMessage }}
|
|
467
|
+
</p>
|
|
468
|
+
|
|
469
|
+
<div
|
|
470
|
+
ref="messagesContainer"
|
|
471
|
+
class="slidev-agent-sidebar__messages"
|
|
472
|
+
@scroll="handleMessagesScroll"
|
|
473
|
+
>
|
|
474
|
+
<MessageItem
|
|
475
|
+
v-for="message in visibleMessages"
|
|
476
|
+
:key="message.key"
|
|
477
|
+
:message="message"
|
|
478
|
+
/>
|
|
479
|
+
|
|
480
|
+
<div
|
|
481
|
+
v-if="showTypingSlot"
|
|
482
|
+
class="slidev-agent-sidebar__typing-slot"
|
|
483
|
+
:aria-busy="isStreamActive ? 'true' : 'false'"
|
|
484
|
+
>
|
|
485
|
+
<Transition name="slidev-agent-typing-fade">
|
|
486
|
+
<div
|
|
487
|
+
v-if="isStreamActive"
|
|
488
|
+
key="typing-trail"
|
|
489
|
+
class="slidev-agent-sidebar__typing-trail"
|
|
490
|
+
aria-live="polite"
|
|
491
|
+
aria-label="Assistant is responding"
|
|
492
|
+
>
|
|
493
|
+
<TypingDots :active="true" />
|
|
494
|
+
</div>
|
|
495
|
+
</Transition>
|
|
496
|
+
</div>
|
|
497
|
+
|
|
498
|
+
<p v-if="visibleMessages.length === 0 && !isStreamActive" class="slidev-agent-sidebar__empty-state">
|
|
499
|
+
Start a conversation to have the agent inspect and edit your Slidev deck.
|
|
500
|
+
</p>
|
|
501
|
+
</div>
|
|
502
|
+
|
|
503
|
+
<form class="slidev-agent-sidebar__composer" @submit.prevent="sendMessage">
|
|
504
|
+
<textarea
|
|
505
|
+
v-model="draft"
|
|
506
|
+
class="slidev-agent-sidebar__input"
|
|
507
|
+
:placeholder="runtimeConfig.inputPlaceholder"
|
|
508
|
+
rows="4"
|
|
509
|
+
@keydown="handleComposerKeydown"
|
|
510
|
+
/>
|
|
511
|
+
|
|
512
|
+
<div class="slidev-agent-sidebar__composer-footer">
|
|
513
|
+
<div class="slidev-agent-sidebar__composer-actions">
|
|
514
|
+
<button
|
|
515
|
+
class="slidev-agent-sidebar__secondary-button"
|
|
516
|
+
type="button"
|
|
517
|
+
:disabled="isSubmitting || isSwitchingThread"
|
|
518
|
+
@click="startNewThread"
|
|
519
|
+
>
|
|
520
|
+
{{ isSwitchingThread ? "Resetting..." : "New thread" }}
|
|
521
|
+
</button>
|
|
522
|
+
<span class="slidev-agent-sidebar__composer-hint">{{ sendShortcutLabel }}</span>
|
|
523
|
+
</div>
|
|
524
|
+
|
|
525
|
+
<button
|
|
526
|
+
class="slidev-agent-sidebar__submit"
|
|
527
|
+
type="submit"
|
|
528
|
+
:disabled="!draft.trim() || isSubmitting || isSwitchingThread"
|
|
529
|
+
>
|
|
530
|
+
Send
|
|
531
|
+
</button>
|
|
532
|
+
</div>
|
|
533
|
+
</form>
|
|
534
|
+
</aside>
|
|
535
|
+
</Teleport>
|
|
536
|
+
</template>
|
|
537
|
+
|
|
538
|
+
<style scoped>
|
|
539
|
+
:global(:root) {
|
|
540
|
+
--slidev-agent-reserved-width: 0px;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
:global(body) {
|
|
544
|
+
overflow-x: hidden;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
:global(#app) {
|
|
548
|
+
width: calc(100vw - var(--slidev-agent-reserved-width));
|
|
549
|
+
max-width: calc(100vw - var(--slidev-agent-reserved-width));
|
|
550
|
+
transition: width 180ms ease, max-width 180ms ease;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
.slidev-agent-sidebar {
|
|
554
|
+
position: fixed;
|
|
555
|
+
top: 0;
|
|
556
|
+
right: 0;
|
|
557
|
+
bottom: 0;
|
|
558
|
+
width: min(26rem, 85vw);
|
|
559
|
+
display: flex;
|
|
560
|
+
flex-direction: column;
|
|
561
|
+
gap: 0.75rem;
|
|
562
|
+
padding: 1rem;
|
|
563
|
+
background: rgba(10, 15, 28, 0.96);
|
|
564
|
+
color: #e5edf9;
|
|
565
|
+
border-left: 1px solid rgba(148, 163, 184, 0.2);
|
|
566
|
+
box-shadow: -12px 0 32px rgba(15, 23, 42, 0.35);
|
|
567
|
+
transform: translateX(calc(100% + 1rem));
|
|
568
|
+
transition: transform 180ms ease;
|
|
569
|
+
z-index: 60;
|
|
570
|
+
backdrop-filter: blur(14px);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
.slidev-agent-sidebar--open {
|
|
574
|
+
transform: translateX(0);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.slidev-agent-sidebar__header {
|
|
578
|
+
display: flex;
|
|
579
|
+
align-items: flex-start;
|
|
580
|
+
justify-content: space-between;
|
|
581
|
+
gap: 0.75rem;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.slidev-agent-sidebar__header-copy {
|
|
585
|
+
min-width: 0;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
.slidev-agent-sidebar__header-actions {
|
|
589
|
+
display: inline-flex;
|
|
590
|
+
align-items: center;
|
|
591
|
+
gap: 0.75rem;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
.slidev-agent-sidebar__eyebrow {
|
|
595
|
+
margin: 0;
|
|
596
|
+
font-size: 0.72rem;
|
|
597
|
+
letter-spacing: 0.08em;
|
|
598
|
+
text-transform: uppercase;
|
|
599
|
+
color: #93c5fd;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
.slidev-agent-sidebar__title {
|
|
603
|
+
margin: 0.2rem 0 0;
|
|
604
|
+
font-size: 1.2rem;
|
|
605
|
+
font-weight: 600;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
.slidev-agent-typing-fade-enter-active,
|
|
609
|
+
.slidev-agent-typing-fade-leave-active {
|
|
610
|
+
transition: opacity 0.28s ease;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.slidev-agent-typing-fade-enter-from,
|
|
614
|
+
.slidev-agent-typing-fade-leave-to {
|
|
615
|
+
opacity: 0;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.slidev-agent-sidebar__typing-slot {
|
|
619
|
+
flex-shrink: 0;
|
|
620
|
+
display: flex;
|
|
621
|
+
flex-direction: column;
|
|
622
|
+
align-items: flex-start;
|
|
623
|
+
justify-content: flex-start;
|
|
624
|
+
margin-top: 0.25rem;
|
|
625
|
+
/* Matches bubble + margin so fade-out does not collapse the thread layout */
|
|
626
|
+
min-height: 3.05rem;
|
|
627
|
+
box-sizing: border-box;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
.slidev-agent-sidebar__typing-trail {
|
|
631
|
+
display: flex;
|
|
632
|
+
align-items: center;
|
|
633
|
+
justify-content: flex-start;
|
|
634
|
+
align-self: flex-start;
|
|
635
|
+
padding: 0.5rem 0.85rem;
|
|
636
|
+
border-radius: 0.9rem;
|
|
637
|
+
background: rgba(30, 41, 59, 0.85);
|
|
638
|
+
border: 1px solid rgba(148, 163, 184, 0.12);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
.slidev-agent-sidebar__close {
|
|
642
|
+
border: 0;
|
|
643
|
+
background: transparent;
|
|
644
|
+
color: inherit;
|
|
645
|
+
font-size: 1.5rem;
|
|
646
|
+
line-height: 1;
|
|
647
|
+
cursor: pointer;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
.slidev-agent-sidebar__connection-dot {
|
|
651
|
+
width: 0.7rem;
|
|
652
|
+
height: 0.7rem;
|
|
653
|
+
border-radius: 999px;
|
|
654
|
+
flex: 0 0 auto;
|
|
655
|
+
background: #22c55e;
|
|
656
|
+
box-shadow: 0 0 0.75rem rgba(34, 197, 94, 0.75);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
.slidev-agent-sidebar__connection-dot--fallback {
|
|
660
|
+
background: #f59e0b;
|
|
661
|
+
box-shadow: 0 0 0.75rem rgba(245, 158, 11, 0.6);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
.slidev-agent-sidebar__connection-dot--error {
|
|
665
|
+
background: #ef4444;
|
|
666
|
+
box-shadow: 0 0 0.75rem rgba(239, 68, 68, 0.75);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
.slidev-agent-sidebar__error-banner {
|
|
670
|
+
margin: 0;
|
|
671
|
+
padding: 0.7rem 0.8rem;
|
|
672
|
+
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
673
|
+
border-radius: 0.85rem;
|
|
674
|
+
background: rgba(127, 29, 29, 0.24);
|
|
675
|
+
color: #fecaca;
|
|
676
|
+
font-size: 0.84rem;
|
|
677
|
+
line-height: 1.45;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
.slidev-agent-sidebar__messages {
|
|
681
|
+
flex: 1;
|
|
682
|
+
overflow-y: auto;
|
|
683
|
+
display: flex;
|
|
684
|
+
flex-direction: column;
|
|
685
|
+
gap: 0.55rem;
|
|
686
|
+
padding-right: 0.25rem;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
.slidev-agent-sidebar__empty-state {
|
|
690
|
+
margin: auto 0;
|
|
691
|
+
font-size: 0.9rem;
|
|
692
|
+
line-height: 1.5;
|
|
693
|
+
color: #cbd5e1;
|
|
694
|
+
text-align: center;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
.slidev-agent-sidebar__composer {
|
|
698
|
+
display: flex;
|
|
699
|
+
flex-direction: column;
|
|
700
|
+
gap: 0.75rem;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
.slidev-agent-sidebar__composer-footer {
|
|
704
|
+
display: flex;
|
|
705
|
+
align-items: center;
|
|
706
|
+
justify-content: space-between;
|
|
707
|
+
gap: 0.75rem;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
.slidev-agent-sidebar__composer-actions {
|
|
711
|
+
display: inline-flex;
|
|
712
|
+
align-items: center;
|
|
713
|
+
gap: 0.6rem;
|
|
714
|
+
min-width: 0;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
.slidev-agent-sidebar__composer-hint {
|
|
718
|
+
font-size: 0.74rem;
|
|
719
|
+
color: #94a3b8;
|
|
720
|
+
white-space: nowrap;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
.slidev-agent-sidebar__secondary-button {
|
|
724
|
+
border: 1px solid rgba(148, 163, 184, 0.22);
|
|
725
|
+
border-radius: 999px;
|
|
726
|
+
padding: 0.45rem 0.8rem;
|
|
727
|
+
background: rgba(15, 23, 42, 0.78);
|
|
728
|
+
color: #e2e8f0;
|
|
729
|
+
font-size: 0.78rem;
|
|
730
|
+
font-weight: 600;
|
|
731
|
+
cursor: pointer;
|
|
732
|
+
white-space: nowrap;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
.slidev-agent-sidebar__secondary-button:disabled {
|
|
736
|
+
opacity: 0.6;
|
|
737
|
+
cursor: not-allowed;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
.slidev-agent-sidebar__input {
|
|
741
|
+
width: 100%;
|
|
742
|
+
resize: vertical;
|
|
743
|
+
min-height: 7rem;
|
|
744
|
+
padding: 0.75rem;
|
|
745
|
+
border-radius: 0.9rem;
|
|
746
|
+
border: 1px solid rgba(148, 163, 184, 0.25);
|
|
747
|
+
background: rgba(15, 23, 42, 0.9);
|
|
748
|
+
color: inherit;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
.slidev-agent-sidebar__submit {
|
|
752
|
+
align-self: flex-end;
|
|
753
|
+
border: 0;
|
|
754
|
+
border-radius: 999px;
|
|
755
|
+
padding: 0.55rem 1rem;
|
|
756
|
+
background: #2563eb;
|
|
757
|
+
color: white;
|
|
758
|
+
font-weight: 600;
|
|
759
|
+
cursor: pointer;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
.slidev-agent-sidebar__submit:disabled {
|
|
763
|
+
opacity: 0.6;
|
|
764
|
+
cursor: not-allowed;
|
|
765
|
+
}
|
|
766
|
+
</style>
|