feat(config): add behavior setting purpose tooltips (#140)

This commit is contained in:
Duy /zuey/ authored and GitHub committed 2026-06-06 07:55:55 +07:00
1 parent 391b6c686a
commit ed1327db16
9 files changed
+59 -18

No files matched your search

+5
View File
@@ -40,6 +40,11 @@ All notable changes to GoClaw are documented here. For full documentation, see [
### Changed
- **Config Behavior explanations** — Adds purpose tooltips for Tool Status
Messages, Intermediate Replies, and Quick Acknowledgement so admins can
distinguish debug progress, natural intermediate updates, and receipt-only
acknowledgement behavior in the web UI.
- **ChatGPT Subscription (OAuth)** — default model and backend-owned model catalog
now prefer `gpt-5.5`, with reasoning metadata and context-window defaults updated
for provider-first model selection.
@@ -1,7 +1,7 @@
---
title: "Human-Like Channel Chat Behavior MVP"
description: "TDD plan for digitopvn/goclaw#67: quick acknowledgement and safe final multi-message splitting for non-streaming channel delivery, with global gateway plus per-channel config only."
status: pending
status: completed
priority: P2
branch: "codex/issue-67-human-like-chat-behavior"
tags: [issue-67, channels, chat-behavior, tdd, web-ui]
@@ -38,11 +38,11 @@ Recommended architecture: resolve behavior config into `RunContext` at run regis
| Phase | Name | Status |
|-------|------|--------|
| 1 | [Contract and Splitter Tests](./phase-01-contract-and-splitter-tests.md) | Pending |
| 2 | [Config Resolution and Preview API](./phase-02-config-resolution-and-preview-api.md) | Pending |
| 3 | [Runtime Acknowledgement and Final Splitting](./phase-03-runtime-acknowledgement-and-final-splitting.md) | Pending |
| 4 | [Dashboard Controls and Channel Overrides](./phase-04-dashboard-controls-and-channel-overrides.md) | Pending |
| 5 | [Validation and Handoff](./phase-05-validation-and-handoff.md) | Pending |
| 1 | [Contract and Splitter Tests](./phase-01-contract-and-splitter-tests.md) | Completed |
| 2 | [Config Resolution and Preview API](./phase-02-config-resolution-and-preview-api.md) | Completed |
| 3 | [Runtime Acknowledgement and Final Splitting](./phase-03-runtime-acknowledgement-and-final-splitting.md) | Completed |
| 4 | [Dashboard Controls and Channel Overrides](./phase-04-dashboard-controls-and-channel-overrides.md) | Completed |
| 5 | [Validation and Handoff](./phase-05-validation-and-handoff.md) | Completed |
## Dependencies
@@ -56,14 +56,21 @@ Recommended architecture: resolve behavior config into `RunContext` at run regis
## Acceptance Criteria
- [ ] Ack sends only for non-streaming channel runs when enabled and gate passes.
- [ ] Ack does not send for Web UI, streaming channel runs, disabled config, silent replies, or quick-complete runs.
- [ ] Final split sends at most configured max messages, with configured delay between extra messages.
- [ ] Splitter preserves fenced code, quotes, tables, lists, structured JSON/YAML/XML, links, and short messages.
- [ ] Global gateway and per-channel config override resolution is deterministic and tested.
- [ ] Preview API returns ack decision and split parts without sending messages.
- [ ] Dashboard exposes global controls and per-channel overrides with i18n in en/vi/zh.
- [ ] No schema/timeline persistence added; no issue #76 file overlap except references.
- [x] Ack sends only for non-streaming channel runs when enabled and gate passes.
- [x] Ack does not send for Web UI, streaming channel runs, disabled config, silent replies, or quick-complete runs.
- [x] Final split sends at most configured max messages, with configured delay between extra messages.
- [x] Splitter preserves fenced code, quotes, tables, lists, structured JSON/YAML/XML, links, and short messages.
- [x] Global gateway and per-channel config override resolution is deterministic and tested.
- [x] Preview API returns ack decision and split parts without sending messages.
- [x] Dashboard exposes global controls and per-channel overrides with i18n in en/vi/zh.
- [x] No schema/timeline persistence added; no issue #76 file overlap except references.
## Completion Evidence
- GitHub issue `digitopvn/goclaw#67` is closed.
- Beta implementation shipped via `digitopvn/goclaw#99`.
- Project changelog records the human-like channel delivery MVP.
- Follow-up channel behavior fixes passed GitHub CI in `digitopvn/goclaw#135` and `digitopvn/goclaw#139`.
## Validation Commands
@@ -3,6 +3,7 @@ import { Info } from "lucide-react";
import { cn } from "@/lib/utils";
import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
import { InfoLabel } from "@/components/shared/info-label";
import {
Card,
CardContent,
@@ -18,6 +19,8 @@ export interface FeatureSwitchItem {
label: string;
/** Short description below the label explaining the impact */
hint: string;
/** Tooltip explaining the purpose of the feature */
tooltip?: string;
checked: boolean;
onCheckedChange: (v: boolean) => void;
/** Contextual info message shown when the toggle is ON */
@@ -64,7 +67,13 @@ export function FeatureSwitchGroup({
<item.icon className={cn("h-4 w-4 shrink-0", item.iconClass ?? "text-muted-foreground")} />
)}
<div className="space-y-1">
<Label className="text-sm font-medium">{item.label}</Label>
{item.tooltip ? (
<InfoLabel tip={item.tooltip} labelClassName="text-sm font-medium">
{item.label}
</InfoLabel>
) : (
<Label className="text-sm font-medium">{item.label}</Label>
)}
<p className="text-xs text-muted-foreground">{item.hint}</p>
</div>
</div>
+10 -2
View File
@@ -8,10 +8,18 @@ import {
} from "@/components/ui/tooltip";
/** Label with an (i) tooltip icon for field descriptions. */
export function InfoLabel({ children, tip }: { children: React.ReactNode; tip: string }) {
export function InfoLabel({
children,
tip,
labelClassName,
}: {
children: React.ReactNode;
tip: string;
labelClassName?: string;
}) {
return (
<div className="flex items-center gap-1.5">
<Label>{children}</Label>
<Label className={labelClassName}>{children}</Label>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
+3
View File
@@ -123,8 +123,10 @@
"behavior.uxTitle": "UX Behavior",
"behavior.uxDescription": "High-impact toggles that change how the gateway interacts with users",
"behavior.toolStatusHint": "Show real-time tool execution status messages to users during agent runs.",
"behavior.toolStatusPurpose": "Purpose: show deterministic technical progress from tool calls, such as reading files, running commands, or calling APIs. Useful for debugging and verbose channel feedback; may overlap with reasoning and intermediate replies.",
"behavior.toolStatusInfo": "Users will see live updates as the agent reads files, runs commands, and calls APIs.",
"behavior.blockReplyHint": "Send tool announcements and partial assistant text to channels before or during tool calls.",
"behavior.blockReplyPurpose": "Purpose: let the assistant send natural intermediate progress before the final answer, especially around tool work. Useful on non-streaming channels that need a more human delivery rhythm.",
"behavior.blockReplyInfo": "Intermediate text is delivered before or during tool execution, not just the final response.",
"behavior.intentClassifyHint": "Classify user intent before routing to reduce unnecessary agent invocations.",
"behavior.intentClassifyInfo": "Agent will only be invoked when the classifier detects actionable intent.",
@@ -134,6 +136,7 @@
"behavior.chatEnabledHint": "Applies globally, with optional per-channel overrides.",
"behavior.quickAck": "Quick acknowledgement",
"behavior.quickAckHint": "Prefer the main LLM's block reply; use templates only as fallback.",
"behavior.quickAckPurpose": "Purpose: send a short receipt so users know their message was accepted before longer work starts. It is an acknowledgement, not reasoning or tool progress.",
"behavior.quickAckMode": "Mode",
"behavior.quickAckMode.llm_generated": "LLM-generated with fallback",
"behavior.quickAckMode.fixed_template": "Fixed template",
+3
View File
@@ -123,8 +123,10 @@
"behavior.uxTitle": "Hành vi giao diện",
"behavior.uxDescription": "Các công tắc có tác động lớn thay đổi cách gateway tương tác với người dùng",
"behavior.toolStatusHint": "Hiện thông báo trạng thái thực thi công cụ theo thời gian thực cho người dùng trong quá trình chạy agent.",
"behavior.toolStatusPurpose": "Mục đích: hiển thị tiến độ kỹ thuật xác định từ tool call, ví dụ đọc tệp, chạy lệnh hoặc gọi API. Hữu ích cho debug và phản hồi kênh dạng verbose; có thể chồng lên reasoning và phản hồi trung gian.",
"behavior.toolStatusInfo": "Người dùng sẽ thấy cập nhật trực tiếp khi agent đọc tệp, chạy lệnh và gọi API.",
"behavior.blockReplyHint": "Gửi thông báo dùng công cụ và văn bản phụ trợ cho kênh trước hoặc trong khi gọi công cụ.",
"behavior.blockReplyPurpose": "Mục đích: cho assistant gửi cập nhật tiến độ tự nhiên trước câu trả lời cuối, nhất là quanh các bước dùng tool. Hữu ích cho kênh không streaming cần nhịp phản hồi giống người hơn.",
"behavior.blockReplyInfo": "Văn bản trung gian được gửi cho người dùng trước hoặc trong quá trình thực thi công cụ.",
"behavior.intentClassifyHint": "Phân loại ý định người dùng trước khi định tuyến để giảm các lần gọi agent không cần thiết.",
"behavior.intentClassifyInfo": "Agent chỉ được gọi khi bộ phân loại phát hiện ý định có thể thực hiện.",
@@ -134,6 +136,7 @@
"behavior.chatEnabledHint": "Áp dụng toàn cục, có thể ghi đè theo từng kênh.",
"behavior.quickAck": "Xác nhận nhanh",
"behavior.quickAckHint": "Ưu tiên block reply từ LLM chính; chỉ dùng mẫu tin làm fallback.",
"behavior.quickAckPurpose": "Mục đích: gửi xác nhận ngắn để người dùng biết tin nhắn đã được nhận trước khi tác vụ dài bắt đầu. Đây là xác nhận, không phải reasoning hay tiến độ tool.",
"behavior.quickAckMode": "Chế độ",
"behavior.quickAckMode.llm_generated": "LLM sinh, có fallback",
"behavior.quickAckMode.fixed_template": "Mẫu cố định",
+3
View File
@@ -123,8 +123,10 @@
"behavior.uxTitle": "界面行为",
"behavior.uxDescription": "影响网关与用户交互方式的高影响开关",
"behavior.toolStatusHint": "在 Agent 运行期间向用户显示实时工具执行状态消息。",
"behavior.toolStatusPurpose": "用途:显示来自工具调用的确定性技术进度,例如读取文件、运行命令或调用 API。适合调试和更详细的频道反馈;可能与 reasoning 和中间回复重叠。",
"behavior.toolStatusInfo": "当 Agent 读取文件、运行命令和调用 API 时,用户将看到实时更新。",
"behavior.blockReplyHint": "在工具调用前或调用过程中向频道发送工具公告和部分助手文本。",
"behavior.blockReplyPurpose": "用途:让助手在最终答案前发送更自然的中间进度,尤其是在工具执行前后。适合需要更拟人化反馈的非流式频道。",
"behavior.blockReplyInfo": "工具执行前或执行期间向用户发送中间文本,而非仅发送最终响应。",
"behavior.intentClassifyHint": "在路由之前对用户意图进行分类,以减少不必要的 Agent 调用。",
"behavior.intentClassifyInfo": "仅当分类器检测到可执行意图时才调用 Agent。",
@@ -134,6 +136,7 @@
"behavior.chatEnabledHint": "全局生效,可按频道覆盖。",
"behavior.quickAck": "快速确认",
"behavior.quickAckHint": "优先使用主 LLM 的 block reply;模板仅作为回退。",
"behavior.quickAckPurpose": "用途:在较长任务开始前发送简短确认,让用户知道消息已被接收。这是确认,不是 reasoning 或工具进度。",
"behavior.quickAckMode": "模式",
"behavior.quickAckMode.llm_generated": "LLM 生成并带回退",
"behavior.quickAckMode.fixed_template": "固定模板",
@@ -9,6 +9,7 @@ import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
import { InfoLabel } from "@/components/shared/info-label";
const quickAckModeValues = ["llm_generated", "fixed_template", "off"] as const;
type QuickAckMode = (typeof quickAckModeValues)[number];
@@ -97,7 +98,7 @@ export function BehaviorChatCard({ value, onChange }: Props) {
<div className="space-y-3 rounded-md border p-3">
<div className="flex items-start justify-between gap-4">
<div>
<Label>{t("behavior.quickAck")}</Label>
<InfoLabel tip={t("behavior.quickAckPurpose")}>{t("behavior.quickAck")}</InfoLabel>
<p className="text-xs text-muted-foreground">{t("behavior.quickAckHint")}</p>
</div>
<Switch
@@ -24,6 +24,7 @@ export function BehaviorUxCard({ value, onChange }: Props) {
iconClass: "text-blue-500",
label: t("gateway.toolStatus"),
hint: t("behavior.toolStatusHint"),
tooltip: t("behavior.toolStatusPurpose"),
checked: value.tool_status !== false,
onCheckedChange: (v) => onChange({ ...value, tool_status: v }),
infoWhenOn: t("behavior.toolStatusInfo"),
@@ -34,6 +35,7 @@ export function BehaviorUxCard({ value, onChange }: Props) {
iconClass: "text-emerald-500",
label: t("gateway.blockReply"),
hint: t("behavior.blockReplyHint"),
tooltip: t("behavior.blockReplyPurpose"),
checked: value.block_reply ?? false,
onCheckedChange: (v) => onChange({ ...value, block_reply: v }),
infoWhenOn: t("behavior.blockReplyInfo"),