Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions docs/en/wework/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ Add files, images, code locations, or an Appshot to the composer when they clari

The model provides the AI capability; the device determines where files and commands run. Local models run on the local device. Cloud models and devices require a Wegent connection.

When the composer has only one model group, the model selector displays all models in that group directly. When multiple groups are available, choose a group first, then select a model from its expanded submenu.

## Control a running task

| Action | Shortcut | Use it when |
| --- | --- | --- |
| Send after the response | `Enter` | Queue the next instruction |
| Steer the response | `Command/Ctrl + Enter` | Adjust the current direction |
| Interrupt and send | `Command/Ctrl + Shift + Enter` | Stop an incorrect direction immediately |
| Action | Shortcut | Use it when |
| ----------------------- | ------------------------------ | --------------------------------------- |
| Send after the response | `Enter` | Queue the next instruction |
| Steer the response | `Command/Ctrl + Enter` | Adjust the current direction |
| Interrupt and send | `Command/Ctrl + Shift + Enter` | Stop an incorrect direction immediately |

Interrupting stops the current response but does not roll back completed file edits or commands.

Expand Down
12 changes: 7 additions & 5 deletions docs/zh/wework/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,17 @@ sidebar_position: 4

模型决定任务使用的 AI 能力,设备决定代码和命令在哪里执行。本地模型只能用于本机设备;云端模型和设备需要先连接 Wegent。

打开输入区的模型选择器后,如果当前只有一个模型分组,Wework 会直接展示该分组中的全部模型;如果存在多个分组,请先选择分组,再从展开的二级菜单中选择模型。

## 任务运行中的控制

任务运行时有三种发送方式:

| 操作 | 快捷键 | 适用场景 |
| --- | --- | --- |
| 当前回复结束后发送 | `Enter` | 补充下一步要求 |
| 引导当前回复 | `Command/Ctrl + Enter` | 调整正在进行的工作方向 |
| 打断并立即发送 | `Command/Ctrl + Shift + Enter` | 当前方向错误,需要马上停止 |
| 操作 | 快捷键 | 适用场景 |
| ------------------ | ------------------------------ | -------------------------- |
| 当前回复结束后发送 | `Enter` | 补充下一步要求 |
| 引导当前回复 | `Command/Ctrl + Enter` | 调整正在进行的工作方向 |
| 打断并立即发送 | `Command/Ctrl + Shift + Enter` | 当前方向错误,需要马上停止 |

打断会停止当前回复,但已经完成的文件修改和命令不会自动回滚。

Expand Down
19 changes: 19 additions & 0 deletions wework/e2e/desktop/task-flow.e2e.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,22 @@ async function sendPromptUntilScenarioRequest(control, selector, prompt, scenari
)
}

async function revealGroupedModelOption(control, targetOptionId) {
const menu = JSON.parse(await control.command('snapshot', 'body'))
const familyTestIds = menu.testIds.filter(testId => testId.startsWith('model-family-'))

for (const familyTestId of familyTestIds) {
await control.command('hover', `[data-testid="${familyTestId}"]`, {
timeoutMs: UI_TIMEOUT_MS,
})
await new Promise(resolvePromise => setTimeout(resolvePromise, 150))
const familyMenu = JSON.parse(await control.command('snapshot', 'body'))
if (familyMenu.testIds.includes(targetOptionId)) return true
}

return false
}

async function selectE2EModel(control, modelId = MODEL_ID, modelLabel = MODEL_LABEL) {
const selectedModelText = await control.command(
'waitFor',
Expand Down Expand Up @@ -730,6 +746,9 @@ async function selectE2EModel(control, modelId = MODEL_ID, modelLabel = MODEL_LA
await new Promise(resolvePromise => setTimeout(resolvePromise, 150))
menu = JSON.parse(await control.command('snapshot', 'body'))
optionVisible = menu.testIds.includes(targetOptionId)
if (!optionVisible) {
optionVisible = await revealGroupedModelOption(control, targetOptionId)
}
}

assert.ok(optionVisible, `Model option ${modelId} did not become visible`)
Expand Down
9 changes: 7 additions & 2 deletions wework/src/components/chat/ChatInput.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2277,7 +2277,7 @@ describe('ChatInput', () => {
expect(menu.queryByText('计划模式')).not.toBeInTheDocument()
})

test('flattens model families while keeping controls from the selected GPT model', async () => {
test('nests models by family while keeping controls from the selected GPT model', async () => {
const gptModel: UnifiedModel = {
name: 'overseas-gpt-5.5',
type: 'user',
Expand Down Expand Up @@ -2322,8 +2322,13 @@ describe('ChatInput', () => {
await userEvent.click(screen.getByTestId('model-selector-button'))
await userEvent.hover(screen.getByTestId('model-control-menu-model'))

expect(screen.getByTestId('model-option-claude-opus')).toBeInTheDocument()
expect(screen.getByTestId('model-family-claude')).toBeInTheDocument()
expect(screen.getByTestId('model-family-gpt')).toBeInTheDocument()
expect(screen.queryByTestId('model-option-claude-opus')).not.toBeInTheDocument()

await userEvent.hover(screen.getByTestId('model-family-gpt'))
expect(screen.getByTestId('model-option-overseas-gpt-5.5')).toBeInTheDocument()
expect(screen.queryByTestId('model-option-claude-opus')).not.toBeInTheDocument()
await userEvent.hover(screen.getByTestId('model-control-menu-reasoning'))
expect(screen.getByTestId('model-control-reasoning-high')).toBeInTheDocument()
expect(screen.queryByTestId('model-control-reasoning-auto')).not.toBeInTheDocument()
Expand Down
62 changes: 62 additions & 0 deletions wework/src/components/chat/composer/ModelSelector.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,17 @@ const SAMPLE_MODEL: UnifiedModel = {
},
}

const SECOND_FAMILY_MODEL: UnifiedModel = {
name: 'claude-sonnet',
modelId: 'claude-sonnet',
displayName: 'Claude Sonnet',
type: 'runtime',
provider: 'local',
config: {
ui: { family: 'claude' },
},
}

describe('ModelSelector desktop layout', () => {
const originalInnerWidth = window.innerWidth
const originalInnerHeight = window.innerHeight
Expand Down Expand Up @@ -139,6 +150,57 @@ describe('ModelSelector desktop layout', () => {
expect(left).toBeLessThanOrEqual(SHELL_LEFT - 16)
})

test('expands model options directly when there is only one family', async () => {
createShellElement({ hidden: true })

render(
<ModelSelector
models={[SAMPLE_MODEL]}
selectedModel={SAMPLE_MODEL}
selectedModelOptions={{}}
disabled={false}
onSelectModel={vi.fn()}
onSelectModelOption={vi.fn()}
/>
)

fireEvent.click(screen.getByTestId('model-selector-button'))
fireEvent.mouseEnter(await screen.findByTestId('model-control-menu-model'))

expect(await screen.findByTestId(`model-option-${SAMPLE_MODEL.name}`)).toBeInTheDocument()
expect(screen.queryByTestId('model-selector-family-submenu')).not.toBeInTheDocument()
expect(screen.queryByTestId('model-family-codex-official')).not.toBeInTheDocument()
})

test('shows family entries before model options when there are multiple families', async () => {
createShellElement({ hidden: true })

render(
<ModelSelector
models={[SAMPLE_MODEL, SECOND_FAMILY_MODEL]}
selectedModel={SAMPLE_MODEL}
selectedModelOptions={{}}
disabled={false}
onSelectModel={vi.fn()}
onSelectModelOption={vi.fn()}
/>
)

fireEvent.click(screen.getByTestId('model-selector-button'))
fireEvent.mouseEnter(await screen.findByTestId('model-control-menu-model'))

expect(await screen.findByTestId('model-family-codex-official')).toBeInTheDocument()
expect(screen.getByTestId('model-family-claude')).toBeInTheDocument()
expect(screen.queryByTestId(`model-option-${SAMPLE_MODEL.name}`)).not.toBeInTheDocument()

fireEvent.mouseEnter(screen.getByTestId('model-family-claude'))

const familySubmenu = await screen.findByTestId('model-selector-family-submenu')
expect(familySubmenu).toHaveTextContent('Claude')
expect(screen.getByTestId(`model-option-${SECOND_FAMILY_MODEL.name}`)).toBeInTheDocument()
expect(screen.queryByTestId(`model-option-${SAMPLE_MODEL.name}`)).not.toBeInTheDocument()
})

test('caps the trigger width when maxClosedWidth is provided', () => {
createShellElement({ hidden: true })

Expand Down
Loading
Loading