mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
feat(native-chat): show the workspace name on each reconnect row
A row read `codex · folder:8f3a1c22-… · 8 hours ago`. Recognising which chats would reconnect is the entire point of the list, and at twenty rows a UUID identifies nothing. No RPC or host change was needed: the renderer can already resolve this id. Resolved the way automation dispatch resolves the same id space (resolveAutomationDispatchWorkspace) -- a folder workspace by its full `folder:<uuid>` key via getKnownWorktreeById, a git worktree by its bare `repoId::path` id via allWorktrees. Both return a Worktree, whose displayName is a required field, and DetectedWorktree extends Worktree so either shape answers. Falls back to the id when nothing resolves, which is what the row showed before and also covers the window before the worktree store has hydrated. The lookup lives in a per-row subcomponent because a hook cannot run inside `map`, and its selector returns a primitive string so repeated selector runs cannot churn referential equality.
This commit is contained in:
@@ -15,6 +15,7 @@ import { useAppStore } from '../store'
|
|||||||
import { callStructuredAgentSession } from '@/runtime/structured-agent-session-client'
|
import { callStructuredAgentSession } from '@/runtime/structured-agent-session-client'
|
||||||
import { translate } from '@/i18n/i18n'
|
import { translate } from '@/i18n/i18n'
|
||||||
import { formatUiRelativeTime } from '@/i18n/relative-time-format'
|
import { formatUiRelativeTime } from '@/i18n/relative-time-format'
|
||||||
|
import { parseWorkspaceKey } from '../../../shared/workspace-scope'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* What would be reconnected, shown before anything runs.
|
* What would be reconnected, shown before anything runs.
|
||||||
@@ -66,6 +67,69 @@ function announceResumed(count: number): void {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A workspace id is not a name. `folder:<uuid>` identifies nothing at twenty rows, and recognising
|
||||||
|
* which chats would reconnect is the entire point of this list.
|
||||||
|
*
|
||||||
|
* Resolved the way automation dispatch already resolves the same id space: a folder workspace is
|
||||||
|
* keyed by its full `folder:<uuid>` key, a git worktree by its bare `repoId::path` id. Falls back
|
||||||
|
* to the id — what the row showed before — when nothing is resolvable, which also covers the window
|
||||||
|
* before the worktree store has hydrated.
|
||||||
|
*/
|
||||||
|
function useWorkspaceLabel(workspaceId: string): string {
|
||||||
|
// Returns a primitive, so the selector re-runs freely without churning referential equality.
|
||||||
|
return useAppStore((store) => {
|
||||||
|
const scope = parseWorkspaceKey(workspaceId)
|
||||||
|
const worktree =
|
||||||
|
scope?.type === 'folder'
|
||||||
|
? store.getKnownWorktreeById(workspaceId)
|
||||||
|
: store.allWorktrees().find((entry) => entry.id === workspaceId)
|
||||||
|
return worktree?.displayName ?? workspaceId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Its own component because a hook cannot run inside `map`, and the label needs one per row. */
|
||||||
|
function ResumeOnRestartRow({
|
||||||
|
candidate,
|
||||||
|
listedAt,
|
||||||
|
busy,
|
||||||
|
onReconnect
|
||||||
|
}: {
|
||||||
|
candidate: ResumeCandidate
|
||||||
|
listedAt: number
|
||||||
|
busy: boolean
|
||||||
|
onReconnect: () => void
|
||||||
|
}): React.JSX.Element {
|
||||||
|
const workspaceLabel = useWorkspaceLabel(candidate.workspaceId)
|
||||||
|
return (
|
||||||
|
<li className="flex items-center gap-2">
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<p className="truncate text-xs font-medium">
|
||||||
|
{candidate.latestPrompt.trim() ||
|
||||||
|
translate('auto.components.NativeChatResumeOnRestartModal.untitled', 'Untitled chat')}
|
||||||
|
</p>
|
||||||
|
{/* Age matters: the TTL is 24h, so an eight-hour-old offer must not look like one from a
|
||||||
|
minute ago. */}
|
||||||
|
<p className="truncate text-[11px] text-muted-foreground">
|
||||||
|
{`${candidate.agent} · ${workspaceLabel} · ${formatUiRelativeTime(
|
||||||
|
candidate.recordedAt - listedAt
|
||||||
|
)}`}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-7 shrink-0 gap-1 px-2"
|
||||||
|
disabled={busy}
|
||||||
|
onClick={onReconnect}
|
||||||
|
>
|
||||||
|
<Play className="size-3" />
|
||||||
|
{translate('auto.components.NativeChatResumeOnRestartModal.resume', 'Reconnect')}
|
||||||
|
</Button>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function NativeChatResumeOnRestartModal(): React.JSX.Element | null {
|
export function NativeChatResumeOnRestartModal(): React.JSX.Element | null {
|
||||||
const structuredEnabled = useAppStore(
|
const structuredEnabled = useAppStore(
|
||||||
(store) => store.settings?.experimentalStructuredNativeChat === true
|
(store) => store.settings?.experimentalStructuredNativeChat === true
|
||||||
@@ -207,34 +271,13 @@ export function NativeChatResumeOnRestartModal(): React.JSX.Element | null {
|
|||||||
className="flex min-h-0 flex-col gap-1 overflow-y-auto scrollbar-sleek rounded-md border bg-muted/35 p-1.5"
|
className="flex min-h-0 flex-col gap-1 overflow-y-auto scrollbar-sleek rounded-md border bg-muted/35 p-1.5"
|
||||||
>
|
>
|
||||||
{candidates.map((candidate) => (
|
{candidates.map((candidate) => (
|
||||||
<li key={candidate.sessionId} className="flex items-center gap-2">
|
<ResumeOnRestartRow
|
||||||
<div className="min-w-0 flex-1">
|
key={candidate.sessionId}
|
||||||
<p className="truncate text-xs font-medium">
|
candidate={candidate}
|
||||||
{candidate.latestPrompt.trim() ||
|
listedAt={listedAt}
|
||||||
translate(
|
busy={busy}
|
||||||
'auto.components.NativeChatResumeOnRestartModal.untitled',
|
onReconnect={() => void resume([candidate.sessionId])}
|
||||||
'Untitled chat'
|
/>
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
{/* Age matters: the TTL is 24h, so an eight-hour-old offer must not look like one
|
|
||||||
from a minute ago. */}
|
|
||||||
<p className="truncate text-[11px] text-muted-foreground">
|
|
||||||
{`${candidate.agent} · ${candidate.workspaceId} · ${formatUiRelativeTime(
|
|
||||||
candidate.recordedAt - listedAt
|
|
||||||
)}`}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="h-7 shrink-0 gap-1 px-2"
|
|
||||||
disabled={busy}
|
|
||||||
onClick={() => void resume([candidate.sessionId])}
|
|
||||||
>
|
|
||||||
<Play className="size-3" />
|
|
||||||
{translate('auto.components.NativeChatResumeOnRestartModal.resume', 'Reconnect')}
|
|
||||||
</Button>
|
|
||||||
</li>
|
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user