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.
Files changed (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +54 -0
  3. package/agent/constants.ts +119 -0
  4. package/agent/deck-context.ts +67 -0
  5. package/agent/index.ts +201 -0
  6. package/agent/middleware.ts +163 -0
  7. package/agent/skills/slidev/README.md +61 -0
  8. package/agent/skills/slidev/SKILL.md +189 -0
  9. package/agent/skills/slidev/references/animation-click-marker.md +37 -0
  10. package/agent/skills/slidev/references/animation-drawing.md +68 -0
  11. package/agent/skills/slidev/references/animation-rough-marker.md +53 -0
  12. package/agent/skills/slidev/references/api-slide-hooks.md +37 -0
  13. package/agent/skills/slidev/references/build-og-image.md +36 -0
  14. package/agent/skills/slidev/references/build-pdf.md +40 -0
  15. package/agent/skills/slidev/references/build-remote-assets.md +34 -0
  16. package/agent/skills/slidev/references/build-seo-meta.md +43 -0
  17. package/agent/skills/slidev/references/code-groups.md +64 -0
  18. package/agent/skills/slidev/references/code-import-snippet.md +55 -0
  19. package/agent/skills/slidev/references/code-line-highlighting.md +50 -0
  20. package/agent/skills/slidev/references/code-line-numbers.md +46 -0
  21. package/agent/skills/slidev/references/code-magic-move.md +57 -0
  22. package/agent/skills/slidev/references/code-max-height.md +37 -0
  23. package/agent/skills/slidev/references/code-twoslash.md +42 -0
  24. package/agent/skills/slidev/references/core-animations.md +196 -0
  25. package/agent/skills/slidev/references/core-cli.md +140 -0
  26. package/agent/skills/slidev/references/core-components.md +197 -0
  27. package/agent/skills/slidev/references/core-exporting.md +148 -0
  28. package/agent/skills/slidev/references/core-frontmatter.md +195 -0
  29. package/agent/skills/slidev/references/core-global-context.md +155 -0
  30. package/agent/skills/slidev/references/core-headmatter.md +188 -0
  31. package/agent/skills/slidev/references/core-hosting.md +152 -0
  32. package/agent/skills/slidev/references/core-layouts.md +286 -0
  33. package/agent/skills/slidev/references/core-syntax.md +155 -0
  34. package/agent/skills/slidev/references/diagram-latex.md +55 -0
  35. package/agent/skills/slidev/references/diagram-mermaid.md +44 -0
  36. package/agent/skills/slidev/references/diagram-plantuml.md +45 -0
  37. package/agent/skills/slidev/references/editor-monaco-run.md +44 -0
  38. package/agent/skills/slidev/references/editor-monaco-write.md +24 -0
  39. package/agent/skills/slidev/references/editor-monaco.md +50 -0
  40. package/agent/skills/slidev/references/editor-prettier.md +40 -0
  41. package/agent/skills/slidev/references/editor-side.md +23 -0
  42. package/agent/skills/slidev/references/editor-vscode.md +55 -0
  43. package/agent/skills/slidev/references/layout-canvas-size.md +25 -0
  44. package/agent/skills/slidev/references/layout-draggable.md +57 -0
  45. package/agent/skills/slidev/references/layout-global-layers.md +50 -0
  46. package/agent/skills/slidev/references/layout-slots.md +75 -0
  47. package/agent/skills/slidev/references/layout-transform.md +33 -0
  48. package/agent/skills/slidev/references/layout-zoom.md +39 -0
  49. package/agent/skills/slidev/references/presenter-notes-ruby.md +35 -0
  50. package/agent/skills/slidev/references/presenter-recording.md +30 -0
  51. package/agent/skills/slidev/references/presenter-remote.md +40 -0
  52. package/agent/skills/slidev/references/presenter-timer.md +34 -0
  53. package/agent/skills/slidev/references/style-direction.md +34 -0
  54. package/agent/skills/slidev/references/style-icons.md +46 -0
  55. package/agent/skills/slidev/references/style-scoped.md +50 -0
  56. package/agent/skills/slidev/references/syntax-block-frontmatter.md +39 -0
  57. package/agent/skills/slidev/references/syntax-frontmatter-merging.md +49 -0
  58. package/agent/skills/slidev/references/syntax-importing-slides.md +60 -0
  59. package/agent/skills/slidev/references/syntax-mdc.md +51 -0
  60. package/agent/skills/slidev/references/tool-eject-theme.md +27 -0
  61. package/agent/tools/export-tool.ts +216 -0
  62. package/agent/tools/review-tool.ts +136 -0
  63. package/app/index.ts +124 -0
  64. package/components/MessageItem.vue +231 -0
  65. package/components/SlidevAgentNavButton.vue +48 -0
  66. package/components/SlidevAgentSidebar.vue +766 -0
  67. package/components/SubagentCard.vue +184 -0
  68. package/components/TypingDots.vue +62 -0
  69. package/dist/agent/constants.js +117 -0
  70. package/dist/agent/deck-context.js +47 -0
  71. package/dist/agent/index.js +167 -0
  72. package/dist/agent/middleware.js +134 -0
  73. package/dist/agent/slide-preview-tool.js +257 -0
  74. package/dist/agent/tools/export-tool.js +167 -0
  75. package/dist/agent/tools/review-tool.js +111 -0
  76. package/dist/app/index.js +101 -0
  77. package/dist/bin/slidev-agent.js +155 -0
  78. package/dist/lib/bridge.js +151 -0
  79. package/dist/lib/env.js +17 -0
  80. package/dist/lib/headless-tools.js +10 -0
  81. package/dist/lib/langgraph-init.js +59 -0
  82. package/dist/lib/review-tool.js +98 -0
  83. package/lib/bridge.ts +212 -0
  84. package/lib/config.ts +79 -0
  85. package/lib/env.ts +38 -0
  86. package/lib/headless-tool-impl.ts +26 -0
  87. package/lib/headless-tools.ts +11 -0
  88. package/lib/langgraph-init.ts +79 -0
  89. package/lib/messages.ts +169 -0
  90. package/lib/render-chat-markdown.ts +19 -0
  91. package/lib/sidebar.ts +573 -0
  92. package/lib/state.ts +44 -0
  93. package/package.json +65 -0
  94. 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>