Show optimistic chat for new workspaces
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildDraftPanelDescriptor } from "@/panels/draft-panel-descriptor";
|
||||
|
||||
function TestIcon() {
|
||||
return null;
|
||||
}
|
||||
|
||||
describe("buildDraftPanelDescriptor", () => {
|
||||
it("uses the initial prompt title and running loader bucket during create", () => {
|
||||
const descriptor = buildDraftPanelDescriptor({
|
||||
isCreating: true,
|
||||
pendingPrompt: "Build the dashboard",
|
||||
icon: TestIcon,
|
||||
});
|
||||
|
||||
expect(descriptor).toMatchObject({
|
||||
label: "Build the dashboard",
|
||||
subtitle: "Creating agent",
|
||||
titleState: "ready",
|
||||
statusBucket: "running",
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to the draft title for empty create prompts", () => {
|
||||
const descriptor = buildDraftPanelDescriptor({
|
||||
isCreating: true,
|
||||
pendingPrompt: " ",
|
||||
icon: TestIcon,
|
||||
});
|
||||
|
||||
expect(descriptor.label).toBe("New Agent");
|
||||
});
|
||||
|
||||
it("keeps ordinary draft tabs labeled as new agents", () => {
|
||||
const descriptor = buildDraftPanelDescriptor({ isCreating: false, icon: TestIcon });
|
||||
|
||||
expect(descriptor).toMatchObject({
|
||||
label: "New Agent",
|
||||
subtitle: "New Agent",
|
||||
titleState: "ready",
|
||||
statusBucket: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { DaemonClient } from "@server/client/daemon-client";
|
||||
import { SquarePen } from "lucide-react-native";
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { ActivityIndicator, Text, View } from "react-native";
|
||||
import ReanimatedAnimated from "react-native-reanimated";
|
||||
@@ -36,6 +37,7 @@ import { useArchiveAgent } from "@/hooks/use-archive-agent";
|
||||
import { useKeyboardShiftStyle } from "@/hooks/use-keyboard-shift-style";
|
||||
import { usePaneContext, usePaneFocus } from "@/panels/pane-context";
|
||||
import type { PanelDescriptor, PanelRegistration } from "@/panels/panel-registry";
|
||||
import { buildDraftPanelDescriptor } from "@/panels/draft-panel-descriptor";
|
||||
import {
|
||||
type HostRuntimeConnectionStatus,
|
||||
useHostRuntimeClient,
|
||||
@@ -48,6 +50,7 @@ import {
|
||||
deriveRouteBottomAnchorIntent,
|
||||
deriveRouteBottomAnchorRequest,
|
||||
} from "@/screens/agent/agent-ready-screen-bottom-anchor";
|
||||
import { WorkspaceDraftAgentTab } from "@/screens/workspace/workspace-draft-agent-tab";
|
||||
import { useCreateFlowStore } from "@/stores/create-flow-store";
|
||||
import { buildDraftStoreKey, generateDraftId } from "@/stores/draft-keys";
|
||||
import { usePanelStore } from "@/stores/panel-store";
|
||||
@@ -302,12 +305,82 @@ function AgentPanel() {
|
||||
);
|
||||
}
|
||||
|
||||
function DraftPanel() {
|
||||
const {
|
||||
serverId,
|
||||
workspaceId,
|
||||
tabId,
|
||||
target,
|
||||
openFileInWorkspace,
|
||||
openImportSheet,
|
||||
retargetCurrentTab,
|
||||
} = usePaneContext();
|
||||
const { isInteractive } = usePaneFocus();
|
||||
invariant(target.kind === "draft", "DraftPanel requires draft target");
|
||||
|
||||
const handleCreated = useCallback(
|
||||
(agentSnapshot: Parameters<typeof normalizeAgentSnapshot>[0]) => {
|
||||
const normalized = normalizeAgentSnapshot(agentSnapshot, serverId);
|
||||
useSessionStore.getState().setAgents(serverId, (prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(agentSnapshot.id, normalized);
|
||||
return next;
|
||||
});
|
||||
retargetCurrentTab({ kind: "agent", agentId: agentSnapshot.id });
|
||||
},
|
||||
[retargetCurrentTab, serverId],
|
||||
);
|
||||
|
||||
return (
|
||||
<WorkspaceDraftAgentTab
|
||||
serverId={serverId}
|
||||
workspaceId={workspaceId}
|
||||
tabId={tabId}
|
||||
draftId={target.draftId}
|
||||
initialSetup={target.setup}
|
||||
isPaneFocused={isInteractive}
|
||||
onOpenWorkspaceFile={openFileInWorkspace}
|
||||
onCreated={handleCreated}
|
||||
onOpenImportSheet={openImportSheet}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function AgentConversationPanel() {
|
||||
const { target } = usePaneContext();
|
||||
if (target.kind === "draft") {
|
||||
return <DraftPanel />;
|
||||
}
|
||||
if (target.kind === "agent") {
|
||||
return <AgentPanel />;
|
||||
}
|
||||
invariant(false, "AgentConversationPanel requires an agent or draft target");
|
||||
}
|
||||
|
||||
export const agentPanelRegistration: PanelRegistration<"agent"> = {
|
||||
kind: "agent",
|
||||
component: AgentPanel,
|
||||
component: AgentConversationPanel,
|
||||
useDescriptor: useAgentPanelDescriptor,
|
||||
};
|
||||
|
||||
export function useDraftPanelDescriptor(
|
||||
target: { kind: "draft"; draftId: string },
|
||||
context: { serverId: string },
|
||||
) {
|
||||
const pendingCreate = useCreateFlowStore((state) => {
|
||||
const pending = state.pendingByDraftId[target.draftId];
|
||||
return pending?.serverId === context.serverId && pending.lifecycle === "active"
|
||||
? pending
|
||||
: null;
|
||||
});
|
||||
|
||||
return buildDraftPanelDescriptor({
|
||||
isCreating: Boolean(pendingCreate),
|
||||
pendingPrompt: pendingCreate?.text,
|
||||
icon: SquarePen,
|
||||
});
|
||||
}
|
||||
|
||||
const EMPTY_STREAM_ITEMS: StreamItem[] = [];
|
||||
const EMPTY_PENDING_PERMISSIONS = new Map<string, PendingPermission>();
|
||||
const EMPTY_PENDING_PERMISSION_LIST: PendingPermission[] = [];
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { ComponentType } from "react";
|
||||
import type { PanelDescriptor, PanelIconProps } from "@/panels/panel-registry";
|
||||
|
||||
export function buildDraftPanelDescriptor(input: {
|
||||
isCreating: boolean;
|
||||
pendingPrompt?: string | null;
|
||||
icon: ComponentType<PanelIconProps>;
|
||||
}): PanelDescriptor {
|
||||
const { icon, isCreating, pendingPrompt } = input;
|
||||
const creatingLabel = pendingPrompt?.trim() || "New Agent";
|
||||
if (isCreating) {
|
||||
return {
|
||||
label: creatingLabel,
|
||||
subtitle: "Creating agent",
|
||||
titleState: "ready",
|
||||
icon,
|
||||
statusBucket: "running",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
label: "New Agent",
|
||||
subtitle: "New Agent",
|
||||
titleState: "ready",
|
||||
icon,
|
||||
statusBucket: null,
|
||||
};
|
||||
}
|
||||
@@ -1,65 +1,8 @@
|
||||
import { SquarePen } from "lucide-react-native";
|
||||
import { useCallback } from "react";
|
||||
import invariant from "tiny-invariant";
|
||||
import { WorkspaceDraftAgentTab } from "@/screens/workspace/workspace-draft-agent-tab";
|
||||
import { usePaneContext, usePaneFocus } from "@/panels/pane-context";
|
||||
import { AgentConversationPanel, useDraftPanelDescriptor } from "@/panels/agent-panel";
|
||||
import type { PanelRegistration } from "@/panels/panel-registry";
|
||||
import { useSessionStore } from "@/stores/session-store";
|
||||
import { normalizeAgentSnapshot } from "@/utils/agent-snapshots";
|
||||
|
||||
function useDraftPanelDescriptor() {
|
||||
return {
|
||||
label: "New Agent",
|
||||
subtitle: "New Agent",
|
||||
titleState: "ready" as const,
|
||||
icon: SquarePen,
|
||||
statusBucket: null,
|
||||
};
|
||||
}
|
||||
|
||||
function DraftPanel() {
|
||||
const {
|
||||
serverId,
|
||||
workspaceId,
|
||||
tabId,
|
||||
target,
|
||||
openFileInWorkspace,
|
||||
openImportSheet,
|
||||
retargetCurrentTab,
|
||||
} = usePaneContext();
|
||||
const { isInteractive } = usePaneFocus();
|
||||
invariant(target.kind === "draft", "DraftPanel requires draft target");
|
||||
|
||||
const handleCreated = useCallback(
|
||||
(agentSnapshot: Parameters<typeof normalizeAgentSnapshot>[0]) => {
|
||||
const normalized = normalizeAgentSnapshot(agentSnapshot, serverId);
|
||||
useSessionStore.getState().setAgents(serverId, (prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(agentSnapshot.id, normalized);
|
||||
return next;
|
||||
});
|
||||
retargetCurrentTab({ kind: "agent", agentId: agentSnapshot.id });
|
||||
},
|
||||
[retargetCurrentTab, serverId],
|
||||
);
|
||||
|
||||
return (
|
||||
<WorkspaceDraftAgentTab
|
||||
serverId={serverId}
|
||||
workspaceId={workspaceId}
|
||||
tabId={tabId}
|
||||
draftId={target.draftId}
|
||||
initialSetup={target.setup}
|
||||
isPaneFocused={isInteractive}
|
||||
onOpenWorkspaceFile={openFileInWorkspace}
|
||||
onCreated={handleCreated}
|
||||
onOpenImportSheet={openImportSheet}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const draftPanelRegistration: PanelRegistration<"draft"> = {
|
||||
kind: "draft",
|
||||
component: DraftPanel,
|
||||
component: AgentConversationPanel,
|
||||
useDescriptor: useDraftPanelDescriptor,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user