chat-ui-agent-demo / src /lib /components /agent /AgentQueue.svelte
Mike0021's picture
deploy: production agent journey fixes
6f07329 verified
Raw History Blame Contribute Delete
6.83 kB
<script lang="ts">
import type { MessageAgentQueueUpdate } from "$lib/types/MessageUpdate";
import type { AgentQueueMutation, AgentQueueReplacement } from "./activityTypes";
import {
agentQueueMatchesSnapshot,
captureAgentQueueMutationTarget,
createAgentQueueReplacement,
type AgentQueueMutationTarget,
} from "./agentQueueMutations";
import CarbonEdit from "~icons/carbon/edit";
import CarbonTrashCan from "~icons/carbon/trash-can";
import CarbonCheckmark from "~icons/carbon/checkmark";
import CarbonClose from "~icons/carbon/close";
interface Props {
update: MessageAgentQueueUpdate;
onchange?: (replacement: AgentQueueReplacement) => void | Promise<void>;
}
let { update, onchange }: Props = $props();
let editingTarget = $state<AgentQueueMutationTarget | undefined>();
let editValue = $state("");
let mutationBusy = $state(false);
let mutationError = $state("");
function startEdit(queue: AgentQueueMutation["queue"], index: number) {
const target = captureAgentQueueMutationTarget(update, queue, index);
if (!target) return;
editingTarget = target;
editValue = target.value;
mutationError = "";
}
async function submitEdit() {
const value = editValue.trim();
const target = editingTarget;
if (!value || !target || !onchange || mutationBusy) return;
await replaceQueue(target, "edit", value);
}
async function replaceQueue(
target: AgentQueueMutationTarget,
action: AgentQueueMutation["action"],
value?: string
) {
if (!onchange || mutationBusy || update.truncated) return;
const change = createAgentQueueReplacement(target, action, value);
if (!change) return;
mutationBusy = true;
mutationError = "";
try {
await onchange(change);
editingTarget = undefined;
} catch (error) {
mutationError =
error instanceof Error ? error.message : "Unable to update pending instructions.";
} finally {
mutationBusy = false;
}
}
function cancelEdit() {
editingTarget = undefined;
editValue = "";
}
$effect(() => {
const target = editingTarget;
if (!target || mutationBusy || agentQueueMatchesSnapshot(update, target.expected)) return;
cancelEdit();
mutationError =
"Pending instructions changed while you were editing. Review the updated queue and try again.";
});
const groups = $derived([
{ key: "steering" as const, label: "Steering now", values: update.steering },
{ key: "followUp" as const, label: "Queued for after", values: update.followUp },
]);
</script>
{#if update.steering.length || update.followUp.length}
<section
aria-label="Pending agent instructions"
class="rounded-xl border border-blue-200 bg-blue-50/60 p-3 dark:border-blue-900 dark:bg-blue-950/20"
>
<h3 class="mb-2 text-xs font-semibold text-blue-900 dark:text-blue-200">
Pending instructions
</h3>
<div class="space-y-2.5">
{#each groups as group (group.key)}
{#if group.values.length}
<div>
<p
class="mb-1 text-[10px] font-semibold tracking-wide text-blue-700 uppercase dark:text-blue-300"
>
{group.label}
</p>
<ul class="space-y-1.5">
{#each group.values as value, index (`${group.key}-${index}-${value}`)}
<li
class="rounded-lg border border-blue-200 bg-white/80 p-2 dark:border-blue-900 dark:bg-gray-900/60"
>
{#if editingTarget?.queue === group.key && editingTarget.index === index && editingTarget.value === value}
<form
class="flex flex-col gap-2 sm:flex-row"
onsubmit={(event) => {
event.preventDefault();
submitEdit();
}}
>
<label class="min-w-0 flex-1">
<span class="sr-only">Edit pending instruction</span>
<input
bind:value={editValue}
maxlength="1024"
disabled={mutationBusy}
class="min-h-11 w-full rounded-lg border border-blue-300 bg-white px-2 text-sm outline-hidden focus:ring-2 focus:ring-blue-500 dark:border-blue-700 dark:bg-gray-900"
/>
</label>
<button
type="submit"
disabled={mutationBusy || !editValue.trim()}
class="grid min-h-11 min-w-11 place-items-center rounded-lg text-blue-700 hover:bg-blue-100 focus-visible:ring-2 focus-visible:ring-blue-500 dark:text-blue-300 dark:hover:bg-blue-900/40"
aria-label="Save pending instruction"
>
<CarbonCheckmark class="size-4" />
</button>
<button
type="button"
disabled={mutationBusy}
class="grid min-h-11 min-w-11 place-items-center rounded-lg text-gray-500 hover:bg-gray-100 focus-visible:ring-2 focus-visible:ring-blue-500 dark:hover:bg-gray-800"
aria-label="Cancel editing"
onclick={cancelEdit}
>
<CarbonClose class="size-4" />
</button>
</form>
{:else}
<div class="flex items-start gap-1.5">
<span
class="min-w-0 flex-1 text-xs leading-relaxed text-gray-700 dark:text-gray-200"
>{value}</span
>
{#if onchange && !update.truncated}
<button
type="button"
disabled={mutationBusy}
class="grid size-9 shrink-0 place-items-center rounded-lg text-gray-500 hover:bg-blue-100 hover:text-blue-800 focus-visible:ring-2 focus-visible:ring-blue-500 dark:hover:bg-blue-900/40 dark:hover:text-blue-200"
aria-label="Edit pending instruction"
onclick={() => startEdit(group.key, index)}
>
<CarbonEdit class="size-4" />
</button>
<button
type="button"
disabled={mutationBusy}
class="grid size-9 shrink-0 place-items-center rounded-lg text-gray-500 hover:bg-red-100 hover:text-red-700 focus-visible:ring-2 focus-visible:ring-blue-500 dark:hover:bg-red-950/40 dark:hover:text-red-300"
aria-label="Remove pending instruction"
onclick={() => {
const target = captureAgentQueueMutationTarget(
update,
group.key,
index
);
if (target) void replaceQueue(target, "remove");
}}
>
<CarbonTrashCan class="size-4" />
</button>
{/if}
</div>
{/if}
</li>
{/each}
</ul>
</div>
{/if}
{/each}
</div>
{#if update.truncated}
<p class="mt-2 text-[11px] text-amber-700 dark:text-amber-300">
Queue truncated. Editing is disabled until the full queue is visible.
</p>
{/if}
{#if mutationError}
<p role="alert" class="mt-2 text-[11px] text-red-700 dark:text-red-300">
{mutationError}
</p>
{/if}
</section>
{/if}