@demicodes/web-ui 0.3.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/package.json +55 -0
- package/src/agent/AgentMessageInput.vue +164 -0
- package/src/agent/AgentMessageList.vue +124 -0
- package/src/agent/AgentPanel.vue +18 -0
- package/src/agent/AgentRoot.vue +17 -0
- package/src/agent/AgentTabBar.vue +422 -0
- package/src/agent/AgentTabItem.vue +150 -0
- package/src/agent/ContextUsageIndicator.vue +136 -0
- package/src/agent/ConversationListDropdown.vue +71 -0
- package/src/agent/ConversationStatusDot.vue +26 -0
- package/src/agent/ConversationView.vue +82 -0
- package/src/agent/MessageQueueBar.vue +67 -0
- package/src/agent/ModelSelector.vue +125 -0
- package/src/agent/ReasoningSelector.vue +106 -0
- package/src/agent/SelectorTrigger.vue +17 -0
- package/src/agent/__tests__/block-helpers.test.ts +73 -0
- package/src/agent/__tests__/input-actions.test.ts +130 -0
- package/src/agent/__tests__/input-editor.test.ts +15 -0
- package/src/agent/__tests__/pending-steers.test.ts +99 -0
- package/src/agent/__tests__/queue-submit.test.ts +14 -0
- package/src/agent/__tests__/reasoning.test.ts +32 -0
- package/src/agent/__tests__/tail-loading.test.ts +131 -0
- package/src/agent/__tests__/tool-rendering.test.ts +35 -0
- package/src/agent/__tests__/visible-blocks.test.ts +125 -0
- package/src/agent/block-helpers.ts +59 -0
- package/src/agent/block-types.ts +12 -0
- package/src/agent/blocks/AbortedBlock.vue +26 -0
- package/src/agent/blocks/AgentMessageVirtualBlock.vue +88 -0
- package/src/agent/blocks/AnsiText.vue +75 -0
- package/src/agent/blocks/AssistantTextBlock.vue +34 -0
- package/src/agent/blocks/CompactionBlock.vue +36 -0
- package/src/agent/blocks/ErrorBlock.vue +79 -0
- package/src/agent/blocks/FunctionalBlock.vue +118 -0
- package/src/agent/blocks/LoadingBlock.vue +28 -0
- package/src/agent/blocks/ThinkingBlock.vue +86 -0
- package/src/agent/blocks/ToolCallBlock.vue +40 -0
- package/src/agent/blocks/ToolGenericBlock.vue +43 -0
- package/src/agent/blocks/ToolShellAbortBlock.vue +13 -0
- package/src/agent/blocks/ToolShellBlock.vue +47 -0
- package/src/agent/blocks/ToolShellControlBlock.vue +51 -0
- package/src/agent/blocks/ToolShellStatusBlock.vue +13 -0
- package/src/agent/blocks/ToolShellWriteBlock.vue +13 -0
- package/src/agent/blocks/ToolStatusBadge.vue +13 -0
- package/src/agent/blocks/ToolYieldBlock.vue +13 -0
- package/src/agent/blocks/UserBlock.vue +112 -0
- package/src/agent/conversation-runtime.ts +205 -0
- package/src/agent/conversation-status.ts +12 -0
- package/src/agent/message-input/input-model.ts +33 -0
- package/src/agent/message-input/useAgentInputActions.ts +96 -0
- package/src/agent/message-input/useAgentInputEditor.ts +90 -0
- package/src/agent/message-input/useAgentInputSessionState.ts +48 -0
- package/src/agent/pending-steers.ts +134 -0
- package/src/agent/providers/ProviderIcon.vue +32 -0
- package/src/agent/providers/icons/claude.svg +1 -0
- package/src/agent/providers/icons/openai.svg +3 -0
- package/src/agent/queue-submit.ts +4 -0
- package/src/agent/reasoning.ts +45 -0
- package/src/agent/tail-loading.ts +31 -0
- package/src/agent/thinking-streaming.ts +7 -0
- package/src/agent/tool-rendering.ts +59 -0
- package/src/agent/types.ts +39 -0
- package/src/agent/ui-options.ts +21 -0
- package/src/agent/visible-blocks.ts +25 -0
- package/src/agent/workspace.ts +355 -0
- package/src/composables/useBlockVirtualizer.ts +290 -0
- package/src/composables/useContextMenuOwner.ts +44 -0
- package/src/composables/useOverlay.ts +18 -0
- package/src/composables/useTerminalTheme.ts +75 -0
- package/src/env.d.ts +10 -0
- package/src/infra/errors.ts +29 -0
- package/src/infra/i18n.ts +41 -0
- package/src/markdown/filePath.ts +54 -0
- package/src/markdown/highlight.ts +139 -0
- package/src/markdown/md.ts +58 -0
- package/src/markdown/render.ts +117 -0
- package/src/markdown/types.ts +20 -0
- package/src/overlay/appOverlay.ts +3 -0
- package/src/overlay/overlayStore.ts +54 -0
- package/src/store/createStore.ts +33 -0
- package/src/store/useStore.ts +7 -0
- package/src/styles/base.css +342 -0
- package/src/theme/appTheme.ts +56 -0
- package/src/theme/codeThemes.ts +454 -0
- package/src/theme/themeStore.ts +33 -0
- package/src/transport/agent-socket.ts +20 -0
- package/src/transport/control-client.ts +64 -0
- package/src/transport/protocol.ts +57 -0
- package/src/ui/Button.vue +30 -0
- package/src/ui/Checkbox.vue +25 -0
- package/src/ui/ContextMenu.vue +33 -0
- package/src/ui/Dialog.vue +48 -0
- package/src/ui/DropdownMenu.vue +77 -0
- package/src/ui/ErrorBox.vue +43 -0
- package/src/ui/HighlightText.vue +39 -0
- package/src/ui/IconButton.vue +30 -0
- package/src/ui/IndeterminateSpinner.vue +72 -0
- package/src/ui/InlineError.vue +25 -0
- package/src/ui/LoadMore.vue +32 -0
- package/src/ui/MarkdownPreview.vue +31 -0
- package/src/ui/Menu.vue +8 -0
- package/src/ui/MenuDivider.vue +6 -0
- package/src/ui/MenuItem.vue +48 -0
- package/src/ui/OptionMenu.vue +8 -0
- package/src/ui/OptionMenuGroup.vue +16 -0
- package/src/ui/OptionMenuItem.vue +28 -0
- package/src/ui/Popover.vue +90 -0
- package/src/ui/ScrollToBottomButton.vue +32 -0
- package/src/ui/SelectDropdown.vue +231 -0
- package/src/ui/Switch.vue +41 -0
- package/src/ui/TextInput.vue +44 -0
- package/src/ui/ThemeToggle.vue +49 -0
- package/src/ui/ToggleSwitch.vue +34 -0
- package/src/ui/Tooltip.vue +143 -0
- package/src/ui/blankClickGuard.ts +12 -0
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@demicodes/web-ui",
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"files": [
|
|
6
|
+
"src"
|
|
7
|
+
],
|
|
8
|
+
"exports": {
|
|
9
|
+
"./*.vue": "./src/*.vue",
|
|
10
|
+
"./*.css": "./src/*.css",
|
|
11
|
+
"./*": "./src/*.ts"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/wspl/demi.git",
|
|
19
|
+
"directory": "packages/web-ui"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/wspl/demi/tree/main/packages/web-ui#readme",
|
|
22
|
+
"bugs": "https://github.com/wspl/demi/issues",
|
|
23
|
+
"scripts": {
|
|
24
|
+
"typecheck": "vue-tsc --noEmit -p tsconfig.json"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@demicodes/agent": "^0.3.1",
|
|
28
|
+
"@demicodes/core": "^0.3.1",
|
|
29
|
+
"@demicodes/utils": "^0.3.1",
|
|
30
|
+
"@floating-ui/vue": "^2.0.0",
|
|
31
|
+
"@mingcute/vue": "^1.4.1",
|
|
32
|
+
"@tanstack/vue-virtual": "^3.13.29",
|
|
33
|
+
"@tiptap/extension-placeholder": "^3.27.1",
|
|
34
|
+
"@tiptap/pm": "^3.27.1",
|
|
35
|
+
"@tiptap/starter-kit": "^3.27.1",
|
|
36
|
+
"@tiptap/vue-3": "^3.27.1",
|
|
37
|
+
"@vueuse/core": "^14.3.0",
|
|
38
|
+
"ansi-to-html": "^0.7.2",
|
|
39
|
+
"dayjs": "^1.11.21",
|
|
40
|
+
"dompurify": "^3.4.11",
|
|
41
|
+
"katex": "^0.17.0",
|
|
42
|
+
"marked": "^18.0.5",
|
|
43
|
+
"marked-katex-extension": "^5.1.10",
|
|
44
|
+
"partial-json": "^0.1.7",
|
|
45
|
+
"pathe": "^2.0.3",
|
|
46
|
+
"shiki": "^4.2.0",
|
|
47
|
+
"vue": "^3.5.38"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@vue/tsconfig": "^0.9.1",
|
|
51
|
+
"typescript": "^6.0.3",
|
|
52
|
+
"vue-tsc": "^3.3.5"
|
|
53
|
+
},
|
|
54
|
+
"license": "Apache-2.0"
|
|
55
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { nextTick } from 'vue'
|
|
3
|
+
import type { UserContentBlock } from '@demicodes/core'
|
|
4
|
+
import { AddLine } from '@mingcute/vue/add'
|
|
5
|
+
import { SendLine } from '@mingcute/vue/send'
|
|
6
|
+
import { StopLine } from '@mingcute/vue/stop'
|
|
7
|
+
import { EditorContent } from '@tiptap/vue-3'
|
|
8
|
+
import { useAgentWorkspace } from './workspace'
|
|
9
|
+
import ModelSelector from './ModelSelector.vue'
|
|
10
|
+
import ContextUsageIndicator from './ContextUsageIndicator.vue'
|
|
11
|
+
import Tooltip from '../ui/Tooltip.vue'
|
|
12
|
+
import { useAgentInputActions } from './message-input/useAgentInputActions'
|
|
13
|
+
import { useAgentInputEditor } from './message-input/useAgentInputEditor'
|
|
14
|
+
import { useAgentInputSessionState } from './message-input/useAgentInputSessionState'
|
|
15
|
+
import { docToContent, type InputModel } from './message-input/input-model'
|
|
16
|
+
|
|
17
|
+
const props = defineProps<{
|
|
18
|
+
conversationId: string
|
|
19
|
+
}>()
|
|
20
|
+
|
|
21
|
+
const emit = defineEmits<{
|
|
22
|
+
'empty-submit': []
|
|
23
|
+
}>()
|
|
24
|
+
|
|
25
|
+
const workspace = useAgentWorkspace()
|
|
26
|
+
|
|
27
|
+
const {
|
|
28
|
+
selectedProviderId,
|
|
29
|
+
selectedModelId,
|
|
30
|
+
thinkingConfig,
|
|
31
|
+
contextWindow,
|
|
32
|
+
inputLimit,
|
|
33
|
+
isRunning,
|
|
34
|
+
isCompacting,
|
|
35
|
+
canCompact,
|
|
36
|
+
usage,
|
|
37
|
+
} = useAgentInputSessionState(workspace, props.conversationId)
|
|
38
|
+
|
|
39
|
+
function buildSubmitPayload(): UserContentBlock[] | null {
|
|
40
|
+
const currentEditor = editor.value
|
|
41
|
+
if (!currentEditor || currentEditor.isEmpty) return null
|
|
42
|
+
const content = docToContent(currentEditor.getJSON() as InputModel)
|
|
43
|
+
return content.length > 0 ? content : null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function clearInput(): void {
|
|
47
|
+
editor.value?.commands.clearContent()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const { handleSubmit, handleSteerSubmit, handleQueueSubmit, handleSelectModel, handleChangeThinking, handleAbort, handleCompact } = useAgentInputActions({
|
|
51
|
+
workspace,
|
|
52
|
+
conversationId: props.conversationId,
|
|
53
|
+
buildSubmitPayload,
|
|
54
|
+
clearInput,
|
|
55
|
+
emitEmptySubmit() {
|
|
56
|
+
emit('empty-submit')
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const { editor, isFocused, hasContent } = useAgentInputEditor({
|
|
61
|
+
handleSubmit,
|
|
62
|
+
handleCancel() {},
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
defineExpose({
|
|
66
|
+
focus() {
|
|
67
|
+
editor.value?.commands.focus('end', { scrollIntoView: false })
|
|
68
|
+
},
|
|
69
|
+
prefill(content: InputModel) {
|
|
70
|
+
editor.value?.commands.setContent(content)
|
|
71
|
+
nextTick(() => editor.value?.commands.focus('end', { scrollIntoView: false }))
|
|
72
|
+
},
|
|
73
|
+
})
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
<template>
|
|
77
|
+
<div>
|
|
78
|
+
<div
|
|
79
|
+
class="input-float rounded-xl outline outline-1 transition-[outline-color] duration-200"
|
|
80
|
+
:class="isFocused ? 'bg-surface-raised outline-line-focus' : 'bg-surface-raised outline-line'"
|
|
81
|
+
>
|
|
82
|
+
<EditorContent v-if="editor" :editor="editor" />
|
|
83
|
+
<div class="flex items-center justify-between px-2 pb-2">
|
|
84
|
+
<div class="flex items-center gap-1">
|
|
85
|
+
<ModelSelector
|
|
86
|
+
:providers="workspace.providers.value"
|
|
87
|
+
:models="workspace.models"
|
|
88
|
+
:selected-provider-id="selectedProviderId"
|
|
89
|
+
:selected-model-id="selectedModelId"
|
|
90
|
+
v-bind="thinkingConfig ? { thinkingConfig } : {}"
|
|
91
|
+
@select-model="handleSelectModel"
|
|
92
|
+
@change-thinking="handleChangeThinking"
|
|
93
|
+
/>
|
|
94
|
+
</div>
|
|
95
|
+
<div class="flex items-center gap-1">
|
|
96
|
+
<ContextUsageIndicator
|
|
97
|
+
:conversation-id="props.conversationId"
|
|
98
|
+
:usage="usage"
|
|
99
|
+
:context-window="contextWindow"
|
|
100
|
+
:input-limit="inputLimit"
|
|
101
|
+
:is-compacting="isCompacting"
|
|
102
|
+
:is-clickable="!isRunning && canCompact"
|
|
103
|
+
@compact="handleCompact"
|
|
104
|
+
/>
|
|
105
|
+
<template v-if="hasContent">
|
|
106
|
+
<template v-if="isRunning">
|
|
107
|
+
<Tooltip content="Steer current turn">
|
|
108
|
+
<span
|
|
109
|
+
class="flex size-7 cursor-pointer items-center justify-center rounded-lg bg-overlay/10 text-fg transition-colors hover:bg-active"
|
|
110
|
+
@click="handleSteerSubmit"
|
|
111
|
+
>
|
|
112
|
+
<SendLine :size="15" />
|
|
113
|
+
</span>
|
|
114
|
+
</Tooltip>
|
|
115
|
+
<Tooltip content="Queue next turn">
|
|
116
|
+
<span
|
|
117
|
+
class="flex size-7 cursor-pointer items-center justify-center rounded-lg bg-overlay/10 text-fg transition-colors hover:bg-active"
|
|
118
|
+
@click="handleQueueSubmit"
|
|
119
|
+
>
|
|
120
|
+
<AddLine :size="15" />
|
|
121
|
+
</span>
|
|
122
|
+
</Tooltip>
|
|
123
|
+
</template>
|
|
124
|
+
<Tooltip v-else content="Send message">
|
|
125
|
+
<span
|
|
126
|
+
class="flex size-7 cursor-pointer items-center justify-center rounded-lg bg-overlay/10 text-fg transition-colors hover:bg-active"
|
|
127
|
+
@click="handleSubmit"
|
|
128
|
+
>
|
|
129
|
+
<SendLine :size="15" />
|
|
130
|
+
</span>
|
|
131
|
+
</Tooltip>
|
|
132
|
+
</template>
|
|
133
|
+
<span
|
|
134
|
+
v-else-if="isRunning || isCompacting"
|
|
135
|
+
class="flex size-7 cursor-pointer items-center justify-center rounded-lg bg-overlay/10 text-fg transition-colors hover:bg-active"
|
|
136
|
+
@click="handleAbort"
|
|
137
|
+
>
|
|
138
|
+
<StopLine :size="15" />
|
|
139
|
+
</span>
|
|
140
|
+
<span
|
|
141
|
+
v-else
|
|
142
|
+
class="flex size-7 items-center justify-center rounded-lg text-fg-faint"
|
|
143
|
+
>
|
|
144
|
+
<SendLine :size="15" />
|
|
145
|
+
</span>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</template>
|
|
151
|
+
|
|
152
|
+
<style>
|
|
153
|
+
.input-float {
|
|
154
|
+
box-shadow: var(--shadow-float);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.tiptap p.is-editor-empty:first-child::before {
|
|
158
|
+
content: attr(data-placeholder);
|
|
159
|
+
float: left;
|
|
160
|
+
color: var(--color-fg-subtle);
|
|
161
|
+
pointer-events: none;
|
|
162
|
+
height: 0;
|
|
163
|
+
}
|
|
164
|
+
</style>
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
|
|
3
|
+
import { useScroll } from '@vueuse/core'
|
|
4
|
+
import type { Block, SessionPhase } from '@demicodes/core'
|
|
5
|
+
import { useBlockVirtualizer, type PersistedScrollState } from '@demicodes/web-ui/composables/useBlockVirtualizer'
|
|
6
|
+
import { getVisibleBlocks } from './visible-blocks'
|
|
7
|
+
import { isThinkingBlockStreaming } from './thinking-streaming'
|
|
8
|
+
import { pendingSteersToRenderBlocks, type MessageListBlock } from './pending-steers'
|
|
9
|
+
import { shouldShowTailLoading } from './tail-loading'
|
|
10
|
+
import type { PendingSteerMessage } from './types'
|
|
11
|
+
import AgentMessageVirtualBlock from './blocks/AgentMessageVirtualBlock.vue'
|
|
12
|
+
import LoadingBlock from './blocks/LoadingBlock.vue'
|
|
13
|
+
import ScrollToBottomButton from '@demicodes/web-ui/ui/ScrollToBottomButton.vue'
|
|
14
|
+
|
|
15
|
+
const INPUT_AREA_PADDING = 48
|
|
16
|
+
|
|
17
|
+
const props = defineProps<{
|
|
18
|
+
conversationId: string
|
|
19
|
+
blocks: Block[]
|
|
20
|
+
pendingSteers: PendingSteerMessage[]
|
|
21
|
+
phase: SessionPhase
|
|
22
|
+
bottomOffset: number
|
|
23
|
+
persistedScrollState: PersistedScrollState | undefined
|
|
24
|
+
}>()
|
|
25
|
+
|
|
26
|
+
const emit = defineEmits<{
|
|
27
|
+
saveScrollState: [conversationId: string, state: PersistedScrollState | undefined]
|
|
28
|
+
continue: []
|
|
29
|
+
retry: []
|
|
30
|
+
deletePendingSteer: [id: string]
|
|
31
|
+
}>()
|
|
32
|
+
|
|
33
|
+
const visibleTranscriptBlocks = computed(() => getVisibleBlocks(props.blocks))
|
|
34
|
+
const renderBlocks = computed<MessageListBlock[]>(() => [
|
|
35
|
+
...visibleTranscriptBlocks.value,
|
|
36
|
+
...pendingSteersToRenderBlocks(props.pendingSteers),
|
|
37
|
+
])
|
|
38
|
+
|
|
39
|
+
const shouldShowLoading = computed(() => shouldShowTailLoading(props.phase, visibleTranscriptBlocks.value, renderBlocks.value))
|
|
40
|
+
|
|
41
|
+
function isStreamingThinkingAt(index: number): boolean {
|
|
42
|
+
const block = renderBlocks.value[index]
|
|
43
|
+
if (!block || block.type !== 'thinking') return false
|
|
44
|
+
const transcriptIndex = visibleTranscriptBlocks.value.findIndex((candidate) => candidate.id === block.id)
|
|
45
|
+
return isThinkingBlockStreaming(visibleTranscriptBlocks.value, props.phase, transcriptIndex)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// The next block's createdAt marks when a thinking block stopped (null while it's still the last,
|
|
49
|
+
// i.e. actively thinking). Lets ThinkingBlock show a frozen "thought for Xs" that survives reload.
|
|
50
|
+
function thinkingEndedAt(index: number): string | null {
|
|
51
|
+
const block = renderBlocks.value[index]
|
|
52
|
+
if (!block || !('createdAt' in block)) return null
|
|
53
|
+
const transcriptIndex = visibleTranscriptBlocks.value.findIndex((candidate) => candidate.id === block.id)
|
|
54
|
+
if (transcriptIndex < 0) return null
|
|
55
|
+
const next = visibleTranscriptBlocks.value[transcriptIndex + 1]
|
|
56
|
+
return next && 'createdAt' in next ? next.createdAt : null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const scrollContainer = ref<HTMLDivElement>()
|
|
60
|
+
|
|
61
|
+
const { virtualItems, totalSize, measureElement, scrollOffset, isAtBottom, scrollToBottom, onScroll, getPersistedState } =
|
|
62
|
+
useBlockVirtualizer(scrollContainer, renderBlocks, props.persistedScrollState)
|
|
63
|
+
|
|
64
|
+
onBeforeUnmount(() => {
|
|
65
|
+
emit('saveScrollState', props.conversationId, getPersistedState())
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const { isScrolling } = useScroll(scrollContainer, { idle: 1500 })
|
|
69
|
+
|
|
70
|
+
watch(
|
|
71
|
+
() => props.bottomOffset,
|
|
72
|
+
() => {
|
|
73
|
+
nextTick(() => {
|
|
74
|
+
scrollOffset.value = scrollContainer.value?.scrollTop ?? 0
|
|
75
|
+
})
|
|
76
|
+
},
|
|
77
|
+
{ flush: 'post' },
|
|
78
|
+
)
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<template>
|
|
82
|
+
<div class="relative h-full">
|
|
83
|
+
<div
|
|
84
|
+
ref="scrollContainer"
|
|
85
|
+
class="h-full overflow-y-auto scrollbar-hidden"
|
|
86
|
+
:class="isScrolling ? 'scrollbar-active' : ''"
|
|
87
|
+
style="overflow-anchor: none;"
|
|
88
|
+
@scroll="onScroll"
|
|
89
|
+
>
|
|
90
|
+
<div v-if="renderBlocks.length === 0" class="grid h-full place-items-center">
|
|
91
|
+
<p class="text-sm text-fg-faint">No messages yet. Start a conversation.</p>
|
|
92
|
+
</div>
|
|
93
|
+
<div
|
|
94
|
+
v-else
|
|
95
|
+
class="w-full pt-2"
|
|
96
|
+
:style="{ paddingBottom: `${props.bottomOffset + INPUT_AREA_PADDING}px` }"
|
|
97
|
+
>
|
|
98
|
+
<div class="relative w-full" :style="{ height: `${totalSize}px` }">
|
|
99
|
+
<div
|
|
100
|
+
v-for="item in virtualItems"
|
|
101
|
+
:key="String(item.key)"
|
|
102
|
+
:data-index="item.index"
|
|
103
|
+
:ref="(el) => measureElement(el as Element)"
|
|
104
|
+
class="absolute inset-x-0 top-0"
|
|
105
|
+
:style="{ transform: `translateY(${item.start}px)` }"
|
|
106
|
+
>
|
|
107
|
+
<AgentMessageVirtualBlock
|
|
108
|
+
:block="renderBlocks[item.index]!"
|
|
109
|
+
:conversation-id="props.conversationId"
|
|
110
|
+
:is-thinking-streaming="isStreamingThinkingAt(item.index)"
|
|
111
|
+
:thinking-ended-at="thinkingEndedAt(item.index)"
|
|
112
|
+
@continue="emit('continue')"
|
|
113
|
+
@retry="emit('retry')"
|
|
114
|
+
@delete-pending-steer="(id) => emit('deletePendingSteer', id)"
|
|
115
|
+
/>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
<LoadingBlock v-if="shouldShowLoading" />
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<ScrollToBottomButton :visible="!isAtBottom" :bottom-offset="props.bottomOffset + 36" @click="scrollToBottom" />
|
|
123
|
+
</div>
|
|
124
|
+
</template>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { useAgentWorkspace } from './workspace'
|
|
4
|
+
import AgentTabBar from './AgentTabBar.vue'
|
|
5
|
+
import ConversationView from './ConversationView.vue'
|
|
6
|
+
|
|
7
|
+
const workspace = useAgentWorkspace()
|
|
8
|
+
|
|
9
|
+
const tabs = computed(() => workspace.tabs.value)
|
|
10
|
+
const activeTabId = computed(() => workspace.activeId.value)
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<template>
|
|
14
|
+
<div class="flex h-full flex-col">
|
|
15
|
+
<AgentTabBar :tabs="tabs" :all-conversations="tabs" :active-tab-id="activeTabId" />
|
|
16
|
+
<ConversationView v-if="activeTabId" :key="activeTabId" :conversation-id="activeTabId" />
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { AgentWorkspace, provideAgentWorkspace } from './workspace'
|
|
3
|
+
import { provideAgentUiOptions, type AgentUiOptions } from './ui-options'
|
|
4
|
+
import AgentPanel from './AgentPanel.vue'
|
|
5
|
+
|
|
6
|
+
const props = defineProps<{
|
|
7
|
+
workspace: AgentWorkspace
|
|
8
|
+
uiOptions?: Partial<AgentUiOptions>
|
|
9
|
+
}>()
|
|
10
|
+
|
|
11
|
+
provideAgentWorkspace(props.workspace)
|
|
12
|
+
provideAgentUiOptions(props.uiOptions)
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<AgentPanel />
|
|
17
|
+
</template>
|