Merge remote-tracking branch 'origin/master' into feat/web-cards-toolrow

# Conflicts:
#	packages/client/ui-conversation/README.i18n.yaml
This commit is contained in:
Chinesezjc
2026-07-31 17:45:31 +08:00
130 changed files with 2023 additions and 936 deletions
@@ -0,0 +1,56 @@
// CompactionItem: the one row a landed compaction contributes to the flow.
// The conversation it shadowed on the model surface stays above it, so this
// marker reports where the model stopped seeing that history — it never
// replaces it. The framed checkpoint payload is written for the model and is
// not rendered; the disclosure shows the summary from the checkpoint's own
// provenance, and a window cut that left that provenance outside makes the row
// non-expandable rather than empty.
import { memo, useState } from 'react'
import type { CompactionSummaryNode } from '@deepseek-ai/dsh-client-runtime/client'
import {
IconChevronDownOutline14,
IconChevronRightOutline14,
MarkdownText,
} from '@deepseek-ai/dsh-client-ui-primitives'
import type { ChatViewSlotProps } from '../contract/slots.ts'
import css from './MessageItem.module.css'
interface CompactionItemProps {
node: CompactionSummaryNode
/** The owning view's locale seat. */
t: ChatViewSlotProps['t']
}
/**
* The collapsed-by-default compaction marker.
* @param props - the marker node off the snapshot cache.
* @returns the marker row, with the summary disclosure when one is available.
*/
export const CompactionItem = memo(function CompactionItem({ node, t }: CompactionItemProps) {
const [expanded, setExpanded] = useState(false)
const expandable = node.summary !== null
const open = expandable && expanded
return (
<div className={css.compactionRow}>
<button
type="button"
className={css.compactionButton}
disabled={!expandable}
aria-expanded={expandable ? open : undefined}
onClick={() => { setExpanded(value => !value) }}
>
<span className={css.compactionLeading}>
{open ? <IconChevronDownOutline14 /> : <IconChevronRightOutline14 />}
</span>
<span className={css.compactionTitle}>{t('message.compaction')}</span>
<span className={css.compactionSep} aria-hidden />
<span className={css.compactionSummary}>
{expandable ? t('message.compaction.expand') : t('message.compaction.unavailable')}
</span>
</button>
{open && node.summary !== null
&& <div className={css.compactionBody}><MarkdownText text={node.summary} /></div>}
</div>
)
})
@@ -34,6 +34,81 @@
padding: 2px 0;
}
/* Compaction marker: one dim 24px row with a chevron disclosure for the
summary body. Dimmed title (not label-primary) — the row is a boundary
notice, not conversation content. */
.compactionRow {
padding: 2px 0;
}
.compactionButton {
display: flex;
align-items: center;
width: 100%;
height: 24px;
min-width: 0;
padding: 0;
border: none;
border-radius: 6px;
background: none;
color: inherit;
font: inherit;
text-align: left;
}
.compactionButton:not(:disabled) {
cursor: pointer;
}
.compactionButton:not(:disabled):hover {
background: var(--dsw-alias-interactive-bg-hover);
}
.compactionLeading {
flex: none;
display: inline-flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
margin-right: 6px;
color: var(--dsw-alias-label-secondary);
}
.compactionTitle {
flex: none;
font-size: 14px;
line-height: 24px;
color: var(--dsw-alias-label-primary-dimmed);
}
.compactionSep {
flex: none;
width: 2px;
height: 2px;
margin: 0 8px;
border-radius: 1px;
background: var(--dsw-alias-label-caption);
}
.compactionSummary {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
color: var(--dsw-alias-label-tertiary);
font-size: 14px;
line-height: 24px;
text-overflow: ellipsis;
white-space: nowrap;
}
.compactionBody {
padding: 4px 0 4px 22px;
color: var(--dsw-alias-label-tertiary);
font-size: 14px;
line-height: 24px;
}
.retryRow {
color: var(--dsw-alias-label-tertiary);
font-size: 13px;
@@ -1,20 +1,22 @@
// MessageItem: simple chat nodes — user bubble (right-aligned, with
// clock + copy / branch IconActions), steering (badged bubble), context
// injection, retry disclosure, and unknown-surface JSON rows.
// injection, compaction marker, retry disclosure, and unknown-surface JSON rows.
import { memo, useEffect, useMemo, useState } from 'react'
import type { ReactNode } from 'react'
import type {
ContextMessageNode, ModelRetryNode, SteeringMessageNode, UnknownSurfaceNode, UserMessageNode,
CompactionSummaryNode, ContextMessageNode, ModelRetryNode, SteeringMessageNode,
UnknownSurfaceNode, UserMessageNode,
} from '@deepseek-ai/dsh-client-runtime/client'
import { JsonBlock, MessageText } from '@deepseek-ai/dsh-client-ui-primitives'
import type { ChatViewSlotProps } from '../contract/slots.ts'
import { CompactionItem } from './CompactionItem.tsx'
import { ContextInjectionRow } from './ContextInjectionRow.tsx'
import { MessageIconActions } from './MessageIconActions.tsx'
import css from './MessageItem.module.css'
export interface MessageItemProps {
node: UserMessageNode | SteeringMessageNode | ContextMessageNode | ModelRetryNode | UnknownSurfaceNode
node: UserMessageNode | SteeringMessageNode | ContextMessageNode | CompactionSummaryNode | ModelRetryNode | UnknownSurfaceNode
retryActive?: boolean
/** Fork the session through the turn containing this message (user-bubble branch action). */
onFork?: (seq: number) => void
@@ -179,6 +181,8 @@ export const MessageItem = memo(function MessageItem({
return (
<ContextInjectionRow content={node.content} source={node.source} t={t} />
)
case 'compaction':
return <CompactionItem node={node} t={t} />
case 'model-retry':
return <ModelRetryItem node={node} active={retryActive} t={t} />
default:
@@ -49,8 +49,8 @@ export function assistantActionsSeqs(nodes: readonly ConversationNode[]): Readon
/**
* Group finalized nodes into the step-summary flow.
* @param nodes - snapshot nodes (surface order).
* @returns flow items; consecutive tool results and retry notices reuse their first key.
* @param nodes - snapshot nodes in human-transcript and durable-notice order.
* @returns flow items; consecutive tool results group and retry notices reuse their first key.
*/
export function deriveChatFlow(nodes: readonly ConversationNode[]): ChatFlowItem[] {
const items: ChatFlowItem[] = []
@@ -49,6 +49,9 @@ export const zh = {
'message.extraBlock': '附加内容块',
'message.steering': '插话',
'message.contextInjection': '上下文注入',
'message.compaction': '上下文已压缩',
'message.compaction.expand': '点击查看压缩摘要',
'message.compaction.unavailable': '压缩摘要不可用',
'message.unknownSurface': '未知 surface 事件:{type}',
'message.unknownBlock': '未知内容块',
'message.stopped': '已停止',
@@ -146,6 +149,9 @@ export const en = {
'message.extraBlock': 'Extra content block',
'message.steering': 'Interjection',
'message.contextInjection': 'Context injection',
'message.compaction': 'Context compacted',
'message.compaction.expand': 'View compaction summary',
'message.compaction.unavailable': 'Compaction summary unavailable',
'message.unknownSurface': 'Unknown surface event: {type}',
'message.unknownBlock': 'Unknown content block',
'message.stopped': 'Stopped',