feat(workspace): composer like Claude Desktop, image thumbnails, tool icons in the picker
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
* @file SessionReplay — an ENDED session opened in its workspace
|
||||
* (`/workspace?lane=<id>&session=<sessionId>`), the way Claude Desktop opens
|
||||
* an old one: its conversation, read-only (`ConversationView`), and a prompt
|
||||
* box under it. Opening it resumes nothing; only SENDING a message does —
|
||||
* box under it (a pasted image rides along as a thumbnail). Opening it resumes nothing; only SENDING a message does —
|
||||
* `POST /api/lanes/:id/start` with `resumeSessionId` and that prompt, in the
|
||||
* first free pane slot, after which `onResumed` opens the pane (`&run=`). The
|
||||
* session's tool and folder come from its row (`GET /api/sessions/:id`), so a
|
||||
@@ -12,11 +12,11 @@
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Send } from "lucide-react";
|
||||
import { ArrowUp } from "lucide-react";
|
||||
import { api, type RunHandle } from "../../lib/api";
|
||||
import type { Lane, Session } from "../../lib/types";
|
||||
import { ConversationView } from "../conversation/ConversationView";
|
||||
import { PromptEditor } from "../run/RunSetup";
|
||||
import { PromptEditor, promptWithImages, type PromptImage } from "../run/RunSetup";
|
||||
import { freeSlot, paneRunId } from "./WorkspaceHome";
|
||||
|
||||
export function SessionReplay({
|
||||
@@ -35,6 +35,7 @@ export function SessionReplay({
|
||||
const { t } = useTranslation("lanes");
|
||||
const [session, setSession] = useState<Session | null>(null);
|
||||
const [prompt, setPrompt] = useState("");
|
||||
const [images, setImages] = useState<PromptImage[]>([]);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
@@ -42,6 +43,7 @@ export function SessionReplay({
|
||||
let live = true;
|
||||
setSession(null);
|
||||
setPrompt("");
|
||||
setImages([]);
|
||||
setError(null);
|
||||
api.sessions
|
||||
.get(sessionId)
|
||||
@@ -53,7 +55,7 @@ export function SessionReplay({
|
||||
}, [sessionId]);
|
||||
|
||||
const send = async () => {
|
||||
const text = prompt.trim();
|
||||
const text = promptWithImages(prompt, images);
|
||||
if (!text || busy) return;
|
||||
const slot = freeSlot(lane.id, liveRuns);
|
||||
if (slot === null) {
|
||||
@@ -69,6 +71,8 @@ export function SessionReplay({
|
||||
prompt: text,
|
||||
});
|
||||
setPrompt("");
|
||||
images.forEach((i) => URL.revokeObjectURL(i.url));
|
||||
setImages([]);
|
||||
onResumed(r.run_id ?? paneRunId(lane.id, slot));
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
@@ -82,8 +86,11 @@ export function SessionReplay({
|
||||
<div className="min-h-0 flex-1 overflow-hidden">
|
||||
<ConversationView sessionId={sessionId} />
|
||||
</div>
|
||||
<div className="rounded-xl border border-border bg-surface-1 p-2">
|
||||
<div className="rounded-2xl border border-border bg-surface-1 p-2 shadow-sm transition-colors focus-within:border-accent/50">
|
||||
<PromptEditor
|
||||
bare
|
||||
images={images}
|
||||
onImagesChange={setImages}
|
||||
value={prompt}
|
||||
onChange={setPrompt}
|
||||
onSubmit={() => void send()}
|
||||
@@ -98,11 +105,12 @@ export function SessionReplay({
|
||||
type="button"
|
||||
data-testid="session-replay-send"
|
||||
onClick={() => void send()}
|
||||
disabled={busy || !prompt.trim()}
|
||||
className="btn-primary ml-auto text-xs disabled:opacity-50"
|
||||
disabled={busy || (!prompt.trim() && images.length === 0)}
|
||||
aria-label={t("replay.send")}
|
||||
title={t("replay.send")}
|
||||
className="ml-auto flex h-8 w-8 items-center justify-center rounded-full bg-accent text-white transition-opacity hover:opacity-90 disabled:opacity-40"
|
||||
>
|
||||
<Send className="h-3.5 w-3.5" />
|
||||
{t("replay.send")}
|
||||
<ArrowUp className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
{error && (
|
||||
|
||||
Reference in New Issue
Block a user