feat: ask_user_choice Tool – interaktive Auswahlmöglichkeiten im Chat
Neues Tool 'ask_user_choice' analog zum AskUserQuestion aus dem Claude Agent SDK. Claude kann dem User Multiple-Choice-Fragen stellen, wenn es mehrere sinnvolle Wege gibt. Der User wählt per Button, Claude arbeitet mit der Antwort weiter. Backend: - ToolService: ask_user_choice Tool-Definition (question + choices[2-5]) - AnthropicService/ProcessStream: Tool wird NICHT ausgeführt, sondern als SSE-Event 'user_choice' an den Client gesendet. stopReason wird auf 'awaiting_user_choice' gesetzt (mit Guard gegen Überschreibung durch message_delta) - ChatEndpoint: Erkennung von Choice-Responses (choiceToolCallId + choiceResult), tool_result wird in die korrekte Chain gespeichert (wo der tool_use liegt), toolChoice wird auf 'auto' gesetzt damit Claude frei antworten kann - ChatService: FindChainWithToolCall() zum Auffinden der richtigen Chain, BuildHistoryFromDb() erweitert um Choice-Chain-Erkennung (wenn letzte Chain leer/nur tool_result → vorletzte Chain vollständig rekonstruieren) - ChatModels: ChatRequest um ChoiceToolCallId + ChoiceResult erweitert Frontend: - api.js: apiChatChoice() für Choice-Responses - chat.js: handleStreamEvent() als shared Handler extrahiert (sendMessage + handleChoiceClick nutzen denselben Code), handleChoiceClick() startet neuen Stream nach Button-Klick - ui.js: addChoiceBubble() (live/interaktiv) + addChoiceBubbleStatic() (History) - history.js: Choice-Bubbles in _renderMessagesIntoFragment + renderMessages, Voraus-Suche nach zugehörigem tool_result, unbeantwortet = klickbare Buttons - chat.css: Styling für Choice-Bubbles (Primary-Color-Outline-Buttons, Hover, Selected/Unselected States) Robustheit: - Backend-Fallback wenn Claude choices als String statt Array sendet - Frontend-Guard: Array.isArray(choices) an allen 4 Render-Stellen - Page-Refresh mit offener Choice → aktive Buttons werden korrekt gerendert
This commit is contained in:
1 parent
72347ed2f5
commit
5f3e308975
10 files changed
+628
-140
No files matched your search
@@ -1134,3 +1134,72 @@
|
||||
.review-iterations-row input[type="number"] {
|
||||
width: 5em;
|
||||
}
|
||||
|
||||
/* ── Choice Bubbles (ask_user_choice) ── */
|
||||
|
||||
.choice-bubble {
|
||||
align-self: flex-start;
|
||||
background: var(--f1-bg);
|
||||
border: var(--border-width) solid var(--f1-border);
|
||||
border-left: 3px solid var(--primary);
|
||||
max-width: 80%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--box-spacing);
|
||||
}
|
||||
|
||||
.choice-question {
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
.choice-question p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.choice-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--badge-spacing);
|
||||
}
|
||||
|
||||
.choice-btn {
|
||||
padding: 0.45em 1em;
|
||||
border: var(--border-width) solid var(--primary);
|
||||
border-radius: var(--border-radius);
|
||||
background: transparent;
|
||||
color: var(--primary);
|
||||
font-size: 0.9em;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.choice-btn:hover:not(:disabled) {
|
||||
background: var(--primary);
|
||||
color: var(--primary-text);
|
||||
}
|
||||
|
||||
.choice-btn:active:not(:disabled) {
|
||||
transform: scale(0.97);
|
||||
}
|
||||
|
||||
.choice-btn:disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.choice-btn.choice-selected {
|
||||
background: var(--primary);
|
||||
color: var(--primary-text);
|
||||
border-color: var(--primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.choice-btn.choice-unselected {
|
||||
opacity: 0.4;
|
||||
border-color: var(--f1-border);
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.choice-answered .choice-question {
|
||||
opacity: 0.7;
|
||||
}
|
||||
Reference in new issue
Block a user