@jskit-ai/assistant-core 0.1.171 → 0.1.173
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/examples/conversation/App.vue +62 -10
- package/examples/conversation/README.md +29 -4
- package/examples/conversation/attachments.js +45 -0
- package/examples/conversation/integrations.json +28 -0
- package/examples/conversation/package.json +6 -4
- package/examples/conversation/server.js +61 -10
- package/fixtures/responsive-assistant/ConversationFixture.vue +54 -1
- package/fixtures/responsive-assistant/ProgressFixture.vue +61 -0
- package/fixtures/responsive-assistant/main.js +3 -1
- package/package.json +21 -7
- package/src/client/conversation/AssistantAttachmentPreview.vue +38 -0
- package/src/client/conversation/AssistantAttachmentQueue.vue +563 -0
- package/src/client/conversation/AssistantComposerSupport.vue +235 -0
- package/src/client/conversation/AssistantConversationElement.vue +103 -9
- package/src/client/conversation/AssistantGoalControl.vue +81 -0
- package/src/client/conversation/AssistantMessageAttachments.vue +114 -0
- package/src/client/conversation/AssistantModelControl.vue +334 -0
- package/src/client/conversation/AssistantQuestionInputs.vue +124 -0
- package/src/client/conversation/AssistantTranscript.vue +139 -174
- package/src/client/conversation/index.js +9 -0
- package/src/client/conversation/useAssistantAttachments.js +991 -0
- package/src/client/conversation/useAssistantSuggestions.js +99 -0
- package/src/server/index.js +1 -0
- package/src/server/lib/aiConnectionClient.js +90 -0
- package/src/server/lib/providers/anthropicClient.js +6 -21
- package/src/shared/assistantResource.js +7 -0
- package/src/shared/conversation/attachments.js +13 -0
- package/src/shared/conversation/index.js +1 -0
- package/test/aiConnectionClient.test.js +159 -0
- package/test/assistantScroll.browser.test.js +217 -0
- package/test/suggestions.test.js +114 -0
|
@@ -98,12 +98,12 @@
|
|
|
98
98
|
</div>
|
|
99
99
|
|
|
100
100
|
<article
|
|
101
|
-
v-for="
|
|
102
|
-
:key="
|
|
101
|
+
v-for="entry in displayEntries"
|
|
102
|
+
:key="entry.key"
|
|
103
103
|
class="assistant-transcript__turn"
|
|
104
104
|
>
|
|
105
105
|
<div
|
|
106
|
-
v-if="
|
|
106
|
+
v-if="entry.role === 'system'"
|
|
107
107
|
class="assistant-transcript__system"
|
|
108
108
|
>
|
|
109
109
|
<v-icon
|
|
@@ -114,55 +114,55 @@
|
|
|
114
114
|
<div class="assistant-transcript__system-body">
|
|
115
115
|
<div class="assistant-transcript__system-meta">
|
|
116
116
|
<span>{{ systemLabel }}</span>
|
|
117
|
-
<time v-if="
|
|
117
|
+
<time v-if="entry.message.displayAt">{{ entry.message.displayAt }}</time>
|
|
118
118
|
</div>
|
|
119
|
-
<slot name="system-message" :message="
|
|
119
|
+
<slot name="system-message" :message="entry.message"><LongTextPreviewBlocks compact :blocks="entry.message.blocks" @link-click="handleLongTextLinkClick" /></slot>
|
|
120
120
|
</div>
|
|
121
121
|
</div>
|
|
122
122
|
|
|
123
123
|
<div
|
|
124
|
-
v-if="
|
|
124
|
+
v-else-if="entry.role === 'user'"
|
|
125
125
|
class="assistant-transcript__message-row assistant-transcript__message-row--user"
|
|
126
126
|
>
|
|
127
127
|
<div class="assistant-transcript__message assistant-transcript__message--user">
|
|
128
128
|
<div
|
|
129
129
|
class="assistant-transcript__user-content"
|
|
130
130
|
>
|
|
131
|
-
<p v-if="userMessageFormat === 'plain'" class="assistant-transcript__plain-user">{{ userMessageExpanded(turn) ?
|
|
131
|
+
<p v-if="userMessageFormat === 'plain'" class="assistant-transcript__plain-user">{{ userMessageExpanded(entry.turn) ? entry.message.text : userMessagePreviewText(entry.message.text) || entry.message.text }}</p>
|
|
132
132
|
<LongTextPreviewBlocks
|
|
133
133
|
v-else
|
|
134
|
-
:blocks="userMessageExpanded(turn) ?
|
|
134
|
+
:blocks="userMessageExpanded(entry.turn) ? entry.message.blocks : (entry.message.previewBlocks || entry.message.blocks)"
|
|
135
135
|
@link-click="handleLongTextLinkClick"
|
|
136
136
|
/>
|
|
137
137
|
</div>
|
|
138
138
|
<button
|
|
139
|
-
v-if="userMessageCollapsible(
|
|
140
|
-
:aria-expanded="userMessageExpanded(turn)"
|
|
139
|
+
v-if="userMessageCollapsible(entry.message)"
|
|
140
|
+
:aria-expanded="userMessageExpanded(entry.turn)"
|
|
141
141
|
class="assistant-transcript__user-content-toggle"
|
|
142
142
|
type="button"
|
|
143
|
-
@click="toggleUserMessage(turn)"
|
|
143
|
+
@click="toggleUserMessage(entry.turn)"
|
|
144
144
|
>
|
|
145
|
-
{{ userMessageExpanded(turn) ? "Show less" : "Read more" }}
|
|
145
|
+
{{ userMessageExpanded(entry.turn) ? "Show less" : "Read more" }}
|
|
146
146
|
</button>
|
|
147
|
-
<slot name="attachments" :items="
|
|
147
|
+
<slot name="attachments" :items="entry.message.attachments" :message="entry.message"><AssistantMessageAttachments :attachments="entry.message.attachments || []" /></slot>
|
|
148
148
|
<div
|
|
149
|
-
v-if="
|
|
149
|
+
v-if="entry.message.displayAt"
|
|
150
150
|
class="assistant-transcript__message-footer assistant-transcript__message-footer--user"
|
|
151
151
|
>
|
|
152
|
-
<time v-if="
|
|
152
|
+
<time v-if="entry.message.displayAt">{{ entry.message.displayAt }}</time>
|
|
153
153
|
</div>
|
|
154
154
|
<div
|
|
155
|
-
v-if="turn.optimistic?.status === 'failed'"
|
|
155
|
+
v-if="entry.turn.optimistic?.status === 'failed'"
|
|
156
156
|
class="assistant-transcript__optimistic-failure"
|
|
157
157
|
>
|
|
158
|
-
<span>{{ turn.optimistic.error || "Message could not be sent." }}</span>
|
|
158
|
+
<span>{{ entry.turn.optimistic.error || "Message could not be sent." }}</span>
|
|
159
159
|
<div class="assistant-transcript__optimistic-actions">
|
|
160
160
|
<v-btn
|
|
161
161
|
color="primary"
|
|
162
162
|
size="x-small"
|
|
163
163
|
type="button"
|
|
164
164
|
variant="tonal"
|
|
165
|
-
@click="emit('resend-turn', turn.optimistic.id)"
|
|
165
|
+
@click="emit('resend-turn', entry.turn.optimistic.id)"
|
|
166
166
|
>
|
|
167
167
|
Resend
|
|
168
168
|
</v-btn>
|
|
@@ -170,7 +170,7 @@
|
|
|
170
170
|
size="x-small"
|
|
171
171
|
type="button"
|
|
172
172
|
variant="text"
|
|
173
|
-
@click="emit('cancel-turn', turn.optimistic.id)"
|
|
173
|
+
@click="emit('cancel-turn', entry.turn.optimistic.id)"
|
|
174
174
|
>
|
|
175
175
|
Cancel
|
|
176
176
|
</v-btn>
|
|
@@ -178,7 +178,7 @@
|
|
|
178
178
|
size="x-small"
|
|
179
179
|
type="button"
|
|
180
180
|
variant="text"
|
|
181
|
-
@click="emit('edit-turn', turn.optimistic.id)"
|
|
181
|
+
@click="emit('edit-turn', entry.turn.optimistic.id)"
|
|
182
182
|
>
|
|
183
183
|
Edit
|
|
184
184
|
</v-btn>
|
|
@@ -190,84 +190,79 @@
|
|
|
190
190
|
</span>
|
|
191
191
|
</div>
|
|
192
192
|
|
|
193
|
-
<
|
|
194
|
-
v-
|
|
195
|
-
:key="entry.key"
|
|
193
|
+
<AssistantProgress
|
|
194
|
+
v-else-if="entry.role === 'thinking'"
|
|
195
|
+
:key="`${scrollKey}:${entry.key}`"
|
|
196
|
+
class="assistant-transcript__thinking"
|
|
197
|
+
:messages="entry.messages"
|
|
198
|
+
:pending="isWorking && entry === displayEntries.at(-1)"
|
|
199
|
+
:preview-limit="progressPreviewLimit"
|
|
200
|
+
:aria-label="`${assistantLabel} progress`"
|
|
201
|
+
/>
|
|
202
|
+
<div
|
|
203
|
+
v-else
|
|
204
|
+
class="assistant-transcript__message-row assistant-transcript__message-row--assistant"
|
|
205
|
+
:data-message-role="entry.role"
|
|
196
206
|
>
|
|
197
|
-
<
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
:preview-limit="progressPreviewLimit"
|
|
204
|
-
:aria-label="`${assistantLabel} progress`"
|
|
205
|
-
/>
|
|
206
|
-
<div
|
|
207
|
-
v-else
|
|
208
|
-
class="assistant-transcript__message-row assistant-transcript__message-row--assistant"
|
|
209
|
-
:data-message-role="entry.role"
|
|
210
|
-
>
|
|
211
|
-
<div class="assistant-transcript__assistant-header">
|
|
212
|
-
<span class="assistant-transcript__avatar assistant-transcript__avatar--assistant">
|
|
213
|
-
<v-icon :icon="mdiRobotOutline" size="16" />
|
|
214
|
-
</span>
|
|
215
|
-
<div class="assistant-transcript__message-header">
|
|
216
|
-
<span>{{ assistantLabel }}</span>
|
|
217
|
-
</div>
|
|
207
|
+
<div class="assistant-transcript__assistant-header">
|
|
208
|
+
<span class="assistant-transcript__avatar assistant-transcript__avatar--assistant">
|
|
209
|
+
<v-icon :icon="mdiRobotOutline" size="16" />
|
|
210
|
+
</span>
|
|
211
|
+
<div class="assistant-transcript__message-header">
|
|
212
|
+
<span>{{ assistantLabel }}</span>
|
|
218
213
|
</div>
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
214
|
+
</div>
|
|
215
|
+
<div class="assistant-transcript__message assistant-transcript__message--assistant">
|
|
216
|
+
<LongTextPreviewBlocks
|
|
217
|
+
v-if="entry.message.blocks.length"
|
|
218
|
+
:blocks="entry.message.blocks"
|
|
219
|
+
@link-click="handleLongTextLinkClick"
|
|
220
|
+
/>
|
|
221
|
+
<ol
|
|
222
|
+
v-if="entry.message.questions.length"
|
|
223
|
+
class="assistant-transcript__questions"
|
|
224
|
+
>
|
|
225
|
+
<li
|
|
226
|
+
v-for="question in entry.message.questions"
|
|
227
|
+
:key="question.name"
|
|
228
|
+
class="assistant-transcript__question"
|
|
228
229
|
>
|
|
229
|
-
<
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
230
|
+
<span class="assistant-transcript__question-number">{{ question.number }}</span>
|
|
231
|
+
<div class="assistant-transcript__question-content">
|
|
232
|
+
<span class="assistant-transcript__question-text">
|
|
233
|
+
<LongTextInlineParts
|
|
234
|
+
:text="question.label"
|
|
235
|
+
@link-click="handleLongTextLinkClick"
|
|
236
|
+
/>
|
|
237
|
+
</span>
|
|
238
|
+
<ul
|
|
239
|
+
v-if="question.choices.length"
|
|
240
|
+
class="assistant-transcript__question-choices"
|
|
241
|
+
>
|
|
242
|
+
<li v-for="choice in question.choices" :key="choice.value">
|
|
237
243
|
<LongTextInlineParts
|
|
238
|
-
:text="
|
|
244
|
+
:text="choice.label"
|
|
239
245
|
@link-click="handleLongTextLinkClick"
|
|
240
|
-
|
|
241
|
-
</
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
:blocks="entry.message.outroBlocks"
|
|
259
|
-
@link-click="handleLongTextLinkClick"
|
|
260
|
-
/>
|
|
261
|
-
<slot name="message-actions" :message="entry.message" :turn="turn" />
|
|
262
|
-
</div>
|
|
263
|
-
<div
|
|
264
|
-
v-if="entry.message.displayAt"
|
|
265
|
-
class="assistant-transcript__message-footer assistant-transcript__message-footer--assistant"
|
|
266
|
-
>
|
|
267
|
-
<time>{{ entry.message.displayAt }}</time>
|
|
268
|
-
</div>
|
|
246
|
+
/><span v-if="choice.recommended"> · Recommended</span>
|
|
247
|
+
</li>
|
|
248
|
+
</ul>
|
|
249
|
+
</div>
|
|
250
|
+
</li>
|
|
251
|
+
</ol>
|
|
252
|
+
<LongTextPreviewBlocks
|
|
253
|
+
v-if="entry.message.outroBlocks.length"
|
|
254
|
+
:blocks="entry.message.outroBlocks"
|
|
255
|
+
@link-click="handleLongTextLinkClick"
|
|
256
|
+
/>
|
|
257
|
+
<slot name="message-actions" :message="entry.message" :turn="entry.turn" />
|
|
258
|
+
</div>
|
|
259
|
+
<div
|
|
260
|
+
v-if="entry.message.displayAt"
|
|
261
|
+
class="assistant-transcript__message-footer assistant-transcript__message-footer--assistant"
|
|
262
|
+
>
|
|
263
|
+
<time>{{ entry.message.displayAt }}</time>
|
|
269
264
|
</div>
|
|
270
|
-
</
|
|
265
|
+
</div>
|
|
271
266
|
</article>
|
|
272
267
|
|
|
273
268
|
<div
|
|
@@ -280,6 +275,7 @@
|
|
|
280
275
|
</template>
|
|
281
276
|
|
|
282
277
|
<script setup>
|
|
278
|
+
import AssistantMessageAttachments from "./AssistantMessageAttachments.vue";
|
|
283
279
|
import { computed, nextTick, onBeforeUnmount, ref, watch } from "vue";
|
|
284
280
|
import {
|
|
285
281
|
mdiAccountOutline,
|
|
@@ -302,6 +298,7 @@ import {
|
|
|
302
298
|
|
|
303
299
|
const props = defineProps({
|
|
304
300
|
systemLabel: { type: String, default: "Status" },
|
|
301
|
+
working: { type: Boolean, default: undefined },
|
|
305
302
|
progressPreviewLimit: { type: Number, default: 2 },
|
|
306
303
|
userMessageFormat: { type: String, default: "formatted", validator: (value) => ["plain", "formatted"].includes(value) },
|
|
307
304
|
assistantLabel: {
|
|
@@ -372,6 +369,7 @@ const DISPLAY_MESSAGE_CACHE_LIMIT = 500;
|
|
|
372
369
|
const bodyElement = ref(null);
|
|
373
370
|
const bottomElement = ref(null);
|
|
374
371
|
const expandedUserMessages = ref(new Set());
|
|
372
|
+
const isWorking = computed(() => props.working ?? props.turns.some(turn => turn.pending));
|
|
375
373
|
const followingLatest = ref(true);
|
|
376
374
|
const initialScrollSettled = ref(false);
|
|
377
375
|
const userScrollIntent = ref(false);
|
|
@@ -529,66 +527,49 @@ function conversationMessageKey(message = {}, index = 0) {
|
|
|
529
527
|
].join(":");
|
|
530
528
|
}
|
|
531
529
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
530
|
+
// Storage rows are not visual boundaries. Only an intervening message ends a
|
|
531
|
+
// reasoning group, including when history is prepended or execution continues.
|
|
532
|
+
const displayEntries = computed(() => {
|
|
533
|
+
const entries = [];
|
|
534
|
+
for (const [index, turn] of props.turns.entries()) {
|
|
535
|
+
const turnId = String(turn.turnId || index + 1);
|
|
536
|
+
const messages = [
|
|
537
|
+
turn.system && { ...turn.system, role: "system" },
|
|
538
|
+
turn.user && { ...turn.user, role: "user" },
|
|
539
|
+
...conversationAgentMessages(turn)
|
|
540
|
+
].filter(Boolean);
|
|
541
|
+
for (const [messageIndex, message] of messages.entries()) {
|
|
542
|
+
const role = String(message.role || "").trim();
|
|
543
|
+
const key = `${turnId}:${conversationMessageKey(message, messageIndex)}`;
|
|
544
|
+
const displayed = role === "thinking" ? displayThinkingMessage(message) : displayMessage(message, {
|
|
545
|
+
allowNumberedQuestions: role !== "user" && role !== "system",
|
|
546
|
+
preserveParagraphLineBreaks: role === "user",
|
|
547
|
+
previewUserMessage: role === "user"
|
|
548
|
+
}, key);
|
|
549
|
+
if (!displayed) continue;
|
|
550
|
+
const previous = entries.at(-1);
|
|
551
|
+
if (role === "thinking" && previous?.role === "thinking") {
|
|
552
|
+
previous.messages.push({ ...displayed, key });
|
|
553
|
+
} else {
|
|
554
|
+
entries.push({
|
|
555
|
+
key, role, turn: { ...turn, turnId }, message: displayed,
|
|
556
|
+
...(role === "thinking" ? { messages: [{ ...displayed, key }] } : {})
|
|
557
|
+
});
|
|
549
558
|
}
|
|
550
|
-
timeline.push({
|
|
551
|
-
key: `thinking:${keyedMessage.key}`,
|
|
552
|
-
messages: [keyedMessage],
|
|
553
|
-
role
|
|
554
|
-
});
|
|
555
|
-
continue;
|
|
556
559
|
}
|
|
557
|
-
timeline.push({
|
|
558
|
-
key: conversationMessageKey(message, index),
|
|
559
|
-
message: displayMessage(message, {
|
|
560
|
-
allowNumberedQuestions: true
|
|
561
|
-
}, `${turnKey}:agent:${conversationMessageKey(message, index)}`),
|
|
562
|
-
role
|
|
563
|
-
});
|
|
564
560
|
}
|
|
565
|
-
|
|
566
|
-
|
|
561
|
+
// Anchor a progress group to the following message, or to the transcript end.
|
|
562
|
+
// Appending progress or prepending history then preserves its expansion state.
|
|
563
|
+
for (const [index, entry] of entries.entries()) {
|
|
564
|
+
if (entry.role === "thinking") entry.key = `progress:${entries[index + 1]?.key || "tail"}`;
|
|
565
|
+
}
|
|
566
|
+
return entries;
|
|
567
|
+
});
|
|
567
568
|
|
|
568
569
|
function handleLongTextLinkClick(payload) {
|
|
569
570
|
emit("link-click", payload);
|
|
570
571
|
}
|
|
571
572
|
|
|
572
|
-
const displayTurns = computed(() => (Array.isArray(props.turns) ? props.turns : [])
|
|
573
|
-
.map((turn, index) => {
|
|
574
|
-
const turnId = String(turn.turnId || index + 1);
|
|
575
|
-
return {
|
|
576
|
-
...turn,
|
|
577
|
-
agentTimeline: displayAgentTimeline(turn, turnId),
|
|
578
|
-
optimistic: turn.optimistic && typeof turn.optimistic === "object" && !Array.isArray(turn.optimistic)
|
|
579
|
-
? turn.optimistic
|
|
580
|
-
: null,
|
|
581
|
-
pending: turn.pending === true,
|
|
582
|
-
system: displayMessage(turn.system, {}, `${turnId}:system`),
|
|
583
|
-
turnId,
|
|
584
|
-
user: displayMessage(turn.user, {
|
|
585
|
-
preserveParagraphLineBreaks: true,
|
|
586
|
-
previewUserMessage: true
|
|
587
|
-
}, `${turnId}:user`)
|
|
588
|
-
};
|
|
589
|
-
})
|
|
590
|
-
.filter((turn) => turn.system || turn.user || turn.agentTimeline.length));
|
|
591
|
-
|
|
592
573
|
function userMessageCollapsible(message = null) {
|
|
593
574
|
return Array.isArray(message?.previewBlocks);
|
|
594
575
|
}
|
|
@@ -617,11 +598,11 @@ function toggleUserMessage(turn = {}) {
|
|
|
617
598
|
|
|
618
599
|
const loadingIndicatorVisible = computed(() => Boolean(
|
|
619
600
|
props.loading &&
|
|
620
|
-
!
|
|
601
|
+
!displayEntries.value.length
|
|
621
602
|
));
|
|
622
603
|
const initialScrollPending = computed(() => Boolean(
|
|
623
604
|
props.visible &&
|
|
624
|
-
|
|
605
|
+
displayEntries.value.length &&
|
|
625
606
|
!initialScrollSettled.value
|
|
626
607
|
));
|
|
627
608
|
|
|
@@ -637,40 +618,24 @@ function messageScrollKey(message = null) {
|
|
|
637
618
|
].join("/");
|
|
638
619
|
}
|
|
639
620
|
|
|
640
|
-
function agentTimelineScrollKey(timeline = []) {
|
|
641
|
-
return (Array.isArray(timeline) ? timeline : []).map((entry) => (
|
|
642
|
-
entry?.role === "thinking"
|
|
643
|
-
? (entry.messages || []).map(messageScrollKey).join("+")
|
|
644
|
-
: messageScrollKey(entry?.message)
|
|
645
|
-
)).join("|");
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
function latestRenderedTailScrollKey(turns = []) {
|
|
649
|
-
const turn = Array.isArray(turns) ? turns.at(-1) : null;
|
|
650
|
-
if (!turn) {
|
|
651
|
-
return "";
|
|
652
|
-
}
|
|
653
|
-
const timeline = Array.isArray(turn.agentTimeline) ? turn.agentTimeline : [];
|
|
654
|
-
return [
|
|
655
|
-
turn.turnId,
|
|
656
|
-
messageScrollKey(turn.system),
|
|
657
|
-
messageScrollKey(turn.user),
|
|
658
|
-
turn.optimistic?.id || "",
|
|
659
|
-
turn.optimistic?.status || "",
|
|
660
|
-
turn.optimistic?.error || "",
|
|
661
|
-
turn.pending ? "pending" : "settled",
|
|
662
|
-
agentTimelineScrollKey(timeline)
|
|
663
|
-
].join(":");
|
|
664
|
-
}
|
|
665
|
-
|
|
666
621
|
const timelineScrollTrigger = computed(() => [
|
|
667
622
|
props.visible ? "visible" : "hidden",
|
|
668
623
|
props.error ? "error" : "body",
|
|
669
624
|
loadingIndicatorVisible.value ? "loading" : "ready",
|
|
670
|
-
|
|
625
|
+
displayEntries.value.length ? "has-messages" : "empty",
|
|
671
626
|
props.scrollKey
|
|
672
627
|
].join(":"));
|
|
673
|
-
const latestRenderedTailKey = computed(() =>
|
|
628
|
+
const latestRenderedTailKey = computed(() => {
|
|
629
|
+
const entry = displayEntries.value.at(-1);
|
|
630
|
+
if (!entry) return "";
|
|
631
|
+
return [
|
|
632
|
+
entry.key,
|
|
633
|
+
isWorking.value,
|
|
634
|
+
entry.role === "thinking" ? entry.messages.map(messageScrollKey).join("+") : messageScrollKey(entry.message),
|
|
635
|
+
entry.turn.optimistic?.status || "",
|
|
636
|
+
entry.turn.optimistic?.error || ""
|
|
637
|
+
].join(":");
|
|
638
|
+
});
|
|
674
639
|
const autoScrollEnabled = computed(() => Boolean(
|
|
675
640
|
props.visible &&
|
|
676
641
|
followingLatest.value
|
|
@@ -3,7 +3,16 @@ export { default as AssistantTranscript } from "./AssistantTranscript.vue";
|
|
|
3
3
|
export { default as AssistantPromptInput } from "./AssistantPromptInput.vue";
|
|
4
4
|
export { default as AssistantComposerActions } from "./AssistantComposerActions.vue";
|
|
5
5
|
export { default as AssistantProgress } from "./AssistantProgress.vue";
|
|
6
|
+
export { default as AssistantComposerSupport } from "./AssistantComposerSupport.vue";
|
|
7
|
+
export { default as AssistantGoalControl } from "./AssistantGoalControl.vue";
|
|
8
|
+
export { useAssistantSuggestions } from "./useAssistantSuggestions.js";
|
|
6
9
|
export { default as LongTextPreviewBlocks } from "./LongTextPreviewBlocks.vue";
|
|
7
10
|
export { default as LongTextInlineParts } from "./LongTextInlineParts.vue";
|
|
8
11
|
export { useScrollToBottom } from "./useScrollToBottom.js";
|
|
9
12
|
export { createAssistantTextSubmission } from "./submitText.js";
|
|
13
|
+
export { useAssistantAttachments } from "./useAssistantAttachments.js";
|
|
14
|
+
export { default as AssistantAttachmentQueue } from "./AssistantAttachmentQueue.vue";
|
|
15
|
+
export { default as AssistantModelControl } from "./AssistantModelControl.vue";
|
|
16
|
+
export { default as AssistantMessageAttachments } from "./AssistantMessageAttachments.vue";
|
|
17
|
+
export { default as AssistantAttachmentPreview } from "./AssistantAttachmentPreview.vue";
|
|
18
|
+
export { default as AssistantQuestionInputs } from "./AssistantQuestionInputs.vue";
|