@demicodes/web-ui 0.3.1 → 0.3.3
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 +4 -4
- package/src/agent/AgentMessageList.vue +1 -0
- package/src/agent/ConversationView.vue +0 -2
- package/src/agent/blocks/AbortedBlock.vue +6 -0
- package/src/agent/blocks/AgentMessageVirtualBlock.vue +6 -0
- package/src/agent/blocks/ErrorBlock.vue +4 -1
- package/src/agent/conversation-runtime.ts +24 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@demicodes/web-ui",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"src"
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"typecheck": "vue-tsc --noEmit -p tsconfig.json"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@demicodes/agent": "^0.3.
|
|
28
|
-
"@demicodes/core": "^0.3.
|
|
29
|
-
"@demicodes/utils": "^0.3.
|
|
27
|
+
"@demicodes/agent": "^0.3.2",
|
|
28
|
+
"@demicodes/core": "^0.3.2",
|
|
29
|
+
"@demicodes/utils": "^0.3.2",
|
|
30
30
|
"@floating-ui/vue": "^2.0.0",
|
|
31
31
|
"@mingcute/vue": "^1.4.1",
|
|
32
32
|
"@tanstack/vue-virtual": "^3.13.29",
|
|
@@ -109,6 +109,7 @@ watch(
|
|
|
109
109
|
:conversation-id="props.conversationId"
|
|
110
110
|
:is-thinking-streaming="isStreamingThinkingAt(item.index)"
|
|
111
111
|
:thinking-ended-at="thinkingEndedAt(item.index)"
|
|
112
|
+
:recoverable="item.index === renderBlocks.length - 1 && props.phase === 'idle'"
|
|
112
113
|
@continue="emit('continue')"
|
|
113
114
|
@retry="emit('retry')"
|
|
114
115
|
@delete-pending-steer="(id) => emit('deletePendingSteer', id)"
|
|
@@ -20,7 +20,6 @@ const pendingSteers = computed(() => session.value?.pendingSteers ?? [])
|
|
|
20
20
|
const phase = computed(() => session.value?.phase ?? 'idle')
|
|
21
21
|
|
|
22
22
|
const bottomAreaRef = ref<HTMLDivElement>()
|
|
23
|
-
const messageInputRef = ref<InstanceType<typeof AgentMessageInput>>()
|
|
24
23
|
const { height: bottomAreaHeight } = useElementSize(bottomAreaRef)
|
|
25
24
|
|
|
26
25
|
function handleEmptySubmit() {
|
|
@@ -71,7 +70,6 @@ function onRetry() {
|
|
|
71
70
|
@clear-all="workspace.clearMessageQueue(conversationId)"
|
|
72
71
|
/>
|
|
73
72
|
<AgentMessageInput
|
|
74
|
-
ref="messageInputRef"
|
|
75
73
|
class="relative z-10"
|
|
76
74
|
:conversation-id="conversationId"
|
|
77
75
|
@empty-submit="handleEmptySubmit"
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
import { ArrowRightLine } from '@mingcute/vue/arrow-right'
|
|
3
3
|
import { t } from '@demicodes/web-ui/infra/i18n'
|
|
4
4
|
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
/** Offer the Continue action; see ErrorBlock — only the tail block recovers. */
|
|
7
|
+
recoverable: boolean
|
|
8
|
+
}>()
|
|
9
|
+
|
|
5
10
|
const emit = defineEmits<{
|
|
6
11
|
continue: []
|
|
7
12
|
retry: []
|
|
@@ -16,6 +21,7 @@ function handleRecovery() {
|
|
|
16
21
|
<div class="flex h-6 items-center justify-between">
|
|
17
22
|
<span class="rounded bg-fg-ghost/60 px-1.5 py-0.5 text-[11px] text-fg-subtle">{{ t('agent.block.aborted') }}</span>
|
|
18
23
|
<div
|
|
24
|
+
v-if="props.recoverable"
|
|
19
25
|
class="flex cursor-pointer items-center gap-1 rounded-md px-1.5 py-0.5 text-[11px] text-fg-subtle transition-colors hover:bg-active hover:text-fg-body"
|
|
20
26
|
@click="handleRecovery"
|
|
21
27
|
>
|
|
@@ -16,6 +16,10 @@ const props = defineProps<{
|
|
|
16
16
|
conversationId: string
|
|
17
17
|
isThinkingStreaming: boolean
|
|
18
18
|
thinkingEndedAt?: string | null
|
|
19
|
+
/** Whether this block may offer recovery actions (Continue) — true only for
|
|
20
|
+
* the conversation's tail block while the session is idle, so stale error
|
|
21
|
+
* blocks never present a second, competing recovery entry point. */
|
|
22
|
+
recoverable: boolean
|
|
19
23
|
}>()
|
|
20
24
|
|
|
21
25
|
const emit = defineEmits<{
|
|
@@ -67,12 +71,14 @@ const attrs = useAttrs()
|
|
|
67
71
|
<ErrorBlock
|
|
68
72
|
:message="block.message"
|
|
69
73
|
:code="block.code"
|
|
74
|
+
:recoverable="props.recoverable"
|
|
70
75
|
@continue="emit('continue')"
|
|
71
76
|
@retry="emit('retry')"
|
|
72
77
|
/>
|
|
73
78
|
</div>
|
|
74
79
|
<div v-else-if="block.type === 'abort'" class="px-[var(--agent-pad-x,2rem)]">
|
|
75
80
|
<AbortedBlock
|
|
81
|
+
:recoverable="props.recoverable"
|
|
76
82
|
@continue="emit('continue')"
|
|
77
83
|
@retry="emit('retry')"
|
|
78
84
|
/>
|
|
@@ -11,6 +11,9 @@ const props = defineProps<{
|
|
|
11
11
|
message: string
|
|
12
12
|
code?: string | null
|
|
13
13
|
stack?: string
|
|
14
|
+
/** Offer the Continue action. Only the conversation's tail error is
|
|
15
|
+
* recoverable — older errors are records, not entry points. */
|
|
16
|
+
recoverable: boolean
|
|
14
17
|
}>()
|
|
15
18
|
|
|
16
19
|
const emit = defineEmits<{
|
|
@@ -66,7 +69,7 @@ const { copy, copied } = useClipboard({ copiedDuring: 1500 })
|
|
|
66
69
|
<pre class="whitespace-pre-wrap break-words font-mono text-[11px] leading-4.5 text-on-danger-muted">{{ props.stack }}</pre>
|
|
67
70
|
</div>
|
|
68
71
|
</div>
|
|
69
|
-
<div class="flex items-center justify-end border-t border-on-danger-muted px-3 py-2">
|
|
72
|
+
<div v-if="props.recoverable" class="flex items-center justify-end border-t border-on-danger-muted px-3 py-2">
|
|
70
73
|
<div
|
|
71
74
|
class="flex cursor-pointer items-center gap-1.5 rounded-md bg-tint-danger-strong px-2.5 py-1 text-[12px] font-medium text-on-danger transition-colors hover:bg-tint-danger-strong"
|
|
72
75
|
@click="handleRecovery"
|
|
@@ -17,11 +17,18 @@ export class ConversationRuntime {
|
|
|
17
17
|
private readonly canceledPendingSteers = new Set<string>()
|
|
18
18
|
private readonly activePendingSteerRequests = new Set<string>()
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
private readonly state: ConversationState
|
|
21
|
+
private readonly baseUrl: string
|
|
22
|
+
private readonly control: ControlApi
|
|
23
|
+
|
|
24
|
+
// Explicit field assignments instead of constructor parameter properties:
|
|
25
|
+
// this package ships as source, and parameter properties are rejected by
|
|
26
|
+
// consumers compiling with erasableSyntaxOnly.
|
|
27
|
+
constructor(state: ConversationState, baseUrl: string, control: ControlApi) {
|
|
28
|
+
this.state = state
|
|
29
|
+
this.baseUrl = baseUrl
|
|
30
|
+
this.control = control
|
|
31
|
+
}
|
|
25
32
|
|
|
26
33
|
async send(content: UserContentBlock[]): Promise<void> {
|
|
27
34
|
const client = await this.ensureOpen()
|
|
@@ -122,7 +129,18 @@ export class ConversationRuntime {
|
|
|
122
129
|
}
|
|
123
130
|
|
|
124
131
|
async resume(): Promise<void> {
|
|
125
|
-
|
|
132
|
+
const client = await this.ensureOpen()
|
|
133
|
+
// Optimistic: the server only reports `running` after a round-trip, which
|
|
134
|
+
// reads as a dead Continue button in the meantime. Flip immediately; the
|
|
135
|
+
// next phase_changed event carries the real phase either way, and a failed
|
|
136
|
+
// resume rolls back so the recovery action reappears.
|
|
137
|
+
this.state.phase = 'running'
|
|
138
|
+
try {
|
|
139
|
+
await client.resume()
|
|
140
|
+
} catch (error) {
|
|
141
|
+
this.state.phase = 'idle'
|
|
142
|
+
throw error
|
|
143
|
+
}
|
|
126
144
|
}
|
|
127
145
|
|
|
128
146
|
async compact(): Promise<void> {
|