增加/context命令、Tab补全与输入历史,命令处理提取到internal/cli
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/openai/openai-go"
|
||||
"github.com/openai/openai-go/option"
|
||||
@@ -83,6 +84,27 @@ func (b *Bot) ThinkingConfig() (string, string) {
|
||||
return b.provider.Thinking, b.provider.ReasoningEffort
|
||||
}
|
||||
|
||||
func (b *Bot) ContextDump() string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("[系统] " + b.systemPrompt + "\n")
|
||||
for i, msg := range b.history {
|
||||
role, content := "", ""
|
||||
switch {
|
||||
case msg.OfUser != nil:
|
||||
role, content = "用户", msg.OfUser.Content.OfString.Value
|
||||
case msg.OfAssistant != nil:
|
||||
role, content = "机器人", msg.OfAssistant.Content.OfString.Value
|
||||
case msg.OfSystem != nil:
|
||||
role, content = "系统", msg.OfSystem.Content.OfString.Value
|
||||
}
|
||||
if content == "" {
|
||||
content = "[多模态内容]"
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("[%d] %s: %s\n", i+1, role, content))
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (b *Bot) Chat(ctx context.Context, userMsg string, onReasoning, onContent func(string)) (string, error) {
|
||||
if b.provider.APIKey == "" {
|
||||
return "", fmt.Errorf("供应商 %s 未配置 api_key,请编辑 data/config.yaml", b.provider.Name)
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"myaibot/internal/bot"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
bot *bot.Bot
|
||||
}
|
||||
|
||||
func New(b *bot.Bot) *Handler {
|
||||
return &Handler{bot: b}
|
||||
}
|
||||
|
||||
func (h *Handler) Handle(input string) bool {
|
||||
fields := strings.Fields(input)
|
||||
cmd, args := fields[0], fields[1:]
|
||||
switch cmd {
|
||||
case "/exit", "/quit":
|
||||
fmt.Println("再见!")
|
||||
return false
|
||||
case "/help":
|
||||
fmt.Println("命令列表:")
|
||||
fmt.Println(" /models 列出所有供应商和模型")
|
||||
fmt.Println(" /use <模型> 切换模型,如 /use deepseek-chat 或 /use deepseek/deepseek-chat")
|
||||
fmt.Println(" /think <on|off> 开启或关闭当前供应商的思考模式")
|
||||
fmt.Println(" /effort <low|high|max> 设置思考强度")
|
||||
fmt.Println(" /context 打印当前聊天上下文")
|
||||
fmt.Println(" /info 显示当前供应商、模型和思考配置")
|
||||
fmt.Println(" /exit 退出")
|
||||
case "/models":
|
||||
for _, m := range h.bot.Models() {
|
||||
fmt.Println(" " + m)
|
||||
}
|
||||
case "/use":
|
||||
if len(args) == 0 {
|
||||
fmt.Println("用法: /use <模型>,如 /use deepseek-chat")
|
||||
return true
|
||||
}
|
||||
if err := h.bot.SwitchModel(args[0]); err != nil {
|
||||
fmt.Printf("⚠️ %v\n", err)
|
||||
return true
|
||||
}
|
||||
provider, model := h.bot.Current()
|
||||
fmt.Printf("已切换到 %s/%s (对话历史已保留)\n", provider, model)
|
||||
case "/think":
|
||||
if len(args) == 0 {
|
||||
fmt.Println("用法: /think <on|off>")
|
||||
return true
|
||||
}
|
||||
v := map[string]string{"on": "enabled", "off": "disabled"}[args[0]]
|
||||
if err := h.bot.SetThinking(v); err != nil {
|
||||
fmt.Printf("⚠️ %v\n", err)
|
||||
return true
|
||||
}
|
||||
fmt.Printf("思考模式已%s\n", map[string]string{"enabled": "开启", "disabled": "关闭"}[v])
|
||||
case "/effort":
|
||||
if len(args) == 0 {
|
||||
fmt.Println("用法: /effort <low|high|max>")
|
||||
return true
|
||||
}
|
||||
if err := h.bot.SetEffort(args[0]); err != nil {
|
||||
fmt.Printf("⚠️ %v\n", err)
|
||||
return true
|
||||
}
|
||||
fmt.Printf("思考强度已设置为 %s\n", args[0])
|
||||
case "/context":
|
||||
fmt.Print(h.bot.ContextDump())
|
||||
case "/info":
|
||||
provider, model := h.bot.Current()
|
||||
thinking, effort := h.bot.ThinkingConfig()
|
||||
if thinking == "" {
|
||||
thinking = "enabled(默认)"
|
||||
}
|
||||
if effort == "" {
|
||||
effort = "high(默认)"
|
||||
}
|
||||
fmt.Printf("供应商: %s, 模型: %s, 思考模式: %s, 思考强度: %s\n", provider, model, thinking, effort)
|
||||
default:
|
||||
fmt.Printf("未知命令: %s,输入 /help 查看命令列表\n", cmd)
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cli
|
||||
|
||||
import "strings"
|
||||
|
||||
var commands = []string{"/exit", "/quit", "/help", "/models", "/use", "/think", "/effort", "/context", "/info"}
|
||||
|
||||
func Complete(line string, models []string) []string {
|
||||
fields := strings.Fields(line)
|
||||
switch len(fields) {
|
||||
case 0:
|
||||
return commands
|
||||
case 1:
|
||||
return prefixMatch(commands, fields[0])
|
||||
}
|
||||
arg := fields[1]
|
||||
switch fields[0] {
|
||||
case "/use":
|
||||
return prefixMatch(models, arg)
|
||||
case "/think":
|
||||
return prefixMatch([]string{"on", "off"}, arg)
|
||||
case "/effort":
|
||||
return prefixMatch([]string{"low", "high", "max"}, arg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func prefixMatch(list []string, prefix string) []string {
|
||||
var out []string
|
||||
for _, s := range list {
|
||||
if strings.HasPrefix(s, prefix) {
|
||||
out = append(out, s)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestComplete(t *testing.T) {
|
||||
models := []string{"deepseek-v4-flash", "deepseek-v4-pro", "gpt-4o"}
|
||||
cases := []struct {
|
||||
name string
|
||||
line string
|
||||
want []string
|
||||
}{
|
||||
{"空行返回全部命令", "", commands},
|
||||
{"命令前缀", "/us", []string{"/use"}},
|
||||
{"模型补全", "/use deepseek", []string{"deepseek-v4-flash", "deepseek-v4-pro"}},
|
||||
{"模型无匹配", "/use claude", nil},
|
||||
{"think 补全", "/think o", []string{"on", "off"}},
|
||||
{"effort 补全", "/effort h", []string{"high"}},
|
||||
{"未知命令不补全", "/foo a", nil},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
got := Complete(c.line, models)
|
||||
if !reflect.DeepEqual(got, c.want) {
|
||||
t.Errorf("Complete(%q) = %v, want %v", c.line, got, c.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user