Merge remote-tracking branch 'origin/master' into feat/web-diff-card
# Conflicts: # packages/client/ui-conversation/README.i18n.yaml
This commit is contained in:
@@ -53,6 +53,10 @@ const ABSENT_LEXICON = {
|
||||
getSnapshot: () => EMPTY_LEXICON,
|
||||
subscribe: () => () => {},
|
||||
}
|
||||
const ABSENT_MENU_LAUNCHER = {
|
||||
getSnapshot: (): string | null => null,
|
||||
subscribe: () => () => {},
|
||||
}
|
||||
|
||||
/** Resolve the session-scoped conversation face (scope-addressed send/cancel), failing loud. */
|
||||
function scopedConversation(sessions: ISessions, id: SessionId): IConversation {
|
||||
@@ -193,14 +197,28 @@ export function apply(ctx: Context): void {
|
||||
if (sessionId === undefined) {
|
||||
return {
|
||||
keyboard: undefined,
|
||||
toggleCommandMenu: undefined,
|
||||
stop: undefined,
|
||||
command: undefined,
|
||||
hooks: { notices: ABSENT_NOTICES, lexicon: ABSENT_LEXICON },
|
||||
hooks: { notices: ABSENT_NOTICES, lexicon: ABSENT_LEXICON, menuLauncher: ABSENT_MENU_LAUNCHER },
|
||||
}
|
||||
}
|
||||
const shell = inputHub.shell(sessionId)
|
||||
const slash = inputHub.slash(sessionId)
|
||||
return {
|
||||
keyboard: shell,
|
||||
toggleCommandMenu: slash === undefined
|
||||
? undefined
|
||||
: (selection) => {
|
||||
shell.dismissPopup()
|
||||
const snapshot = shell.snapshot
|
||||
slash.toggleSource('command', {
|
||||
trigger: '/',
|
||||
query: '',
|
||||
position: snapshot.draft.slice(0, selection.start).trim() === '' ? 'leading' : 'inline',
|
||||
span: { ...selection, draftRev: snapshot.draftRev },
|
||||
})
|
||||
},
|
||||
stop: () => {
|
||||
scopedConversation(sessions, sessionId).cancel().catch(() => {
|
||||
// Stop failure surfaces via snapshot.promptError; nothing to restore.
|
||||
@@ -212,7 +230,11 @@ export function apply(ctx: Context): void {
|
||||
const result = await session.command(line)
|
||||
return result.ok && result.value.matched
|
||||
},
|
||||
hooks: { notices: shell.notices, lexicon: shell.lexicon },
|
||||
hooks: {
|
||||
notices: shell.notices,
|
||||
lexicon: shell.lexicon,
|
||||
menuLauncher: slash?.launcher ?? ABSENT_MENU_LAUNCHER,
|
||||
},
|
||||
}
|
||||
},
|
||||
}, InputBar)
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
} from '@deepseek-ai/dsh-client-ui-slots'
|
||||
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, InputActions, InputNotice, InputState } from '../input/contract.ts'
|
||||
import type { ComposerKeyboard, EditSelection, InputActions, InputNotice, InputState } from '../input/contract.ts'
|
||||
import type { createChatStore } from '../stores.ts'
|
||||
import type { CallId, SelectionTarget, ViewTab } from './views.ts'
|
||||
|
||||
@@ -274,14 +274,14 @@ export interface ComposerBarOwnerProps {
|
||||
rightItems?: ReactNode
|
||||
/** composer.dock entries (stats line), rendered under the card inside the bar's width column. */
|
||||
footer?: ReactNode
|
||||
onAdd?: () => void
|
||||
addLabel?: string
|
||||
}
|
||||
|
||||
/** Injected share of the composer-bar entry (package-internal faces). */
|
||||
export interface ComposerBarInjected {
|
||||
/** The InputBar-exclusive keyboard/DOM command face (decision 20 private plane); absent with the session. */
|
||||
keyboard: ComposerKeyboard | undefined
|
||||
/** Toggle the shared slash menu with only its command source; absent without ui-slash or a session. */
|
||||
toggleCommandMenu: ((selection: EditSelection) => void) | undefined
|
||||
/** Cancel the in-flight turn; absent with the session. */
|
||||
stop: (() => void) | undefined
|
||||
/**
|
||||
@@ -301,6 +301,8 @@ export interface ComposerBarInjected {
|
||||
notices: ObservableSnapshot<InputNotice | null>
|
||||
/** Hot plain-text reference lexicon for the decoration scan (decision 21). */
|
||||
lexicon: ObservableSnapshot<ReadonlyMap<'/' | '@', readonly string[]>>
|
||||
/** Source name opened by the programmatic menu launcher, or null. */
|
||||
menuLauncher: ObservableSnapshot<string | null>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -106,6 +106,17 @@ export class InputHub implements InputService {
|
||||
return this.shell(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the optional slash controller for composer chrome that launches
|
||||
* the shared candidate menu without typing a trigger.
|
||||
* @param id - session id.
|
||||
* @returns the resident controller, or undefined when ui-slash is absent.
|
||||
*/
|
||||
slash(id: SessionId): SlashController | undefined {
|
||||
const actx = this.sessions().scope(id)
|
||||
return actx === undefined ? undefined : this.controller(actx)
|
||||
}
|
||||
|
||||
/**
|
||||
* Default sink: optimistic clear + prompt. The session is always a real
|
||||
* host entity (materialized when its workspace was picked), so there is
|
||||
|
||||
@@ -19,7 +19,7 @@ export const zh = {
|
||||
'placeholder.unavailable': '会话不可用',
|
||||
'placeholder.hero': '描述你想要构建的内容',
|
||||
'placeholder.workspace': '选择一个工作区开始',
|
||||
'input.addAttachment': '添加附件',
|
||||
'input.commands': '命令',
|
||||
'input.stop': '停止生成',
|
||||
'input.send': '发送消息',
|
||||
'input.accessMode': '访问模式,当前:{name}',
|
||||
@@ -101,7 +101,7 @@ export const en = {
|
||||
'placeholder.unavailable': 'Session unavailable',
|
||||
'placeholder.hero': 'Describe what you want to build',
|
||||
'placeholder.workspace': 'Choose a workspace to start',
|
||||
'input.addAttachment': 'Add attachment',
|
||||
'input.commands': 'Commands',
|
||||
'input.stop': 'Stop generating',
|
||||
'input.send': 'Send message',
|
||||
'input.accessMode': 'Access mode, current: {name}',
|
||||
|
||||
@@ -34,13 +34,14 @@ export interface InputBarError {
|
||||
export type InputBarProps = ComposerBarProps
|
||||
|
||||
export function InputBar({
|
||||
useSession, useInput, inputActions, keyboard, stop, command, t, renderSlot, useNotices, useLexicon,
|
||||
useSession, useInput, inputActions, keyboard, toggleCommandMenu, stop, command, t,
|
||||
renderSlot, useNotices, useLexicon, useMenuLauncher,
|
||||
useProjection, sessionId, variant, disabled: inert = false, placeholder, accessory, overlay, leftItems, rightItems, footer,
|
||||
onAdd, addLabel,
|
||||
}: InputBarProps) {
|
||||
const input = useInput(s => s)
|
||||
const notice = useNotices(s => s)
|
||||
const lexicon = useLexicon(s => s)
|
||||
const commandMenuOpen = useMenuLauncher(source => source === 'command')
|
||||
const promptError = useSession(s => s.promptError) ?? null
|
||||
const running = useSession(s => s.running) ?? false
|
||||
const removed = useSession(s => s.removed) ?? false
|
||||
@@ -257,7 +258,11 @@ export function InputBar({
|
||||
inputRef.current?.focus()
|
||||
}
|
||||
|
||||
const addText = addLabel ?? t('input.addAttachment')
|
||||
const onToggleCommandMenu = (): void => {
|
||||
const el = inputRef.current
|
||||
if (el !== null) toggleCommandMenu?.(selectionOf(el))
|
||||
}
|
||||
|
||||
const primaryLabel = running ? t('input.stop') : t('input.send')
|
||||
const onPrimary = (): void => {
|
||||
if (inputActions === undefined || stop === undefined) return // absent machine: the button is disabled
|
||||
@@ -399,11 +404,13 @@ export function InputBar({
|
||||
<button
|
||||
type="button"
|
||||
className={css.add}
|
||||
aria-label={addText}
|
||||
title={addText}
|
||||
disabled={locked}
|
||||
aria-label={t('input.commands')}
|
||||
title={t('input.commands')}
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded={commandMenuOpen}
|
||||
disabled={locked || toggleCommandMenu === undefined}
|
||||
onMouseDown={keepFocus}
|
||||
onClick={onAdd}
|
||||
onClick={onToggleCommandMenu}
|
||||
>
|
||||
<IconPlusOutline16 size={14} />
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user