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
378 changes: 378 additions & 0 deletions design-prototypes/ask-user-question-prototype.html

Large diffs are not rendered by default.

44 changes: 44 additions & 0 deletions docs/ask-user-question-interaction-decisions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# AskUserQuestion interaction decisions

This document records the agreed prototype changes for ACECode Desktop/Web. It describes the intended user-visible behavior; it is not an implementation plan.

## Pending question

- The pending question replaces the normal composer in the same composer dock. It must not overlay or coexist with the composer.
- Its left and right edges align exactly with the normal composer card.
- It uses the main UI font-size tokens and the composer's compact visual density rather than fixed, oversized typography.
- The header allows the question text to wrap while progress and navigation controls remain visible.
- The header and footer remain fixed when content is tall; only the options region scrolls.
- The footer uses compact, right-aligned actions.
- When collapsed, the pending question becomes one header row containing the pending-question count, current progress, and expand control. There is no second “继续回答” row.
- Desktop does not expose a separate “直接输入” action while a question is pending. The user must cancel the question or answer through the custom option.

## Submitted answers

- A completed AskUserQuestion uses the existing `AskUserQuestion` activity/tool line as its only result header.
- Submitted answers are collapsed by default, including after history reload.
- The entire activity line toggles the answer details and remains keyboard accessible through the existing activity-line interaction.
- Expanded answers appear as a compact inline list directly beneath the activity line.
- The inline list has no independent result heading, summary row, rounded card, success badge, or outer border.
- Questions remain in their original order. Skipped questions are retained and display the muted text “未作答”.
- The independent feedback-card presentation, including “全部提交完成” and “已回答 4/5”, is removed.

## Cancelled answer

- Explicit cancellation keeps a non-expandable `AskUserQuestion` activity/tool line.
- Muted plain text “用户已取消回答” appears directly beneath the activity line.
- Results produced by interjection on another interaction surface use the same Desktop/Web presentation text, while the persisted domain outcome remains an interjection.
- Cancellation has no card, large icon, or expand control.

## Unchanged behavior

The redesign does not change single-select, multi-select, custom-answer, skip, question navigation, copy, or keyboard semantics.

## Validation matrix

Validate all states in:

- normal and near-minimum chat-pane widths;
- compact, default, and large UI font-size modes;
- light and dark themes;
- long questions, many options, long submitted answers, and skipped questions.
3 changes: 3 additions & 0 deletions web/scripts/i18n-en-overrides.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ export const ENGLISH_SOURCE_OVERRIDES = Object.freeze({
'操作未完成:': 'Action did not complete: ',
'建议状态更新失败。': 'Could not refresh suggestion status.',
'已取消全部回答': 'All answers cancelled',
'用户已取消回答': 'User cancelled the questions',
'选择自定义答案': 'Select custom answer',
'取消自定义答案': 'Unselect custom answer',
'仅显示 file_edit / file_write / apply_patch 工具的改动': 'Show only changes made by the file_edit / file_write / apply_patch tools',
'国庆节': 'National Day',
'下载国庆节主题': 'Download National Day theme',
Expand Down
3 changes: 1 addition & 2 deletions web/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -1990,8 +1990,7 @@ export function App() {
&& !configRecoveryBlocking
&& !guidedTourPreparing
&& !guidedTourRun
&& !sessionNavigationPending
&& !visibleQuestionReq;
&& !sessionNavigationPending;
const resolveVisibleQuestion = () => {
if (!visibleQuestionReq?.request_id) return;
setQuestionReqs((prev) => closePendingQuestionRequest(prev, visibleQuestionReq, {
Expand Down
120 changes: 63 additions & 57 deletions web/src/components/ChatView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4470,6 +4470,7 @@ export function ChatView({ children, sessionRef, sessionId, homeLogoEffectEnable
requestAnimationFrame(() => inputRef.current?.focus());
}, [onQuestionResolve]);


const sidePanelMounted = showSidePanel;
const sidePanelNavigationCollapsed = sidePanelCollapsed || sidePanelListCollapsed;
const previewScope = useMemo(
Expand Down Expand Up @@ -4943,51 +4944,58 @@ export function ChatView({ children, sessionRef, sessionId, homeLogoEffectEnable
<InteractiveHomeLogo enabled={homeLogoEffectEnabled} />
<h1 className="ace-home-title">{homeProjectTitle}</h1>
<div data-tour-target="home-composer" className="ace-home-composer">
<InputBar
ref={inputRef}
variant="hero"
attentionRequest={homeComposerAttentionRequest}
pathReferenceApi={api}
currentSessionId=""
cwd={selectedHomeWorkspace?.cwd || ''}
expertOptions={recentExperts}
selectedExpertId={composerExpertId}
selectedExpertName={composerExpert?.display_name || ''}
selectedExpertType={composerExpert?.type || 'agent'}
expertRemoving={expertDetaching}
onSelectExpert={selectComposerExpert}
onRemoveExpert={detachComposerExpert}
onOpenExpertComponents={() => setExpertPickerOpen(true)}
history={composerHistory}
historyEntries={composerHistoryEntries}
value={composerValue}
onChange={handleComposerChange}
onSubmit={submit}
disabled={!!questionForView}
submitting={homeSubmitting || reasoningSwitching}
placeholder="向 ACECode 描述任务,或输入 / 命令..."
{...composerInputProps}
fileDropManagedExternally
onFileDragActiveChange={setChatFileDropActive}
sessionControls={{
model: homeModelLabel,
modelOptions,
selectedModelName: homeModelName,
modelLoad: homeModelLoad,
modelSwitching: modelSwitching || reasoningSwitching,
modelRefreshing,
reasoningOptions: composerReasoningOptions(selectedHomeModel, homeReasoningEffort),
reasoningDisabled: busy || homeSubmitting || composerSubmitting || reasoningSwitching || modelSwitching || modelRefreshing,
onReasoningChange: changeComposerReasoning,
onModelChange: changeComposerModel,
onRefreshModels: refreshSessionModels,
onOpenModelSettings,
tokenBudget: homeTokenBudget,
permissionMode,
permissionSwitching,
onPermissionModeChange: changeComposerPermissionMode,
}}
/>
{questionForView ? (
<QuestionPicker
request={questionForView}
onResolve={resolveQuestion}
originLabel={questionOriginLabel}
/>
) : (
<InputBar
ref={inputRef}
variant="hero"
attentionRequest={homeComposerAttentionRequest}
pathReferenceApi={api}
currentSessionId=""
cwd={selectedHomeWorkspace?.cwd || ''}
expertOptions={recentExperts}
selectedExpertId={composerExpertId}
selectedExpertName={composerExpert?.display_name || ''}
selectedExpertType={composerExpert?.type || 'agent'}
expertRemoving={expertDetaching}
onSelectExpert={selectComposerExpert}
onRemoveExpert={detachComposerExpert}
onOpenExpertComponents={() => setExpertPickerOpen(true)}
history={composerHistory}
historyEntries={composerHistoryEntries}
value={composerValue}
onChange={handleComposerChange}
onSubmit={submit}
submitting={homeSubmitting || reasoningSwitching}
placeholder="向 ACECode 描述任务,或输入 / 命令..."
{...composerInputProps}
fileDropManagedExternally
onFileDragActiveChange={setChatFileDropActive}
sessionControls={{
model: homeModelLabel,
modelOptions,
selectedModelName: homeModelName,
modelLoad: homeModelLoad,
modelSwitching: modelSwitching || reasoningSwitching,
modelRefreshing,
reasoningOptions: composerReasoningOptions(selectedHomeModel, homeReasoningEffort),
reasoningDisabled: busy || homeSubmitting || composerSubmitting || reasoningSwitching || modelSwitching || modelRefreshing,
onReasoningChange: changeComposerReasoning,
onModelChange: changeComposerModel,
onRefreshModels: refreshSessionModels,
onOpenModelSettings,
tokenBudget: homeTokenBudget,
permissionMode,
permissionSwitching,
onPermissionModeChange: changeComposerPermissionMode,
}}
/>
)}
</div>
<div className="flex items-center gap-2 mr-auto ml-0">
<div className="relative">
Expand Down Expand Up @@ -5090,9 +5098,7 @@ export function ChatView({ children, sessionRef, sessionId, homeLogoEffectEnable
</div>
</div>
</div>
{questionForView && (
<QuestionPicker request={questionForView} onResolve={resolveQuestion} />
)}

{createProjectOpen && (
<CreateProjectModal
api={api}
Expand Down Expand Up @@ -5488,13 +5494,6 @@ export function ChatView({ children, sessionRef, sessionId, homeLogoEffectEnable
</Suspense>
)}

{questionForView && (
<QuestionPicker
request={questionForView}
onResolve={resolveQuestion}
originLabel={questionOriginLabel}
/>
)}

<SideChatWindow
{...sideChatState}
Expand Down Expand Up @@ -5523,7 +5522,14 @@ export function ChatView({ children, sessionRef, sessionId, homeLogoEffectEnable
</div>
) : (
<div className="ace-composer-dock">
{!questionForView ? (
{questionForView ? (
<QuestionPicker
request={questionForView}
onResolve={resolveQuestion}
originLabel={questionOriginLabel}
className="mx-2.5"
/>
) : (
<>
<InputBar
ref={inputRef}
Expand Down Expand Up @@ -5588,7 +5594,7 @@ export function ChatView({ children, sessionRef, sessionId, homeLogoEffectEnable
onIntentChange={handleGitPillIntentChange}
/>
</>
) : null}
)}
</div>
)}
<SessionContentLoading
Expand Down
3 changes: 3 additions & 0 deletions web/src/components/QuestionFeedbackCard.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// AskUserQuestion 提交/取消/插话后的反馈卡(设计稿样式):
// - submit:左侧小对勾 + 「全部提交完成」+ 逐题 Q&A 清单(未作答灰字)
// - cancel:左侧小叉 + 「已取消全部回答」

// - interject:单行灰字提示「已改为直接输入,取消作答」(TUI/IM 等通道把问题
// 以「用户改为直接输入」收掉时落盘的 interjected 标记)
// 结果态卡片无投影,与对话流气泡融合(设计稿 .ask-box:has(.ask-summary/.ask-canceled))。
import { VsIcon } from './Icon.jsx';

export function QuestionFeedbackCard({ feedback }) {
Expand Down Expand Up @@ -56,6 +58,7 @@ export function QuestionFeedbackCard({ feedback }) {
</ul>
)}
</div>

</section>
);
}
Loading
Loading