diff --git a/config/config.example.json b/config/config.example.json index a8400ad5f..ff2969dcb 100644 --- a/config/config.example.json +++ b/config/config.example.json @@ -130,6 +130,10 @@ "encrypt_key": "", "verification_token": "", "allow_from": [], + "placeholder": { + "enabled": true, + "text": ["Thinking...", "Processing...", "Typing..."] + }, "reasoning_channel_id": "", "random_reaction_emoji": [], "is_lark": false @@ -161,7 +165,7 @@ }, "placeholder": { "enabled": true, - "text": "Thinking... 💭" + "text": ["Thinking...", "Processing...", "Typing..."] }, "reasoning_channel_id": "", "crypto_database_path": "", diff --git a/docs/channels/matrix/README.md b/docs/channels/matrix/README.md index dd4b45eba..baded984e 100644 --- a/docs/channels/matrix/README.md +++ b/docs/channels/matrix/README.md @@ -22,7 +22,7 @@ Add this to `config.json`: }, "placeholder": { "enabled": true, - "text": "Thinking..." + "text": ["Thinking...", "Processing...", "Typing..."] }, "reasoning_channel_id": "", "message_format": "richtext", @@ -45,12 +45,19 @@ Add this to `config.json`: | join_on_invite | bool | No | Auto-join invited rooms | | allow_from | []string | No | User whitelist (Matrix user IDs) | | group_trigger | object | No | Group trigger strategy (`mention_only` / `prefixes`) | -| placeholder | object | No | Placeholder message config | +| placeholder | object | No | Placeholder message config (see below) | | reasoning_channel_id | string | No | Target channel for reasoning output | | message_format | string | No | Output format: `"richtext"` (default) renders markdown as HTML; `"plain"` sends plain text only | | crypto_database_path | string | No | Path to store the crypto database (uses workspace path `~/.picoclaw/workspace` if empty) | | crypto_passphrase | string | No | Serialization key for encrypting session keys in the database; must remain unchanged once set | +### Placeholder Config + +| Field | Type | Required | Description | +|---------|----------------|----------|-------------| +| enabled | bool | No | Enable placeholder messages (default: false) | +| text | string/[]string | No | Placeholder text(s). Can be a single string or array of strings. If multiple texts are provided, one is randomly selected at runtime. Default: "Thinking..." | + ## 3. Currently Supported - Text message send/receive with markdown rendering (bold, italic, headers, code blocks, etc.) diff --git a/docs/channels/matrix/README.zh.md b/docs/channels/matrix/README.zh.md index cd68a057e..81afa550b 100644 --- a/docs/channels/matrix/README.zh.md +++ b/docs/channels/matrix/README.zh.md @@ -22,7 +22,7 @@ }, "placeholder": { "enabled": true, - "text": "Thinking... 💭" + "text": ["Thinking...", "Processing...", "Typing..."] }, "reasoning_channel_id": "", "message_format": "richtext", @@ -51,6 +51,13 @@ | crypto_database_path | string | 否 | 加密数据库存储路径(为空时使用工作空间路径 `~/.picoclaw/workspace`) | | crypto_passphrase | string | 否 | 加密数据库中 session key 的序列化密钥;设置后不能更改 | +### 占位消息配置 (Placeholder) + +| 字段 | 类型 | 必填 | 说明 | +|---------|-----------------|------|------| +| enabled | bool | 否 | 是否启用占位消息(默认:false) | +| text | string/[]string | 否 | 占位文本。可以是单个字符串或字符串数组。如果提供多个文本,运行时会随机选择一个。默认:"Thinking..." | + ## 3. 当前支持 - 文本消息收发 diff --git a/pkg/channels/discord/discord.go b/pkg/channels/discord/discord.go index 3b5b4f8bb..2385544a6 100644 --- a/pkg/channels/discord/discord.go +++ b/pkg/channels/discord/discord.go @@ -254,10 +254,7 @@ func (c *DiscordChannel) SendPlaceholder(ctx context.Context, chatID string) (st return "", nil } - text := c.config.Placeholder.Text - if text == "" { - text = "Thinking... 💭" - } + text := c.config.Placeholder.GetRandomText() msg, err := c.session.ChannelMessageSend(chatID, text) if err != nil { diff --git a/pkg/channels/feishu/feishu_64.go b/pkg/channels/feishu/feishu_64.go index 0ab70649f..76df988ad 100644 --- a/pkg/channels/feishu/feishu_64.go +++ b/pkg/channels/feishu/feishu_64.go @@ -211,10 +211,7 @@ func (c *FeishuChannel) SendPlaceholder(ctx context.Context, chatID string) (str return "", nil } - text := c.config.Placeholder.Text - if text == "" { - text = "Thinking..." - } + text := c.config.Placeholder.GetRandomText() cardContent, err := buildMarkdownCard(text) if err != nil { diff --git a/pkg/channels/matrix/matrix.go b/pkg/channels/matrix/matrix.go index 50b86158d..f6370fa20 100644 --- a/pkg/channels/matrix/matrix.go +++ b/pkg/channels/matrix/matrix.go @@ -573,10 +573,7 @@ func (c *MatrixChannel) SendPlaceholder(ctx context.Context, chatID string) (str return "", fmt.Errorf("matrix room ID is empty") } - text := strings.TrimSpace(c.config.Placeholder.Text) - if text == "" { - text = "Thinking... 💭" - } + text := c.config.Placeholder.GetRandomText() resp, err := c.client.SendMessageEvent(ctx, roomID, event.EventMessage, &event.MessageEventContent{ MsgType: event.MsgNotice, diff --git a/pkg/channels/pico/pico.go b/pkg/channels/pico/pico.go index f3ba55a92..1aa1941cf 100644 --- a/pkg/channels/pico/pico.go +++ b/pkg/channels/pico/pico.go @@ -275,10 +275,7 @@ func (c *PicoChannel) SendPlaceholder(ctx context.Context, chatID string) (strin return "", nil } - text := c.config.Placeholder.Text - if text == "" { - text = "Thinking... 💭" - } + text := c.config.Placeholder.GetRandomText() msgID := uuid.New().String() outMsg := newMessage(TypeMessageCreate, map[string]any{ diff --git a/pkg/channels/telegram/telegram.go b/pkg/channels/telegram/telegram.go index e7da1d615..5adb40a7e 100644 --- a/pkg/channels/telegram/telegram.go +++ b/pkg/channels/telegram/telegram.go @@ -402,10 +402,7 @@ func (c *TelegramChannel) SendPlaceholder(ctx context.Context, chatID string) (s return "", nil } - text := phCfg.Text - if text == "" { - text = "Thinking... 💭" - } + text := phCfg.GetRandomText() cid, threadID, err := parseTelegramChatID(chatID) if err != nil { diff --git a/pkg/config/config.go b/pkg/config/config.go index fd7c48964..367952301 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -3,6 +3,7 @@ package config import ( "encoding/json" "fmt" + "math/rand" "os" "path/filepath" "strings" @@ -382,8 +383,20 @@ type TypingConfig struct { // PlaceholderConfig controls placeholder message behavior (Phase 10). type PlaceholderConfig struct { - Enabled bool `json:"enabled"` - Text string `json:"text,omitempty"` + Enabled bool `json:"enabled"` + Text FlexibleStringSlice `json:"text,omitempty"` +} + +// GetRandomText returns a random placeholder text, or default if none set. +func (p *PlaceholderConfig) GetRandomText() string { + if len(p.Text) == 0 { + return "Thinking..." + } + if len(p.Text) == 1 { + return p.Text[0] + } + idx := rand.Intn(len(p.Text)) + return p.Text[idx] } type StreamingConfig struct { diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go index c56cd8596..44fc1f049 100644 --- a/pkg/config/defaults.go +++ b/pkg/config/defaults.go @@ -63,7 +63,7 @@ func DefaultConfig() *Config { Typing: TypingConfig{Enabled: true}, Placeholder: PlaceholderConfig{ Enabled: true, - Text: "Thinking... 💭", + Text: FlexibleStringSlice{"Thinking... 💭"}, }, Streaming: StreamingConfig{Enabled: true, ThrottleSeconds: 3, MinGrowthChars: 200}, UseMarkdownV2: false, @@ -112,7 +112,7 @@ func DefaultConfig() *Config { }, Placeholder: PlaceholderConfig{ Enabled: true, - Text: "Thinking... 💭", + Text: FlexibleStringSlice{"Thinking... 💭"}, }, CryptoDatabasePath: "", CryptoPassphrase: "",