feat(AI设计): 支持输入框回车发送
需求:AI 设计输入框监听 Enter 并发送消息。 实现:复用现有发送链路,保留 Shift+Enter 换行并保护输入法组合状态;补充聚焦回归测试。
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
# Task: 支持 AI 设计输入框回车发送
|
||||
|
||||
## Identity
|
||||
|
||||
- Task ID: 20260806-design-enter-send-b83f
|
||||
- Mode: Feature
|
||||
- Branch: codex/design-enter-send-b83f
|
||||
- Worktree: D:\Datas\OthersProjects\makelore-design-enter-send-b83f
|
||||
- Base commit: 7b23cce67a1b9e61a48d89dedbd088d37b10ecd4
|
||||
- Owner: codex
|
||||
- Status: Completed
|
||||
|
||||
## Scope
|
||||
|
||||
- 让 AI 设计输入框在非输入法组合状态下按 Enter 直接发送。
|
||||
- 保留 Shift+Enter 输入换行,并同步更新快捷键提示。
|
||||
- 增加回车发送、换行和输入法保护的回归测试。
|
||||
|
||||
## Intent And Constraints
|
||||
|
||||
- 复用现有 `handleSend`,不改变消息、Quote 确认与任务创建链路。
|
||||
- 中文输入法候选确认期间的 Enter 不得误发消息。
|
||||
- 改动仅限 AI 设计页及其聚焦测试,不触碰原主工作区中的未归属改动。
|
||||
|
||||
## Plan
|
||||
|
||||
1. 调整输入框键盘事件判定与可见快捷键提示。
|
||||
2. 覆盖 Enter、Shift+Enter 和 IME composition 三种行为。
|
||||
3. 执行聚焦测试、类型检查、Lint 与 Vite 构建。
|
||||
|
||||
## Outcome
|
||||
|
||||
- AI 设计输入框现在按 Enter 即可发送,继续复用既有消息与结构化 Quote 确认逻辑。
|
||||
- Shift+Enter 保留多行输入;输入法组合状态下按 Enter 不会误发。
|
||||
- 输入框下方提示已更新为 `Enter 发送 · Shift+Enter 换行`。
|
||||
|
||||
## Verification
|
||||
|
||||
- `vitest run tests/unit/image-canvas-page.test.tsx`:21 passed。
|
||||
- `tsc --noEmit`:通过。
|
||||
- `eslint src/pages/ImageCanvas/index.tsx tests/unit/image-canvas-page.test.tsx`:通过。
|
||||
- `vite build`:通过,仅有既有动态导入与 chunk 体积警告。
|
||||
- 全仓 `eslint .`:未通过,命中本任务之外的既有问题:`src/components/layout/MainLayout.tsx` effect 内同步 setState、`tests/unit/chat-command-dialogs.test.tsx` 未使用变量,以及 6 个既有 warning。
|
||||
|
||||
## Follow-ups
|
||||
|
||||
- 后续可单独清理全仓既有 Lint 基线问题,不阻塞本次交互修复。
|
||||
|
||||
## Promotion Candidates
|
||||
|
||||
- None recorded.
|
||||
@@ -536,10 +536,9 @@ export function ImageCanvas() {
|
||||
};
|
||||
|
||||
const handleComposerKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
|
||||
if (event.key !== 'Enter' || event.shiftKey || event.nativeEvent.isComposing) return;
|
||||
event.preventDefault();
|
||||
void handleSend();
|
||||
}
|
||||
};
|
||||
|
||||
const handleFirstFrameSelect = async (asset: DesignAsset) => {
|
||||
@@ -819,7 +818,7 @@ export function ImageCanvas() {
|
||||
/>
|
||||
{actionError ? <p role="alert" className="mt-2 rounded-xl bg-destructive/10 px-3 py-2 text-xs font-semibold text-destructive">{actionError}</p> : null}
|
||||
<div className="mt-2 flex items-center justify-between gap-3 border-t border-border/70 pt-2">
|
||||
<span className="text-[10px] font-medium text-muted-foreground">⌘/Ctrl + Enter 发送</span>
|
||||
<span className="text-[10px] font-medium text-muted-foreground">Enter 发送 · Shift+Enter 换行</span>
|
||||
<Button type="submit" className="h-9 rounded-full bg-brand px-4 text-primary-foreground" disabled={submitting || !prompt.trim()}>
|
||||
{submitting ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Send className="mr-2 h-4 w-4" />}
|
||||
发送给设计 Agent
|
||||
|
||||
@@ -372,6 +372,42 @@ describe('ImageCanvas Workspace-first design experience', () => {
|
||||
expect(confirmImageWorkspaceGenerationMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('sends the design message with Enter', async () => {
|
||||
render(<MemoryRouter><ImageCanvas /></MemoryRouter>);
|
||||
await screen.findByText('云端角色设定');
|
||||
const composer = screen.getByLabelText('设计需求');
|
||||
|
||||
fireEvent.change(composer, {
|
||||
target: { value: '让画面增加一层晨雾' },
|
||||
});
|
||||
fireEvent.keyDown(composer, { key: 'Enter' });
|
||||
|
||||
await waitFor(() => expect(sendImageWorkspaceMessageMock).toHaveBeenCalledWith(
|
||||
'workspace-cloud',
|
||||
1,
|
||||
'让画面增加一层晨雾',
|
||||
expect.stringMatching(/^turn-/),
|
||||
));
|
||||
expect(composer).toHaveValue('');
|
||||
expect(screen.getByTestId('image-workspace-composer'))
|
||||
.toHaveTextContent('Enter 发送 · Shift+Enter 换行');
|
||||
});
|
||||
|
||||
it('keeps Shift+Enter for newlines and ignores Enter during IME composition', async () => {
|
||||
render(<MemoryRouter><ImageCanvas /></MemoryRouter>);
|
||||
await screen.findByText('云端角色设定');
|
||||
const composer = screen.getByLabelText('设计需求');
|
||||
|
||||
fireEvent.change(composer, {
|
||||
target: { value: '正在输入中文设计描述' },
|
||||
});
|
||||
fireEvent.keyDown(composer, { key: 'Enter', shiftKey: true });
|
||||
fireEvent.keyDown(composer, { key: 'Enter', isComposing: true });
|
||||
|
||||
expect(sendImageWorkspaceMessageMock).not.toHaveBeenCalled();
|
||||
expect(composer).toHaveValue('正在输入中文设计描述');
|
||||
});
|
||||
|
||||
it('renders the pending user turn and assistant deltas while the command is running', async () => {
|
||||
const response = deferred<DesignWorkspace>();
|
||||
sendImageWorkspaceMessageMock.mockReturnValueOnce(response.promise);
|
||||
|
||||
Reference in New Issue
Block a user