Spaces:
Sleeping
Sleeping
Andrew
commited on
Commit
·
a0e1d3c
1
Parent(s):
3a8705d
refactor(ui): update ChatWindow for branching and persona UI
Browse files
src/lib/components/chat/ChatWindow.svelte
CHANGED
|
@@ -47,12 +47,18 @@
|
|
| 47 |
preprompt?: string | undefined;
|
| 48 |
personaId?: string;
|
| 49 |
lockedPersonaId?: string;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
files?: File[];
|
| 51 |
onmessage?: (content: string) => void;
|
| 52 |
onstop?: () => void;
|
| 53 |
-
onretry?: (payload: { id: Message["id"]; content?: string }) => void;
|
| 54 |
oncontinue?: (payload: { id: Message["id"] }) => void;
|
| 55 |
onshowAlternateMsg?: (payload: { id: Message["id"] }) => void;
|
|
|
|
| 56 |
}
|
| 57 |
|
| 58 |
let {
|
|
@@ -66,12 +72,14 @@
|
|
| 66 |
preprompt = undefined,
|
| 67 |
personaId,
|
| 68 |
lockedPersonaId,
|
|
|
|
| 69 |
files = $bindable([]),
|
| 70 |
onmessage,
|
| 71 |
onstop,
|
| 72 |
onretry,
|
| 73 |
oncontinue,
|
| 74 |
onshowAlternateMsg,
|
|
|
|
| 75 |
}: Props = $props();
|
| 76 |
|
| 77 |
let isReadOnly = $derived(!models.some((model) => model.id === currentModel.id));
|
|
@@ -82,6 +90,26 @@
|
|
| 82 |
if (!personaId) return undefined;
|
| 83 |
return $userSettings.personas?.find((p) => p.id === personaId);
|
| 84 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
let message: string = $state("");
|
| 87 |
let shareModalOpen = $state(false);
|
|
@@ -368,9 +396,12 @@
|
|
| 368 |
personaName={message.from === "assistant" && !message.personaResponses ? persona?.name : undefined}
|
| 369 |
personaOccupation={message.from === "assistant" && !message.personaResponses ? persona?.jobSector : undefined}
|
| 370 |
personaStance={message.from === "assistant" && !message.personaResponses ? persona?.stance : undefined}
|
|
|
|
|
|
|
| 371 |
bind:editMsdgId
|
| 372 |
onretry={(payload) => onretry?.(payload)}
|
| 373 |
onshowAlternateMsg={(payload) => onshowAlternateMsg?.(payload)}
|
|
|
|
| 374 |
/>
|
| 375 |
{/each}
|
| 376 |
{#if isReadOnly}
|
|
@@ -427,7 +458,6 @@
|
|
| 427 |
<div
|
| 428 |
class="no-scrollbar mb-3 flex w-full select-none justify-start gap-2 overflow-x-auto whitespace-nowrap text-gray-400 dark:text-gray-500"
|
| 429 |
>
|
| 430 |
-
<!-- <span class=" text-gray-500 dark:text-gray-400">Follow ups</span> -->
|
| 431 |
{#each routerFollowUps as followUp}
|
| 432 |
<button
|
| 433 |
class="flex items-center gap-1 rounded-lg bg-gray-100/90 px-2 py-0.5 text-center text-sm backdrop-blur hover:text-gray-500 dark:bg-gray-700/50 dark:hover:text-gray-400"
|
|
@@ -460,19 +490,6 @@
|
|
| 460 |
<div class="w-full">
|
| 461 |
<div class="flex w-full *:mb-3">
|
| 462 |
{#if !loading}
|
| 463 |
-
<!-- Retry button commented out - regeneration disabled -->
|
| 464 |
-
<!-- {#if lastIsError}
|
| 465 |
-
<RetryBtn
|
| 466 |
-
classNames="ml-auto"
|
| 467 |
-
onClick={() => {
|
| 468 |
-
if (lastMessage && lastMessage.ancestors) {
|
| 469 |
-
onretry?.({
|
| 470 |
-
id: lastMessage.id,
|
| 471 |
-
});
|
| 472 |
-
}
|
| 473 |
-
}}
|
| 474 |
-
/>
|
| 475 |
-
{:else -->
|
| 476 |
{#if messages && lastMessage && lastMessage.interrupted && !isReadOnly}
|
| 477 |
<div class="ml-auto gap-2">
|
| 478 |
<ContinueBtn
|
|
@@ -563,7 +580,11 @@
|
|
| 563 |
<ChatInput value="Sorry, something went wrong. Please try again." disabled={true} />
|
| 564 |
{:else}
|
| 565 |
<ChatInput
|
| 566 |
-
placeholder={isReadOnly
|
|
|
|
|
|
|
|
|
|
|
|
|
| 567 |
{loading}
|
| 568 |
bind:value={message}
|
| 569 |
bind:files
|
|
|
|
| 47 |
preprompt?: string | undefined;
|
| 48 |
personaId?: string;
|
| 49 |
lockedPersonaId?: string;
|
| 50 |
+
branchState?: {
|
| 51 |
+
messageId: string;
|
| 52 |
+
personaId: string;
|
| 53 |
+
personaName: string;
|
| 54 |
+
} | null;
|
| 55 |
files?: File[];
|
| 56 |
onmessage?: (content: string) => void;
|
| 57 |
onstop?: () => void;
|
| 58 |
+
onretry?: (payload: { id: Message["id"]; content?: string; personaId?: string }) => void;
|
| 59 |
oncontinue?: (payload: { id: Message["id"] }) => void;
|
| 60 |
onshowAlternateMsg?: (payload: { id: Message["id"] }) => void;
|
| 61 |
+
onbranch?: (messageId: string, personaId: string) => void;
|
| 62 |
}
|
| 63 |
|
| 64 |
let {
|
|
|
|
| 72 |
preprompt = undefined,
|
| 73 |
personaId,
|
| 74 |
lockedPersonaId,
|
| 75 |
+
branchState,
|
| 76 |
files = $bindable([]),
|
| 77 |
onmessage,
|
| 78 |
onstop,
|
| 79 |
onretry,
|
| 80 |
oncontinue,
|
| 81 |
onshowAlternateMsg,
|
| 82 |
+
onbranch,
|
| 83 |
}: Props = $props();
|
| 84 |
|
| 85 |
let isReadOnly = $derived(!models.some((model) => model.id === currentModel.id));
|
|
|
|
| 90 |
if (!personaId) return undefined;
|
| 91 |
return $userSettings.personas?.find((p) => p.id === personaId);
|
| 92 |
});
|
| 93 |
+
|
| 94 |
+
// Compute branch points: messages that other messages branch from
|
| 95 |
+
// Map of messageId -> array of persona names that branched from it
|
| 96 |
+
let branchPointInfo = $derived.by(() => {
|
| 97 |
+
const branchPoints = new Map<string, string[]>();
|
| 98 |
+
messages.forEach(msg => {
|
| 99 |
+
if (msg.branchedFrom) {
|
| 100 |
+
const messageId = msg.branchedFrom.messageId;
|
| 101 |
+
const personaName = $userSettings.personas?.find(p => p.id === msg.branchedFrom?.personaId)?.name || msg.branchedFrom.personaId;
|
| 102 |
+
if (!branchPoints.has(messageId)) {
|
| 103 |
+
branchPoints.set(messageId, []);
|
| 104 |
+
}
|
| 105 |
+
const personas = branchPoints.get(messageId)!;
|
| 106 |
+
if (!personas.includes(personaName)) {
|
| 107 |
+
personas.push(personaName);
|
| 108 |
+
}
|
| 109 |
+
}
|
| 110 |
+
});
|
| 111 |
+
return branchPoints;
|
| 112 |
+
});
|
| 113 |
|
| 114 |
let message: string = $state("");
|
| 115 |
let shareModalOpen = $state(false);
|
|
|
|
| 396 |
personaName={message.from === "assistant" && !message.personaResponses ? persona?.name : undefined}
|
| 397 |
personaOccupation={message.from === "assistant" && !message.personaResponses ? persona?.jobSector : undefined}
|
| 398 |
personaStance={message.from === "assistant" && !message.personaResponses ? persona?.stance : undefined}
|
| 399 |
+
{branchState}
|
| 400 |
+
branchPersonas={branchPointInfo.get(message.id) ?? []}
|
| 401 |
bind:editMsdgId
|
| 402 |
onretry={(payload) => onretry?.(payload)}
|
| 403 |
onshowAlternateMsg={(payload) => onshowAlternateMsg?.(payload)}
|
| 404 |
+
onbranch={(messageId, personaId) => onbranch?.(messageId, personaId)}
|
| 405 |
/>
|
| 406 |
{/each}
|
| 407 |
{#if isReadOnly}
|
|
|
|
| 458 |
<div
|
| 459 |
class="no-scrollbar mb-3 flex w-full select-none justify-start gap-2 overflow-x-auto whitespace-nowrap text-gray-400 dark:text-gray-500"
|
| 460 |
>
|
|
|
|
| 461 |
{#each routerFollowUps as followUp}
|
| 462 |
<button
|
| 463 |
class="flex items-center gap-1 rounded-lg bg-gray-100/90 px-2 py-0.5 text-center text-sm backdrop-blur hover:text-gray-500 dark:bg-gray-700/50 dark:hover:text-gray-400"
|
|
|
|
| 490 |
<div class="w-full">
|
| 491 |
<div class="flex w-full *:mb-3">
|
| 492 |
{#if !loading}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 493 |
{#if messages && lastMessage && lastMessage.interrupted && !isReadOnly}
|
| 494 |
<div class="ml-auto gap-2">
|
| 495 |
<ContinueBtn
|
|
|
|
| 580 |
<ChatInput value="Sorry, something went wrong. Please try again." disabled={true} />
|
| 581 |
{:else}
|
| 582 |
<ChatInput
|
| 583 |
+
placeholder={isReadOnly
|
| 584 |
+
? "This conversation is read-only."
|
| 585 |
+
: branchState
|
| 586 |
+
? `Branched from ${branchState.personaName}`
|
| 587 |
+
: "Ask anything"}
|
| 588 |
{loading}
|
| 589 |
bind:value={message}
|
| 590 |
bind:files
|