@copilotkit/vue 2.0.0-rc.26 → 2.0.0-rc.27
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/dist/index.cjs +1 -1
- package/dist/index.mjs +2 -2
- package/dist/use-render-activity-message-C7b4K-TN.cjs +113 -0
- package/dist/use-render-activity-message-C7b4K-TN.cjs.map +1 -0
- package/dist/{use-render-activity-message-BVZr0Z-G.js → use-render-activity-message-Dpqx4DE8.js} +5686 -5983
- package/dist/use-render-activity-message-Dpqx4DE8.js.map +1 -0
- package/dist/v2/components/a2ui/A2UIBuiltInToolCallRenderer.d.ts +3 -6
- package/dist/v2/components/a2ui/A2UIBuiltInToolCallRenderer.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChat.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatAssistantMessage.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatFeedbackCommentPopover.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatInput.vue.d.ts +1 -16
- package/dist/v2/components/chat/CopilotChatInput.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatReasoningMessage.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatView.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotPopupViewInternal.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotSidebarViewInternal.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotThreadsDrawer.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/message-feedback.d.ts +20 -0
- package/dist/v2/components/chat/message-feedback.d.ts.map +1 -1
- package/dist/v2/hooks/use-configure-suggestions.d.ts.map +1 -1
- package/dist/v2/hooks/use-default-render-tool.d.ts +2 -7
- package/dist/v2/hooks/use-default-render-tool.d.ts.map +1 -1
- package/dist/v2/hooks/use-frontend-tool.d.ts +2 -2
- package/dist/v2/hooks/use-frontend-tool.d.ts.map +1 -1
- package/dist/v2/hooks/use-render-activity-message.d.ts.map +1 -1
- package/dist/v2/hooks/use-suggestions.d.ts.map +1 -1
- package/dist/v2/hooks/use-threads.d.ts +6 -15
- package/dist/v2/hooks/use-threads.d.ts.map +1 -1
- package/dist/v2/index.cjs +1 -1
- package/dist/v2/index.mjs +1 -1
- package/dist/v2/lib/vue-core.d.ts +16 -2
- package/dist/v2/lib/vue-core.d.ts.map +1 -1
- package/dist/v2/providers/CopilotKitProvider.vue.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/__tests__/build-entries.test.ts +3 -1
- package/src/v2/components/a2ui/A2UIBuiltInToolCallRenderer.ts +4 -19
- package/src/v2/components/chat/CopilotChat.vue +50 -218
- package/src/v2/components/chat/CopilotChatAssistantMessage.vue +28 -141
- package/src/v2/components/chat/CopilotChatFeedbackCommentPopover.vue +11 -76
- package/src/v2/components/chat/CopilotChatInput.vue +82 -269
- package/src/v2/components/chat/CopilotChatMessageView.vue +14 -64
- package/src/v2/components/chat/CopilotChatReasoningMessage.vue +25 -52
- package/src/v2/components/chat/CopilotChatView.vue +92 -135
- package/src/v2/components/chat/CopilotPopupViewInternal.vue +18 -58
- package/src/v2/components/chat/CopilotSidebarViewInternal.vue +22 -45
- package/src/v2/components/chat/CopilotThreadsDrawer.vue +6 -3
- package/src/v2/components/chat/__tests__/CopilotChatInput.test.ts +43 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.pinToSend.test.ts +43 -1
- package/src/v2/components/chat/message-feedback.ts +33 -1
- package/src/v2/hooks/use-configure-suggestions.ts +3 -4
- package/src/v2/hooks/use-default-render-tool.ts +7 -68
- package/src/v2/hooks/use-frontend-tool.ts +10 -20
- package/src/v2/hooks/use-render-activity-message.ts +4 -2
- package/src/v2/hooks/use-suggestions.ts +9 -4
- package/src/v2/hooks/use-threads.ts +6 -15
- package/src/v2/lib/vue-core.ts +19 -2
- package/src/v2/providers/CopilotKitProvider.vue +107 -184
- package/src/v2/providers/__tests__/CopilotKitProvider.a2ui.test.ts +18 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.test.ts +35 -2
- package/dist/use-render-activity-message-8p2HwK2y.cjs +0 -114
- package/dist/use-render-activity-message-8p2HwK2y.cjs.map +0 -1
- package/dist/use-render-activity-message-BVZr0Z-G.js.map +0 -1
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { AGUIConnectNotImplementedError, AbstractAgent } from "@ag-ui/client";
|
|
3
|
-
import { DEFAULT_AGENT_ID, randomUUID } from "@copilotkit/shared";
|
|
4
2
|
import {
|
|
5
3
|
computed,
|
|
6
4
|
onBeforeUnmount,
|
|
@@ -14,14 +12,15 @@ import {
|
|
|
14
12
|
} from "vue";
|
|
15
13
|
import {
|
|
16
14
|
CopilotChatDefaultLabels,
|
|
17
|
-
|
|
15
|
+
ɵChatConnection,
|
|
18
16
|
ɵcreateChatSubmitter,
|
|
19
17
|
ɵcreateLastUserMessageTracker,
|
|
20
|
-
|
|
18
|
+
ɵFreshThreadId,
|
|
19
|
+
ɵresolveAgentId,
|
|
21
20
|
ɵresolveChatNotice,
|
|
22
|
-
|
|
21
|
+
ɵsubscribeToAgentErrors,
|
|
23
22
|
} from "@copilotkit/core";
|
|
24
|
-
import type { Suggestion
|
|
23
|
+
import type { Suggestion } from "@copilotkit/core";
|
|
25
24
|
import CopilotChatConfigurationProvider from "../../providers/CopilotChatConfigurationProvider.vue";
|
|
26
25
|
import { useCopilotChatConfiguration } from "../../providers/useCopilotChatConfiguration";
|
|
27
26
|
import { useCopilotKit } from "../../providers/useCopilotKit";
|
|
@@ -92,29 +91,35 @@ const forwardedChatViewSlotNames = computed(() =>
|
|
|
92
91
|
Object.keys(componentSlots).filter((slotName) => slotName !== "chat-view"),
|
|
93
92
|
);
|
|
94
93
|
|
|
95
|
-
const generatedThreadId = ref(randomUUID());
|
|
96
94
|
const transcribeMode = ref<"input" | "transcribe" | "processing">("input");
|
|
97
95
|
const inputValue = ref(props.inputValue ?? "");
|
|
98
96
|
const isMounted = ref(false);
|
|
99
|
-
const isUnmounting = ref(false);
|
|
100
|
-
|
|
101
|
-
type ActiveConnectCycle = {
|
|
102
|
-
core: object;
|
|
103
|
-
selection: ɵThreadSelection;
|
|
104
|
-
abortController: AbortController;
|
|
105
|
-
detached: boolean;
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const activeConnectCycle = shallowRef<ActiveConnectCycle | null>(null);
|
|
109
|
-
// Keeps the agent on the thread this chat shows, orders the teardown of the
|
|
110
|
-
// previous thread, and tracks connections a send must wait for.
|
|
111
|
-
const threads = new ɵThreadSwitchCoordinator({
|
|
112
|
-
stopAgent: (params) => copilotkit.value.stopAgent(params),
|
|
113
|
-
subscribe: (subscriber) => copilotkit.value.subscribe(subscriber),
|
|
114
|
-
});
|
|
115
97
|
|
|
116
|
-
|
|
117
|
-
|
|
98
|
+
// Loads the thread this chat shows: keeps the agent on it, orders the
|
|
99
|
+
// teardown of the previous thread, owns the connect's abort and detach, and
|
|
100
|
+
// tracks connections a send must wait for.
|
|
101
|
+
const connection = new ɵChatConnection({
|
|
102
|
+
core: {
|
|
103
|
+
connectAgent: (params) => copilotkit.value.connectAgent(params),
|
|
104
|
+
stopAgent: (params) => copilotkit.value.stopAgent(params),
|
|
105
|
+
subscribe: (subscriber) => copilotkit.value.subscribe(subscriber),
|
|
106
|
+
},
|
|
107
|
+
onInspectorOverrideFailed: (requestId) => failInspectorOverride(requestId),
|
|
108
|
+
});
|
|
109
|
+
const { threads } = connection;
|
|
110
|
+
const connectionState = shallowRef(connection.getSnapshot());
|
|
111
|
+
connection.subscribe(() => {
|
|
112
|
+
connectionState.value = connection.getSnapshot();
|
|
113
|
+
});
|
|
114
|
+
// The locally minted thread used while no thread is provided. A new one is
|
|
115
|
+
// minted each time the provided thread goes away.
|
|
116
|
+
const freshThreadId = new ɵFreshThreadId();
|
|
117
|
+
|
|
118
|
+
const resolvedAgentId = computed(() =>
|
|
119
|
+
ɵresolveAgentId({
|
|
120
|
+
agentId: props.agentId,
|
|
121
|
+
chatAgentId: existingConfig.value?.agentId,
|
|
122
|
+
}),
|
|
118
123
|
);
|
|
119
124
|
// "Provided" — supplied by either the caller's prop or a wrapping
|
|
120
125
|
// CopilotChatConfigurationProvider. Distinct from `resolvedThreadId`, which
|
|
@@ -124,8 +129,8 @@ const resolvedAgentId = computed(
|
|
|
124
129
|
const providedThreadId = computed(
|
|
125
130
|
() => props.threadId ?? existingConfig.value?.threadId,
|
|
126
131
|
);
|
|
127
|
-
const baseThreadId = computed(
|
|
128
|
-
(
|
|
132
|
+
const baseThreadId = computed(() =>
|
|
133
|
+
freshThreadId.resolve(providedThreadId.value),
|
|
129
134
|
);
|
|
130
135
|
// "Explicit" means the caller actually picked this thread — via the
|
|
131
136
|
// `threadId` prop on CopilotChat or a wrapping provider that flagged its
|
|
@@ -145,12 +150,14 @@ const resolvedThreadId = computed(
|
|
|
145
150
|
const hasExplicitThreadId = computed(
|
|
146
151
|
() => inspectorThreadId.value !== null || baseHasExplicitThreadId.value,
|
|
147
152
|
);
|
|
148
|
-
|
|
149
|
-
const isConnecting = computed(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
// An explicit thread shows as connecting until its connect has settled.
|
|
154
|
+
const isConnecting = computed(() => {
|
|
155
|
+
void connectionState.value;
|
|
156
|
+
return connection.isConnecting({
|
|
157
|
+
threadId: resolvedThreadId.value,
|
|
158
|
+
explicit: hasExplicitThreadId.value,
|
|
159
|
+
});
|
|
160
|
+
});
|
|
154
161
|
const stableLabels = useShallowStableRef(computed(() => props.labels));
|
|
155
162
|
const resolvedLabels = computed(() => stableLabels.value);
|
|
156
163
|
// The labels this chat shows, the same merge its configuration provider makes.
|
|
@@ -263,61 +270,13 @@ watch(
|
|
|
263
270
|
},
|
|
264
271
|
);
|
|
265
272
|
|
|
266
|
-
watch(
|
|
267
|
-
[() => props.threadId, () => existingConfig.value?.threadId],
|
|
268
|
-
([threadId, inheritedThreadId], previousValues) => {
|
|
269
|
-
if (threadId || inheritedThreadId) {
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
if (previousValues && (previousValues[0] || previousValues[1])) {
|
|
274
|
-
generatedThreadId.value = randomUUID();
|
|
275
|
-
}
|
|
276
|
-
},
|
|
277
|
-
);
|
|
278
|
-
|
|
279
273
|
watch(
|
|
280
274
|
[() => copilotkit.value, resolvedAgentId, () => props.onError],
|
|
281
275
|
([core, agentId, onError], _old, onCleanup) => {
|
|
282
276
|
if (!onError) {
|
|
283
277
|
return;
|
|
284
278
|
}
|
|
285
|
-
|
|
286
|
-
const subscription = core.subscribe({
|
|
287
|
-
onError: (event) => {
|
|
288
|
-
if (event.context?.agentId === agentId || !event.context?.agentId) {
|
|
289
|
-
void onError({
|
|
290
|
-
error: event.error,
|
|
291
|
-
code: event.code,
|
|
292
|
-
context: event.context,
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
},
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
onCleanup(() => subscription.unsubscribe());
|
|
299
|
-
},
|
|
300
|
-
{ immediate: true },
|
|
301
|
-
);
|
|
302
|
-
|
|
303
|
-
watch(
|
|
304
|
-
[
|
|
305
|
-
() => copilotkit.value,
|
|
306
|
-
resolvedAgentId,
|
|
307
|
-
resolvedThreadId,
|
|
308
|
-
inspectorRequestId,
|
|
309
|
-
],
|
|
310
|
-
([core, agentId, threadId, requestId], _old, onCleanup) => {
|
|
311
|
-
if (!requestId) return;
|
|
312
|
-
const subscription = core.subscribe({
|
|
313
|
-
onError: (event) => {
|
|
314
|
-
if (event.code !== CopilotKitCoreErrorCode.AGENT_CONNECT_FAILED) return;
|
|
315
|
-
if (event.context?.agentId !== agentId) return;
|
|
316
|
-
if (event.context?.threadId !== threadId) return;
|
|
317
|
-
failInspectorOverride(requestId);
|
|
318
|
-
},
|
|
319
|
-
});
|
|
320
|
-
onCleanup(() => subscription.unsubscribe());
|
|
279
|
+
onCleanup(ɵsubscribeToAgentErrors(core, agentId, onError));
|
|
321
280
|
},
|
|
322
281
|
{ immediate: true },
|
|
323
282
|
);
|
|
@@ -332,150 +291,24 @@ watch(
|
|
|
332
291
|
inspectorRequestId,
|
|
333
292
|
],
|
|
334
293
|
(
|
|
335
|
-
[mounted,
|
|
294
|
+
[mounted, _core, currentAgent, threadId, isExplicit, requestId],
|
|
336
295
|
_old,
|
|
337
296
|
onCleanup,
|
|
338
297
|
) => {
|
|
339
|
-
if (!mounted) {
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
if (typeof window === "undefined") {
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
if (!currentAgent) {
|
|
298
|
+
if (!mounted || typeof window === "undefined" || !currentAgent) {
|
|
346
299
|
return;
|
|
347
300
|
}
|
|
348
|
-
|
|
349
|
-
//
|
|
350
|
-
//
|
|
351
|
-
//
|
|
352
|
-
|
|
353
|
-
// override stops the run.
|
|
354
|
-
const selection = threads.select({
|
|
301
|
+
// Pins the thread on the agent before anything issues a request, and
|
|
302
|
+
// connects a thread the caller picked. A switch to a fresh thread (a
|
|
303
|
+
// "+ New" reset) empties the agent so the welcome screen shows; moving in
|
|
304
|
+
// or out of an Inspector override stops the run.
|
|
305
|
+
const handle = connection.open({
|
|
355
306
|
agent: currentAgent,
|
|
356
307
|
threadId,
|
|
357
308
|
explicit: isExplicit,
|
|
358
309
|
inspectorRequestId: requestId,
|
|
359
310
|
});
|
|
360
|
-
|
|
361
|
-
// When the caller hasn't picked a specific thread, resolvedThreadId is
|
|
362
|
-
// a UUID minted locally. The backend has never seen it, so /connect
|
|
363
|
-
// would always 404 — skip the call. A real thread is only created
|
|
364
|
-
// once the user runs the agent for the first time.
|
|
365
|
-
if (!selection.shouldConnect) {
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const abstractAgentPrototype = AbstractAgent.prototype as unknown as {
|
|
370
|
-
connect?: unknown;
|
|
371
|
-
connectAgent?: unknown;
|
|
372
|
-
};
|
|
373
|
-
const inspectableAgent = currentAgent as unknown as {
|
|
374
|
-
connect?: unknown;
|
|
375
|
-
connectAgent?: unknown;
|
|
376
|
-
};
|
|
377
|
-
const hasCustomConnect =
|
|
378
|
-
inspectableAgent.connect !== abstractAgentPrototype.connect;
|
|
379
|
-
const hasCustomConnectAgent =
|
|
380
|
-
inspectableAgent.connectAgent !== abstractAgentPrototype.connectAgent;
|
|
381
|
-
if (!hasCustomConnect && !hasCustomConnectAgent) {
|
|
382
|
-
// No custom connect to wait for — mark as connected so the welcome
|
|
383
|
-
// suppression / suggestion gating release immediately.
|
|
384
|
-
lastConnectedThreadId.value = threadId;
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
const existingCycle = activeConnectCycle.value;
|
|
389
|
-
const hasSameDeps =
|
|
390
|
-
existingCycle &&
|
|
391
|
-
existingCycle.core === (core as object) &&
|
|
392
|
-
existingCycle.selection === selection;
|
|
393
|
-
|
|
394
|
-
let cycle: ActiveConnectCycle;
|
|
395
|
-
if (hasSameDeps && existingCycle) {
|
|
396
|
-
cycle = existingCycle;
|
|
397
|
-
} else {
|
|
398
|
-
const connectAbortController = new AbortController();
|
|
399
|
-
cycle = {
|
|
400
|
-
core: core as object,
|
|
401
|
-
selection,
|
|
402
|
-
abortController: connectAbortController,
|
|
403
|
-
detached: false,
|
|
404
|
-
};
|
|
405
|
-
activeConnectCycle.value = cycle;
|
|
406
|
-
|
|
407
|
-
// Tracked, so a send waits for the thread to load instead of racing it.
|
|
408
|
-
void threads.trackConnection(selection, () =>
|
|
409
|
-
threads
|
|
410
|
-
.whenTornDown()
|
|
411
|
-
.then(() => {
|
|
412
|
-
if (cycle.detached) return;
|
|
413
|
-
if (ɵisHttpAgent(currentAgent))
|
|
414
|
-
currentAgent.abortController = connectAbortController;
|
|
415
|
-
return core.connectAgent({ agent: currentAgent });
|
|
416
|
-
})
|
|
417
|
-
.catch((error: unknown) => {
|
|
418
|
-
if (cycle.detached) {
|
|
419
|
-
selection.clearDiscardedBaseline();
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
if (error instanceof AGUIConnectNotImplementedError) {
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
if (requestId) {
|
|
426
|
-
failInspectorOverride(requestId);
|
|
427
|
-
}
|
|
428
|
-
// The coordinator logs the failure and blocks sends on it.
|
|
429
|
-
throw error;
|
|
430
|
-
})
|
|
431
|
-
.finally(() => {
|
|
432
|
-
// Whether the connect succeeded or failed, we're no longer in
|
|
433
|
-
// the transitional "connecting" state for this thread — release
|
|
434
|
-
// the welcome / suggestion gating so the view can settle.
|
|
435
|
-
//
|
|
436
|
-
// Defer one animation frame so any trailing reactive commits
|
|
437
|
-
// from the bootstrap replay (final assistant message content)
|
|
438
|
-
// paint before isConnecting flips off. Without this, suggestions
|
|
439
|
-
// can briefly render against an incompletely-laid-out message
|
|
440
|
-
// tree and visibly snap once the last text chunk lands.
|
|
441
|
-
if (cycle.detached) {
|
|
442
|
-
selection.clearDiscardedBaseline();
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
const raf =
|
|
446
|
-
typeof requestAnimationFrame === "function"
|
|
447
|
-
? requestAnimationFrame
|
|
448
|
-
: (cb: () => void) => setTimeout(cb, 16);
|
|
449
|
-
raf(() => {
|
|
450
|
-
if (cycle.detached) return;
|
|
451
|
-
lastConnectedThreadId.value = selection.threadId;
|
|
452
|
-
});
|
|
453
|
-
}),
|
|
454
|
-
);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
onCleanup(() => {
|
|
458
|
-
const activeCycle = activeConnectCycle.value;
|
|
459
|
-
if (!activeCycle || activeCycle !== cycle) {
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
const { selection: cycleSelection } = activeCycle;
|
|
464
|
-
const shouldDetach =
|
|
465
|
-
isUnmounting.value ||
|
|
466
|
-
copilotkit.value !== activeCycle.core ||
|
|
467
|
-
agent.value !== cycleSelection.agent ||
|
|
468
|
-
resolvedThreadId.value !== cycleSelection.threadId;
|
|
469
|
-
if (!shouldDetach) {
|
|
470
|
-
return;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
cycleSelection.release();
|
|
474
|
-
activeCycle.detached = true;
|
|
475
|
-
activeCycle.abortController.abort();
|
|
476
|
-
void threads.detach(cycleSelection.agent);
|
|
477
|
-
activeConnectCycle.value = null;
|
|
478
|
-
});
|
|
311
|
+
onCleanup(() => handle.release());
|
|
479
312
|
},
|
|
480
313
|
{ immediate: true },
|
|
481
314
|
);
|
|
@@ -522,10 +355,9 @@ const transcriptionError = computed(() => {
|
|
|
522
355
|
});
|
|
523
356
|
|
|
524
357
|
onBeforeUnmount(() => {
|
|
525
|
-
isUnmounting.value = true;
|
|
526
358
|
unsubscribeSubmitter();
|
|
527
359
|
submitter.dispose();
|
|
528
|
-
|
|
360
|
+
connection.dispose();
|
|
529
361
|
});
|
|
530
362
|
|
|
531
363
|
onMounted(() => {
|
|
@@ -1,13 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
computed,
|
|
4
|
-
inject,
|
|
5
|
-
onUpdated,
|
|
6
|
-
ref,
|
|
7
|
-
shallowRef,
|
|
8
|
-
toRef,
|
|
9
|
-
watch,
|
|
10
|
-
} from "vue";
|
|
2
|
+
import { computed, inject, onUpdated, ref, toRef, watch } from "vue";
|
|
11
3
|
import type { Component } from "vue";
|
|
12
4
|
import type { AssistantMessage, Message } from "@ag-ui/core";
|
|
13
5
|
import { useCopilotChatConfiguration } from "../../providers/useCopilotChatConfiguration";
|
|
@@ -34,6 +26,7 @@ import { useInspectorShortcutsHidden } from "./inspector-shortcuts";
|
|
|
34
26
|
import { InspectorKey } from "../../providers/keys";
|
|
35
27
|
import { useFeedbackComment, useMessageFeedback } from "./message-feedback";
|
|
36
28
|
import {
|
|
29
|
+
ɵflattenUserMessageContent,
|
|
37
30
|
ɵhasAssistantMessageText,
|
|
38
31
|
ɵshouldShowAssistantToolbar,
|
|
39
32
|
} from "@copilotkit/core";
|
|
@@ -115,39 +108,9 @@ const config = useCopilotChatConfiguration();
|
|
|
115
108
|
const labels = computed(() => config.value?.labels ?? CopilotChatDefaultLabels);
|
|
116
109
|
const { copied, copy } = useCopyFeedback();
|
|
117
110
|
|
|
118
|
-
|
|
119
|
-
if (!content) {
|
|
120
|
-
return "";
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (typeof content === "string") {
|
|
124
|
-
return content;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (Array.isArray(content)) {
|
|
128
|
-
const parts = content as Array<{ type?: unknown; text?: unknown }>;
|
|
129
|
-
return parts
|
|
130
|
-
.map((part) => {
|
|
131
|
-
if (
|
|
132
|
-
part &&
|
|
133
|
-
typeof part === "object" &&
|
|
134
|
-
"type" in part &&
|
|
135
|
-
part.type === "text" &&
|
|
136
|
-
typeof part.text === "string"
|
|
137
|
-
) {
|
|
138
|
-
return part.text;
|
|
139
|
-
}
|
|
140
|
-
return "";
|
|
141
|
-
})
|
|
142
|
-
.filter((text) => text.length > 0)
|
|
143
|
-
.join("\n");
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return "";
|
|
147
|
-
}
|
|
148
|
-
|
|
111
|
+
// Assistant text, also from untyped data that sends content parts.
|
|
149
112
|
const normalizedContent = computed(() =>
|
|
150
|
-
|
|
113
|
+
ɵflattenUserMessageContent(props.message.content),
|
|
151
114
|
);
|
|
152
115
|
const hasContent = computed(() =>
|
|
153
116
|
ɵhasAssistantMessageText(normalizedContent.value),
|
|
@@ -179,10 +142,6 @@ const feedback = useMessageFeedback(toRef(props, "message"));
|
|
|
179
142
|
const comment = useFeedbackComment(feedback);
|
|
180
143
|
const rating = computed(() => feedback.value?.feedback?.rating);
|
|
181
144
|
const root = ref<HTMLElement | null>(null);
|
|
182
|
-
// The chosen thumb the comment popover points at, and the rating the open
|
|
183
|
-
// comment answers.
|
|
184
|
-
const commentAnchor = shallowRef<HTMLElement | null>(null);
|
|
185
|
-
let commentRating = rating.value;
|
|
186
145
|
|
|
187
146
|
const hasThumbsUp = computed(
|
|
188
147
|
() => typeof props.onThumbsUp === "function" || !!feedback.value,
|
|
@@ -207,106 +166,40 @@ const shouldShowToolbar = computed(() =>
|
|
|
207
166
|
|
|
208
167
|
// A custom thumb that calls its handler without an event gives no element:
|
|
209
168
|
// point at the chosen thumb once it renders, before the popover reads it.
|
|
210
|
-
|
|
211
|
-
if (
|
|
212
|
-
comment.open.value &&
|
|
213
|
-
!commentAnchor.value?.isConnected &&
|
|
214
|
-
rating.value &&
|
|
215
|
-
root.value
|
|
216
|
-
) {
|
|
217
|
-
commentAnchor.value =
|
|
218
|
-
root.value.querySelector<HTMLElement>(
|
|
219
|
-
`[data-copilotkit-rating="${rating.value}"]`,
|
|
220
|
-
) ??
|
|
221
|
-
// A custom thumb that binds only `aria-pressed`.
|
|
222
|
-
root.value.querySelector<HTMLElement>('button[aria-pressed="true"]') ??
|
|
223
|
-
null;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
169
|
+
const findCommentAnchor = () => comment.findAnchor(root.value);
|
|
226
170
|
onUpdated(findCommentAnchor);
|
|
227
171
|
watch(() => comment.open.value, findCommentAnchor, { flush: "post" });
|
|
228
172
|
|
|
229
|
-
/** Whether focus came from the keyboard; a pointer click also focuses buttons. */
|
|
230
|
-
function isFocusVisible(element: Element): boolean {
|
|
231
|
-
try {
|
|
232
|
-
return element.matches(":focus-visible");
|
|
233
|
-
} catch {
|
|
234
|
-
return false;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Checks `visibility: hidden` too, which a detached popover uses.
|
|
239
|
-
const HIDDEN_TOO = { checkVisibilityCSS: true, visibilityProperty: true };
|
|
240
|
-
|
|
241
173
|
/**
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
* (`detail` 0) moves focus into the comment, as there is no pointer. Custom
|
|
245
|
-
* thumb slots may call the handler without an event.
|
|
246
|
-
*
|
|
247
|
-
* @returns Whether the app's handler should run: true for a new rating and
|
|
248
|
-
* whenever this chat does not record ratings, false when one is removed.
|
|
174
|
+
* A thumb click. A keyboard click (`detail` 0) moves focus into the comment;
|
|
175
|
+
* custom thumb slots may call the handler without an event.
|
|
249
176
|
*/
|
|
250
177
|
function rate(value: MessageFeedbackRating, event?: MouseEvent): boolean {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
return false;
|
|
257
|
-
}
|
|
258
|
-
controls.rate(value);
|
|
259
|
-
commentRating = value;
|
|
260
|
-
commentAnchor.value =
|
|
261
|
-
event?.currentTarget instanceof HTMLElement ? event.currentTarget : null;
|
|
262
|
-
comment.show({ comment: "", focus: event?.detail === 0 });
|
|
263
|
-
return true;
|
|
178
|
+
return comment.rate(value, {
|
|
179
|
+
target:
|
|
180
|
+
event?.currentTarget instanceof HTMLElement ? event.currentTarget : null,
|
|
181
|
+
keyboard: event?.detail === 0,
|
|
182
|
+
});
|
|
264
183
|
}
|
|
265
184
|
|
|
266
185
|
/** Attributes that let the chosen thumb open and point at its comment. */
|
|
267
186
|
function thumbFeedbackProps(
|
|
268
187
|
value: MessageFeedbackRating,
|
|
269
188
|
): Record<string, unknown> {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
};
|
|
189
|
+
// Read so the thumbs update when the rating or the popover changes.
|
|
190
|
+
void feedback.value;
|
|
191
|
+
void comment.open.value;
|
|
192
|
+
const thumb = comment.thumb(value);
|
|
193
|
+
if (!thumb) return {};
|
|
194
|
+
const { handlers } = thumb;
|
|
195
|
+
if (!handlers) return thumb.attributes;
|
|
278
196
|
return {
|
|
279
|
-
...thumb,
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
enter(event.currentTarget as HTMLElement, "pointer");
|
|
286
|
-
},
|
|
287
|
-
onPointerleave: (event: PointerEvent) => {
|
|
288
|
-
if (event.pointerType !== "touch") comment.leave("pointer");
|
|
289
|
-
},
|
|
290
|
-
// Keyboard focus opens the comment; Tab moves on into its field.
|
|
291
|
-
onFocus: (event: FocusEvent) => {
|
|
292
|
-
const target = event.currentTarget as HTMLElement;
|
|
293
|
-
if (isFocusVisible(target)) enter(target, "focus");
|
|
294
|
-
},
|
|
295
|
-
onBlur: () => comment.leave(),
|
|
296
|
-
onKeydown: (event: KeyboardEvent) => {
|
|
297
|
-
const input = comment.input.value;
|
|
298
|
-
// Not while the popover hides because its thumb scrolled away.
|
|
299
|
-
if (
|
|
300
|
-
event.key === "Tab" &&
|
|
301
|
-
!event.shiftKey &&
|
|
302
|
-
comment.open.value &&
|
|
303
|
-
input &&
|
|
304
|
-
input.checkVisibility?.(HIDDEN_TOO) !== false
|
|
305
|
-
) {
|
|
306
|
-
event.preventDefault();
|
|
307
|
-
input.focus();
|
|
308
|
-
}
|
|
309
|
-
},
|
|
197
|
+
...thumb.attributes,
|
|
198
|
+
onPointerenter: handlers.pointerEnter,
|
|
199
|
+
onPointerleave: handlers.pointerLeave,
|
|
200
|
+
onFocus: handlers.focus,
|
|
201
|
+
onBlur: handlers.blur,
|
|
202
|
+
onKeydown: handlers.keyDown,
|
|
310
203
|
};
|
|
311
204
|
}
|
|
312
205
|
|
|
@@ -315,14 +208,8 @@ const thumbsDownProps = computed(() => thumbFeedbackProps("down"));
|
|
|
315
208
|
|
|
316
209
|
// The popover shows only with the toolbar and a rating. Hiding the toolbar
|
|
317
210
|
// keeps the edit; a rating removed elsewhere or Learning turned off ends it.
|
|
318
|
-
watch(
|
|
319
|
-
|
|
320
|
-
([open, showToolbar, currentRating]) => {
|
|
321
|
-
if (!open) return;
|
|
322
|
-
// Changed or removed elsewhere: the draft answered the old rating.
|
|
323
|
-
if (currentRating !== commentRating) comment.close({ save: false });
|
|
324
|
-
else if (!showToolbar) comment.close();
|
|
325
|
-
},
|
|
211
|
+
watch([() => comment.open.value, shouldShowToolbar, rating], () =>
|
|
212
|
+
comment.sync({ showsToolbar: shouldShowToolbar.value }),
|
|
326
213
|
);
|
|
327
214
|
|
|
328
215
|
function handleInspect() {
|
|
@@ -539,7 +426,7 @@ function handleRegenerate() {
|
|
|
539
426
|
:comment="comment"
|
|
540
427
|
:saved-comment="feedback.feedback.comment ?? ''"
|
|
541
428
|
:rating="feedback.feedback.rating"
|
|
542
|
-
:anchor="
|
|
429
|
+
:anchor="comment.anchor.value"
|
|
543
430
|
:message-id="message.id"
|
|
544
431
|
>
|
|
545
432
|
<template v-if="$slots['feedback-comment']" #feedback-comment="slotProps">
|