KeepLost/harniverse--packages-client-ui-conversation ↗★ 1
@deepseek-ai/dsh-client-ui-conversation
Conversation domain: skeleton, ordered chat flow, composer with the Host-backed busy-Enter preference, and details host 管理聊天视图、步骤折叠、输入框及会话状态,适合需要定制或扩展聊天界面的开发者。
같은 패키지 이름의 다른 저장소
- deepseek-ai/deepseek-harness--packages-client-ui-conversation
- whitelonng/dshcode--packages-client-ui-conversation
- fufankeji/deepseek-harness-studio--packages-client-ui-conversation
- op7418/pilot-harness--packages-client-ui-conversation
- See-Sol-Lab/DeepSeekGUI--packages-client-ui-conversation
- peiyuwang54/deepseek-harness-cli--packages-client-ui-conversation
설치
검증된 bundle이 없거나 호환성 검사에 실패했습니다. 먼저 저장소 설명을 읽어 주세요. 전체 README 읽기 ↗
@deepseek-ai/dsh-client-ui-conversation
English | 中文
Conversation domain: skeleton (header/tabs/composer/empty state), chat view (grouped step-summary flow, streaming tail isolation, and turn status), composer dock (session stats sticky with the input), input dock (queue rows plus the todo plan strip), details shell, and scope-addressed ConversationController. Tool presentation belongs to ui-tool.
Compaction renders as one collapsed row at the checkpoint's flow position without replacing the transcript above it. Automatic compaction uses the context-compacted title. Every completed marker with a loaded compaction/summary event shows the replaced-item and estimated-token counts and discloses the summary on click. Manual /compact starts as a running compact row; on successful settlement its explicit summary-event reference folds that command into the checkpoint row under the same React key. A completed checkpoint keeps the context-compaction icon at rest and replaces it with the collapsed or expanded disclosure only on hover or keyboard focus. Input rejection, no compactable history, cancellation, and failure retain the generic command row and its handler-authored text. Pairing never depends on adjacency because durable context may be injected while compaction is running. The framed checkpoint payload is model-facing and never renders; when the cited compaction/summary event is outside the loaded window, the checkpoint remains visible but non-expandable.
The resident conversation shell survives no-session and session transitions. Without a current session it locks message actions and presents the whole dashed composer card as a trigger for the root-scoped conversation.hero.workspace Workspace picker; the textarea remains read-only and keyboard-accessible. Selecting a Workspace connects or reuses its Host-owned blank session and opens that session without replacing the shell. The hero Workspace chip has two native buttons: the folder/name opens the existing workbench for a resolved session Workspace, while the arrow opens the picker; the name is disabled during a pending switch or without an owning Workspace, including when a loading list supplies only a cwd label. The root always owns the same scrollport and Hero/composer subtree; separate strict-session header and body outlets fill their regions when the first Session arrives, so the Workspace picker, scroll body, composer seat, and textarea retain their React and DOM identity. Blank sessions render the same composer body as active sessions, while the InputHub carries drafts across Workspace switches and mirrors them into the session store. In the active phase the session header shows only the current session title and view tabs as ordinary column chrome; fork lineage remains session data and is not projected into the header. On the phone form the header cedes its leading 56px notch to the frame's floating sidebar entry (ui-layout), so the breadcrumb row never shares the corner with that control. Beneath it the scrollport (data-conversation-scroll) holds the flowing views and the sticky composer stack (stats dock + input docks + bar). That scrollport reserves its scrollbar gutter unconditionally, and a view opting into a composer overlay leaves it a scroll container, so the input card keeps one horizontal position whether or not the transcript scrolls and whichever view tab is shown (decision). Wheel over the textarea chains: the capped draft scrolls locally until its edge, then forwards to that host. Safari alone receives a pre-paint recovery when a native edit shortens the draft and leaves stale soft-wrap overflow; draft growth, programmatic updates, and other browsers never read layout for that recovery (decision).
The conversation column's shared content-width axis (--dsh-chat-content-width, consumed by the transcript, the dock and takeover cards, and the input card at content+32px) adapts to the column instead of the viewport: ConversationRoot publishes the column's live width as --dsh-conversation-column-width through a ResizeObserver, and the axis resolves clamp(748px, 64% of the column, 920px) — the historical fixed width stays the floor, wider columns grow the reading measure up to a line-length cap. The user bubble's 525px cap rides the same axis as its historical share (≈0.702). The content font-size axis described in ui-theme drives the transcript's flow furniture (message bodies, disclosure/tool rows, think/compaction/retry rows, the clock and icon actions) through per-component calc( + delta) adoption pinned by the content-font-axis style contract; the composer draft follows the axis size itself.
Bare Tab while the slash menu is open completes the highlighted candidate through the same arbitrate pipeline as Enter (skills, commands, subagents); Shift+Tab, IME composition, a closed menu, and a menu without a highlight all keep native focus traversal.
Another plugin can make one session's composer inert through ctx.conversation.blocks: it sets a block carrying its own localized reason, and the bar renders the same disabled textarea with that reason as the placeholder — the no-workspace posture, reused. The push direction is the constraint, not a preference: the plugins that know a session cannot send (ui-model-selection, when no adapter serves its route) already depend on this package, so this package cannot read them. The model seat is the one control a block leaves live — every block this contract has is cleared by choosing a model, so locking it too would leave the composer asking for the only thing it prevents. A block is an affordance only; the Host refuses a prompt it cannot route regardless of what any client disables. The no-workspace state wins when both hold, because picking a workspace is the earlier prerequisite.
The view ring is a slot: the strict session-body registration declares the session-scoped 'conversation.view' list in its children table, that body renders the active entry through its renderSlot share (only: ), and view tabs project from registration options (id/order/label). The chat view is this package's own entry; plugins such as ui-trajectory contribute tabs through ctx.slots.register, and each view owns its chrome.
Chat business rows are independent registry contributions rather than a closed built-in union. A client plugin declaration-merges its typed ChatNodeDataMap key, registers a ConversationNodeDefinition on ctx.conversationEvents, and registers the matching keyed renderer on conversation.chat.node; it does not modify Session folds or a central renderer switch. The Conversation Node cookbook covers stable event ids, append/prepend replay, Location data, and renderer constraints.
Approvals take over the composer through the chain this package declares: ApprovalPanel registers as a selector-routed 'conversation.composer' entry (the ui-user-questions pattern) and occupies the composer in place of the InputBar while an approval wait is pending (amber strip, justification headline, paired command line from the running call's args, one-shot refuse/allow). The PendingApproval domain face in contract/slots.ts owns the wire encoding — the ApprovalResponsePayload value with the audit correlation — over the runtime's PendingWait carrier; the broadcast approval/resolved frame settles the wait and restores the composer. The runtime manager projects every approval or question wait through SessionSummary.pendingInteraction, including sessions never instantiated; ui-workspace owns its sidebar presentation. Pending waits leave the message flow entirely: questions (ui-user-questions) and approvals (ApprovalPanel) both answer through the composer takeover, so no display-only placeholder card remains. The composer's bottom-row Access seat mounts PermissionSelect, fed by the host-computed permissions projection through the standard-kit useProjection (key absence hides the chip); the chip opens a Menu-primitive dropdown whose built-in presets render under their locale product names (仅可查看 / 工作区内修改 / 完全权限 in Chinese) while a host-customized preset keeps its own name. Safe preset picks submit /permission immediately through the bar's injected command callback, while danger-full-access first opens an in-page Modal risk confirmation. The neighbouring SupervisionSelect names its modes the same way: the built-in supervised/unsupervised pair renders under its locale product names (随时监督 / 无人值守) with localized descriptions, and host-configured modes keep their own names and descriptions. The enabling action stays disabled until the user checks the acknowledgement; cancel, Escape, close, and mask click submit nothing.
The session header renders the session-scoped 'conversation.session.header.actions' list beside the title and the independent 'conversation.session.header.utilities' list at the right edge. Session context and lineage controls remain in actions; optional Session utilities cannot reorder or move them. The composer chain currency includes the current conversation session; ui-subagent selects one-shot or parent-unavailable addressed sessions for reason-specific read-only copy, while the ordinary InputBar keeps every addressed child Send-only because the continuation service exposes no public per-Activation cancellation operation and session.cancel would bypass its ownership.
Logged non-user messages render as a default-collapsed disclosure whose header names the role the runtime projected for the message — 上下文注入 for an injection, 跨会话召回 for a recalled session — followed by the producer name that projection read out of the durable source, so a reader distinguishes a skill catalog from a workspace instruction file or a recalled session without expanding. A source that names no producer shows the role alone. The shared DisclosureRow primitive gives this context surface the same compact geometry as other flow rows while retaining context semantics: the expanded body follows its content height up to a 141px scrolling cap and synthesizes no tool state or summary (historical disclosure decision, producer-label decision). That body follows the form the producer declared on its durable source: instructions names the reconciled files above their text, catalog lists the entries the source recorded instead of the model-facing prose, and every other value — absent, unknown to this version, or carrying no usable fields — renders the opaque body, which shows the model-facing text with its real line breaks and the remaining source fields. The opaque body is the documented default, not a leftover: a resumed, forked, or foreign log must render whether or not its producer is mounted here. A durable or pending steering bubble shares the user bubble's presentation unadorned; its mid-turn position in the flow is the only steering signal the transcript shows.
A Think row stays collapsed by default and exposes live reasoning throughput without expanding the chain of thought: while its reasoning block is the streaming tail, the summary switches from the settled first line to the latest non-blank line and its one-line scrollport follows each delta to the inline end. Expanding the row removes the moving summary and leaves the full reasoning in ordinary page flow, so page reading never fights an internal follower; settlement restores the stable first-line summary at the left edge (decision).
The chat view keeps Tool placement but delegates Tool presentation. Each ordered tool-call Conversation Node dispatches through the matching key of conversation.chat.node, while the details shell passes the selected call through conversation.details.tool. The assembled Web bundle registers ui-tool for that Chat Node key; it renders the Runtime-projected recursive root/child tree and owns per-name dispatch, generic rendering, and render-intent cards. The details seat alone retains a raw-result fallback when that renderer is absent.
The chat flow projects consecutive model-retry nodes across retry turns into one stable, muted status row updated to the latest attempt; every retry event remains in the runtime snapshot and session log. Its frontend countdown anchors the scheduled delay to client receipt, avoiding host/browser clock skew, rounds remaining time up to seconds, and has a one-second floor. The latest unresolved retry uses a left-to-right text shimmer. Subsequent turn facts distinguish an attempt that started from one cancelled during backoff, while the Host running bit only controls the live animation; the row then shows a static completed or cancelled label. Normal policy rows show the finite retry maximum; always policy rows show ∞. Activating the row reveals the latest exact retry delay and failure message. The client runtime removes each failed step's streaming tail before its retry node arrives, while the status remains visible after a later attempt succeeds. An unretried terminal failure renders as a persistent inline status at its turn boundary, showing the display-safe durable message and optional error code without offering an action the Host cannot fulfill; AUTH copy never echoes provider-supplied credential fragments.
TodoDock takes the 'conversation.input.dock' list slot at order: 0 — before Goal and Queue — and is the plan strip: it reads the host-computed todos projection via useProjection (standing plan: latest todo/write, retained until a later write replaces it) and renders TodoPanel, which takes the plain list, hides itself while the list is empty, and starts collapsed as a header of title plus its own ·-joined per-status counts (localized, 1 completed · 2 in progress · 1 pending, zero-count segments omitted). The dock adapter owns selection so the panel stays a pure function of its props. Anything the input-zone composer chain hides (a conversation.composer takeover such as ui-user-questions's) hides the whole dock, this strip included. The todo_write Tool row belongs to ui-tool.
QueueDock is the terminal input-dock entry at order: 20. It hides while empty, renders one pending row directly, and defaults two or more rows to a collapsed " 条排队消息" header whose button expands or collapses the complete list. The header exposes aria-expanded and aria-controls; the expanded list scrolls within a 180px height bound. An active edit or mutation keeps its rows visible, and emptying the queue restores the collapsed default for the next queue. Each visible ordinary-session row remains a single-line preview with its exact-occurrence edit, delete, and strict-steer actions; addressed subagents retain the rows as a read-only projection because their continuation transport does not expose queue mutation. If strict steer loses to a closed window, the original occurrence remains queued for normal delivery; if the driver already claimed it, normal delivery is already underway. Neither converged race displays a failure, while transport and unknown failures do.
The Host's placement-aware session/queue snapshot also carries pending steering. QueueDock filters it out, while ChatView projects it as a user-style bubble with Copy at the conversation tail; non-user next-step items (injected context) carry the context pl