feat: revert crumbs navigation for subagents
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
- banner:
|
||||
- 'heading "Using ONE run_code program: run" [level=1]'
|
||||
- navigation "Session hierarchy":
|
||||
- 'button "Using ONE run_code program: run" [disabled]'
|
||||
- tablist:
|
||||
- tab "Chat" [selected]
|
||||
- tab "Trajectory"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
- banner:
|
||||
- heading "Use only Cordis tools. First" [level=1]
|
||||
- navigation "Session hierarchy":
|
||||
- button "Use only Cordis tools. First" [disabled]
|
||||
- tablist:
|
||||
- tab "Chat" [selected]
|
||||
- tab "Trajectory"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
- banner:
|
||||
- heading "Use the bash tool to" [level=1]
|
||||
- navigation "Session hierarchy":
|
||||
- button "Use the bash tool to" [disabled]
|
||||
- tablist:
|
||||
- tab "Chat" [selected]
|
||||
- tab "Trajectory"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
- banner:
|
||||
- heading "Reply with the single word" [level=1]
|
||||
- navigation "Session hierarchy":
|
||||
- button "Reply with the single word" [disabled]
|
||||
- tablist:
|
||||
- tab "Chat" [selected]
|
||||
- tab "Trajectory"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
- banner:
|
||||
- heading "Reply with a one-sentence description" [level=1]
|
||||
- navigation "Session hierarchy":
|
||||
- button "Reply with a one-sentence description" [disabled]
|
||||
- tablist:
|
||||
- tab "Chat" [selected]
|
||||
- tab "Trajectory"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
- banner:
|
||||
- heading "Reply with a one-sentence description" [level=1]
|
||||
- navigation "Session hierarchy":
|
||||
- button "Reply with a one-sentence description" [disabled]
|
||||
- tablist:
|
||||
- tab "Chat" [selected]
|
||||
- tab "Trajectory"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
- banner:
|
||||
- heading "Reply with a one-sentence description" [level=1]
|
||||
- navigation "Session hierarchy":
|
||||
- button "Reply with a one-sentence description" [disabled]
|
||||
- tablist:
|
||||
- tab "Chat" [selected]
|
||||
- tab "Trajectory"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
- banner:
|
||||
- heading "Reply with a one-sentence description" [level=1]
|
||||
- navigation "Session hierarchy":
|
||||
- button "Reply with a one-sentence description" [disabled]
|
||||
- tablist:
|
||||
- tab "Chat" [selected]
|
||||
- tab "Trajectory"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
- banner:
|
||||
- heading "Use the read tool twice" [level=1]
|
||||
- navigation "Session hierarchy":
|
||||
- button "Use the read tool twice" [disabled]
|
||||
- tablist:
|
||||
- tab "Chat" [selected]
|
||||
- tab "Trajectory"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
- banner:
|
||||
- 'heading "Plan a small change: add" [level=1]'
|
||||
- navigation "Session hierarchy":
|
||||
- 'button "Plan a small change: add" [disabled]'
|
||||
- tablist:
|
||||
- tab "Chat" [selected]
|
||||
- tab "Trajectory"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
- banner:
|
||||
- heading "Use the ask_user_question tool to" [level=1]
|
||||
- navigation "Session hierarchy":
|
||||
- button "Use the ask_user_question tool to" [disabled]
|
||||
- tablist:
|
||||
- tab "Chat" [selected]
|
||||
- tab "Trajectory"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
- banner:
|
||||
- heading "Reply with a one-sentence description" [level=1]
|
||||
- navigation "Session hierarchy":
|
||||
- button "Reply with a one-sentence description" [disabled]
|
||||
- tablist:
|
||||
- tab "Chat" [selected]
|
||||
- tab "Trajectory"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
- banner:
|
||||
- heading "Reply with a one-sentence description" [level=1]
|
||||
- navigation "Session hierarchy":
|
||||
- button "Reply with a one-sentence description" [disabled]
|
||||
- tablist:
|
||||
- tab "Chat" [selected]
|
||||
- tab "Trajectory"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
- banner:
|
||||
- heading "Reply with a one-sentence description" [level=1]
|
||||
- navigation "Session hierarchy":
|
||||
- button "Reply with a one-sentence description" [disabled]
|
||||
- tablist:
|
||||
- tab "Chat" [selected]
|
||||
- tab "Trajectory"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
- banner:
|
||||
- heading "Reply with a one-sentence description" [level=1]
|
||||
- navigation "Session hierarchy":
|
||||
- button "Reply with a one-sentence description" [disabled]
|
||||
- tablist:
|
||||
- tab "Chat" [selected]
|
||||
- tab "Trajectory"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
- banner:
|
||||
- heading "Use the read tool twice" [level=1]
|
||||
- navigation "Session hierarchy":
|
||||
- button "Use the read tool twice" [disabled]
|
||||
- tablist:
|
||||
- tab "Chat" [selected]
|
||||
- tab "Trajectory"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
- banner:
|
||||
- heading "Use the read tool twice" [level=1]
|
||||
- navigation "Session hierarchy":
|
||||
- button "Use the read tool twice" [disabled]
|
||||
- tablist:
|
||||
- tab "Chat" [selected]
|
||||
- tab "Trajectory"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
- banner:
|
||||
- heading "Use the ask_user_question tool to" [level=1]
|
||||
- navigation "Session hierarchy":
|
||||
- button "Use the ask_user_question tool to" [disabled]
|
||||
- tablist:
|
||||
- tab "Chat" [selected]
|
||||
- tab "Trajectory"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
- banner:
|
||||
- heading "Use the ask_user_question tool to" [level=1]
|
||||
- navigation "Session hierarchy":
|
||||
- button "Use the ask_user_question tool to" [disabled]
|
||||
- tablist:
|
||||
- tab "Chat" [selected]
|
||||
- tab "Trajectory"
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
- banner:
|
||||
- heading "event-sourcing researcher" [level=1]
|
||||
- navigation "Session hierarchy":
|
||||
- button "Ask a research subagent to"
|
||||
- text: /
|
||||
- button "event-sourcing researcher" [disabled]
|
||||
- button "1 个子代理":
|
||||
- text: 1 个子代理
|
||||
- img
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
- banner:
|
||||
- heading "Use web_search to search exactly" [level=1]
|
||||
- navigation "Session hierarchy":
|
||||
- button "Use web_search to search exactly" [disabled]
|
||||
- tablist:
|
||||
- tab "Chat" [selected]
|
||||
- tab "Trajectory"
|
||||
|
||||
@@ -165,6 +165,7 @@ export function apply(ctx: Context): void {
|
||||
// the resident parent keeps Hero and composer layout identity stable.
|
||||
slots.register({
|
||||
name: 'conversation.session',
|
||||
locale: NS,
|
||||
children: {
|
||||
'conversation.view': { kind: 'list', scope: 'session' },
|
||||
'conversation.session.header.actions': { kind: 'list', scope: 'session' },
|
||||
@@ -177,6 +178,7 @@ export function apply(ctx: Context): void {
|
||||
version: () => slots.getVersion('conversation.view'),
|
||||
},
|
||||
bindDraftMirror: write => inputHub.shell(sessionId).bindMirror(write),
|
||||
open: (id) => { sessions.open(id) },
|
||||
}),
|
||||
}, ConversationSession)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { ReactNode, RefObject } from 'react'
|
||||
import type {
|
||||
InjectFace, MaybeSnapshotSelectorHook, PropsLocale, PropsRenderSlots, PropsRuntime, PropsStore, SnapshotSelectorHook,
|
||||
} from '@deepseek-ai/dsh-client-ui-slots'
|
||||
import type { CommandNode, ConversationSnapshot, ObservableSnapshot, PendingInteraction, PendingWait, ToolCallBlock, WorkspaceId } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import type { CommandNode, ConversationSnapshot, ObservableSnapshot, PendingInteraction, PendingWait, SessionId, ToolCallBlock, WorkspaceId } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import type {} from '@deepseek-ai/dsh-client-ui-layout/client'
|
||||
import type { ComposerKeyboard, EditSelection, InputActions, InputNotice, InputState } from '../input/contract.ts'
|
||||
import type { createChatStore } from '../stores.ts'
|
||||
@@ -249,6 +249,8 @@ export interface ConversationSessionInjected {
|
||||
}
|
||||
/** Bind the input machine's draft persistence mirror to the session store. */
|
||||
bindDraftMirror: (write: (text: string) => void) => () => void
|
||||
/** Select a real Session through the runtime navigation owner. */
|
||||
open: (sessionId: SessionId) => void
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -360,6 +362,7 @@ export type ConversationSessionSlotProps =
|
||||
& PropsRenderSlots<'conversation.view' | 'conversation.session.header.actions'>
|
||||
& PropsStore<ChatStore>
|
||||
& ConversationSessionInjected
|
||||
& PropsLocale<'conversation'>
|
||||
|
||||
/** The pending approval carrier the owner dispatches into the composer chain. */
|
||||
export type ApprovalWait = PendingWait<'approval'>
|
||||
|
||||
@@ -30,6 +30,7 @@ export const zh = {
|
||||
'access.confirm.enable': '启用 Full access',
|
||||
'hero.headline': '开始构建吧',
|
||||
'hero.chooseWorkspace': '选择工作区',
|
||||
'session.hierarchy': '会话层级',
|
||||
'details.title': '详情',
|
||||
'details.close': '关闭详情',
|
||||
'details.empty': '点击消息流中的工具行查看详情',
|
||||
@@ -129,6 +130,7 @@ export const en = {
|
||||
'access.confirm.enable': 'Enable Full access',
|
||||
'hero.headline': 'Let\'s start building',
|
||||
'hero.chooseWorkspace': 'Choose workspace',
|
||||
'session.hierarchy': 'Session hierarchy',
|
||||
'details.title': 'Details',
|
||||
'details.close': 'Close details',
|
||||
'details.empty': 'Click a tool row in the message flow to view its details',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Conversation column skeleton: header (session title + tabs) over the view
|
||||
/* Conversation column skeleton: header (breadcrumb row only for subagents not fork + tabs) over the view
|
||||
area, composer InputBar at the bottom. Column width/squeeze is layout's;
|
||||
this fills its cell. Figma: Header 39:27730 (83px two-row), tabs 13px with
|
||||
a 3px active bar. */
|
||||
@@ -26,23 +26,55 @@
|
||||
.titleRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
.sessionTitle {
|
||||
.crumbs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
padding: 4px 8px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.crumbSeg {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.crumbSep {
|
||||
/* figma: "/" separators are 14px caption gray (75:7903), one tint lighter than crumb text. */
|
||||
color: var(--dsw-alias-label-caption);
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
font-weight: 500;
|
||||
color: var(--dsw-alias-label-primary);
|
||||
}
|
||||
|
||||
.crumb {
|
||||
max-width: 220px;
|
||||
overflow: hidden;
|
||||
padding: 4px 8px;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
background: transparent;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.crumb:hover:not(:disabled) {
|
||||
background: var(--dsw-alias-interactive-bg-hover);
|
||||
}
|
||||
|
||||
.crumbCurrent {
|
||||
font-weight: 500;
|
||||
color: var(--dsw-alias-label-primary);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.headerActions {
|
||||
|
||||
@@ -2,21 +2,35 @@
|
||||
|
||||
import { useEffect, useSyncExternalStore, type ReactNode } from 'react'
|
||||
import clsx from 'clsx'
|
||||
import { shallowEqual } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import type { SessionId, SessionListState, SessionSummary } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import type { ConversationSessionSlotProps } from '../contract/slots.ts'
|
||||
import css from './ConversationRoot.module.css'
|
||||
|
||||
/** Full props composed from the strict session slot contract. */
|
||||
export type ConversationSessionProps = ConversationSessionSlotProps
|
||||
|
||||
function deriveAncestry(list: SessionListState, id: SessionId): readonly SessionSummary[] {
|
||||
const chain: SessionSummary[] = []
|
||||
let cursor: SessionId | undefined = id
|
||||
while (cursor !== undefined) {
|
||||
const summary: SessionSummary | undefined = list.byId[cursor]
|
||||
if (summary === undefined || chain.includes(summary)) break
|
||||
chain.unshift(summary)
|
||||
cursor = summary.parentId
|
||||
}
|
||||
return chain
|
||||
}
|
||||
|
||||
export function ConversationSession({
|
||||
sessionId, useSession, useSessions, useInput, inputActions, useStore, actions,
|
||||
renderSlot, views, bindDraftMirror, wrapActiveBody,
|
||||
renderSlot, views, bindDraftMirror, open, wrapActiveBody, t
|
||||
}: ConversationSessionProps) {
|
||||
useSyncExternalStore(views.subscribe, views.version)
|
||||
const tabs = views.list()
|
||||
const activeId = useStore(s => s.view) ?? 'chat'
|
||||
const active = tabs.find(view => view.id === activeId) ?? tabs[0]
|
||||
const title = useSessions(s => s.byId[sessionId]?.displayTitle ?? sessionId)
|
||||
const ancestry = useSessions(s => deriveAncestry(s, sessionId), shallowEqual)
|
||||
const composerPhase = useSession(s => s.composerPhase)
|
||||
const blank = useSession(s => s.blank)
|
||||
const inputState = useInput(s => s)
|
||||
@@ -56,7 +70,25 @@ export function ConversationSession({
|
||||
{!hideChrome && (
|
||||
<>
|
||||
<div className={css.titleRow}>
|
||||
<h1 className={css.sessionTitle}>{title}</h1>
|
||||
<nav className={css.crumbs} aria-label={t('session.hierarchy')}>
|
||||
{ancestry.map((summary, index) => {
|
||||
const last = index === ancestry.length - 1
|
||||
return (
|
||||
<span key={summary.id} className={css.crumbSeg}>
|
||||
{index > 0 && <span className={css.crumbSep}>/</span>}
|
||||
<button
|
||||
type="button"
|
||||
className={clsx(css.crumb, last && css.crumbCurrent)}
|
||||
disabled={last}
|
||||
onClick={() => { open(summary.id) }}
|
||||
>
|
||||
{summary.displayTitle}
|
||||
</button>
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
{ancestry.length === 0 && <span className={css.crumbCurrent}>{sessionId}</span>}
|
||||
</nav>
|
||||
<div className={css.headerActions}>
|
||||
{renderSlot('conversation.session.header.actions', {})}
|
||||
</div>
|
||||
|
||||
@@ -111,6 +111,7 @@ function mount(
|
||||
const useInput = bindSnapshotSelector(wiring.state)
|
||||
const inputActions = wiring.actions
|
||||
const stop = vi.fn()
|
||||
const open = vi.fn()
|
||||
const slotCalls: string[] = []
|
||||
let pickerOwner: unknown
|
||||
const renderSlot = ((key: string, owner: object, opts?: { only?: string }) => {
|
||||
@@ -139,6 +140,8 @@ function mount(
|
||||
version: () => 1,
|
||||
}}
|
||||
bindDraftMirror={write => wiring.bindMirror(write)}
|
||||
open={open}
|
||||
t={t}
|
||||
{...owner}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -23,6 +23,7 @@ import type {
|
||||
import type { ConvViewProps, ViewTab } from '@deepseek-ai/dsh-client-ui-conversation/client'
|
||||
import { ConversationSession, type ConversationSessionProps } from '@deepseek-ai/dsh-client-ui-conversation/src/client/skeleton/ConversationSession.tsx'
|
||||
import { createChatStore } from '@deepseek-ai/dsh-client-ui-conversation/src/client/stores.ts'
|
||||
import { zh as conversationZh } from '@deepseek-ai/dsh-client-ui-conversation/src/client/locales.ts'
|
||||
import { apply, inject } from '@deepseek-ai/dsh-client-ui-trajectory/client'
|
||||
import { apply as nodeApply } from '@deepseek-ai/dsh-client-ui-trajectory'
|
||||
import type { TrajectoryTurnModel } from '../src/client/layout.ts'
|
||||
@@ -35,6 +36,11 @@ import { deriveTrajectoryTimeline } from '../src/client/timeline.ts'
|
||||
|
||||
const SID = 's1' as SessionId
|
||||
|
||||
// Stub of the conversation package's standard locale seat (this spec mounts
|
||||
// its ConversationSession chrome); answers from the zh dictionary and falls
|
||||
// back to the key like the real chain.
|
||||
const tConversation: ConversationSessionProps['t'] =
|
||||
key => (conversationZh as Record<string, string>)[key] ?? key
|
||||
afterEach(cleanup)
|
||||
// The chat store persists under its declared key; clear so one case's active
|
||||
// view cannot rehydrate into the next.
|
||||
@@ -213,6 +219,7 @@ function mount(slots: SlotsService, nodes: ConversationSnapshot['nodes'] = NODES
|
||||
return render(
|
||||
<ConversationSession
|
||||
sessionId={SID}
|
||||
t={tConversation}
|
||||
SessionProvider={({ children }) => children(SID)}
|
||||
useSession={useSession}
|
||||
useSessions={emptySessions()}
|
||||
@@ -229,6 +236,7 @@ function mount(slots: SlotsService, nodes: ConversationSnapshot['nodes'] = NODES
|
||||
useInput={bindSnapshotSelector(createSnapshotStore({ draft: '', draftRev: 0, phase: 'plain', queue: [] })) as never}
|
||||
inputActions={{ setDraft: vi.fn(), submit: vi.fn() }}
|
||||
bindDraftMirror={() => () => {}}
|
||||
open={vi.fn()}
|
||||
/>,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user