|
|
@@ -2,35 +2,21 @@
|
|
|
|
|
|
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, open, wrapActiveBody, t,
|
|
|
+ renderSlot, views, bindDraftMirror, wrapActiveBody,
|
|
|
}: 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 ancestry = useSessions(s => deriveAncestry(s, sessionId), shallowEqual)
|
|
|
+ const title = useSessions(s => s.byId[sessionId]?.displayTitle ?? sessionId)
|
|
|
const composerPhase = useSession(s => s.composerPhase)
|
|
|
const blank = useSession(s => s.blank)
|
|
|
const inputState = useInput(s => s)
|
|
|
@@ -69,26 +55,8 @@ export function ConversationSession({
|
|
|
>
|
|
|
{!hideChrome && (
|
|
|
<>
|
|
|
- <div className={css.crumbRow}>
|
|
|
- <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.titleRow}>
|
|
|
+ <h1 className={css.sessionTitle}>{title}</h1>
|
|
|
</div>
|
|
|
{tabs.length > 1 && (
|
|
|
<div className={css.tabs} role="tablist">
|