Something went wrong. Try again.
small gleam coding and persistent agent daemon with a detachable cli and a canvas ui forked from okami.mom/albedo
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122<script lang="ts"> import type { ChipKind, SessionWindow, WindowProps } from '../board.svelte'; import Chat from '../chat/Chat.svelte'; import ModelPicker from '../chat/ModelPicker.svelte'; import { place } from '../format'; import { boards } from '../boards.svelte'; import { busy } from '../live.svelte'; import { prefs } from '../prefs.svelte'; import AgentsButton from '../AgentsButton.svelte'; import ArtifactsButton from '../ArtifactsButton.svelte'; import SkillsButton from '../SkillsButton.svelte'; import JobsButton from '../JobsButton.svelte'; import { sessions } from '../sessions.svelte'; import WindowFrame from './WindowFrame.svelte';
let { id, data, selected }: WindowProps<SessionWindow> = $props(); const session = $derived(sessions.byId.get(data.sessionId)); const working = $derived(busy(session)); const rootId = $derived(session?.root_id ?? data.sessionId); const reading = $derived(sessions.readings.get(data.sessionId)); const docked = (chip: ChipKind) => !boards.board?.hasChip(id, chip);
$effect(() => { const sid = data.sessionId; return () => sessions.clearReading(sid); });
// An agent's window is not in the list of sessions a person started, so its // record is read when the board opens it again. let missing = $state(false); $effect(() => { if (session) missing = false; else if (sessions.loaded) sessions.ensure(data.sessionId).then((found) => (missing = !found)); });</script>
<!-- A chat's chips sit in its title bar, or under its composer when bars are compact. -->{#snippet chips(under: boolean)} {#if session && docked('model')} <span class="chip-slot nodrag" data-chip="model"> <ModelPicker {session} usage={reading?.usage ?? null} entries={reading?.entries ?? []} down={!under} /> </span> {/if} {#if docked('artifacts')} <span class="chip-slot nodrag" data-chip="artifacts"> <ArtifactsButton sessionId={data.sessionId} busy={working} up={under} onopen={(artifact) => boards.board?.openArtifact(artifact.id, data.sessionId, artifact.version, id)} /> </span> {/if} {#if docked('skills')} <span class="chip-slot nodrag" data-chip="skills"> <SkillsButton sessionId={data.sessionId} busy={working} up={under} /> </span> {/if} {#if docked('agents')} <span class="chip-slot nodrag" data-chip="agents"> <AgentsButton sessionId={data.sessionId} {rootId} busy={working} up={under} onopen={(agent) => (sessions.adopt(agent), boards.board?.openSession(agent.id, id))} ontree={() => boards.board?.openAgents(rootId, id)} /> </span> {/if} {#if docked('jobs')} <span class="chip-slot nodrag" data-chip="jobs"> <JobsButton sessionId={data.sessionId} up={under} onopen={(job) => boards.board?.openTerminal(data.sessionId, job, id)} /> </span> {/if} {#if session} <button class="terminal nodrag" aria-label="Open a terminal in {place(session.workspace)}" title="Open a terminal in {session.workspace}" onclick={() => boards.board?.openPty({ cwd: session.workspace, sessionId: data.sessionId, parent: id })}> <svg viewBox="0 0 16 16" width="15" height="15" aria-hidden="true"><path d="M3 4.5l3.5 3.5L3 11.5M8 12h5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /></svg> </button> {/if}{/snippet}{#snippet bar()}{@render chips(false)}{/snippet}{#snippet below()}{@render chips(true)}{/snippet}
<WindowFrame {id} selected={!!selected} title={session?.name || 'untitled session'} place={session ? place(session.workspace) : undefined} meta={prefs.compactBars ? undefined : bar}> {#if session || !missing} <Chat sessionId={data.sessionId} {session} windowId={id} chips={prefs.compactBars ? below : undefined} onusage={(next, items) => sessions.recordReading(data.sessionId, next, items)} /> {:else} <p class="gone">(・_・;) this session is no longer on the daemon</p> {/if}</WindowFrame>
<style> .chip-slot { display: contents; }
/* As the menu icons beside it (MenuButton's .icon). */ .terminal { display: inline-flex; flex: none; align-items: center; height: 26px; padding: 0 7px; color: var(--muted); background: var(--well); border: 0; border-radius: 8px; cursor: pointer; transition: background-color 120ms ease, color 120ms ease; } .terminal:hover { color: var(--text); background: var(--field); } .gone { margin: 20px; color: var(--muted); }</style>