Merge remote-tracking branch 'origin/master' into worktree/web-ask-user-question
This commit is contained in:
@@ -38,6 +38,7 @@
|
||||
"@deepseek-ai/dsh-client-runtime": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-conversation": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "workspace:^",
|
||||
"@deepseek-ai/dsh-tool-ask-user": "workspace:^",
|
||||
"clsx": "^2.0.0",
|
||||
"react": "^18.2.0"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState, type KeyboardEvent } from 'react'
|
||||
import clsx from 'clsx'
|
||||
import type { QuestionResponsePayload } from '@deepseek-ai/dsh-client-connection/client'
|
||||
import type { PendingInteraction } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import {
|
||||
Button, IconCheckOutline16, IconChevronLeftOutline14, IconChevronRightOutline14,
|
||||
IconCloseOutline16, IconEditOutline16,
|
||||
@@ -9,7 +8,7 @@ import {
|
||||
import type { QuestionComposerOwnerProps } from '@deepseek-ai/dsh-client-ui-conversation/client'
|
||||
import css from './QuestionComposer.module.css'
|
||||
|
||||
type QuestionInteraction = Extract<PendingInteraction, { kind: 'question' }>
|
||||
type QuestionInteraction = QuestionComposerOwnerProps['interaction']
|
||||
type Answer = QuestionResponsePayload['answer']
|
||||
|
||||
interface DraftAnswer {
|
||||
@@ -27,7 +26,9 @@ export interface QuestionComposerInjected {
|
||||
}
|
||||
}
|
||||
|
||||
/** Full question-composer props. */
|
||||
/** Consumed question-composer props: the slot's owner share & the injected
|
||||
* share. A strict subset of the composed props the register site proves
|
||||
* (the framework session/global standard kit goes unconsumed here). */
|
||||
export type QuestionComposerProps = QuestionComposerOwnerProps & QuestionComposerInjected
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
/**
|
||||
* Web question plugin, browser half: registers a composer replacement for
|
||||
* pending ask_user_question requests.
|
||||
* pending ask_user_question requests into the conversation-declared keyed
|
||||
* `conversation.composer` slot (single register API — the slot exists because
|
||||
* the conversation entry's children declaration created it).
|
||||
*/
|
||||
import type { Context } from 'cordis'
|
||||
import type { SessionId, SessionsService } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import type { SessionId, SessionsService, SlotsService } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import { QuestionComposer, type QuestionComposerInjected } from './QuestionComposer.tsx'
|
||||
|
||||
export { QuestionComposer, parseRecommendedLabel } from './QuestionComposer.tsx'
|
||||
@@ -17,15 +19,16 @@ export const inject = ['slots', 'sessions']
|
||||
* @param ctx - Browser plugin context carrying slots and sessions.
|
||||
*/
|
||||
export function apply(ctx: Context): void {
|
||||
const slots = ctx.get('slots')
|
||||
const slots = ctx.get('slots') as SlotsService | undefined
|
||||
const sessions = ctx.get('sessions') as SessionsService | undefined
|
||||
if (slots === undefined || sessions === undefined) {
|
||||
throw new Error('ui-question: slots and sessions services are required')
|
||||
}
|
||||
slots.register<'conversation.composer', QuestionComposerInjected>('conversation.composer', QuestionComposer, {
|
||||
slots.register({
|
||||
name: 'conversation.composer',
|
||||
key: 'question',
|
||||
inject(binding): QuestionComposerInjected {
|
||||
const session = sessions.manager.get(binding.sessionId as SessionId)
|
||||
inject: (sessionId: SessionId): QuestionComposerInjected => {
|
||||
const session = sessions.manager.get(sessionId)
|
||||
return {
|
||||
actions: {
|
||||
async answer(interaction, answer) {
|
||||
@@ -43,5 +46,5 @@ export function apply(ctx: Context): void {
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
}, QuestionComposer)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,15 @@ function interaction(): QuestionInteraction {
|
||||
}
|
||||
}
|
||||
|
||||
/** Declare the conversation-owned composer slot the way production does: a
|
||||
* parent entry's children table (register is the single declaration API). */
|
||||
function declareComposerSlot(slots: SlotsService): void {
|
||||
slots.register({
|
||||
name: 'root',
|
||||
children: { 'conversation.composer': { kind: 'keyed', scope: 'session' } },
|
||||
} as never, (() => null) as never)
|
||||
}
|
||||
|
||||
describe('ui-question browser plugin', () => {
|
||||
it('declares its services and fails loud without them', () => {
|
||||
expect(inject).toEqual(['slots', 'sessions'])
|
||||
@@ -33,17 +42,19 @@ describe('ui-question browser plugin', () => {
|
||||
manager: { get: vi.fn(() => ({ answerQuestion, cancelQuestion })) },
|
||||
})
|
||||
const slots = ctx.get('slots') as SlotsService
|
||||
slots.define('conversation.composer', { kind: 'keyed', scope: 'session' })
|
||||
declareComposerSlot(slots)
|
||||
const fiber = ctx.plugin({ inject: [...inject], apply })
|
||||
await fiber.await()
|
||||
|
||||
const entry = slots.entries('conversation.composer')[0] as unknown as {
|
||||
options: { inject(binding: { sessionId: SessionId }): { actions: {
|
||||
options: { key: string }
|
||||
inject(sessionId: SessionId): { actions: {
|
||||
answer: (item: QuestionInteraction, answer: { answers: { id: string; selected: string[] }[] }) => Promise<void>
|
||||
cancel: (item: QuestionInteraction) => Promise<void>
|
||||
} } }
|
||||
} }
|
||||
}
|
||||
const actions = entry.options.inject({ sessionId: 'session-1' as SessionId }).actions
|
||||
expect(entry.options.key).toBe('question')
|
||||
const actions = entry.inject('session-1' as SessionId).actions
|
||||
const item = interaction()
|
||||
const answer = { answers: [{ id: 'mode', selected: ['Fast'] }] }
|
||||
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"extends": "../../../tsconfig.base.client.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "lib/types",
|
||||
"jsx": "react-jsx",
|
||||
"lib": [
|
||||
"ES2024",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"types": []
|
||||
"outDir": "lib/types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
@@ -18,6 +11,9 @@
|
||||
{
|
||||
"path": "../../../vendor/cordis"
|
||||
},
|
||||
{
|
||||
"path": "../connection"
|
||||
},
|
||||
{
|
||||
"path": "../runtime"
|
||||
},
|
||||
@@ -30,9 +26,6 @@
|
||||
{
|
||||
"path": "../ui-slots"
|
||||
},
|
||||
{
|
||||
"path": "../web-react"
|
||||
},
|
||||
{
|
||||
"path": "../../ui/tool-ask-user"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user