mirror of
https://github.com/eryajf/chatgpt-dingtalk.git
synced 2025-12-24 12:57:50 +08:00
增加卡片交互流式输出的能力 (#315)
* 将ai交互切换为go-openai * add stream * ✨ feat(stream): 优化流式响应机制,实现实时卡片更新 - 将固定1.5秒更新改为基于300ms最小间隔的实时更新策略 - 新增内容缓冲区机制,避免过于频繁的API调用 - 改进流式中断处理,保护已接收的内容不丢失 🔧 chore(llm): 优化HTTP客户端配置 - 增加连接池设置(MaxIdleConns: 100, MaxIdleConnsPerHost: 10) - 设置空闲连接超时时间为90秒 - 添加HTTP/2禁用选项注释,用于解决流式错误问题 📝 docs(stream): 更新流式更新策略文档 - 详细说明实时流式更新机制和缓冲策略 - 新增HTTP/2流式错误的故障排除指南 - 更新配置参数说明和建议范围 🐛 fix(stream): 修复流式中断时的内容丢失问题 - 在流式接收中断时,确保已接收的内容不会丢失 - 改进错误处理逻辑,区分有内容和无内容的情况 * modify ai
This commit is contained in:
12
.claude/settings.local.json
Normal file
12
.claude/settings.local.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(tree:*)",
|
||||
"Bash(rm:*)",
|
||||
"Bash(mkdir:*)",
|
||||
"Bash(go mod tidy:*)",
|
||||
"Bash(go build:*)",
|
||||
"Bash(go get:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
337
STREAM_MODE.md
Normal file
337
STREAM_MODE.md
Normal file
@@ -0,0 +1,337 @@
|
||||
# 流式输出功能使用指南
|
||||
|
||||
## 功能概述
|
||||
|
||||
项目已支持钉钉机器人的流式输出功能,可以让 AI 回答像打字一样逐字显示,提供更好的用户体验。
|
||||
|
||||
## 两种流式模式
|
||||
|
||||
### 1. 简化流式模式(推荐快速开始)
|
||||
|
||||
**特点**:
|
||||
- 无需配置钉钉卡片模板
|
||||
- 直接使用累积内容一次性回复
|
||||
- 配置简单,开箱即用
|
||||
|
||||
**配置方式**:
|
||||
|
||||
在 `config.yml` 中添加:
|
||||
|
||||
```yaml
|
||||
# 启用流式输出
|
||||
stream_mode: true
|
||||
```
|
||||
|
||||
### 2. 高级流式卡片模式
|
||||
|
||||
**特点**:
|
||||
- 使用钉钉互动卡片实现真正的流式更新
|
||||
- 内容逐步显示,类似 ChatGPT 网页版效果
|
||||
- 需要在钉钉开放平台创建卡片模板
|
||||
|
||||
**配置方式**:
|
||||
|
||||
在 `config.yml` 中添加:
|
||||
|
||||
```yaml
|
||||
# 启用流式输出
|
||||
stream_mode: true
|
||||
# 钉钉卡片模板ID (可选,用于高级流式卡片模式)
|
||||
card_template_id: "your-card-template-id"
|
||||
```
|
||||
|
||||
## 配置钉钉卡片模板(高级模式)
|
||||
|
||||
### 步骤 1: 创建卡片模板
|
||||
|
||||
1. 登录 [钉钉开放平台](https://open.dingtalk.com/)
|
||||
2. 进入你的应用 -> 互动卡片 -> 卡片模板管理
|
||||
3. 创建新模板,使用以下 JSON Schema:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string",
|
||||
"title": "内容"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 步骤 2: 设计卡片样式
|
||||
|
||||
在卡片编辑器中,添加一个 Markdown 组件来显示 `content` 字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "markdown",
|
||||
"text": "{{content}}"
|
||||
}
|
||||
```
|
||||
|
||||
### 步骤 3: 发布并获取模板ID
|
||||
|
||||
1. 保存并发布卡片模板
|
||||
2. 复制模板ID(类似: `4d18414c-aabc-4ec8-9e67-4ceefeada72a.schema`)
|
||||
3. 将模板ID填入 `config.yml` 的 `card_template_id` 字段
|
||||
|
||||
## 完整配置示例
|
||||
|
||||
```yaml
|
||||
# 日志级别
|
||||
log_level: "info"
|
||||
|
||||
# OpenAI 配置
|
||||
api_key: "sk-..."
|
||||
model: "gpt-4o"
|
||||
base_url: "" # 可选,用于 API 中转
|
||||
|
||||
# 流式输出配置
|
||||
stream_mode: true # 启用流式输出
|
||||
card_template_id: "" # 可选:钉钉卡片模板ID
|
||||
|
||||
# 其他配置...
|
||||
session_timeout: 600s
|
||||
max_question_len: 2048
|
||||
max_answer_len: 2048
|
||||
max_text: 4096
|
||||
default_mode: "单聊"
|
||||
```
|
||||
|
||||
## 实现原理
|
||||
|
||||
### 简化模式流程
|
||||
|
||||
```
|
||||
用户提问 → OpenAI 流式响应 → 累积完整内容 → 一次性回复
|
||||
```
|
||||
|
||||
### 高级卡片模式流程
|
||||
|
||||
```
|
||||
用户提问
|
||||
↓
|
||||
创建钉钉卡片(空内容)
|
||||
↓
|
||||
发送初始状态 "稍等,让我想一想..."
|
||||
↓
|
||||
OpenAI 流式响应
|
||||
↓
|
||||
接收到内容 → 立即累积到缓冲区
|
||||
↓
|
||||
距离上次更新超过300ms? → 是 → 更新卡片
|
||||
↓ 否 ↓
|
||||
继续接收 ←-----------┘
|
||||
↓
|
||||
流式结束,发送最终内容(标记为完成)
|
||||
```
|
||||
|
||||
## 技术架构
|
||||
|
||||
### 新增文件
|
||||
|
||||
1. **[pkg/llm/stream.go](pkg/llm/stream.go)**
|
||||
- 实现 OpenAI 流式响应
|
||||
- 提供 `SingleQaStream()` 和 `ContextQaStream()` API
|
||||
- 支持 ChatCompletion 流式调用
|
||||
|
||||
2. **[pkg/dingbot/stream.go](pkg/dingbot/stream.go)**
|
||||
- 实现钉钉流式卡片更新
|
||||
- 封装钉钉 Streaming Update API
|
||||
- 提供 `UpdateAIStreamCard()` 方法
|
||||
|
||||
3. **[pkg/process/stream.go](pkg/process/stream.go)**
|
||||
- 实现流式处理逻辑
|
||||
- `DoStream()` - 简化流式模式
|
||||
- `DoStreamWithCard()` - 高级卡片模式
|
||||
- 包含定时更新和错误处理
|
||||
|
||||
### 改动文件
|
||||
|
||||
1. **[config/config.go](config/config.go)**
|
||||
- 添加 `StreamMode` 配置项
|
||||
- 添加 `CardTemplateID` 配置项
|
||||
|
||||
2. **[pkg/process/process_request.go](pkg/process/process_request.go)**
|
||||
- 根据配置自动选择流式或普通模式
|
||||
- 支持流式卡片和流式普通两种方式
|
||||
|
||||
## API 使用示例
|
||||
|
||||
### 在代码中使用流式 API
|
||||
|
||||
```go
|
||||
import "github.com/eryajf/chatgpt-dingtalk/pkg/llm"
|
||||
|
||||
// 单聊流式
|
||||
contentCh, cleanup, err := llm.SingleQaStream("你好", "user123")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
for content := range contentCh {
|
||||
fmt.Print(content) // 逐块输出
|
||||
}
|
||||
|
||||
// 串聊流式
|
||||
client, contentCh, err := llm.ContextQaStream("继续", "user123")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
fullAnswer := ""
|
||||
for content := range contentCh {
|
||||
fullAnswer += content
|
||||
fmt.Print(content)
|
||||
}
|
||||
|
||||
// 保存对话上下文
|
||||
client.ChatContext.SaveConversation("user123")
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 流式更新策略
|
||||
|
||||
高级卡片模式采用**实时流式更新**策略:
|
||||
|
||||
- 从大模型接收到内容后立即更新卡片
|
||||
- 使用缓冲机制避免更新过于频繁(默认最小间隔 **300ms**)
|
||||
- 这样可以实现真正的实时流式体验,类似 ChatGPT 网页版
|
||||
|
||||
可以在 [pkg/process/stream.go](pkg/process/stream.go) 中修改最小更新间隔:
|
||||
|
||||
```go
|
||||
minUpdateInterval := 300 * time.Millisecond // 修改这里
|
||||
```
|
||||
|
||||
建议范围:200ms - 500ms
|
||||
- 更小的间隔:更实时,但 API 调用更频繁
|
||||
- 更大的间隔:API 调用较少,但流式感觉不明显
|
||||
|
||||
### 流式模式选择建议
|
||||
|
||||
| 场景 | 推荐模式 | 原因 |
|
||||
|------|---------|------|
|
||||
| 快速部署 | 简化模式 | 无需额外配置 |
|
||||
| 追求体验 | 高级卡片模式 | 真正的流式显示 |
|
||||
| 高频使用 | 简化模式 | 减少 API 调用 |
|
||||
| 演示展示 | 高级卡片模式 | 视觉效果更好 |
|
||||
|
||||
## 兼容性
|
||||
|
||||
- ✅ 保持原有非流式模式完全兼容
|
||||
- ✅ 支持单聊和串聊两种对话模式
|
||||
- ✅ 支持所有 OpenAI 兼容的模型
|
||||
- ✅ 支持 Azure OpenAI
|
||||
- ✅ 保留敏感词过滤、请求限制等功能
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 问题 1: 流式模式不生效
|
||||
|
||||
**检查项**:
|
||||
1. 确认 `config.yml` 中 `stream_mode: true`
|
||||
2. 重启应用以加载新配置
|
||||
3. 查看日志是否有错误信息
|
||||
|
||||
### 问题 2: 卡片模式无法显示 / 日志显示 "robot code is empty"
|
||||
|
||||
**原因**: 这是正常的降级行为
|
||||
|
||||
**说明**:
|
||||
- 高级卡片模式需要通过 `credentials` 配置才能工作
|
||||
- 如果没有配置 `credentials`,系统会自动降级为简化流式模式
|
||||
- 简化流式模式不需要卡片,依然可以正常工作
|
||||
|
||||
**解决方案**:
|
||||
1. 如果想使用高级卡片模式,需要在 `config.yml` 中配置 `credentials`:
|
||||
```yaml
|
||||
credentials:
|
||||
- client_id: "your-app-key"
|
||||
client_secret: "your-app-secret"
|
||||
```
|
||||
2. 如果不需要卡片模式,可以忽略这个警告,或者将 `card_template_id` 留空
|
||||
|
||||
### 问题 3: 卡片模板配置正确但不显示
|
||||
|
||||
**检查项**:
|
||||
1. 确认卡片模板ID正确
|
||||
2. 确认卡片模板已发布
|
||||
3. 确认钉钉应用有卡片权限
|
||||
4. 确认 `credentials` 配置正确
|
||||
5. 查看日志是否有降级提示
|
||||
|
||||
### 问题 4: 流式响应中断
|
||||
|
||||
**可能原因**:
|
||||
1. OpenAI API 超时 - 检查网络连接
|
||||
2. 钉钉 Access Token 过期 - 会自动刷新
|
||||
3. 上下文超过限制 - 减少 `max_text` 配置
|
||||
|
||||
### 问题 5: HTTP/2 流式错误 "stream error: INTERNAL_ERROR"
|
||||
|
||||
**错误信息**:
|
||||
```
|
||||
Post "https://api.xxx.com/v1/completions": stream error: stream ID 5; INTERNAL_ERROR; received from peer
|
||||
```
|
||||
|
||||
**可能原因**:
|
||||
1. 上游 API 服务器内部错误或资源不足
|
||||
2. 网络不稳定,长连接中断
|
||||
3. HTTP/2 连接管理问题
|
||||
|
||||
**解决方案**:
|
||||
1. **已优化**: 代码已优化 HTTP 客户端配置,增加连接池和超时设置
|
||||
2. **部分内容保护**: 如果已接收到部分内容,不会因错误而丢失
|
||||
3. **禁用 HTTP/2** (如果问题频繁): 在 [pkg/llm/client.go](pkg/llm/client.go) 中取消注释:
|
||||
```go
|
||||
Transport: &http.Transport{
|
||||
// ...
|
||||
ForceAttemptHTTP2: false, // 取消注释这行
|
||||
}
|
||||
```
|
||||
注意:禁用 HTTP/2 会降低性能,但可能更稳定
|
||||
|
||||
4. **检查 API 服务器**: 如果使用中转服务,检查中转服务器的健康状况和资源
|
||||
|
||||
## 智能降级机制
|
||||
|
||||
系统实现了智能降级机制,确保即使高级功能无法使用,基础功能依然可用:
|
||||
|
||||
```
|
||||
尝试高级卡片模式
|
||||
↓
|
||||
检查 RobotCode 是否存在
|
||||
↓ (否)
|
||||
降级为简化流式模式
|
||||
↓
|
||||
检查 credentials 配置
|
||||
↓ (无配置)
|
||||
降级为简化流式模式
|
||||
↓
|
||||
尝试创建卡片
|
||||
↓ (失败)
|
||||
降级为简化流式模式
|
||||
↓
|
||||
正常流式输出
|
||||
```
|
||||
|
||||
这意味着:
|
||||
- ✅ 即使配置不完整,流式功能依然可用
|
||||
- ✅ 不会因为卡片失败而导致整个功能不可用
|
||||
- ✅ 日志会清楚地告诉你当前使用的是哪种模式
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [钉钉流式消息更新 API](https://open.dingtalk.com/document/development/api-streamingupdate)
|
||||
- [OpenAI Streaming API](https://platform.openai.com/docs/api-reference/streaming)
|
||||
- [PandaWiki 项目参考实现](tmp/PandaWiki)
|
||||
|
||||
## 贡献
|
||||
|
||||
欢迎提交 Issue 和 Pull Request 来改进流式功能!
|
||||
@@ -71,3 +71,14 @@ azure_openai_token: "xxxxxxx"
|
||||
credentials:
|
||||
- client_id: "put-your-client-id-here"
|
||||
client_secret: "put-your-client-secret-here"
|
||||
|
||||
# ==================== 流式输出配置 (新功能) ====================
|
||||
# 启用后,AI 回答将像打字一样逐字显示
|
||||
stream_mode: false # true=启用流式输出, false=使用传统一次性回复
|
||||
|
||||
# 钉钉卡片模板ID (可选,用于高级流式卡片模式)
|
||||
# 如果不配置,将使用简化流式模式(直接累积后回复)
|
||||
# 配置后,将使用钉钉互动卡片实现真正的流式更新
|
||||
# 获取方式: 在钉钉开放平台创建卡片模板后获得
|
||||
# 详细配置教程: 请查看 STREAM_MODE.md
|
||||
card_template_id: "" # 例如: "4d18414c-aabc-4ec8-9e67-4ceefeada72a.schema"
|
||||
|
||||
@@ -79,6 +79,10 @@ type Configuration struct {
|
||||
AzureOpenAIToken string `yaml:"azure_openai_token"`
|
||||
// 钉钉应用鉴权凭据
|
||||
Credentials []Credential `yaml:"credentials"`
|
||||
// 是否启用流式输出
|
||||
StreamMode bool `yaml:"stream_mode"`
|
||||
// 钉钉卡片模板ID(用于流式输出)
|
||||
CardTemplateID string `yaml:"card_template_id"`
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
25
go.mod
25
go.mod
@@ -3,25 +3,37 @@ module github.com/eryajf/chatgpt-dingtalk
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.12
|
||||
github.com/alibabacloud-go/dingtalk v1.6.96
|
||||
github.com/alibabacloud-go/tea v1.3.14
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.7
|
||||
github.com/charmbracelet/log v0.4.0
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/go-resty/resty/v2 v2.13.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.0
|
||||
github.com/pandodao/tokenizer-go v0.2.0
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/sashabaranov/go-openai v1.27.1
|
||||
github.com/solywsh/chatgpt v0.0.14
|
||||
github.com/sashabaranov/go-openai v1.41.2
|
||||
golang.org/x/image v0.18.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/gorm v1.25.11
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/avast/retry-go v3.0.0+incompatible // indirect
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
|
||||
github.com/alibabacloud-go/debug v1.0.1 // indirect
|
||||
github.com/alibabacloud-go/gateway-dingtalk v1.0.2 // indirect
|
||||
github.com/alibabacloud-go/openapi-util v0.1.1 // indirect
|
||||
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
|
||||
github.com/aliyun/credentials-go v1.4.6 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/bytedance/sonic v1.12.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.0 // indirect
|
||||
github.com/charmbracelet/lipgloss v0.12.1 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.1.4 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.2 // indirect
|
||||
@@ -38,7 +50,6 @@ require (
|
||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
|
||||
github.com/goccy/go-json v0.10.3 // indirect
|
||||
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
@@ -52,24 +63,22 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/pandodao/tokenizer-go v0.2.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.25.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
golang.org/x/image v0.18.0 // indirect
|
||||
golang.org/x/net v0.27.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
modernc.org/libc v1.55.6 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
modernc.org/sqlite v1.31.1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/solywsh/chatgpt => ./pkg/chatgpt
|
||||
|
||||
197
go.sum
197
go.sum
@@ -1,5 +1,57 @@
|
||||
github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
|
||||
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA=
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo=
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 h1:zE8vH9C7JiZLNJJQ5OwjU9mSi4T9ef9u3BURT6LCLC8=
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g=
|
||||
github.com/alibabacloud-go/darabonba-array v0.1.0 h1:vR8s7b1fWAQIjEjWnuF0JiKsCvclSRTfDzZHTYqfufY=
|
||||
github.com/alibabacloud-go/darabonba-array v0.1.0/go.mod h1:BLKxr0brnggqOJPqT09DFJ8g3fsDshapUD3C3aOEFaI=
|
||||
github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC2NG0Ax+GpOM5gtupki31XE=
|
||||
github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8=
|
||||
github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc=
|
||||
github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc=
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.12 h1:Dqhik/9iK3/ltjMuVy2kkuuWK3KPRes2vSzxnrehT74=
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.12/go.mod h1:cgtLEj8i4ddXMcQgq4PnpVQvlzS+y5B+QtdSfmcLM3A=
|
||||
github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg=
|
||||
github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ=
|
||||
github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo=
|
||||
github.com/alibabacloud-go/darabonba-string v1.0.2/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA=
|
||||
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY=
|
||||
github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
|
||||
github.com/alibabacloud-go/debug v1.0.1 h1:MsW9SmUtbb1Fnt3ieC6NNZi6aEwrXfDksD4QA6GSbPg=
|
||||
github.com/alibabacloud-go/debug v1.0.1/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
|
||||
github.com/alibabacloud-go/dingtalk v1.6.96 h1:oV4qOgvIYjVph8ksk8DNKtoZMIdxKgE4SuMMBGB9j50=
|
||||
github.com/alibabacloud-go/dingtalk v1.6.96/go.mod h1:mUcgNRgMGQzABtiZtTK8a3b6LwQBQ8t9WsDKzklqVpg=
|
||||
github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q=
|
||||
github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
|
||||
github.com/alibabacloud-go/gateway-dingtalk v1.0.2 h1:+etjmc64QTmYvHlc6eFkH9y2DOc3UPcyD2nF3IXsVqw=
|
||||
github.com/alibabacloud-go/gateway-dingtalk v1.0.2/go.mod h1:JUvHpkJtlPFpgJcfXqc9Y4mk2JnoRn5XpKbRz38jJho=
|
||||
github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
|
||||
github.com/alibabacloud-go/openapi-util v0.1.1 h1:ujGErJjG8ncRW6XtBBMphzHTvCxn4DjrVw4m04HsS28=
|
||||
github.com/alibabacloud-go/openapi-util v0.1.1/go.mod h1:/UehBSE2cf1gYT43GV4E+RxTdLRzURImCYY0aRmlXpw=
|
||||
github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg=
|
||||
github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
||||
github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
||||
github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
||||
github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
|
||||
github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
|
||||
github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk=
|
||||
github.com/alibabacloud-go/tea v1.3.14 h1:/Uzj5ZCFPpbPR+Bs7jfzsyXkYIVsi5TOIuQNOWwc/9c=
|
||||
github.com/alibabacloud-go/tea v1.3.14/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg=
|
||||
github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.1/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4=
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4=
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.7 h1:WDx5qW3Xa5ZgJ1c8NfqJkF6w+AU5wB8835UdhPr6Ax0=
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.7/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
|
||||
github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0=
|
||||
github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
|
||||
github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
|
||||
github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
|
||||
github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM=
|
||||
github.com/aliyun/credentials-go v1.4.6 h1:CG8rc/nxCNKfXbZWpWDzI9GjF4Tuu3Es14qT8Y0ClOk=
|
||||
github.com/aliyun/credentials-go v1.4.6/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/bytedance/sonic v1.12.0 h1:YGPgxF9xzaCNvd/ZKdQ28yRovhfMFZQjuk6fKBzZ3ls=
|
||||
@@ -7,16 +59,22 @@ github.com/bytedance/sonic v1.12.0/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKz
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM=
|
||||
github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/charmbracelet/lipgloss v0.12.1 h1:/gmzszl+pedQpjCOH+wFkZr/N90Snz40J/NR7A0zQcs=
|
||||
github.com/charmbracelet/lipgloss v0.12.1/go.mod h1:V2CiwIuhx9S1S1ZlADfOj9HmxeMAORuz5izHb0zGbB8=
|
||||
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
|
||||
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
|
||||
github.com/charmbracelet/x/ansi v0.1.4 h1:IEU3D6+dWwPSgZ6HBH+v6oUuZ/nVawMiWj5831KfiLM=
|
||||
github.com/charmbracelet/x/ansi v0.1.4/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
||||
github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
|
||||
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -28,6 +86,9 @@ github.com/dop251/goja_nodejs v0.0.0-20240728170619-29b559befffc h1:MKYt39yZJi0Z
|
||||
github.com/dop251/goja_nodejs v0.0.0-20240728170619-29b559befffc/go.mod h1:VULptt4Q/fNzQUJlqY/GP3qHyU7ZH46mFkBZe0ZTokU=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
|
||||
github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
@@ -54,6 +115,21 @@ github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TC
|
||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@@ -61,18 +137,25 @@ github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQu
|
||||
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
@@ -84,12 +167,16 @@ github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.0 h1:DL64ORGMk6AUB8q5LbRp8KRFn4oHhdrSepBmbMrtmNo=
|
||||
github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.0/go.mod h1:ln3IqPYYocZbYvl9TAOrG/cxGR9xcn4pnZRLdCTEGEU=
|
||||
github.com/pandodao/tokenizer-go v0.2.0 h1:NhfI8fGvQkDld2cZCag6NEU3pJ/ugU9zoY1R/zi9YCs=
|
||||
@@ -100,18 +187,24 @@ github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/sashabaranov/go-openai v1.27.1 h1:7Nx6db5NXbcoutNmAUQulEQZEpHG/SkzfexP2X5RWMk=
|
||||
github.com/sashabaranov/go-openai v1.27.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/sashabaranov/go-openai v1.41.2 h1:vfPRBZNMpnqu8ELsclWcAvF19lDNgh1t6TVfFFOPiSM=
|
||||
github.com/sashabaranov/go-openai v1.41.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
@@ -119,42 +212,91 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
|
||||
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
||||
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
||||
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -162,21 +304,34 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
@@ -184,16 +339,46 @@ golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
||||
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -201,6 +386,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
|
||||
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
|
||||
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.20.5 h1:s04akhT2dysD0DFOlv9fkQ6oUTLPYgMnnDk9oaqjszM=
|
||||
|
||||
2
main.go
2
main.go
@@ -86,7 +86,7 @@ func (r *ChatReceiver) OnChatBotMessageReceived(ctx context.Context, data *chatb
|
||||
IsInAtList: data.IsInAtList,
|
||||
SessionWebhook: data.SessionWebhook,
|
||||
Text: dingbot.Text(data.Text),
|
||||
RobotCode: "",
|
||||
RobotCode: r.clientId, // 使用 clientId 作为 RobotCode
|
||||
Msgtype: dingbot.MsgType(data.Msgtype),
|
||||
}
|
||||
clientId := r.clientId
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Shihao
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1 +0,0 @@
|
||||
> 因为三方包写死了很多参数,这里转到本地,便于二次改造。 感谢:https://github.com/solywsh/chatgpt
|
||||
@@ -1,31 +0,0 @@
|
||||
package chatgpt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestChatGPT_ChatWithContext(t *testing.T) {
|
||||
chat := New("")
|
||||
defer chat.Close()
|
||||
//go func() {
|
||||
// select {
|
||||
// case <-chat.GetDoneChan():
|
||||
// fmt.Println("time out")
|
||||
// }
|
||||
//}()
|
||||
question := "现在你是一只猫,接下来你只能用\"喵喵喵\"回答."
|
||||
fmt.Printf("Q: %s\n", question)
|
||||
answer, err := chat.ChatWithContext(question)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Printf("A: %s\n", answer)
|
||||
question = "你是一只猫吗?"
|
||||
fmt.Printf("Q: %s\n", question)
|
||||
answer, err = chat.ChatWithContext(question)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Printf("A: %s\n", answer)
|
||||
}
|
||||
@@ -1,343 +0,0 @@
|
||||
package chatgpt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/image/webp"
|
||||
"image"
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
"image/png"
|
||||
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pandodao/tokenizer-go"
|
||||
openai "github.com/sashabaranov/go-openai"
|
||||
|
||||
"github.com/eryajf/chatgpt-dingtalk/pkg/dingbot"
|
||||
"github.com/eryajf/chatgpt-dingtalk/public"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultAiRole = "AI"
|
||||
DefaultHumanRole = "Human"
|
||||
|
||||
DefaultCharacter = []string{"helpful", "creative", "clever", "friendly", "lovely", "talkative"}
|
||||
DefaultBackground = "The following is a conversation with AI assistant. The assistant is %s"
|
||||
DefaultPreset = "\n%s: 你好,让我们开始愉快的谈话!\n%s: 我是 AI assistant ,请问你有什么问题?"
|
||||
)
|
||||
|
||||
type (
|
||||
ChatContext struct {
|
||||
background string // 对话背景
|
||||
preset string // 预设对话
|
||||
maxSeqTimes int // 最大对话次数
|
||||
aiRole *role // AI角色
|
||||
humanRole *role // 人类角色
|
||||
|
||||
old []conversation // 旧对话
|
||||
restartSeq string // 重新开始对话的标识
|
||||
startSeq string // 开始对话的标识
|
||||
|
||||
seqTimes int // 对话次数
|
||||
|
||||
maintainSeqTimes bool // 是否维护对话次数 (自动移除旧对话)
|
||||
}
|
||||
|
||||
ChatContextOption func(*ChatContext)
|
||||
|
||||
conversation struct {
|
||||
Role *role
|
||||
Prompt string
|
||||
}
|
||||
|
||||
role struct {
|
||||
Name string
|
||||
}
|
||||
)
|
||||
|
||||
func NewContext(options ...ChatContextOption) *ChatContext {
|
||||
ctx := &ChatContext{
|
||||
aiRole: &role{Name: DefaultAiRole},
|
||||
humanRole: &role{Name: DefaultHumanRole},
|
||||
background: "",
|
||||
maxSeqTimes: 1000,
|
||||
preset: "",
|
||||
old: []conversation{},
|
||||
seqTimes: 0,
|
||||
restartSeq: "\n" + DefaultHumanRole + ": ",
|
||||
startSeq: "\n" + DefaultAiRole + ": ",
|
||||
maintainSeqTimes: false,
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
option(ctx)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
// PollConversation 移除最旧的一则对话
|
||||
func (c *ChatContext) PollConversation() {
|
||||
c.old = c.old[1:]
|
||||
c.seqTimes--
|
||||
}
|
||||
|
||||
// ResetConversation 重置对话
|
||||
func (c *ChatContext) ResetConversation(userid string) {
|
||||
public.UserService.ClearUserSessionContext(userid)
|
||||
}
|
||||
|
||||
// SaveConversation 保存对话
|
||||
func (c *ChatContext) SaveConversation(userid string) error {
|
||||
var buffer bytes.Buffer
|
||||
enc := gob.NewEncoder(&buffer)
|
||||
err := enc.Encode(c.old)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
public.UserService.SetUserSessionContext(userid, buffer.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadConversation 加载对话
|
||||
func (c *ChatContext) LoadConversation(userid string) error {
|
||||
dec := gob.NewDecoder(strings.NewReader(public.UserService.GetUserSessionContext(userid)))
|
||||
err := dec.Decode(&c.old)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.seqTimes = len(c.old)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ChatContext) SetHumanRole(role string) {
|
||||
c.humanRole.Name = role
|
||||
c.restartSeq = "\n" + c.humanRole.Name + ": "
|
||||
}
|
||||
|
||||
func (c *ChatContext) SetAiRole(role string) {
|
||||
c.aiRole.Name = role
|
||||
c.startSeq = "\n" + c.aiRole.Name + ": "
|
||||
}
|
||||
|
||||
func (c *ChatContext) SetMaxSeqTimes(times int) {
|
||||
c.maxSeqTimes = times
|
||||
}
|
||||
|
||||
func (c *ChatContext) GetMaxSeqTimes() int {
|
||||
return c.maxSeqTimes
|
||||
}
|
||||
|
||||
func (c *ChatContext) SetBackground(background string) {
|
||||
c.background = background
|
||||
}
|
||||
|
||||
func (c *ChatContext) SetPreset(preset string) {
|
||||
c.preset = preset
|
||||
}
|
||||
|
||||
// 通过 base64 编码字符串开头字符判断图像类型
|
||||
func getImageTypeFromBase64(base64Str string) string {
|
||||
switch {
|
||||
case strings.HasPrefix(base64Str, "/9j/"):
|
||||
return "JPEG"
|
||||
case strings.HasPrefix(base64Str, "iVBOR"):
|
||||
return "PNG"
|
||||
case strings.HasPrefix(base64Str, "R0lG"):
|
||||
return "GIF"
|
||||
case strings.HasPrefix(base64Str, "UklG"):
|
||||
return "WebP"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ChatGPT) ChatWithContext(question string) (answer string, err error) {
|
||||
question = question + "."
|
||||
if tokenizer.MustCalToken(question) > c.maxQuestionLen {
|
||||
return "", OverMaxQuestionLength
|
||||
}
|
||||
if c.ChatContext.seqTimes >= c.ChatContext.maxSeqTimes {
|
||||
if c.ChatContext.maintainSeqTimes {
|
||||
c.ChatContext.PollConversation()
|
||||
} else {
|
||||
return "", OverMaxSequenceTimes
|
||||
}
|
||||
}
|
||||
var promptTable []string
|
||||
promptTable = append(promptTable, c.ChatContext.background)
|
||||
promptTable = append(promptTable, c.ChatContext.preset)
|
||||
for _, v := range c.ChatContext.old {
|
||||
if v.Role == c.ChatContext.humanRole {
|
||||
promptTable = append(promptTable, "\n"+v.Role.Name+": "+v.Prompt)
|
||||
} else {
|
||||
promptTable = append(promptTable, v.Role.Name+": "+v.Prompt)
|
||||
}
|
||||
}
|
||||
promptTable = append(promptTable, "\n"+c.ChatContext.restartSeq+question)
|
||||
prompt := strings.Join(promptTable, "\n")
|
||||
prompt += c.ChatContext.startSeq
|
||||
// 删除对话,直到prompt的长度满足条件
|
||||
for tokenizer.MustCalToken(prompt) > c.maxText {
|
||||
if len(c.ChatContext.old) > 1 { // 至少保留一条记录
|
||||
c.ChatContext.PollConversation() // 删除最旧的一条对话
|
||||
// 重新构建 prompt,计算长度
|
||||
promptTable = promptTable[1:] // 删除promptTable中对应的对话
|
||||
prompt = strings.Join(promptTable, "\n") + c.ChatContext.startSeq
|
||||
} else {
|
||||
break // 如果已经只剩一条记录,那么跳出循环
|
||||
}
|
||||
}
|
||||
// if tokenizer.MustCalToken(prompt) > c.maxText-c.maxAnswerLen {
|
||||
// return "", OverMaxTextLength
|
||||
// }
|
||||
model := public.Config.Model
|
||||
userId := c.userId
|
||||
if public.Config.AzureOn {
|
||||
userId = ""
|
||||
}
|
||||
if isModelSupportedChatCompletions(model) {
|
||||
req := openai.ChatCompletionRequest{
|
||||
Model: model,
|
||||
Messages: []openai.ChatCompletionMessage{
|
||||
{
|
||||
Role: "user",
|
||||
Content: prompt,
|
||||
},
|
||||
},
|
||||
MaxTokens: c.maxAnswerLen,
|
||||
Temperature: 0.6,
|
||||
User: userId,
|
||||
}
|
||||
resp, err := c.client.CreateChatCompletion(c.ctx, req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
resp.Choices[0].Message.Content = formatAnswer(resp.Choices[0].Message.Content)
|
||||
c.ChatContext.old = append(c.ChatContext.old, conversation{
|
||||
Role: c.ChatContext.humanRole,
|
||||
Prompt: question,
|
||||
})
|
||||
c.ChatContext.old = append(c.ChatContext.old, conversation{
|
||||
Role: c.ChatContext.aiRole,
|
||||
Prompt: resp.Choices[0].Message.Content,
|
||||
})
|
||||
c.ChatContext.seqTimes++
|
||||
return resp.Choices[0].Message.Content, nil
|
||||
} else {
|
||||
req := openai.CompletionRequest{
|
||||
Model: model,
|
||||
MaxTokens: c.maxAnswerLen,
|
||||
Prompt: prompt,
|
||||
Temperature: 0.6,
|
||||
User: c.userId,
|
||||
Stop: []string{c.ChatContext.aiRole.Name + ":", c.ChatContext.humanRole.Name + ":"},
|
||||
}
|
||||
resp, err := c.client.CreateCompletion(c.ctx, req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
resp.Choices[0].Text = formatAnswer(resp.Choices[0].Text)
|
||||
c.ChatContext.old = append(c.ChatContext.old, conversation{
|
||||
Role: c.ChatContext.humanRole,
|
||||
Prompt: question,
|
||||
})
|
||||
c.ChatContext.old = append(c.ChatContext.old, conversation{
|
||||
Role: c.ChatContext.aiRole,
|
||||
Prompt: resp.Choices[0].Text,
|
||||
})
|
||||
c.ChatContext.seqTimes++
|
||||
return resp.Choices[0].Text, nil
|
||||
}
|
||||
}
|
||||
func (c *ChatGPT) GenerateImage(ctx context.Context, prompt string) (string, error) {
|
||||
model := public.Config.Model
|
||||
imageModel := public.Config.ImageModel
|
||||
if isModelSupportedChatCompletions(model) {
|
||||
req := openai.ImageRequest{
|
||||
Prompt: prompt,
|
||||
Model: imageModel,
|
||||
Size: openai.CreateImageSize1024x1024,
|
||||
ResponseFormat: openai.CreateImageResponseFormatB64JSON,
|
||||
N: 1,
|
||||
User: c.userId,
|
||||
}
|
||||
respBase64, err := c.client.CreateImage(c.ctx, req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
imgBytes, err := base64.StdEncoding.DecodeString(respBase64.Data[0].B64JSON)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
r := bytes.NewReader(imgBytes)
|
||||
|
||||
// dall-e-3 返回的是 WebP 格式的图片,需要判断处理
|
||||
imgType := getImageTypeFromBase64(respBase64.Data[0].B64JSON)
|
||||
var imgData image.Image
|
||||
var imgErr error
|
||||
if imgType == "WebP" {
|
||||
imgData, imgErr = webp.Decode(r)
|
||||
} else {
|
||||
imgData, _, imgErr = image.Decode(r)
|
||||
}
|
||||
if imgErr != nil {
|
||||
return "", imgErr
|
||||
}
|
||||
|
||||
imageName := time.Now().Format("20060102-150405") + ".png"
|
||||
clientId, _ := ctx.Value(public.DingTalkClientIdKeyName).(string)
|
||||
client := public.DingTalkClientManager.GetClientByOAuthClientID(clientId)
|
||||
mediaResult, uploadErr := &dingbot.MediaUploadResult{}, errors.New(fmt.Sprintf("unknown clientId: %s", clientId))
|
||||
if client != nil {
|
||||
mediaResult, uploadErr = client.UploadMedia(imgBytes, imageName, dingbot.MediaTypeImage, dingbot.MimeTypeImagePng)
|
||||
}
|
||||
|
||||
err = os.MkdirAll("data/images", 0755)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
file, err := os.Create("data/images/" + imageName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if err := png.Encode(file, imgData); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if uploadErr == nil {
|
||||
return mediaResult.MediaID, nil
|
||||
} else {
|
||||
return public.Config.ServiceURL + "/images/" + imageName, nil
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func WithMaxSeqTimes(times int) ChatContextOption {
|
||||
return func(c *ChatContext) {
|
||||
c.SetMaxSeqTimes(times)
|
||||
}
|
||||
}
|
||||
|
||||
// WithOldConversation 从文件中加载对话
|
||||
func WithOldConversation(userid string) ChatContextOption {
|
||||
return func(c *ChatContext) {
|
||||
_ = c.LoadConversation(userid)
|
||||
}
|
||||
}
|
||||
|
||||
func WithMaintainSeqTimes(maintain bool) ChatContextOption {
|
||||
return func(c *ChatContext) {
|
||||
c.maintainSeqTimes = maintain
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package chatgpt
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOfflineContext(t *testing.T) {
|
||||
key := os.Getenv("CHATGPT_API_KEY")
|
||||
if key == "" {
|
||||
t.Skip("CHATGPT_API_KEY is not set")
|
||||
}
|
||||
cli := New("")
|
||||
reply, err := cli.ChatWithContext("我叫老三,你是?")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Logf("我叫老三,你是? => %s", reply)
|
||||
|
||||
err = cli.ChatContext.SaveConversation("test.conversation")
|
||||
if err != nil {
|
||||
t.Fatalf("储存对话记录失败: %v", err)
|
||||
}
|
||||
cli.ChatContext.ResetConversation("")
|
||||
|
||||
reply, err = cli.ChatWithContext("你知道我是谁吗?")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Logf("你知道我是谁吗? => %s", reply)
|
||||
// assert.NotContains(t, reply, "老三")
|
||||
|
||||
err = cli.ChatContext.LoadConversation("test.conversation")
|
||||
if err != nil {
|
||||
t.Fatalf("读取对话记录失败: %v", err)
|
||||
}
|
||||
|
||||
reply, err = cli.ChatWithContext("你知道我是谁吗?")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Logf("你知道我是谁吗? => %s", reply)
|
||||
|
||||
// AI 理应知道他叫老三
|
||||
// assert.Contains(t, reply, "老三")
|
||||
}
|
||||
|
||||
func TestMaintainContext(t *testing.T) {
|
||||
key := os.Getenv("CHATGPT_API_KEY")
|
||||
if key == "" {
|
||||
t.Skip("CHATGPT_API_KEY is not set")
|
||||
}
|
||||
cli := New("")
|
||||
cli.ChatContext = NewContext(
|
||||
WithMaxSeqTimes(1),
|
||||
WithMaintainSeqTimes(true),
|
||||
)
|
||||
|
||||
reply, err := cli.ChatWithContext("我叫老三,你是?")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("我叫老三,你是? => %s", reply)
|
||||
|
||||
reply, err = cli.ChatWithContext("你知道我是谁吗?")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("你知道我是谁吗? => %s", reply)
|
||||
|
||||
// 对话次数已经超过 1 次,因此最先前的对话已被移除,AI 理应不知道他叫老三
|
||||
// assert.NotContains(t, reply, "老三")
|
||||
}
|
||||
|
||||
func init() {
|
||||
// 本地加载适用于本地测试,如果要在github进行测试,可以透过传入 secrets 到环境参数
|
||||
// _ = godotenv.Load(".env.local")
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package chatgpt
|
||||
|
||||
import "errors"
|
||||
|
||||
// OverMaxSequenceTimes 超过最大对话时间
|
||||
var OverMaxSequenceTimes = errors.New("maximum conversation times exceeded")
|
||||
|
||||
// OverMaxTextLength 超过最大文本长度
|
||||
var OverMaxTextLength = errors.New("maximum text length exceeded")
|
||||
|
||||
// OverMaxQuestionLength 超过最大问题长度
|
||||
var OverMaxQuestionLength = errors.New("maximum question length exceeded")
|
||||
@@ -1,83 +0,0 @@
|
||||
package chatgpt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/avast/retry-go"
|
||||
|
||||
"github.com/eryajf/chatgpt-dingtalk/pkg/logger"
|
||||
"github.com/eryajf/chatgpt-dingtalk/public"
|
||||
)
|
||||
|
||||
// SingleQa 单聊
|
||||
func SingleQa(question, userId string) (answer string, err error) {
|
||||
chat := New(userId)
|
||||
defer chat.Close()
|
||||
// 定义一个重试策略
|
||||
retryStrategy := []retry.Option{
|
||||
retry.Delay(100 * time.Millisecond),
|
||||
retry.Attempts(3),
|
||||
retry.LastErrorOnly(true),
|
||||
}
|
||||
// 使用重试策略进行重试
|
||||
err = retry.Do(
|
||||
func() error {
|
||||
answer, err = chat.ChatWithContext(question)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
retryStrategy...)
|
||||
return
|
||||
}
|
||||
|
||||
// ContextQa 串聊
|
||||
func ContextQa(question, userId string) (chat *ChatGPT, answer string, err error) {
|
||||
chat = New(userId)
|
||||
if public.UserService.GetUserSessionContext(userId) != "" {
|
||||
err := chat.ChatContext.LoadConversation(userId)
|
||||
if err != nil {
|
||||
logger.Warning("load station failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
retryStrategy := []retry.Option{
|
||||
retry.Delay(100 * time.Millisecond),
|
||||
retry.Attempts(3),
|
||||
retry.LastErrorOnly(true)}
|
||||
// 使用重试策略进行重试
|
||||
err = retry.Do(
|
||||
func() error {
|
||||
answer, err = chat.ChatWithContext(question)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
retryStrategy...)
|
||||
return
|
||||
}
|
||||
|
||||
// ImageQa 生成图片
|
||||
func ImageQa(ctx context.Context, question, userId string) (answer string, err error) {
|
||||
chat := New(userId)
|
||||
defer chat.Close()
|
||||
// 定义一个重试策略
|
||||
retryStrategy := []retry.Option{
|
||||
retry.Delay(100 * time.Millisecond),
|
||||
retry.Attempts(3),
|
||||
retry.LastErrorOnly(true),
|
||||
}
|
||||
// 使用重试策略进行重试
|
||||
err = retry.Do(
|
||||
func() error {
|
||||
answer, err = chat.GenerateImage(ctx, question)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
retryStrategy...)
|
||||
return
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package chatgpt
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 适配 deepseek r1
|
||||
func formatAnswer(answer string) string {
|
||||
answer = strings.TrimSpace(answer)
|
||||
|
||||
re := regexp.MustCompile(`(?s)<think>.*?</think>`)
|
||||
answer = re.ReplaceAllString(answer, "")
|
||||
|
||||
answer = strings.ReplaceAll(answer, "<think>", "")
|
||||
answer = strings.ReplaceAll(answer, "</think>", "")
|
||||
|
||||
answer = strings.TrimSpace(answer)
|
||||
|
||||
return answer
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
module chatgpt
|
||||
|
||||
go 1.22
|
||||
|
||||
toolchain go1.22.5
|
||||
|
||||
require (
|
||||
github.com/avast/retry-go v3.0.0+incompatible
|
||||
github.com/chai2010/webp v1.1.1
|
||||
github.com/eryajf/chatgpt-dingtalk v1.0.11
|
||||
github.com/pandodao/tokenizer-go v0.2.0
|
||||
github.com/sashabaranov/go-openai v1.27.1
|
||||
)
|
||||
|
||||
replace github.com/eryajf/chatgpt-dingtalk v1.0.11 => ../..
|
||||
|
||||
require (
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/charmbracelet/lipgloss v0.12.1 // indirect
|
||||
github.com/charmbracelet/log v0.4.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.2 // indirect
|
||||
github.com/dop251/goja v0.0.0-20240707163329-b1681fb2a2f5 // indirect
|
||||
github.com/dop251/goja_nodejs v0.0.0-20240728170619-29b559befffc // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||
github.com/glebarez/sqlite v1.11.0 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/go-resty/resty/v2 v2.13.1 // indirect
|
||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
|
||||
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
golang.org/x/net v0.27.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gorm.io/gorm v1.25.11 // indirect
|
||||
modernc.org/libc v1.55.6 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
modernc.org/sqlite v1.31.1 // indirect
|
||||
)
|
||||
@@ -1,186 +0,0 @@
|
||||
github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
|
||||
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk=
|
||||
github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
|
||||
github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E=
|
||||
github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=
|
||||
github.com/charmbracelet/lipgloss v0.12.1/go.mod h1:V2CiwIuhx9S1S1ZlADfOj9HmxeMAORuz5izHb0zGbB8=
|
||||
github.com/charmbracelet/log v0.2.1 h1:1z7jpkk4yKyjwlmKmKMM5qnEDSpV32E7XtWhuv0mTZE=
|
||||
github.com/charmbracelet/log v0.2.1/go.mod h1:GwFfjewhcVDWLrpAbY5A0Hin9YOlEn40eWT4PNaxFT4=
|
||||
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
|
||||
github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
|
||||
github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
|
||||
github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.9.0 h1:pTK/l/3qYIKaRXuHnEnIf7Y5NxfRPfpb7dis6/gdlVI=
|
||||
github.com/dlclark/regexp2 v1.9.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.11.2/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
|
||||
github.com/dop251/goja v0.0.0-20221118162653-d4bf6fde1b86/go.mod h1:yRkwfj0CBpOGre+TwBsqPV0IH0Pk73e4PXJOeNDboGs=
|
||||
github.com/dop251/goja v0.0.0-20230402114112-623f9dda9079 h1:xkbJGxVnk5sM8/LXeTKaBOfAZrI+iqvIPyH8oK1c6CQ=
|
||||
github.com/dop251/goja v0.0.0-20230402114112-623f9dda9079/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
|
||||
github.com/dop251/goja v0.0.0-20240707163329-b1681fb2a2f5/go.mod h1:o31y53rb/qiIAONF7w3FHJZRqqP3fzHUr1HqanthByw=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20230322100729-2550c7b6c124 h1:QDuDMgEkC/lnmvk0d/fZfcUUml18uUbS9TY5QtbdFhs=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20230322100729-2550c7b6c124/go.mod h1:0tlktQL7yHfYEtjcRGi/eiOkbDR5XF7gyFFvbC5//E0=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20240728170619-29b559befffc/go.mod h1:VULptt4Q/fNzQUJlqY/GP3qHyU7ZH46mFkBZe0ZTokU=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4=
|
||||
github.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0=
|
||||
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
||||
github.com/glebarez/sqlite v1.7.0 h1:A7Xj/KN2Lvie4Z4rrgQHY8MsbebX3NyWsL3n2i82MVI=
|
||||
github.com/glebarez/sqlite v1.7.0/go.mod h1:PkeevrRlF/1BhQBCnzcMWzgrIk7IOop+qS2jUYLfHhk=
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
|
||||
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
|
||||
github.com/go-resty/resty/v2 v2.13.1/go.mod h1:GznXlLxkq6Nh4sU59rPmUw3VtgpO3aS96ORAI6Q7d+0=
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
||||
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
|
||||
github.com/google/pprof v0.0.0-20230406165453-00490a63f317 h1:hFhpt7CTmR3DX+b4R19ydQFtofxT0Sv3QsKNMVQYTMQ=
|
||||
github.com/google/pprof v0.0.0-20230406165453-00490a63f317/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs=
|
||||
github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ=
|
||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||
github.com/pandodao/tokenizer-go v0.2.0 h1:NhfI8fGvQkDld2cZCag6NEU3pJ/ugU9zoY1R/zi9YCs=
|
||||
github.com/pandodao/tokenizer-go v0.2.0/go.mod h1:t6qFbaleKxbv0KNio2XUN/mfGM5WKv4haPXDQWVDG00=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/sashabaranov/go-openai v1.17.6 h1:hYXRPM1xO6QLOJhWEOMlSg/l3jERiKDKd1qIoK22lvs=
|
||||
github.com/sashabaranov/go-openai v1.17.6/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/sashabaranov/go-openai v1.27.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gorm.io/gorm v1.24.6 h1:wy98aq9oFEetsc4CAbKD2SoBCdMzsbSIvSUUFJuHi5s=
|
||||
gorm.io/gorm v1.24.6/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY=
|
||||
modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw=
|
||||
modernc.org/libc v1.55.6/go.mod h1:JXguUpMkbw1gknxspNE9XaG+kk9hDAAnBxpA6KGLiyA=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||
modernc.org/sqlite v1.20.4 h1:J8+m2trkN+KKoE7jglyHYYYiaq5xmz2HoHJIiBlRzbE=
|
||||
modernc.org/sqlite v1.20.4/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A=
|
||||
modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA=
|
||||
@@ -1,30 +0,0 @@
|
||||
package chatgpt
|
||||
|
||||
import openai "github.com/sashabaranov/go-openai"
|
||||
|
||||
var ModelsSupportChatCompletions = []string{
|
||||
openai.GPT432K0613,
|
||||
openai.GPT432K0314,
|
||||
openai.GPT432K,
|
||||
openai.GPT40613,
|
||||
openai.GPT40314,
|
||||
openai.GPT4TurboPreview,
|
||||
openai.GPT4VisionPreview,
|
||||
openai.GPT4,
|
||||
openai.GPT4oMini,
|
||||
openai.GPT3Dot5Turbo1106,
|
||||
openai.GPT3Dot5Turbo0613,
|
||||
openai.GPT3Dot5Turbo0301,
|
||||
openai.GPT3Dot5Turbo16K,
|
||||
openai.GPT3Dot5Turbo16K0613,
|
||||
openai.GPT3Dot5Turbo,
|
||||
}
|
||||
|
||||
func isModelSupportedChatCompletions(model string) bool {
|
||||
for _, m := range ModelsSupportChatCompletions {
|
||||
if m == model {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
154
pkg/dingbot/stream.go
Normal file
154
pkg/dingbot/stream.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package dingbot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||
dingtalkcard "github.com/alibabacloud-go/dingtalk/card_1_0"
|
||||
util "github.com/alibabacloud-go/tea-utils/v2/service"
|
||||
"github.com/alibabacloud-go/tea/tea"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// StreamCardClient 流式卡片客户端
|
||||
type StreamCardClient struct {
|
||||
client *dingtalkcard.Client
|
||||
}
|
||||
|
||||
// NewStreamCardClient 创建流式卡片客户端
|
||||
func NewStreamCardClient() (*StreamCardClient, error) {
|
||||
config := &openapi.Config{}
|
||||
config.Protocol = tea.String("https")
|
||||
config.RegionId = tea.String("central")
|
||||
client, err := dingtalkcard.NewClient(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &StreamCardClient{
|
||||
client: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateAndDeliverCardRequest 创建并投放卡片请求
|
||||
type CreateAndDeliverCardRequest struct {
|
||||
CardTemplateID string
|
||||
OutTrackID string
|
||||
ConversationID string
|
||||
SenderStaffID string
|
||||
RobotCode string
|
||||
OpenSpaceID string
|
||||
ConversationType string // "1" for private chat, "2" for group chat
|
||||
CardData map[string]string
|
||||
}
|
||||
|
||||
// CreateAndDeliverCard 创建并投放流式卡片
|
||||
func (s *StreamCardClient) CreateAndDeliverCard(accessToken string, req *CreateAndDeliverCardRequest) error {
|
||||
headers := &dingtalkcard.CreateAndDeliverHeaders{
|
||||
XAcsDingtalkAccessToken: tea.String(accessToken),
|
||||
}
|
||||
|
||||
cardData := &dingtalkcard.CreateAndDeliverRequestCardData{
|
||||
CardParamMap: make(map[string]*string),
|
||||
}
|
||||
for k, v := range req.CardData {
|
||||
cardData.CardParamMap[k] = tea.String(v)
|
||||
}
|
||||
|
||||
createReq := &dingtalkcard.CreateAndDeliverRequest{
|
||||
CardTemplateId: tea.String(req.CardTemplateID),
|
||||
OutTrackId: tea.String(req.OutTrackID),
|
||||
CardData: cardData,
|
||||
CallbackType: tea.String("STREAM"),
|
||||
UserIdType: tea.Int32(1),
|
||||
ImGroupOpenSpaceModel: &dingtalkcard.CreateAndDeliverRequestImGroupOpenSpaceModel{
|
||||
SupportForward: tea.Bool(true),
|
||||
},
|
||||
ImRobotOpenSpaceModel: &dingtalkcard.CreateAndDeliverRequestImRobotOpenSpaceModel{
|
||||
SupportForward: tea.Bool(true),
|
||||
},
|
||||
}
|
||||
|
||||
if req.OpenSpaceID != "" {
|
||||
createReq.SetOpenSpaceId(req.OpenSpaceID)
|
||||
}
|
||||
|
||||
// Handle different conversation types with appropriate delivery models
|
||||
switch req.ConversationType {
|
||||
case "2": // Group chat
|
||||
if req.RobotCode != "" {
|
||||
createReq.SetImGroupOpenDeliverModel(
|
||||
&dingtalkcard.CreateAndDeliverRequestImGroupOpenDeliverModel{
|
||||
RobotCode: tea.String(req.RobotCode),
|
||||
})
|
||||
}
|
||||
case "1": // Private chat with robot
|
||||
// For private chat, use ImRobotOpenDeliverModel with SpaceType
|
||||
createReq.SetImRobotOpenDeliverModel(
|
||||
&dingtalkcard.CreateAndDeliverRequestImRobotOpenDeliverModel{
|
||||
SpaceType: tea.String("IM_GROUP"),
|
||||
})
|
||||
default:
|
||||
// Fallback to group model if conversation type is unknown
|
||||
if req.RobotCode != "" {
|
||||
createReq.SetImGroupOpenDeliverModel(
|
||||
&dingtalkcard.CreateAndDeliverRequestImGroupOpenDeliverModel{
|
||||
RobotCode: tea.String(req.RobotCode),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
_, err := s.client.CreateAndDeliverWithOptions(createReq, headers, &util.RuntimeOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
// StreamingUpdateRequest 流式更新请求
|
||||
type StreamingUpdateRequest struct {
|
||||
OutTrackID string
|
||||
Key string
|
||||
Content string
|
||||
IsFull bool
|
||||
IsFinalize bool
|
||||
}
|
||||
|
||||
// StreamingUpdate 流式更新卡片内容
|
||||
func (s *StreamCardClient) StreamingUpdate(accessToken string, req *StreamingUpdateRequest) error {
|
||||
headers := &dingtalkcard.StreamingUpdateHeaders{
|
||||
XAcsDingtalkAccessToken: tea.String(accessToken),
|
||||
}
|
||||
|
||||
updateReq := &dingtalkcard.StreamingUpdateRequest{
|
||||
OutTrackId: tea.String(req.OutTrackID),
|
||||
Guid: tea.String(uuid.New().String()),
|
||||
Key: tea.String(req.Key),
|
||||
Content: tea.String(req.Content),
|
||||
IsFull: tea.Bool(req.IsFull),
|
||||
IsFinalize: tea.Bool(req.IsFinalize),
|
||||
IsError: tea.Bool(false),
|
||||
}
|
||||
|
||||
_, err := s.client.StreamingUpdateWithOptions(updateReq, headers, &util.RuntimeOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateAIStreamCard 更新AI流式卡片 (简化版本,不依赖卡片模板)
|
||||
func (c *DingTalkClient) UpdateAIStreamCard(trackID, content string, isFinalize bool) error {
|
||||
cardClient, err := NewStreamCardClient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create stream card client: %w", err)
|
||||
}
|
||||
|
||||
accessToken, err := c.GetAccessToken()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get access token: %w", err)
|
||||
}
|
||||
|
||||
req := &StreamingUpdateRequest{
|
||||
OutTrackID: trackID,
|
||||
Key: "content",
|
||||
Content: content,
|
||||
IsFull: true,
|
||||
IsFinalize: isFinalize,
|
||||
}
|
||||
|
||||
return cardClient.StreamingUpdate(accessToken, req)
|
||||
}
|
||||
34
pkg/llm/api.go
Normal file
34
pkg/llm/api.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package llm
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/eryajf/chatgpt-dingtalk/public"
|
||||
)
|
||||
|
||||
// SingleQa 单聊
|
||||
func SingleQa(question, userId string) (string, error) {
|
||||
client := NewClient(userId)
|
||||
defer client.Close()
|
||||
|
||||
return client.ChatWithContext(question)
|
||||
}
|
||||
|
||||
// ContextQa 串聊
|
||||
func ContextQa(question, userId string) (*Client, string, error) {
|
||||
client := NewClient(userId)
|
||||
if public.UserService.GetUserSessionContext(userId) != "" {
|
||||
_ = client.ChatContext.LoadConversation(userId)
|
||||
}
|
||||
|
||||
answer, err := client.ChatWithContext(question)
|
||||
return client, answer, err
|
||||
}
|
||||
|
||||
// ImageQa 生成图片
|
||||
func ImageQa(ctx context.Context, question, userId string) (string, error) {
|
||||
client := NewClient(userId)
|
||||
defer client.Close()
|
||||
|
||||
return client.GenerateImage(ctx, question)
|
||||
}
|
||||
48
pkg/llm/chat.go
Normal file
48
pkg/llm/chat.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package llm
|
||||
|
||||
import (
|
||||
"github.com/pandodao/tokenizer-go"
|
||||
openai "github.com/sashabaranov/go-openai"
|
||||
|
||||
"github.com/eryajf/chatgpt-dingtalk/public"
|
||||
)
|
||||
|
||||
// ChatWithContext 对话接口
|
||||
func (c *Client) ChatWithContext(question string) (string, error) {
|
||||
if tokenizer.MustCalToken(question) > c.maxQuestionLen {
|
||||
return "", ErrOverMaxQuestionLength
|
||||
}
|
||||
|
||||
// 构建消息列表
|
||||
messages := c.buildMessages(question)
|
||||
|
||||
model := public.Config.Model
|
||||
userId := c.userId
|
||||
if public.Config.AzureOn {
|
||||
userId = ""
|
||||
}
|
||||
|
||||
req := openai.ChatCompletionRequest{
|
||||
Model: model,
|
||||
Messages: messages,
|
||||
MaxTokens: c.maxAnswerLen,
|
||||
Temperature: 0.6,
|
||||
User: userId,
|
||||
}
|
||||
|
||||
resp, err := c.client.CreateChatCompletion(c.ctx, req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
answer := resp.Choices[0].Message.Content
|
||||
|
||||
// 保存对话上下文
|
||||
c.ChatContext.old = append(c.ChatContext.old,
|
||||
conversation{Role: c.ChatContext.humanRole, Prompt: question},
|
||||
conversation{Role: c.ChatContext.aiRole, Prompt: answer},
|
||||
)
|
||||
c.ChatContext.seqTimes++
|
||||
|
||||
return answer, nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package chatgpt
|
||||
package llm
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -11,32 +11,31 @@ import (
|
||||
"github.com/eryajf/chatgpt-dingtalk/public"
|
||||
)
|
||||
|
||||
type ChatGPT struct {
|
||||
type Client struct {
|
||||
client *openai.Client
|
||||
ctx context.Context
|
||||
userId string
|
||||
maxQuestionLen int
|
||||
maxText int
|
||||
maxAnswerLen int
|
||||
timeOut time.Duration // 超时时间, 0表示不超时
|
||||
timeOut time.Duration
|
||||
doneChan chan struct{}
|
||||
cancel func()
|
||||
|
||||
ChatContext *ChatContext
|
||||
ChatContext *Context
|
||||
}
|
||||
|
||||
func New(userId string) *ChatGPT {
|
||||
var ctx context.Context
|
||||
var cancel func()
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), 600*time.Second)
|
||||
func NewClient(userId string) *Client {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 600*time.Second)
|
||||
timeOutChan := make(chan struct{}, 1)
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
timeOutChan <- struct{}{} // 发送超时信号,或是提示结束,用于聊天机器人场景,配合GetTimeOutChan() 使用
|
||||
timeOutChan <- struct{}{}
|
||||
}()
|
||||
|
||||
config := openai.DefaultConfig(public.Config.ApiKey)
|
||||
|
||||
// Azure配置
|
||||
if public.Config.AzureOn {
|
||||
config = openai.DefaultAzureConfig(
|
||||
public.Config.AzureOpenAIToken,
|
||||
@@ -47,42 +46,48 @@ func New(userId string) *ChatGPT {
|
||||
return public.Config.AzureDeploymentName
|
||||
}
|
||||
} else {
|
||||
if public.Config.HttpProxy != "" {
|
||||
config.HTTPClient.Transport = &http.Transport{
|
||||
// 设置代理
|
||||
Proxy: func(req *http.Request) (*url.URL, error) {
|
||||
return url.Parse(public.Config.HttpProxy)
|
||||
}}
|
||||
// HTTP客户端配置
|
||||
transport := &http.Transport{
|
||||
MaxIdleConns: 100,
|
||||
MaxIdleConnsPerHost: 10,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
}
|
||||
|
||||
if public.Config.HttpProxy != "" {
|
||||
proxyURL, _ := url.Parse(public.Config.HttpProxy)
|
||||
transport.Proxy = http.ProxyURL(proxyURL)
|
||||
}
|
||||
|
||||
config.HTTPClient = &http.Client{Transport: transport}
|
||||
|
||||
if public.Config.BaseURL != "" {
|
||||
config.BaseURL = public.Config.BaseURL + "/v1"
|
||||
}
|
||||
}
|
||||
|
||||
return &ChatGPT{
|
||||
return &Client{
|
||||
client: openai.NewClientWithConfig(config),
|
||||
ctx: ctx,
|
||||
userId: userId,
|
||||
maxQuestionLen: public.Config.MaxQuestionLen, // 最大问题长度
|
||||
maxAnswerLen: public.Config.MaxAnswerLen, // 最大答案长度
|
||||
maxText: public.Config.MaxText, // 最大文本 = 问题 + 回答, 接口限制
|
||||
maxQuestionLen: public.Config.MaxQuestionLen,
|
||||
maxAnswerLen: public.Config.MaxAnswerLen,
|
||||
maxText: public.Config.MaxText,
|
||||
timeOut: public.Config.SessionTimeout,
|
||||
doneChan: timeOutChan,
|
||||
cancel: func() {
|
||||
cancel()
|
||||
},
|
||||
ChatContext: NewContext(),
|
||||
cancel: cancel,
|
||||
ChatContext: NewContext(),
|
||||
}
|
||||
}
|
||||
func (c *ChatGPT) Close() {
|
||||
|
||||
func (c *Client) Close() {
|
||||
c.cancel()
|
||||
}
|
||||
|
||||
func (c *ChatGPT) GetDoneChan() chan struct{} {
|
||||
func (c *Client) GetDoneChan() chan struct{} {
|
||||
return c.doneChan
|
||||
}
|
||||
|
||||
func (c *ChatGPT) SetMaxQuestionLen(maxQuestionLen int) int {
|
||||
func (c *Client) SetMaxQuestionLen(maxQuestionLen int) int {
|
||||
if maxQuestionLen > c.maxText-c.maxAnswerLen {
|
||||
maxQuestionLen = c.maxText - c.maxAnswerLen
|
||||
}
|
||||
139
pkg/llm/context.go
Normal file
139
pkg/llm/context.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package llm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"strings"
|
||||
|
||||
"github.com/eryajf/chatgpt-dingtalk/public"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultAiRole = "AI"
|
||||
DefaultHumanRole = "Human"
|
||||
|
||||
DefaultCharacter = []string{"helpful", "creative", "clever", "friendly", "lovely", "talkative"}
|
||||
DefaultBackground = "The following is a conversation with AI assistant. The assistant is %s"
|
||||
DefaultPreset = "\n%s: 你好,让我们开始愉快的谈话!\n%s: 我是 AI assistant ,请问你有什么问题?"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
background string
|
||||
preset string
|
||||
maxSeqTimes int
|
||||
aiRole *role
|
||||
humanRole *role
|
||||
|
||||
old []conversation
|
||||
restartSeq string
|
||||
startSeq string
|
||||
|
||||
seqTimes int
|
||||
|
||||
maintainSeqTimes bool
|
||||
}
|
||||
|
||||
type ContextOption func(*Context)
|
||||
|
||||
type conversation struct {
|
||||
Role *role
|
||||
Prompt string
|
||||
}
|
||||
|
||||
type role struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func NewContext(options ...ContextOption) *Context {
|
||||
ctx := &Context{
|
||||
aiRole: &role{Name: DefaultAiRole},
|
||||
humanRole: &role{Name: DefaultHumanRole},
|
||||
background: "",
|
||||
maxSeqTimes: 1000,
|
||||
preset: "",
|
||||
old: []conversation{},
|
||||
seqTimes: 0,
|
||||
restartSeq: "\n" + DefaultHumanRole + ": ",
|
||||
startSeq: "\n" + DefaultAiRole + ": ",
|
||||
maintainSeqTimes: false,
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
option(ctx)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (c *Context) PollConversation() {
|
||||
c.old = c.old[1:]
|
||||
c.seqTimes--
|
||||
}
|
||||
|
||||
func (c *Context) ResetConversation(userid string) {
|
||||
public.UserService.ClearUserSessionContext(userid)
|
||||
}
|
||||
|
||||
func (c *Context) SaveConversation(userid string) error {
|
||||
var buffer bytes.Buffer
|
||||
enc := gob.NewEncoder(&buffer)
|
||||
err := enc.Encode(c.old)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
public.UserService.SetUserSessionContext(userid, buffer.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Context) LoadConversation(userid string) error {
|
||||
dec := gob.NewDecoder(strings.NewReader(public.UserService.GetUserSessionContext(userid)))
|
||||
err := dec.Decode(&c.old)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.seqTimes = len(c.old)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Context) SetHumanRole(role string) {
|
||||
c.humanRole.Name = role
|
||||
c.restartSeq = "\n" + c.humanRole.Name + ": "
|
||||
}
|
||||
|
||||
func (c *Context) SetAiRole(role string) {
|
||||
c.aiRole.Name = role
|
||||
c.startSeq = "\n" + c.aiRole.Name + ": "
|
||||
}
|
||||
|
||||
func (c *Context) SetMaxSeqTimes(times int) {
|
||||
c.maxSeqTimes = times
|
||||
}
|
||||
|
||||
func (c *Context) GetMaxSeqTimes() int {
|
||||
return c.maxSeqTimes
|
||||
}
|
||||
|
||||
func (c *Context) SetBackground(background string) {
|
||||
c.background = background
|
||||
}
|
||||
|
||||
func (c *Context) SetPreset(preset string) {
|
||||
c.preset = preset
|
||||
}
|
||||
|
||||
func WithMaxSeqTimes(times int) ContextOption {
|
||||
return func(c *Context) {
|
||||
c.SetMaxSeqTimes(times)
|
||||
}
|
||||
}
|
||||
|
||||
func WithOldConversation(userid string) ContextOption {
|
||||
return func(c *Context) {
|
||||
_ = c.LoadConversation(userid)
|
||||
}
|
||||
}
|
||||
|
||||
func WithMaintainSeqTimes(maintain bool) ContextOption {
|
||||
return func(c *Context) {
|
||||
c.maintainSeqTimes = maintain
|
||||
}
|
||||
}
|
||||
10
pkg/llm/errors.go
Normal file
10
pkg/llm/errors.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package llm
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrOverMaxQuestionLength = errors.New("maximum question length exceeded")
|
||||
ErrOverMaxAnswerLength = errors.New("maximum answer length exceeded")
|
||||
ErrOverMaxTextLength = errors.New("maximum text length exceeded")
|
||||
ErrOverMaxSequenceTimes = errors.New("maximum number of sequence exceeded")
|
||||
)
|
||||
102
pkg/llm/image.go
Normal file
102
pkg/llm/image.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package llm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
"image/png"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/image/webp"
|
||||
|
||||
openai "github.com/sashabaranov/go-openai"
|
||||
|
||||
"github.com/eryajf/chatgpt-dingtalk/pkg/dingbot"
|
||||
"github.com/eryajf/chatgpt-dingtalk/public"
|
||||
)
|
||||
|
||||
func getImageTypeFromBase64(base64Str string) string {
|
||||
switch {
|
||||
case strings.HasPrefix(base64Str, "/9j/"):
|
||||
return "JPEG"
|
||||
case strings.HasPrefix(base64Str, "iVBOR"):
|
||||
return "PNG"
|
||||
case strings.HasPrefix(base64Str, "R0lG"):
|
||||
return "GIF"
|
||||
case strings.HasPrefix(base64Str, "UklG"):
|
||||
return "WebP"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GenerateImage(ctx context.Context, prompt string) (string, error) {
|
||||
imageModel := public.Config.ImageModel
|
||||
req := openai.ImageRequest{
|
||||
Prompt: prompt,
|
||||
Model: imageModel,
|
||||
Size: openai.CreateImageSize1024x1024,
|
||||
ResponseFormat: openai.CreateImageResponseFormatB64JSON,
|
||||
N: 1,
|
||||
User: c.userId,
|
||||
}
|
||||
|
||||
respBase64, err := c.client.CreateImage(c.ctx, req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
imgBytes, err := base64.StdEncoding.DecodeString(respBase64.Data[0].B64JSON)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
r := bytes.NewReader(imgBytes)
|
||||
imgType := getImageTypeFromBase64(respBase64.Data[0].B64JSON)
|
||||
|
||||
var imgData image.Image
|
||||
if imgType == "WebP" {
|
||||
imgData, err = webp.Decode(r)
|
||||
} else {
|
||||
imgData, _, err = image.Decode(r)
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
imageName := time.Now().Format("20060102-150405") + ".png"
|
||||
clientId, _ := ctx.Value(public.DingTalkClientIdKeyName).(string)
|
||||
client := public.DingTalkClientManager.GetClientByOAuthClientID(clientId)
|
||||
|
||||
mediaResult, uploadErr := &dingbot.MediaUploadResult{}, errors.New(fmt.Sprintf("unknown clientId: %s", clientId))
|
||||
if client != nil {
|
||||
mediaResult, uploadErr = client.UploadMedia(imgBytes, imageName, dingbot.MediaTypeImage, dingbot.MimeTypeImagePng)
|
||||
}
|
||||
|
||||
err = os.MkdirAll("data/images", 0755)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
file, err := os.Create("data/images/" + imageName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if err := png.Encode(file, imgData); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if uploadErr == nil {
|
||||
return mediaResult.MediaID, nil
|
||||
}
|
||||
return public.Config.ServiceURL + "/images/" + imageName, nil
|
||||
}
|
||||
153
pkg/llm/stream.go
Normal file
153
pkg/llm/stream.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package llm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/pandodao/tokenizer-go"
|
||||
openai "github.com/sashabaranov/go-openai"
|
||||
|
||||
"github.com/eryajf/chatgpt-dingtalk/public"
|
||||
)
|
||||
|
||||
// ChatWithContextStream 流式对话,返回一个channel用于接收流式内容
|
||||
func (c *Client) ChatWithContextStream(question string) (<-chan string, error) {
|
||||
if tokenizer.MustCalToken(question) > c.maxQuestionLen {
|
||||
return nil, ErrOverMaxQuestionLength
|
||||
}
|
||||
|
||||
// 构建消息列表
|
||||
messages := c.buildMessages(question)
|
||||
|
||||
model := public.Config.Model
|
||||
userId := c.userId
|
||||
if public.Config.AzureOn {
|
||||
userId = ""
|
||||
}
|
||||
|
||||
req := openai.ChatCompletionRequest{
|
||||
Model: model,
|
||||
Messages: messages,
|
||||
MaxTokens: c.maxAnswerLen,
|
||||
Temperature: 0.6,
|
||||
User: userId,
|
||||
Stream: true,
|
||||
}
|
||||
|
||||
contentCh := make(chan string, 10)
|
||||
|
||||
go func() {
|
||||
defer close(contentCh)
|
||||
|
||||
stream, err := c.client.CreateChatCompletionStream(c.ctx, req)
|
||||
if err != nil {
|
||||
contentCh <- err.Error()
|
||||
return
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
fullAnswer := ""
|
||||
for {
|
||||
response, err := stream.Recv()
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
if fullAnswer == "" {
|
||||
contentCh <- err.Error()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if len(response.Choices) > 0 {
|
||||
delta := response.Choices[0].Delta.Content
|
||||
if delta != "" {
|
||||
fullAnswer += delta
|
||||
contentCh <- delta
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 保存对话上下文
|
||||
c.ChatContext.old = append(c.ChatContext.old,
|
||||
conversation{Role: c.ChatContext.humanRole, Prompt: question},
|
||||
conversation{Role: c.ChatContext.aiRole, Prompt: fullAnswer},
|
||||
)
|
||||
c.ChatContext.seqTimes++
|
||||
}()
|
||||
|
||||
return contentCh, nil
|
||||
}
|
||||
|
||||
// buildMessages 构建消息列表
|
||||
func (c *Client) buildMessages(question string) []openai.ChatCompletionMessage {
|
||||
var messages []openai.ChatCompletionMessage
|
||||
|
||||
// 添加历史对话
|
||||
for _, v := range c.ChatContext.old {
|
||||
role := "assistant"
|
||||
if v.Role == c.ChatContext.humanRole {
|
||||
role = "user"
|
||||
}
|
||||
messages = append(messages, openai.ChatCompletionMessage{
|
||||
Role: role,
|
||||
Content: v.Prompt,
|
||||
})
|
||||
}
|
||||
|
||||
// 添加当前问题
|
||||
messages = append(messages, openai.ChatCompletionMessage{
|
||||
Role: "user",
|
||||
Content: question,
|
||||
})
|
||||
|
||||
return messages
|
||||
}
|
||||
|
||||
// SingleQaStream 单聊流式版本
|
||||
func SingleQaStream(question, userId string) (<-chan string, func(), error) {
|
||||
client := NewClient(userId)
|
||||
|
||||
contentCh := make(chan string, 10)
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(contentCh)
|
||||
defer close(done)
|
||||
|
||||
stream, err := client.ChatWithContextStream(question)
|
||||
if err != nil {
|
||||
contentCh <- err.Error()
|
||||
client.Close()
|
||||
return
|
||||
}
|
||||
|
||||
for content := range stream {
|
||||
contentCh <- content
|
||||
}
|
||||
|
||||
client.Close()
|
||||
}()
|
||||
|
||||
cleanup := func() {
|
||||
<-done
|
||||
}
|
||||
|
||||
return contentCh, cleanup, nil
|
||||
}
|
||||
|
||||
// ContextQaStream 串聊流式版本
|
||||
func ContextQaStream(question, userId string) (*Client, <-chan string, error) {
|
||||
client := NewClient(userId)
|
||||
if public.UserService.GetUserSessionContext(userId) != "" {
|
||||
_ = client.ChatContext.LoadConversation(userId)
|
||||
}
|
||||
|
||||
stream, err := client.ChatWithContextStream(question)
|
||||
if err != nil {
|
||||
client.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return client, stream, nil
|
||||
}
|
||||
@@ -5,10 +5,9 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/solywsh/chatgpt"
|
||||
|
||||
"github.com/eryajf/chatgpt-dingtalk/pkg/db"
|
||||
"github.com/eryajf/chatgpt-dingtalk/pkg/dingbot"
|
||||
"github.com/eryajf/chatgpt-dingtalk/pkg/llm"
|
||||
"github.com/eryajf/chatgpt-dingtalk/pkg/logger"
|
||||
"github.com/eryajf/chatgpt-dingtalk/public"
|
||||
)
|
||||
@@ -34,7 +33,7 @@ func ImageGenerate(ctx context.Context, rmsg *dingbot.ReceiveMsg) error {
|
||||
if err != nil {
|
||||
logger.Error("往MySQL新增数据失败,错误信息:", err)
|
||||
}
|
||||
reply, err := chatgpt.ImageQa(ctx, rmsg.Text.Content, rmsg.GetSenderIdentifier())
|
||||
reply, err := llm.ImageQa(ctx, rmsg.Text.Content, rmsg.GetSenderIdentifier())
|
||||
if err != nil {
|
||||
logger.Info(fmt.Errorf("gpt request error: %v", err))
|
||||
_, err = rmsg.ReplyToDingtalk(string(dingbot.TEXT), fmt.Sprintf("请求openai失败了,错误信息:%v", err))
|
||||
|
||||
@@ -6,10 +6,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/solywsh/chatgpt"
|
||||
|
||||
"github.com/eryajf/chatgpt-dingtalk/pkg/db"
|
||||
"github.com/eryajf/chatgpt-dingtalk/pkg/dingbot"
|
||||
"github.com/eryajf/chatgpt-dingtalk/pkg/llm"
|
||||
"github.com/eryajf/chatgpt-dingtalk/pkg/logger"
|
||||
"github.com/eryajf/chatgpt-dingtalk/public"
|
||||
)
|
||||
@@ -94,8 +93,36 @@ func ProcessRequest(rmsg *dingbot.ReceiveMsg) error {
|
||||
}
|
||||
default:
|
||||
if public.FirstCheck(rmsg) {
|
||||
// 检查是否启用流式模式
|
||||
if public.Config.StreamMode {
|
||||
logger.Info("📡 使用串聊流式模式")
|
||||
if public.Config.CardTemplateID != "" {
|
||||
logger.Info("🎴 使用流式卡片输出")
|
||||
// 使用流式卡片输出
|
||||
return DoStreamWithCard("串聊", rmsg, public.Config.CardTemplateID)
|
||||
} else {
|
||||
logger.Info("💬 使用简化流式输出")
|
||||
// 使用流式普通输出
|
||||
return DoStream("串聊", rmsg)
|
||||
}
|
||||
}
|
||||
logger.Info("💭 使用传统串聊模式")
|
||||
return Do("串聊", rmsg)
|
||||
} else {
|
||||
// 检查是否启用流式模式
|
||||
if public.Config.StreamMode {
|
||||
logger.Info("📡 使用单聊流式模式")
|
||||
if public.Config.CardTemplateID != "" {
|
||||
logger.Info("🎴 使用流式卡片输出")
|
||||
// 使用流式卡片输出
|
||||
return DoStreamWithCard("单聊", rmsg, public.Config.CardTemplateID)
|
||||
} else {
|
||||
logger.Info("💬 使用简化流式输出")
|
||||
// 使用流式普通输出
|
||||
return DoStream("单聊", rmsg)
|
||||
}
|
||||
}
|
||||
logger.Info("💭 使用传统单聊模式")
|
||||
return Do("单聊", rmsg)
|
||||
}
|
||||
}
|
||||
@@ -120,7 +147,7 @@ func Do(mode string, rmsg *dingbot.ReceiveMsg) error {
|
||||
if err != nil {
|
||||
logger.Error("往MySQL新增数据失败,错误信息:", err)
|
||||
}
|
||||
reply, err := chatgpt.SingleQa(rmsg.Text.Content, rmsg.GetSenderIdentifier())
|
||||
reply, err := llm.SingleQa(rmsg.Text.Content, rmsg.GetSenderIdentifier())
|
||||
if err != nil {
|
||||
logger.Info(fmt.Errorf("gpt request error: %v", err))
|
||||
if strings.Contains(fmt.Sprintf("%v", err), "maximum question length exceeded") {
|
||||
@@ -179,7 +206,7 @@ func Do(mode string, rmsg *dingbot.ReceiveMsg) error {
|
||||
if err != nil {
|
||||
logger.Error("往MySQL新增数据失败,错误信息:", err)
|
||||
}
|
||||
cli, reply, err := chatgpt.ContextQa(rmsg.Text.Content, rmsg.GetSenderIdentifier())
|
||||
cli, reply, err := llm.ContextQa(rmsg.Text.Content, rmsg.GetSenderIdentifier())
|
||||
if err != nil {
|
||||
logger.Info(fmt.Sprintf("gpt request error: %v", err))
|
||||
if strings.Contains(fmt.Sprintf("%v", err), "maximum text length exceeded") {
|
||||
|
||||
404
pkg/process/stream.go
Normal file
404
pkg/process/stream.go
Normal file
@@ -0,0 +1,404 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/eryajf/chatgpt-dingtalk/pkg/db"
|
||||
"github.com/eryajf/chatgpt-dingtalk/pkg/dingbot"
|
||||
"github.com/eryajf/chatgpt-dingtalk/pkg/llm"
|
||||
"github.com/eryajf/chatgpt-dingtalk/pkg/logger"
|
||||
"github.com/eryajf/chatgpt-dingtalk/public"
|
||||
)
|
||||
|
||||
// DoStream 使用流式输出执行处理请求
|
||||
func DoStream(mode string, rmsg *dingbot.ReceiveMsg) error {
|
||||
// 先把模式注入
|
||||
public.UserService.SetUserMode(rmsg.GetSenderIdentifier(), mode)
|
||||
|
||||
switch mode {
|
||||
case "单聊":
|
||||
return doSingleChatStream(rmsg)
|
||||
case "串聊":
|
||||
return doContextChatStream(rmsg)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// doSingleChatStream 单聊流式处理
|
||||
func doSingleChatStream(rmsg *dingbot.ReceiveMsg) error {
|
||||
// 保存问题到数据库
|
||||
qObj := db.Chat{
|
||||
Username: rmsg.SenderNick,
|
||||
Source: rmsg.GetChatTitle(),
|
||||
ChatType: db.Q,
|
||||
ParentContent: 0,
|
||||
Content: rmsg.Text.Content,
|
||||
}
|
||||
qid, err := qObj.Add()
|
||||
if err != nil {
|
||||
logger.Error("往MySQL新增数据失败,错误信息:", err)
|
||||
}
|
||||
|
||||
// 获取流式内容
|
||||
contentCh, cleanup, err := llm.SingleQaStream(rmsg.Text.Content, rmsg.GetSenderIdentifier())
|
||||
if err != nil {
|
||||
logger.Info(fmt.Errorf("gpt request error: %v", err))
|
||||
if strings.Contains(fmt.Sprintf("%v", err), "maximum question length exceeded") {
|
||||
public.UserService.ClearUserSessionContext(rmsg.GetSenderIdentifier())
|
||||
_, err = rmsg.ReplyToDingtalk(string(dingbot.MARKDOWN), fmt.Sprintf("[Wrong] 请求 OpenAI 失败了\n\n> 错误信息:%v\n\n> 已超过最大文本限制,请缩短提问文字的字数。", err))
|
||||
if err != nil {
|
||||
logger.Warning(fmt.Errorf("send message error: %v", err))
|
||||
}
|
||||
} else {
|
||||
_, err = rmsg.ReplyToDingtalk(string(dingbot.MARKDOWN), fmt.Sprintf("[Wrong] 请求 OpenAI 失败了\n\n> 错误信息:%v", err))
|
||||
if err != nil {
|
||||
logger.Warning(fmt.Errorf("send message error: %v", err))
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
// 使用简化版本:直接累积内容后一次性回复
|
||||
fullContent := ""
|
||||
for content := range contentCh {
|
||||
fullContent += content
|
||||
}
|
||||
|
||||
if fullContent == "" {
|
||||
logger.Warning("get gpt result failed: empty response")
|
||||
return nil
|
||||
}
|
||||
|
||||
// 格式化和处理答案
|
||||
fullContent = strings.TrimSpace(fullContent)
|
||||
fullContent = strings.Trim(fullContent, "\n")
|
||||
|
||||
// 保存答案到数据库
|
||||
aObj := db.Chat{
|
||||
Username: rmsg.SenderNick,
|
||||
Source: rmsg.GetChatTitle(),
|
||||
ChatType: db.A,
|
||||
ParentContent: qid,
|
||||
Content: fullContent,
|
||||
}
|
||||
_, err = aObj.Add()
|
||||
if err != nil {
|
||||
logger.Error("往MySQL新增数据失败,错误信息:", err)
|
||||
}
|
||||
|
||||
logger.Info(fmt.Sprintf("🤖 %s得到的答案: %#v", rmsg.SenderNick, fullContent))
|
||||
|
||||
// 敏感词过滤
|
||||
if public.JudgeSensitiveWord(fullContent) {
|
||||
fullContent = public.SolveSensitiveWord(fullContent)
|
||||
}
|
||||
|
||||
// 回复用户
|
||||
_, err = rmsg.ReplyToDingtalk(string(dingbot.MARKDOWN), FormatMarkdown(fullContent))
|
||||
if err != nil {
|
||||
logger.Warning(fmt.Errorf("send message error: %v", err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// doContextChatStream 串聊流式处理
|
||||
func doContextChatStream(rmsg *dingbot.ReceiveMsg) error {
|
||||
// 保存问题到数据库
|
||||
lastAid := public.UserService.GetAnswerID(rmsg.SenderNick, rmsg.GetChatTitle())
|
||||
qObj := db.Chat{
|
||||
Username: rmsg.SenderNick,
|
||||
Source: rmsg.GetChatTitle(),
|
||||
ChatType: db.Q,
|
||||
ParentContent: lastAid,
|
||||
Content: rmsg.Text.Content,
|
||||
}
|
||||
qid, err := qObj.Add()
|
||||
if err != nil {
|
||||
logger.Error("往MySQL新增数据失败,错误信息:", err)
|
||||
}
|
||||
|
||||
// 获取流式内容
|
||||
cli, contentCh, err := llm.ContextQaStream(rmsg.Text.Content, rmsg.GetSenderIdentifier())
|
||||
if err != nil {
|
||||
logger.Info(fmt.Sprintf("gpt request error: %v", err))
|
||||
if strings.Contains(fmt.Sprintf("%v", err), "maximum text length exceeded") {
|
||||
public.UserService.ClearUserSessionContext(rmsg.GetSenderIdentifier())
|
||||
_, err = rmsg.ReplyToDingtalk(string(dingbot.MARKDOWN), fmt.Sprintf("[Wrong] 请求 OpenAI 失败了\n\n> 错误信息:%v\n\n> 串聊已超过最大文本限制,对话已重置,请重新发起。", err))
|
||||
if err != nil {
|
||||
logger.Warning(fmt.Errorf("send message error: %v", err))
|
||||
}
|
||||
} else {
|
||||
_, err = rmsg.ReplyToDingtalk(string(dingbot.MARKDOWN), fmt.Sprintf("[Wrong] 请求 OpenAI 失败了\n\n> 错误信息:%v", err))
|
||||
if err != nil {
|
||||
logger.Warning(fmt.Errorf("send message error: %v", err))
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
// 使用简化版本:直接累积内容后一次性回复
|
||||
fullContent := ""
|
||||
for content := range contentCh {
|
||||
fullContent += content
|
||||
}
|
||||
|
||||
if fullContent == "" {
|
||||
logger.Warning("get gpt result failed: empty response")
|
||||
return nil
|
||||
}
|
||||
|
||||
// 格式化和处理答案
|
||||
fullContent = strings.TrimSpace(fullContent)
|
||||
fullContent = strings.Trim(fullContent, "\n")
|
||||
|
||||
// 保存答案到数据库
|
||||
aObj := db.Chat{
|
||||
Username: rmsg.SenderNick,
|
||||
Source: rmsg.GetChatTitle(),
|
||||
ChatType: db.A,
|
||||
ParentContent: qid,
|
||||
Content: fullContent,
|
||||
}
|
||||
aid, err := aObj.Add()
|
||||
if err != nil {
|
||||
logger.Error("往MySQL新增数据失败,错误信息:", err)
|
||||
}
|
||||
|
||||
// 将当前回答的ID放入缓存
|
||||
public.UserService.SetAnswerID(rmsg.SenderNick, rmsg.GetChatTitle(), aid)
|
||||
|
||||
logger.Info(fmt.Sprintf("🤖 %s得到的答案: %#v", rmsg.SenderNick, fullContent))
|
||||
|
||||
// 敏感词过滤
|
||||
if public.JudgeSensitiveWord(fullContent) {
|
||||
fullContent = public.SolveSensitiveWord(fullContent)
|
||||
}
|
||||
|
||||
// 回复用户
|
||||
_, err = rmsg.ReplyToDingtalk(string(dingbot.MARKDOWN), FormatMarkdown(fullContent))
|
||||
if err != nil {
|
||||
logger.Warning(fmt.Errorf("send message error: %v", err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 保存对话上下文
|
||||
_ = cli.ChatContext.SaveConversation(rmsg.GetSenderIdentifier())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DoStreamWithCard 使用流式卡片输出执行处理请求 (需要配置卡片模板)
|
||||
func DoStreamWithCard(mode string, rmsg *dingbot.ReceiveMsg, cardTemplateID string) error {
|
||||
// 先把模式注入
|
||||
public.UserService.SetUserMode(rmsg.GetSenderIdentifier(), mode)
|
||||
|
||||
// 检查是否有 RobotCode,如果没有则降级为简化流式模式
|
||||
clientId := rmsg.RobotCode
|
||||
if clientId == "" {
|
||||
logger.Warning("RobotCode is empty, fallback to simple stream mode")
|
||||
return DoStream(mode, rmsg)
|
||||
}
|
||||
|
||||
// 获取钉钉客户端
|
||||
dingClient := public.DingTalkClientManager.GetClientByOAuthClientID(clientId)
|
||||
if dingClient == nil {
|
||||
logger.Warning(fmt.Errorf("dingtalk client not found for robot code: %s, fallback to simple stream mode", clientId))
|
||||
return DoStream(mode, rmsg)
|
||||
}
|
||||
|
||||
client, ok := dingClient.(*dingbot.DingTalkClient)
|
||||
if !ok {
|
||||
logger.Warning("invalid dingtalk client type, fallback to simple stream mode")
|
||||
return DoStream(mode, rmsg)
|
||||
}
|
||||
|
||||
// 生成唯一追踪ID
|
||||
trackID := uuid.New().String()
|
||||
|
||||
// 创建并投放卡片
|
||||
accessToken, err := client.GetAccessToken()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get access token: %w", err)
|
||||
}
|
||||
|
||||
cardClient, err := dingbot.NewStreamCardClient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create stream card client: %w", err)
|
||||
}
|
||||
|
||||
// 构建OpenSpaceID
|
||||
var openSpaceID string
|
||||
if rmsg.ConversationType == "2" { // 群聊
|
||||
openSpaceID = fmt.Sprintf("dtv1.card//IM_GROUP.%s", rmsg.ConversationID)
|
||||
logger.Info(fmt.Sprintf("🎴 群聊模式 - OpenSpaceID: %s, RobotCode: %s", openSpaceID, rmsg.RobotCode))
|
||||
} else { // 单聊
|
||||
openSpaceID = fmt.Sprintf("dtv1.card//IM_ROBOT.%s", rmsg.SenderStaffId)
|
||||
logger.Info(fmt.Sprintf("🎴 私聊模式 - OpenSpaceID: %s, ConversationType: %s", openSpaceID, rmsg.ConversationType))
|
||||
}
|
||||
|
||||
createReq := &dingbot.CreateAndDeliverCardRequest{
|
||||
CardTemplateID: cardTemplateID,
|
||||
OutTrackID: trackID,
|
||||
ConversationID: rmsg.ConversationID,
|
||||
SenderStaffID: rmsg.SenderStaffId,
|
||||
RobotCode: rmsg.RobotCode,
|
||||
OpenSpaceID: openSpaceID,
|
||||
ConversationType: rmsg.ConversationType,
|
||||
CardData: map[string]string{
|
||||
"content": "",
|
||||
},
|
||||
}
|
||||
|
||||
if err := cardClient.CreateAndDeliverCard(accessToken, createReq); err != nil {
|
||||
logger.Warning(fmt.Errorf("failed to create card: %v", err))
|
||||
// 卡片创建失败,降级为普通消息
|
||||
return DoStream(mode, rmsg)
|
||||
}
|
||||
|
||||
// 发送初始状态
|
||||
initialContent := fmt.Sprintf("**%s**\n\n%s", rmsg.Text.Content, "稍等,让我想一想……")
|
||||
if err := client.UpdateAIStreamCard(trackID, initialContent, false); err != nil {
|
||||
logger.Warning(fmt.Errorf("failed to update initial card: %v", err))
|
||||
}
|
||||
|
||||
// 获取流式内容
|
||||
var contentCh <-chan string
|
||||
var cli *llm.Client
|
||||
if mode == "单聊" {
|
||||
var cleanup func()
|
||||
contentCh, cleanup, err = llm.SingleQaStream(rmsg.Text.Content, rmsg.GetSenderIdentifier())
|
||||
defer cleanup()
|
||||
} else {
|
||||
cli, contentCh, err = llm.ContextQaStream(rmsg.Text.Content, rmsg.GetSenderIdentifier())
|
||||
defer cli.Close()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errorMsg := fmt.Sprintf("**%s**\n\n出错了: %v", rmsg.Text.Content, err)
|
||||
if err := client.UpdateAIStreamCard(trackID, errorMsg, true); err != nil {
|
||||
logger.Warning(fmt.Errorf("failed to update error card: %v", err))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 实时流式更新卡片内容
|
||||
questionHeader := fmt.Sprintf("**%s**\n\n", rmsg.Text.Content)
|
||||
fullContent := questionHeader
|
||||
|
||||
// 使用缓冲机制避免更新过于频繁
|
||||
updateBuffer := ""
|
||||
lastUpdateTime := time.Now()
|
||||
minUpdateInterval := 300 * time.Millisecond // 最小更新间隔300ms
|
||||
|
||||
for {
|
||||
content, ok := <-contentCh
|
||||
if !ok {
|
||||
// 流结束,发送最后的更新(如果有未发送的缓冲内容)
|
||||
if updateBuffer != "" {
|
||||
fullContent += updateBuffer
|
||||
if err := client.UpdateAIStreamCard(trackID, fullContent, true); err != nil {
|
||||
logger.Error(fmt.Errorf("failed to finalize card: %v", err))
|
||||
}
|
||||
} else {
|
||||
// 标记为完成
|
||||
if err := client.UpdateAIStreamCard(trackID, fullContent, true); err != nil {
|
||||
logger.Error(fmt.Errorf("failed to finalize card: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// 保存到数据库并处理后续逻辑
|
||||
saveStreamResult(mode, rmsg, fullContent[len(questionHeader):], cli)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 累积接收到的内容到缓冲区
|
||||
updateBuffer += content
|
||||
|
||||
// 检查是否应该更新(距离上次更新超过最小间隔)
|
||||
if time.Since(lastUpdateTime) >= minUpdateInterval {
|
||||
fullContent += updateBuffer
|
||||
updateBuffer = ""
|
||||
|
||||
// 立即更新卡片
|
||||
if err := client.UpdateAIStreamCard(trackID, fullContent, false); err != nil {
|
||||
logger.Warning(fmt.Errorf("failed to update card: %v", err))
|
||||
}
|
||||
|
||||
lastUpdateTime = time.Now()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// saveStreamResult 保存流式结果到数据库
|
||||
func saveStreamResult(mode string, rmsg *dingbot.ReceiveMsg, answer string, cli *llm.Client) {
|
||||
answer = strings.TrimSpace(answer)
|
||||
answer = strings.Trim(answer, "\n")
|
||||
|
||||
if mode == "单聊" {
|
||||
qObj := db.Chat{
|
||||
Username: rmsg.SenderNick,
|
||||
Source: rmsg.GetChatTitle(),
|
||||
ChatType: db.Q,
|
||||
ParentContent: 0,
|
||||
Content: rmsg.Text.Content,
|
||||
}
|
||||
qid, err := qObj.Add()
|
||||
if err != nil {
|
||||
logger.Error("往MySQL新增数据失败,错误信息:", err)
|
||||
}
|
||||
|
||||
aObj := db.Chat{
|
||||
Username: rmsg.SenderNick,
|
||||
Source: rmsg.GetChatTitle(),
|
||||
ChatType: db.A,
|
||||
ParentContent: qid,
|
||||
Content: answer,
|
||||
}
|
||||
_, err = aObj.Add()
|
||||
if err != nil {
|
||||
logger.Error("往MySQL新增数据失败,错误信息:", err)
|
||||
}
|
||||
} else { // 串聊
|
||||
lastAid := public.UserService.GetAnswerID(rmsg.SenderNick, rmsg.GetChatTitle())
|
||||
qObj := db.Chat{
|
||||
Username: rmsg.SenderNick,
|
||||
Source: rmsg.GetChatTitle(),
|
||||
ChatType: db.Q,
|
||||
ParentContent: lastAid,
|
||||
Content: rmsg.Text.Content,
|
||||
}
|
||||
qid, err := qObj.Add()
|
||||
if err != nil {
|
||||
logger.Error("往MySQL新增数据失败,错误信息:", err)
|
||||
}
|
||||
|
||||
aObj := db.Chat{
|
||||
Username: rmsg.SenderNick,
|
||||
Source: rmsg.GetChatTitle(),
|
||||
ChatType: db.A,
|
||||
ParentContent: qid,
|
||||
Content: answer,
|
||||
}
|
||||
aid, err := aObj.Add()
|
||||
if err != nil {
|
||||
logger.Error("往MySQL新增数据失败,错误信息:", err)
|
||||
}
|
||||
|
||||
public.UserService.SetAnswerID(rmsg.SenderNick, rmsg.GetChatTitle(), aid)
|
||||
|
||||
if cli != nil {
|
||||
_ = cli.ChatContext.SaveConversation(rmsg.GetSenderIdentifier())
|
||||
}
|
||||
}
|
||||
|
||||
logger.Info(fmt.Sprintf("🤖 %s得到的答案: %#v", rmsg.SenderNick, answer))
|
||||
}
|
||||
Reference in New Issue
Block a user