From f88977c98faae3ce54020ce8cd30c8e798a5e49e Mon Sep 17 00:00:00 2001 From: renzhiyuan <465386466@qq.com> Date: Sun, 26 Apr 2026 23:57:44 +0800 Subject: [PATCH] 3232 --- cmd/server/wire_gen.go | 2 +- deepseek_test.go | 194 +++++++++++ doubao_test.go | 2 +- internal/biz/ai_collect.go | 69 ++-- internal/collect/base.go | 3 +- internal/collect/deepseek.go | 106 ++++-- internal/collect/doubao.go | 135 ++------ internal/collect/interface.go | 6 + internal/collect/qianwen.go | 171 +++++----- internal/collect/utils.go | 10 +- internal/collect/utils_test.go | 92 +----- internal/collect/wenxin.go | 2 +- internal/collect/yuanbao.go | 301 ++++++++++++++++++ internal/config/config.go | 4 + internal/entitys/product.go | 4 + internal/entitys/request.go | 5 + internal/server/router/app.go | 1 + internal/service/collect.go | 197 +++++++++--- plat_cookies/deepseek/deepseek.json | 1 + .../deepseek/deepseek_localstorage.json | 1 + .../deepseek/deepseek_sessionstorage.json | 1 + plat_cookies/doubao/doubao.json | 1 + plat_cookies/wenxin/wenxin.json | 1 + qianwen_test.go | 142 +++++++++ yuanbao_test.go | 142 +++++++++ 25 files changed, 1228 insertions(+), 365 deletions(-) create mode 100644 deepseek_test.go create mode 100644 internal/collect/yuanbao.go create mode 100644 plat_cookies/deepseek/deepseek.json create mode 100644 plat_cookies/deepseek/deepseek_localstorage.json create mode 100644 plat_cookies/deepseek/deepseek_sessionstorage.json create mode 100644 plat_cookies/doubao/doubao.json create mode 100644 plat_cookies/wenxin/wenxin.json create mode 100644 qianwen_test.go create mode 100644 yuanbao_test.go diff --git a/cmd/server/wire_gen.go b/cmd/server/wire_gen.go index 70c8ad6..92a0a6e 100644 --- a/cmd/server/wire_gen.go +++ b/cmd/server/wire_gen.go @@ -48,7 +48,7 @@ func InitializeApp(configConfig *config.Config, allLogger log.AllLogger) (*serve collectTaskImpl := impl.NewCollectTaskImpl(db) collectBiz := biz.NewCollectBiz(context.Background(), configConfig, allLogger) productService := service.NewProductService(configConfig, productImpl, authBiz, productBiz, aiBiz) - collectService := service.NewCollectService(configConfig, collectBiz, collectImpl, collectTaskImpl, authBiz) + collectService := service.NewCollectService(configConfig, collectBiz, collectImpl, collectTaskImpl, authBiz, productBiz) productSourceService := service.NewProductSourceService(configConfig, productImpl, authBiz, aiBiz, productBiz, productSourceImpl, publishBiz, articleTypeImpl) appModule := router.NewAppModule(configConfig, appService, loginService, publishService, productService, productSourceService, collectService) routerServer := router.NewRouterServer(appModule) diff --git a/deepseek_test.go b/deepseek_test.go new file mode 100644 index 0000000..2f8da59 --- /dev/null +++ b/deepseek_test.go @@ -0,0 +1,194 @@ +package collect + +import ( + "context" + "geo/internal/collect" + "geo/internal/config" + "github.com/gofiber/fiber/v2/log" + "testing" + "time" +) + +var ( + deepseekCfg, _ = config.LoadConfig() + + deepseekManager = collect.NewCollectManager(context.Background(), deepseekCfg, log.DefaultLogger()) +) + +// TestDeepseekCollector_WaitLogin 测试DeepSeek登录功能 +func TestDeepseekCollector_WaitLogin(t *testing.T) { + if testing.Short() { + t.Skip("跳过需要浏览器交互的测试") + } + + params := &collect.CollectParams{ + Headless: false, // 显示浏览器窗口以便扫码登录 + RequestID: "test_deepseek_login_001", + Platform: "deepseek", + } + + t.Log("开始测试DeepSeek登录...") + t.Log("请在打开的浏览器窗口中完成DeepSeek账号登录(扫码或输入账号密码)") + + success, msg := deepseekManager.WaitLogin("deepseek", params) + + if !success { + t.Errorf("DeepSeek登录失败: %s", msg) + return + } + + t.Logf("DeepSeek登录成功: %s", msg) + t.Log("Cookie已保存,后续测试可以使用已登录状态") +} + +// TestDeepseekCollector_AskQuestion 测试DeepSeek提问功能 +// 注意:此测试需要有效的登录状态 +func TestDeepseekCollector_AskQuestion(t *testing.T) { + if testing.Short() { + t.Skip("跳过需要浏览器交互的测试") + } + + // 设置收集参数 + params := &collect.CollectParams{ + Headless: false, // 显示浏览器以便调试 + RequestID: "test_deepseek_001", + Platform: "deepseek", + } + + // 定义提问内容 + question := "四川房地产软件排名" + t.Logf("向DeepSeek提问: %s", question) + + // 调用管理器提问并获取答案 + result, err := deepseekManager.AskQuestion("deepseek", params, question) + if err != nil { + t.Errorf("提问失败: %v", err) + return + } + + t.Logf("获取到答案:\n%s", result.Answer) + t.Logf("分享链接: %s", result.ShareLink) + + // 验证答案非空 + if len(result.Answer) == 0 { + t.Error("答案为空") + } +} + +// TestDeepseekCollector_MultipleQuestions 测试DeepSeek多次提问功能 +func TestDeepseekCollector_MultipleQuestions(t *testing.T) { + if testing.Short() { + t.Skip("跳过需要浏览器交互的测试") + } + + // 设置收集参数 + params := &collect.CollectParams{ + Headless: true, // 使用无头模式提高速度 + RequestID: "test_deepseek_multi_001", + Platform: "deepseek", + } + + // 定义多个问题 + questions := []string{ + "什么是人工智能?", + "如何学习Go语言?", + "推荐几个优秀的开源项目", + } + + t.Logf("开始测试DeepSeek多次提问,共 %d 个问题", len(questions)) + + for i, question := range questions { + t.Logf("[%d/%d] 提问: %s", i+1, len(questions), question) + + result, err := deepseekManager.AskQuestion("deepseek", params, question) + if err != nil { + t.Errorf("第 %d 个问题提问失败: %v", i+1, err) + continue + } + + previewLen := min(len(result.Answer), 100) + t.Logf("第 %d 个回答长度: %d, 预览: %s...", i+1, len(result.Answer), result.Answer[:previewLen]) + } + + t.Log("多次提问测试完成") +} + +// TestDeepseekCollector_SpeedTest 测试DeepSeek响应速度 +func TestDeepseekCollector_SpeedTest(t *testing.T) { + if testing.Short() { + t.Skip("跳过需要浏览器交互的测试") + } + + params := &collect.CollectParams{ + Headless: true, + RequestID: "test_deepseek_speed_001", + Platform: "deepseek", + } + + question := "用一句话介绍你自己" + t.Logf("测试DeepSeek响应速度,问题: %s", question) + + startTime := time.Now() + result, err := deepseekManager.AskQuestion("deepseek", params, question) + elapsed := time.Since(startTime) + + if err != nil { + t.Errorf("提问失败: %v", err) + return + } + + t.Logf("响应时间: %v", elapsed) + t.Logf("答案长度: %d 字符", len(result.Answer)) + t.Logf("答案: %s", result.Answer) + + // 验证响应时间在合理范围内(例如60秒内) + if elapsed.Seconds() > 60 { + t.Logf("警告: 响应时间过长: %v", elapsed) + } +} + +// TestDeepseekCollector_BrowserStorage 测试浏览器存储功能 +func TestDeepseekCollector_BrowserStorage(t *testing.T) { + if testing.Short() { + t.Skip("跳过需要浏览器交互的测试") + } + + t.Log("测试DeepSeek浏览器存储保存和加载功能") + t.Log("此测试将验证Cookies、LocalStorage和SessionStorage的保存和加载") + + // 第一步:登录并保存浏览器存储 + loginParams := &collect.CollectParams{ + Headless: false, + RequestID: "test_deepseek_storage_001", + Platform: "deepseek", + } + + t.Log("步骤1: 请登录DeepSeek账号...") + success, msg := deepseekManager.WaitLogin("deepseek", loginParams) + if !success { + t.Errorf("登录失败: %s", msg) + return + } + t.Logf("登录成功: %s", msg) + + // 第二步:使用保存的存储进行提问 + askParams := &collect.CollectParams{ + Headless: true, + RequestID: "test_deepseek_storage_002", + Platform: "deepseek", + } + + question := "你好" + t.Logf("步骤2: 使用保存的浏览器存储提问: %s", question) + + result, err := deepseekManager.AskQuestion("deepseek", askParams, question) + if err != nil { + t.Errorf("提问失败: %v", err) + return + } + + t.Logf("提问成功,答案长度: %d 字符", len(result.Answer)) + t.Logf("预览: %s...", result.Answer[:min(len(result.Answer), 50)]) + + t.Log("浏览器存储测试完成") +} diff --git a/doubao_test.go b/doubao_test.go index 016e6e4..9fbbbd8 100644 --- a/doubao_test.go +++ b/doubao_test.go @@ -57,7 +57,7 @@ func TestDoubaoCollector_AskQuestion(t *testing.T) { } // 定义提问内容 - question := "今天天气怎么样" + question := "云案场怎么样" t.Logf("向豆包提问: %s", question) // 调用管理器提问并获取答案 diff --git a/internal/biz/ai_collect.go b/internal/biz/ai_collect.go index 94d51d2..9f13c83 100644 --- a/internal/biz/ai_collect.go +++ b/internal/biz/ai_collect.go @@ -5,6 +5,11 @@ import ( "fmt" "geo/internal/collect" "geo/internal/config" + "geo/internal/data/model" + "geo/pkg" + volmodle "github.com/volcengine/volcengine-go-sdk/service/arkruntime/model" + "github.com/volcengine/volcengine-go-sdk/volcengine" + "strings" "github.com/gofiber/fiber/v2/log" ) @@ -31,11 +36,12 @@ func NewCollectBiz(ctx context.Context, cfg *config.Config, logger log.AllLogger // requestID: 请求ID // question: 问题内容 // headless: 是否无头模式 -func (b *CollectBiz) AskAIQuestion(platform string, requestID, question string, headless bool) (*collect.CollectResult, error) { +func (b *CollectBiz) AskAIQuestion(platform string, requestID, question string, headless bool, keywords []string) (*collect.CollectResult, error) { params := &collect.CollectParams{ Headless: headless, RequestID: requestID, Platform: platform, + KeyWords: keywords, } result, err := b.manager.AskQuestion(platform, params, question) @@ -62,25 +68,44 @@ func (b *CollectBiz) ListAIPlatforms() []string { return b.manager.ListPlatforms() } -// AskMultipleAI 向多个AI平台提问并收集答案 -func (b *CollectBiz) AskMultipleAI(platforms []string, requestID, question string, headless bool) map[string]*collect.CollectResult { - results := make(map[string]*collect.CollectResult) - - for _, platform := range platforms { - // 为每个平台生成唯一的 requestID - platformRequestID := requestID + "_" + platform - result, err := b.AskAIQuestion(platform, platformRequestID, question, headless) - if err != nil { - b.logger.Errorf("向%s提问失败: %v", platform, err) - // 创建一个包含错误信息的结果 - results[platform] = &collect.CollectResult{ - Answer: fmt.Sprintf("错误: %v", err), - ShareLink: "", - } - } else { - results[platform] = result - } - } - - return results +type AnaProject struct { + Ques string `json:"ques"` + Keywords []string `json:"keywords"` + Tasks []*Task `json:"tasks"` +} + +type Task struct { + ContentHtml string `json:"content_html"` + PlatName string `json:"plat_name"` + IsExposure bool `json:"isExposure"` +} + +func (b *CollectBiz) CreateAndPrompt(ctx context.Context, collectInfo *model.Collect, tasks []model.CollectTask) []*volmodle.ChatCompletionMessage { + var col = &AnaProject{ + Ques: collectInfo.Question, + Keywords: strings.Split(collectInfo.Keywords, ","), + } + var resultMap = make([]*Task, 0, len(tasks)) + for _, v := range tasks { + var exposure bool + if v.IsExposure == 2 { + exposure = true + } + resultMap = append(resultMap, &Task{ + ContentHtml: v.ContentHTML, + PlatName: collect.CollectorMap[v.AiPlatformIndex].Name, + IsExposure: exposure, + }) + } + col.Tasks = resultMap + colStr := pkg.JsonStringIgonErr(col) + mes := []*volmodle.ChatCompletionMessage{ + { + Role: volmodle.ChatMessageRoleUser, + Content: &volmodle.ChatCompletionMessageContent{ + StringValue: volcengine.String(colStr), + }, + }, + } + return mes } diff --git a/internal/collect/base.go b/internal/collect/base.go index 8dd36df..7aade1f 100644 --- a/internal/collect/base.go +++ b/internal/collect/base.go @@ -299,7 +299,8 @@ func (b *BaseCollector) SafeElement(selector string) (*rod.Element, error) { // cookiesDir 获取cookie目录 - 按平台区分 func (b *BaseCollector) cookiesDir() string { - dir := filepath.Join(b.config.Sys.CookiesDir, b.Platform) + // 将cookie存储在 cookies/platform/{Platform} 目录下 + dir := filepath.Join(b.config.Sys.PlatformCookieDir, b.Platform) os.MkdirAll(dir, 0755) return dir } diff --git a/internal/collect/deepseek.go b/internal/collect/deepseek.go index f2a439b..075a2e2 100644 --- a/internal/collect/deepseek.go +++ b/internal/collect/deepseek.go @@ -189,7 +189,7 @@ func (c *DeepseekCollector) WaitLogin() (bool, string) { } defer c.Close() - c.Page.MustNavigate(c.ChatURL) + c.Page.MustNavigate(c.LoginURL) c.Sleep(3) if c.CheckLoginStatus() { @@ -197,6 +197,9 @@ func (c *DeepseekCollector) WaitLogin() (bool, string) { return true, "already_logged_in" } + c.LogInfo("等待用户登录...") + + // 最多等待300秒 for i := 0; i < 300; i++ { if c.CheckLoginStatus() { c.Sleep(2) @@ -204,6 +207,11 @@ func (c *DeepseekCollector) WaitLogin() (bool, string) { return true, "login_success" } time.Sleep(1 * time.Second) + + // 每30秒提醒一次 + if (i+1)%30 == 0 { + c.LogInfo(fmt.Sprintf("仍在等待登录... 已等待 %d 秒", i+1)) + } } return false, "登录超时" @@ -230,6 +238,8 @@ func (c *DeepseekCollector) InitPage() error { // AskQuestion 提问并获取答案 func (c *DeepseekCollector) AskQuestion(question string) (*CollectResult, error) { + c.LogInfo("开始提问流程...") + if err := c.SetupDriver(); err != nil { return nil, fmt.Errorf("浏览器启动失败: %v", err) } @@ -251,15 +261,18 @@ func (c *DeepseekCollector) AskQuestion(question string) (*CollectResult, error) if err != nil { return nil, fmt.Errorf("获取答案失败: %v", err) } - + answerStr, isExposure := HighlightKeywordsInText(answer, c.KeyWords) return &CollectResult{ - Answer: answer, - ShareLink: "", + Answer: answerStr, + ShareLink: "", + IsExposure: isExposure, }, nil } // inputQuestion 输入问题 func (c *DeepseekCollector) inputQuestion(question string) error { + c.LogInfo("输入问题...") + // DeepSeek的输入框选择器 inputSelectors := []string{ "textarea[placeholder*='Message DeepSeek']", @@ -271,6 +284,7 @@ func (c *DeepseekCollector) inputQuestion(question string) error { for _, selector := range inputSelectors { inputBox, err = c.WaitForElementVisible(selector, 10) if err == nil && inputBox != nil { + c.LogInfo(fmt.Sprintf("找到输入框: %s", selector)) break } } @@ -296,12 +310,12 @@ func (c *DeepseekCollector) inputQuestion(question string) error { inputBox.Input(question) } + c.LogInfo(fmt.Sprintf("问题已输入: %s", question)) c.SleepMs(1000) return nil } -// clickSendButton 点击发送按钮 func (c *DeepseekCollector) clickSendButton() error { // 使用JavaScript直接找到input的父级下的第三个div并点击 clickJS := ` @@ -361,49 +375,85 @@ func (c *DeepseekCollector) clickSendButton() error { // waitForAnswer 等待并获取答案 func (c *DeepseekCollector) waitForAnswer() (string, error) { - timeout := 120 // 最大等待时间(秒) + c.LogInfo("等待AI回答...") + + timeout := 180 // 最大等待时间(秒) startTime := time.Now() - lastAnswerLength := 0 + + var lastAnswer string + var stableCount int // 稳定计数器 + const requiredStableCount = 3 // 需要连续3次内容不变才认为完成 + isAnswering := false // 标记是否正在回答中 for time.Since(startTime).Seconds() < float64(timeout) { - // 查找答案区域 + // 查找答案区域 - DeepSeek 使用 ds-markdown 类 answerSelectors := []string{ "div[class='ds-markdown']", + ".message-content", + ".response-text", + "[class*='assistant'] [class*='content']", + "[class*='ai'] [class*='message']", + ".chat-message.ai", + ".answer-content", + "div[data-message-id]", // 通用的消息ID选择器 } + var answerHTML string + for _, selector := range answerSelectors { answerElements, err := c.Page.Elements(selector) if err == nil && len(answerElements) > 0 { // 获取最后一个答案元素 - lastAnswer := answerElements[len(answerElements)-1] + lastAnswerElem := answerElements[len(answerElements)-1] - visible, _ := lastAnswer.Visible() + visible, _ := lastAnswerElem.Visible() if visible { - text, err := lastAnswer.Text() - if err == nil && len(strings.TrimSpace(text)) > 0 { - // 检查是否正在生成 - isGenerating := strings.Contains(text, "正在") || - strings.Contains(text, "思考") || - strings.Contains(text, "generating") - - if !isGenerating { - // 检查答案是否还在增长 - currentLength := len(text) - if currentLength == lastAnswerLength && currentLength > 10 { - // 答案不再增长,认为已完成 - return strings.TrimSpace(text), nil - } - lastAnswerLength = currentLength - } + // 直接获取原始HTML内容,不做任何处理 + htmlContent, err := lastAnswerElem.HTML() + if err == nil && htmlContent != "" { + answerHTML = strings.TrimSpace(htmlContent) + c.LogInfo(fmt.Sprintf("找到答案容器: %s, HTML长度: %d", selector, len(answerHTML))) + break } } } } - c.SleepMs(1500) + // 检查是否获取到答案 + if answerHTML != "" { + if !isAnswering { + c.LogInfo("检测到AI开始回答...") + isAnswering = true + } + + // 检查内容是否稳定(流式输出完成) + if answerHTML == lastAnswer { + stableCount++ + c.LogInfo(fmt.Sprintf("答案稳定中... (%d/%d), 长度: %d", stableCount, requiredStableCount, len(answerHTML))) + + // 如果内容稳定足够次数,说明回答完成 + if stableCount >= requiredStableCount { + c.LogInfo(fmt.Sprintf("✓ AI回答完成,最终HTML长度: %d 字符", len(answerHTML))) + return answerHTML, nil + } + } else { + // 内容还在变化,重置计数器 + stableCount = 0 + lastAnswer = answerHTML + c.LogInfo(fmt.Sprintf("检测到流式输出,当前HTML长度: %d 字符", len(answerHTML))) + } + } + + c.SleepMs(1000) // 每1秒检查一次 + + // 每10秒输出一次等待状态 + elapsed := int(time.Since(startTime).Seconds()) + if elapsed > 0 && elapsed%10 == 0 { + c.LogInfo(fmt.Sprintf("等待AI回答中... 已等待 %d 秒", elapsed)) + } } - return "", fmt.Errorf("等待答案超时") + return "", fmt.Errorf("等待答案超时(%d秒)", timeout) } // SafeElement 安全地获取元素 diff --git a/internal/collect/doubao.go b/internal/collect/doubao.go index 0e00dde..85cb50f 100644 --- a/internal/collect/doubao.go +++ b/internal/collect/doubao.go @@ -126,7 +126,9 @@ func (c *DoubaoCollector) AskQuestion(question string) (*CollectResult, error) { if err != nil { return nil, fmt.Errorf("获取答案失败: %v", err) } - answerStr, isExposure := HighlightKeywordsInHTML(answer, c.KeyWords) + + // 直接使用原始HTML格式,不进行关键词高亮处理 + answerStr, isExposure := HighlightKeywordsInText(answer, c.KeyWords) //// 获取分享链接 shareLink := "" @@ -148,10 +150,6 @@ func (c *DoubaoCollector) inputQuestion(question string) error { // 豆包的输入框选择器 - 使用精确的class匹配 inputSelectors := []string{ "textarea[placeholder*='发消息...']", - "[class*='input'] textarea", - "textarea.semi-input-textarea", - "textarea[placeholder='发消息...']", - "textarea[class*='semi-input-textarea']", } var inputBox *rod.Element @@ -174,11 +172,6 @@ func (c *DoubaoCollector) inputQuestion(question string) error { return fmt.Errorf("点击输入框失败: %v", err) } - // 清空输入框(如果失败也继续) - if err := c.ClearInput(inputBox); err != nil { - c.LogInfo(fmt.Sprintf("清空输入框失败: %v", err)) - } - // 使用原生Input方法输入(更稳定) inputBox.Input(question) c.LogInfo(fmt.Sprintf("问题已输入: %s", question)) @@ -206,7 +199,7 @@ func (c *DoubaoCollector) clickSendButton() error { if classAttr != nil && (strings.Contains(strings.ToLower(*classAttr), "send") || strings.Contains(strings.ToLower(*classAttr), "submit")) { sendBtn = btn - c.LogInfo(fmt.Sprintf("通过class找到发送按钮: class=%s", *classAttr)) + break } @@ -249,114 +242,56 @@ func (c *DoubaoCollector) waitForAnswer() (string, error) { var lastAnswer string var stableCount int // 稳定计数器 - const requiredStableCount = 5 // 需要连续5次内容不变才认为完成 + const requiredStableCount = 3 // 需要连续3次内容不变才认为完成(减少到3次以更快响应) isAnswering := false // 标记是否正在回答中 for time.Since(startTime).Seconds() < float64(timeout) { - // 尝试多种方式查找答案容器 - answerSelectors := []string{ - "div[data-message-id]", - "div[data-message-id*='']", - } + // 直接查找包含 data-message-id 的元素,这是豆包答案的标准标识 + answerElements, err := c.Page.Elements("div[data-message-id]") + if err == nil && len(answerElements) > 0 { + // 取最后一个元素(最新的回答) + lastAnswerElem := answerElements[len(answerElements)-1] - var answerText string + visible, _ := lastAnswerElem.Visible() + if visible { + // 直接获取原始HTML内容,不做任何处理 + htmlContent, err := lastAnswerElem.HTML() + if err == nil && htmlContent != "" { + answerHTML := strings.TrimSpace(htmlContent) - for _, selector := range answerSelectors { - answerElements, err := c.Page.Elements(selector) - if err == nil && len(answerElements) > 0 { - // 取最后一个元素(最新的回答) - lastAnswerElem := answerElements[len(answerElements)-1] - - visible, _ := lastAnswerElem.Visible() - if visible { - // 尝试获取HTML内容 - htmlContent, err := lastAnswerElem.HTML() - if err == nil && len(strings.TrimSpace(htmlContent)) > 30 { - // 清理HTML标签,只保留纯文本 - answerText = CleanHTMLTags(htmlContent) - c.LogInfo(fmt.Sprintf("找到答案容器: %s, 清理后文本长度: %d", selector, len(answerText))) - break + if !isAnswering && answerHTML != "" { + c.LogInfo("检测到AI开始回答...") + isAnswering = true } - // 如果HTML获取失败,尝试获取文本 - text, err := lastAnswerElem.Text() - if err == nil && len(strings.TrimSpace(text)) > 30 { - answerText = strings.TrimSpace(text) - c.LogInfo(fmt.Sprintf("找到答案容器: %s, 文本长度: %d", selector, len(answerText))) - break - } - } - } - } + // 检查内容是否稳定(流式输出完成) + if answerHTML == lastAnswer && answerHTML != "" { + stableCount++ + c.LogInfo(fmt.Sprintf("答案稳定中... (%d/%d), 长度: %d", stableCount, requiredStableCount, len(answerHTML))) - // 如果常规方法没找到,尝试查找所有包含较多文本的div - if answerText == "" { - allDivs, _ := c.Page.Elements("div") - for _, div := range allDivs { - visible, _ := div.Visible() - if !visible { - continue - } - - text, err := div.Text() - if err == nil { - trimmedText := strings.TrimSpace(text) - // 查找包含较多文本且不是输入框的div - if len(trimmedText) > 50 && len(trimmedText) < 5000 { - // 排除输入框相关的div - classAttr, _ := div.Attribute("class") - if classAttr != nil { - classLower := strings.ToLower(*classAttr) - if strings.Contains(classLower, "input") || - strings.Contains(classLower, "textarea") || - strings.Contains(classLower, "send") { - continue - } + // 如果内容稳定足够次数,说明回答完成 + if stableCount >= requiredStableCount { + c.LogInfo(fmt.Sprintf("✓ AI回答完成,最终HTML长度: %d 字符", len(answerHTML))) + return answerHTML, nil + } + } else { + // 内容还在变化,重置计数器 + stableCount = 0 + lastAnswer = answerHTML + if answerHTML != "" { + c.LogInfo(fmt.Sprintf("检测到流式输出,当前HTML长度: %d 字符", len(answerHTML))) } - - answerText = CleanHTMLTags(trimmedText) - c.LogInfo(fmt.Sprintf("通过遍历div找到答案,文本长度: %d", len(answerText))) - break } } } } - // 检查是否获取到答案 - if answerText != "" && len(answerText) > 30 { - if !isAnswering { - c.LogInfo("检测到AI开始回答...") - isAnswering = true - } - - // 检查内容是否稳定(流式输出完成) - if answerText == lastAnswer { - stableCount++ - c.LogInfo(fmt.Sprintf("答案稳定中... (%d/%d), 长度: %d", stableCount, requiredStableCount, len(answerText))) - - // 如果内容稳定足够次数,说明回答完成 - if stableCount >= requiredStableCount { - c.LogInfo(fmt.Sprintf("✓ AI回答完成,最终长度: %d 字符", len(answerText))) - return answerText, nil - } - } else { - // 内容还在变化,重置计数器 - stableCount = 0 - lastAnswer = answerText - c.LogInfo(fmt.Sprintf("检测到流式输出,当前长度: %d 字符", len(answerText))) - } - } - - c.SleepMs(1500) // 每1.5秒检查一次 + c.SleepMs(1000) // 每1秒检查一次 // 每10秒输出一次等待状态 elapsed := int(time.Since(startTime).Seconds()) if elapsed > 0 && elapsed%10 == 0 { c.LogInfo(fmt.Sprintf("等待AI回答中... 已等待 %d 秒", elapsed)) - // 截图帮助调试 - if elapsed%30 == 0 { - c.Screenshot(fmt.Sprintf("doubao_wait_answer_%d", elapsed)) - } } } diff --git a/internal/collect/interface.go b/internal/collect/interface.go index 4a38b6a..b0b0ea2 100644 --- a/internal/collect/interface.go +++ b/internal/collect/interface.go @@ -71,4 +71,10 @@ var CollectorMap = map[string]*CollectorValue{ Platform: "qianwen", Icon: "https://attachment-public.oss-cn-hangzhou.aliyuncs.com/geo/platform/qianwen.png", }, + "yuanbao": { + Name: "元宝", + InitMethod: NewYuanbaoCollector, + Platform: "yuanbao", + Icon: "https://attachment-public.oss-cn-hangzhou.aliyuncs.com/geo/platform/yuanbao.png", + }, } diff --git a/internal/collect/qianwen.go b/internal/collect/qianwen.go index d760e1f..d1ed07d 100644 --- a/internal/collect/qianwen.go +++ b/internal/collect/qianwen.go @@ -32,24 +32,19 @@ func NewQianwenCollector(ctx context.Context, params *CollectParams, cfg *config // CheckLoginStatus 检查登录状态 func (c *QianwenCollector) CheckLoginStatus() bool { - currentURL := c.GetCurrentURL() - - // 检查是否在通义千问页面 - if strings.Contains(currentURL, "tongyi.aliyun.com") { - // 查找用户信息元素 - userInfo, err := c.SafeElement(".user-avatar, .avatar, [class*='user'], [class*='profile']") - if err == nil && userInfo != nil { - return true - } - - // 检查是否有输入框 - inputBox, err := c.SafeElement("textarea, [contenteditable='true']") - if err == nil && inputBox != nil { - return true + // 检查页面上是否存在内容为"登录"或"Login"的button,如果存在说明未登录 + loginButtons, err := c.Page.Elements("button") + if err == nil { + for _, btn := range loginButtons { + text, _ := btn.Text() + trimmedText := strings.TrimSpace(text) + if trimmedText == "登录" || trimmedText == "Login" { + c.LogInfo(fmt.Sprintf("检测到页面上有'%s'按钮,说明未登录", trimmedText)) + return false + } } } - - return false + return true } // WaitLogin 等待登录 @@ -59,21 +54,32 @@ func (c *QianwenCollector) WaitLogin() (bool, string) { } defer c.Close() + c.LogInfo(fmt.Sprintf("正在导航至通义千问: %s", c.ChatURL)) c.Page.MustNavigate(c.ChatURL) c.Sleep(3) if c.CheckLoginStatus() { + c.LogInfo("检测到已登录状态") c.SaveCookies() return true, "already_logged_in" } + c.LogInfo("未检测到登录状态,等待用户登录...") + + // 最多等待300秒 for i := 0; i < 300; i++ { if c.CheckLoginStatus() { + c.LogInfo("检测到登录成功") c.Sleep(2) c.SaveCookies() return true, "login_success" } time.Sleep(1 * time.Second) + + // 每30秒提醒一次 + if (i+1)%30 == 0 { + c.LogInfo(fmt.Sprintf("仍在等待登录... 已等待 %d 秒", i+1)) + } } return false, "登录超时" @@ -81,6 +87,8 @@ func (c *QianwenCollector) WaitLogin() (bool, string) { // AskQuestion 提问并获取答案 func (c *QianwenCollector) AskQuestion(question string) (*CollectResult, error) { + c.LogInfo("开始提问流程...") + if err := c.SetupDriver(); err != nil { return nil, fmt.Errorf("浏览器启动失败: %v", err) } @@ -90,8 +98,6 @@ func (c *QianwenCollector) AskQuestion(question string) (*CollectResult, error) return nil, fmt.Errorf("页面初始化失败: %v", err) } - c.Sleep(3) - if err := c.inputQuestion(question); err != nil { return nil, fmt.Errorf("输入问题失败: %v", err) } @@ -104,20 +110,21 @@ func (c *QianwenCollector) AskQuestion(question string) (*CollectResult, error) if err != nil { return nil, fmt.Errorf("获取答案失败: %v", err) } - + answerStr, isExposure := HighlightKeywordsInText(answer, c.KeyWords) return &CollectResult{ - Answer: answer, - ShareLink: "", + Answer: answerStr, + ShareLink: "", + IsExposure: isExposure, }, nil } // inputQuestion 输入问题 func (c *QianwenCollector) inputQuestion(question string) error { + c.LogInfo("开始输入问题...") + // 通义千问的输入框选择器 inputSelectors := []string{ - "textarea[placeholder*='输入']", - "textarea[placeholder*='问']", - "textarea", + "[contenteditable='true']", ".chat-input textarea", "#chat-input", @@ -131,48 +138,44 @@ func (c *QianwenCollector) inputQuestion(question string) error { for _, selector := range inputSelectors { inputBox, err = c.WaitForElementVisible(selector, 10) if err == nil && inputBox != nil { + c.LogInfo(fmt.Sprintf("找到输入框,使用选择器: %s", selector)) break } } if inputBox == nil { + c.LogError("未找到输入框") return fmt.Errorf("未找到输入框") } // 点击获取焦点 + c.LogInfo("点击输入框获取焦点...") if err := inputBox.Click(proto.InputMouseButtonLeft, 1); err != nil { + c.LogError(fmt.Sprintf("点击输入框失败: %v", err)) return fmt.Errorf("点击输入框失败: %v", err) } c.SleepMs(500) // 清空输入框 - if err := c.ClearInput(inputBox); err != nil { - // Ignore clear error - } - c.SleepMs(300) + c.LogInfo("清空输入框...") // 输入问题 - if err := c.SetInputValue(inputBox, question); err != nil { - inputBox.Input(question) - } + c.LogInfo(fmt.Sprintf("正在输入问题: %s", question)) + inputBox.Input(question) c.SleepMs(1000) + c.LogInfo("问题输入完成") return nil } // clickSendButton 点击发送按钮 func (c *QianwenCollector) clickSendButton() error { + c.LogInfo("开始点击发送按钮...") + // 发送按钮选择器 sendSelectors := []string{ - "button[class*='send']", - "button[class*='submit']", - ".send-btn", - ".submit-btn", - "button svg[path*='send']", "[aria-label*='发送']", - ".send-icon", - ".submit-icon", } var sendBtn *rod.Element @@ -181,25 +184,32 @@ func (c *QianwenCollector) clickSendButton() error { for _, selector := range sendSelectors { sendBtn, err = c.WaitForElementClickable(selector, 5) if err == nil && sendBtn != nil { + c.LogInfo(fmt.Sprintf("找到发送按钮,使用选择器: %s", selector)) break } } if sendBtn == nil { + c.LogInfo("未通过常规选择器找到发送按钮,尝试查找 SVG 图标...") // 尝试通过SVG图标查找 sendBtn, err = c.Page.Element("button svg") if err != nil { + c.LogError("未找到发送按钮或相关图标") return fmt.Errorf("未找到发送按钮") } + c.LogInfo("找到 SVG 图标作为发送按钮") } c.SleepMs(500) // 点击发送按钮 + c.LogInfo("执行点击发送操作...") if err := c.JSClick(sendBtn); err != nil { + c.LogError(fmt.Sprintf("点击发送按钮失败: %v", err)) return fmt.Errorf("点击发送按钮失败: %v", err) } + c.LogInfo("发送按钮点击完成,等待响应...") c.SleepMs(2000) return nil @@ -207,56 +217,67 @@ func (c *QianwenCollector) clickSendButton() error { // waitForAnswer 等待并获取答案 func (c *QianwenCollector) waitForAnswer() (string, error) { - timeout := 120 // 最大等待时间(秒) + c.LogInfo("等待AI回答...") + + timeout := 180 // 最大等待时间(秒) startTime := time.Now() - lastAnswerLength := 0 + + var lastAnswer string + var stableCount int // 稳定计数器 + const requiredStableCount = 5 // 需要连续5次内容不变才认为完成 + isAnswering := false // 标记是否正在回答中 for time.Since(startTime).Seconds() < float64(timeout) { - // 查找答案区域 - answerSelectors := []string{ - ".message-content", - ".response-text", - "[class*='assistant'] [class*='content']", - "[class*='ai'] [class*='message']", - ".chat-message.ai", - ".answer-content", - ".qianwen-answer", + // 直接通过ID查找答案容器 + answerElem, err := c.Page.Element("#qk-markdown-react") + var answerHTML string + + if err == nil && answerElem != nil { + // 获取整个HTML内容 + htmlContent, err := answerElem.HTML() + if err == nil && htmlContent != "" { + answerHTML = strings.TrimSpace(htmlContent) + c.LogInfo(fmt.Sprintf("找到答案容器 #qk-markdown-react,HTML长度: %d", len(answerHTML))) + } + } else { + c.LogInfo("未找到#qk-markdown-react元素") } - for _, selector := range answerSelectors { - answerElements, err := c.Page.Elements(selector) - if err == nil && len(answerElements) > 0 { - // 获取最后一个答案元素 - lastAnswer := answerElements[len(answerElements)-1] + // 检查是否获取到答案 + if answerHTML != "" { + if !isAnswering { + c.LogInfo("检测到AI开始回答...") + isAnswering = true + } - visible, _ := lastAnswer.Visible() - if visible { - text, err := lastAnswer.Text() - if err == nil && len(strings.TrimSpace(text)) > 0 { - // 检查是否正在生成 - isGenerating := strings.Contains(text, "正在") || - strings.Contains(text, "思考中") || - strings.Contains(text, "typing") || - strings.Contains(text, "生成中") + // 检查内容是否稳定(流式输出完成) + if answerHTML == lastAnswer { + stableCount++ + c.LogInfo(fmt.Sprintf("答案稳定中... (%d/%d), 长度: %d", stableCount, requiredStableCount, len(answerHTML))) - if !isGenerating { - // 检查答案是否还在增长 - currentLength := len(text) - if currentLength == lastAnswerLength && currentLength > 10 { - // 答案不再增长,认为已完成 - return strings.TrimSpace(text), nil - } - lastAnswerLength = currentLength - } - } + // 如果内容稳定足够次数,说明回答完成 + if stableCount >= requiredStableCount { + c.LogInfo(fmt.Sprintf("✓ AI回答完成,最终HTML长度: %d 字符", len(answerHTML))) + return answerHTML, nil } + } else { + // 内容还在变化,重置计数器 + stableCount = 0 + lastAnswer = answerHTML + c.LogInfo(fmt.Sprintf("检测到流式输出,当前HTML长度: %d 字符", len(answerHTML))) } } - c.SleepMs(1500) + c.SleepMs(1500) // 每1.5秒检查一次 + + // 每10秒输出一次等待状态 + elapsed := int(time.Since(startTime).Seconds()) + if elapsed > 0 && elapsed%10 == 0 { + c.LogInfo(fmt.Sprintf("等待AI回答中... 已等待 %d 秒", elapsed)) + } } - return "", fmt.Errorf("等待答案超时") + return "", fmt.Errorf("等待答案超时(%d秒)", timeout) } // SafeElement 安全地获取元素 diff --git a/internal/collect/utils.go b/internal/collect/utils.go index 4aa62d5..eb554fc 100644 --- a/internal/collect/utils.go +++ b/internal/collect/utils.go @@ -69,9 +69,10 @@ func CleanDivTags(html string) string { // pointKeys: 需要高亮的关键词列表 // 返回处理后的HTML内容,每个关键词会被不同颜色的span标签包裹 func HighlightKeywordsInHTML(htmlContent string, pointKeys []string) (string, bool) { + var isExposure bool if htmlContent == "" || len(pointKeys) == 0 { - return htmlContent, isExposure + return htmlContent, false } // 预定义的颜色列表(使用CSS颜色值) @@ -130,12 +131,13 @@ func HighlightKeywordsInHTML(htmlContent string, pointKeys []string) (string, bo // pointKeys: 需要高亮的关键词列表 // 返回带有高亮标记的HTML内容 func HighlightKeywordsInText(textContent string, pointKeys []string) (string, bool) { - if textContent == "" || len(pointKeys) == 0 { + if textContent == "" { return textContent, false } - + htmlContent := CleanDivTags(textContent) // 将纯文本转换为HTML段落格式 - htmlContent := fmt.Sprintf("

%s

", strings.ReplaceAll(textContent, "\n", "

")) + + htmlContent = fmt.Sprintf("

%s

", strings.ReplaceAll(htmlContent, "\n", "

")) // 使用HTML高亮方法 return HighlightKeywordsInHTML(htmlContent, pointKeys) diff --git a/internal/collect/utils_test.go b/internal/collect/utils_test.go index 3ca8700..651ba83 100644 --- a/internal/collect/utils_test.go +++ b/internal/collect/utils_test.go @@ -1,103 +1,13 @@ package collect import ( - "strings" "testing" ) // TestHighlightKeywordsInHTML 测试HTML内容关键词高亮功能 func TestHighlightKeywordsInHTML(t *testing.T) { - html := `

在四川房地产软件领域,根据功能深度、本地化服务、技术实力及性价比等维度评测,以下软件表现突出且排名靠前:

1. 云案场

核心优势

适用场景

2. 明源云客

核心优势

适用场景

3. 用友地产CRM / 金蝶我家云售楼版

核心优势

适用场景

4. 元度云案场

核心优势

适用场景

5. 贝壳找房/链家网

核心优势

适用场景:

排名依据与选型建议

  1. 功能深度:云案场与明源云客在全流程覆盖与风控领域表现突出,适合大型房企;用友/金蝶强于业财一体化。
  2. 本地化服务:云案场在四川本地响应速度与案例经验占优。
  3. 性价比:元度云案场实施成本低,适合中小型房企;云案场提供灵活模块组合,适配不同规模需求。
  4. 技术实力:云案场、明源云客等获等保认证,数据安全有保障。

建议

` + html := `

在四川地区,选择稳定好用且受众较多的售楼软件时,云案场明源云客是两个值得重点考虑的品牌,以下是具体分析:

云案场

明源云客

对比与建议

` keyWords := []string{"云案场", "关键词2"} result, _ := HighlightKeywordsInText(html, keyWords) t.Log(result) } - -// TestHighlightKeywordsInHTML_ColorAssignment 测试颜色分配逻辑 -func TestHighlightKeywordsInHTML_ColorAssignment(t *testing.T) { - // 创建一个包含所有关键词的HTML内容 - keywords := make([]string, 20) - htmlParts := make([]string, 20) - for i := 0; i < 20; i++ { - keyword := "关键词" + string(rune('A'+i)) - keywords[i] = keyword - htmlParts[i] = "

" + keyword + "

" - } - - htmlContent := strings.Join(htmlParts, "") - - result := HighlightKeywordsInHTML(htmlContent, keywords) - - // 验证所有关键词都被处理(应该都有span标签) - spanCount := strings.Count(result, ` 0 { + // 获取最后一个答案元素(最新的回答) + lastAnswerElem := answerElements[len(answerElements)-1] + + visible, _ := lastAnswerElem.Visible() + if visible { + // 直接获取原始HTML内容,不做任何处理 + htmlContent, err := lastAnswerElem.HTML() + if err == nil && htmlContent != "" { + answerHTML = strings.TrimSpace(htmlContent) + c.LogInfo(fmt.Sprintf("找到答案容器: %s, HTML长度: %d", selector, len(answerHTML))) + break + } + } + } + } + + // 检查是否获取到答案 + if answerHTML != "" { + if !isAnswering { + c.LogInfo("检测到AI开始回答...") + isAnswering = true + } + + // 检查内容是否稳定(流式输出完成) + if answerHTML == lastAnswer { + stableCount++ + c.LogInfo(fmt.Sprintf("答案稳定中... (%d/%d), 长度: %d", stableCount, requiredStableCount, len(answerHTML))) + + // 如果内容稳定足够次数,说明回答完成 + if stableCount >= requiredStableCount { + c.LogInfo(fmt.Sprintf("✓ AI回答完成,最终HTML长度: %d 字符", len(answerHTML))) + return answerHTML, nil + } + } else { + // 内容还在变化,重置计数器 + stableCount = 0 + lastAnswer = answerHTML + c.LogInfo(fmt.Sprintf("检测到流式输出,当前HTML长度: %d 字符", len(answerHTML))) + } + } + + c.SleepMs(1500) // 每1.5秒检查一次 + + // 每10秒输出一次等待状态 + elapsed := int(time.Since(startTime).Seconds()) + if elapsed > 0 && elapsed%10 == 0 { + c.LogInfo(fmt.Sprintf("等待AI回答中... 已等待 %d 秒", elapsed)) + } + } + + return "", fmt.Errorf("等待答案超时(%d秒)", timeout) +} diff --git a/internal/config/config.go b/internal/config/config.go index 89142ce..2ecf70c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -23,6 +23,7 @@ type Collect struct { type AiBot struct { Article string `mapstructure:"article"` ProductInfo string `mapstructure:"product_info"` + CollectInfo string `mapstructure:"collectInfo"` } type Oss struct { @@ -64,6 +65,7 @@ type Sys struct { VideosDir string `mapstructure:"videosDir"` DocsDir string `mapstructure:"docsDir"` CookiesDir string `mapstructure:"cookiesDir"` + PlatformCookieDir string `mapstructure:"platformCookieDir"` QrcodesDir string `mapstructure:"qrcodesDir"` ChromePath string `mapstructure:"chromePath"` ChromeDataDir string `mapstructure:"chromeDataDir"` @@ -93,6 +95,7 @@ func LoadConfig() (*Config, error) { VideosDir: filepath.Join(BaseDir, "videos"), DocsDir: filepath.Join(BaseDir, "docs"), CookiesDir: filepath.Join(BaseDir, "cookies"), + PlatformCookieDir: filepath.Join(BaseDir, "plat_cookies"), QrcodesDir: filepath.Join(BaseDir, "qrcodes"), ChromePath: filepath.Join(BaseDir, "chrome", "chrome.exe"), ChromeDataDir: filepath.Join(BaseDir, "chrome_data"), @@ -112,6 +115,7 @@ func LoadConfig() (*Config, error) { AiBot: AiBot{ Article: "bot-20260413000114-8bw62", ProductInfo: "bot-20260422010906-hvtbd", + CollectInfo: "bot-20260426225808-wcxs7", }, Collect: Collect{ ApiKey: "sk_7bac5df901aa8933a238fcfec363f4a0", diff --git a/internal/entitys/product.go b/internal/entitys/product.go index c5b3247..76a9258 100644 --- a/internal/entitys/product.go +++ b/internal/entitys/product.go @@ -14,3 +14,7 @@ type ProductInfo struct { ServiceScope string `json:"service_scope" zh:"服务范围"` TargetAudience string `json:"target_audience" zh:"目标客户群体"` } + +type CollectInfo struct { + Name string `json:"name" validate:"required" zh:"产品名称"` +} diff --git a/internal/entitys/request.go b/internal/entitys/request.go index 5ffbf88..20cfa0c 100644 --- a/internal/entitys/request.go +++ b/internal/entitys/request.go @@ -210,6 +210,11 @@ type ( Limit int `json:"limit" zh:"每页数量"` } + CollectAnaRequest struct { + AccessToken string `json:"access_token" validate:"required" zh:"access_token"` + CollectId int32 `json:"collect_id" validate:"required" zh:"分析id"` + } + // PageRequest 分页请求 PageRequest struct { AccessToken string `json:"access_token" validate:"required" zh:"access_token"` diff --git a/internal/server/router/app.go b/internal/server/router/app.go index 2b997ac..db518a6 100644 --- a/internal/server/router/app.go +++ b/internal/server/router/app.go @@ -76,4 +76,5 @@ func (m *AppModule) Register(router fiber.Router) { router.Post("/collect/create", vali(m.collectService.Collect, &entitys.ProductCollectRequest{})) router.Get("/collect/platforms", m.collectService.GetCollectPlatForms) router.Post("/collect/list", vali(m.collectService.CollectList, &entitys.CollectListRequest{})) + router.Post("/collect/ana", vali(m.collectService.CollectAan, &entitys.CollectAnaRequest{})) } diff --git a/internal/service/collect.go b/internal/service/collect.go index 31b999b..416e47b 100644 --- a/internal/service/collect.go +++ b/internal/service/collect.go @@ -3,17 +3,23 @@ package service import ( "context" "fmt" + "geo/internal/ai_tool" "geo/internal/biz" "geo/internal/collect" "geo/internal/config" "geo/internal/data/impl" "geo/internal/data/model" "geo/internal/entitys" + "geo/pkg" "geo/tmpl/dataTemp" + "geo/tmpl/errcode" "github.com/gofiber/fiber/v2" "log" + "os" + "path/filepath" "strings" "sync" + "sync/atomic" "time" "xorm.io/builder" @@ -25,6 +31,7 @@ type CollectService struct { collectBiz *biz.CollectBiz collect *impl.CollectImpl collectTask *impl.CollectTaskImpl + productBiz *biz.ProductBiz authBiz *biz.AuthBiz } @@ -35,6 +42,7 @@ func NewCollectService( collect *impl.CollectImpl, collectTask *impl.CollectTaskImpl, authBiz *biz.AuthBiz, + productBiz *biz.ProductBiz, ) *CollectService { return &CollectService{ cfg: cfg, @@ -42,6 +50,7 @@ func NewCollectService( collect: collect, collectTask: collectTask, authBiz: authBiz, + productBiz: productBiz, } } @@ -131,7 +140,6 @@ func (c *CollectService) GetCollectPlatForms(ctx *fiber.Ctx) error { return ctx.JSON(list) } -// Collect 创建收集任务 func (c *CollectService) Collect(ctx *fiber.Ctx, req *entitys.ProductCollectRequest) error { _, err := c.authBiz.ValidateAccessToken(ctx.UserContext(), req.AccessToken) if err != nil { @@ -146,6 +154,7 @@ func (c *CollectService) Collect(ctx *fiber.Ctx, req *entitys.ProductCollectRequ Platform: strings.Join(req.PlatformIndex, ","), Question: req.Question, CreatedAt: time.Now(), + Status: 0, // 0:处理中 } err = c.collect.Add(ctx.UserContext(), collectData) @@ -153,66 +162,172 @@ func (c *CollectService) Collect(ctx *fiber.Ctx, req *entitys.ProductCollectRequ return err } - go c.doCollectAsync(collectCode, req.PlatformIndex, req.Question) + // 启动异步任务 + go c.doCollectAsync(collectCode, req.PlatformIndex, req.Question, req.Keywords) return ctx.JSON(fiber.Map{"message": "收录生成中"}) } -// doCollectAsync 异步执行收集任务 -func (c *CollectService) doCollectAsync(collectCode string, platforms []string, question string) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*240) - defer cancel() +// doCollectAsync 异步执行收集任务(简化稳定版) +func (c *CollectService) doCollectAsync(collectCode string, platforms []string, question string, keywords []string) { defer func() { - c.collect.UpdateByKey(context.Background(), "collect_code", collectCode, map[string]interface{}{"status": 2}) + if r := recover(); r != nil { + log.Printf("任务panic [%s]: %v", collectCode, r) + c.collect.UpdateByKey(context.Background(), "collect_code", collectCode, map[string]interface{}{"status": 3}) + } }() + + totalPlatforms := len(platforms) + if totalPlatforms == 0 { + c.collect.UpdateByKey(context.Background(), "collect_code", collectCode, map[string]interface{}{"status": 2, "progress": 100}) + return + } + + // 初始化进度 + c.collect.UpdateByKey(context.Background(), "collect_code", collectCode, map[string]interface{}{"status": 1, "progress": 10}) + + var completed int32 var wg sync.WaitGroup - var mu sync.Mutex - tasks := make([]*model.CollectTask, 0, len(platforms)) for _, platIndex := range platforms { wg.Add(1) - go func(platIndex string) { defer wg.Done() - platformName, exist := collect.CollectorMap[platIndex] - if !exist { - log.Printf("未知的平台索引: %d", platIndex) - return - } + c.processOnePlatform(collectCode, platIndex, question, keywords) - requestID := fmt.Sprintf("%s_%s", collectCode, platIndex) - result, err := c.collectBiz.AskAIQuestion(platIndex, requestID, question, true) - if err != nil { - log.Printf("平台 %s 收集失败: %v", platformName, err) - return - } - ise := 1 - if result.IsExposure { - ise = 2 - } - task := &model.CollectTask{ - CollectCode: collectCode, - AiPlatformIndex: platIndex, - ContentHTML: result.Answer, - ShareURL: result.ShareLink, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - IsExposure: int32(ise), - Status: 1, - } + // 原子操作增加计数 + done := atomic.AddInt32(&completed, 1) - mu.Lock() - tasks = append(tasks, task) - mu.Unlock() + // 计算并更新进度 + progress := 10 + int(float64(done)/float64(totalPlatforms)*90) + c.collect.UpdateByKey(context.Background(), "collect_code", collectCode, map[string]interface{}{"progress": progress}) }(platIndex) } wg.Wait() - if len(tasks) > 0 { - if err := c.collectTask.Add(ctx, tasks); err != nil { - log.Printf("保存收集任务失败: %v", err) + // 全部完成 + c.collect.UpdateByKey(context.Background(), "collect_code", collectCode, map[string]interface{}{"status": 2, "progress": 100}) +} + +// processOnePlatform 处理单个平台(带超时) +func (c *CollectService) processOnePlatform(collectCode, platIndex, question string, keywords []string) { + // 创建120秒超时的context + ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) + defer cancel() + + // 检查平台是否存在 + platformName, exist := collect.CollectorMap[platIndex] + if !exist { + log.Printf("未知平台: %s", platIndex) + return + } + + // 使用channel控制超时 + type result struct { + data *collect.CollectResult + err error + } + resultChan := make(chan result, 1) + + go func() { + requestID := fmt.Sprintf("%s_%s", collectCode, platIndex) + res, err := c.collectBiz.AskAIQuestion(platIndex, requestID, question, true, keywords) + resultChan <- result{data: res, err: err} + }() + + // 等待结果或超时 + select { + case <-ctx.Done(): + log.Printf("平台 %s 超时", platformName.Name) + return + case res := <-resultChan: + if res.err != nil { + log.Printf("平台 %s 失败: %v", platformName.Name, res.err) + return } + + // 保存结果 + ise := 1 + if res.data.IsExposure { + ise = 2 + } + task := &model.CollectTask{ + CollectCode: collectCode, + AiPlatformIndex: platIndex, + ContentHTML: res.data.Answer, + ShareURL: res.data.ShareLink, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + IsExposure: int32(ise), + Status: 1, + } + + if err := c.collectTask.Add(context.Background(), task); err != nil { + log.Printf("保存失败: %v", err) + return + } + + log.Printf("平台 %s 完成", platformName.Name) } } + +func (c *CollectService) CollectAan(ctx *fiber.Ctx, req *entitys.CollectAnaRequest) error { + + var collectInfo model.Collect + err := c.collect.GetByKey(ctx.UserContext(), c.collect.PrimaryKey(), req.CollectId, &collectInfo) + if err != nil { + return err + } + if collectInfo.ID == 0 { + return errcode.NotFound("为找到收录计划") + } + var tasks []model.CollectTask + taskCond := builder.NewCond().And(builder.In("collect_code", collectInfo.CollectCode)) + _, err = c.collectTask.GetListToStruct(ctx.UserContext(), &taskCond, nil, &tasks, "created_at ASC") + if err != nil { + log.Printf("查询 collect_task 失败: %v", err) + } + mes := c.collectBiz.CreateAndPrompt(ctx.UserContext(), &collectInfo, tasks) + + content, err := ai_tool.NewHsyq().RequestHsyqBot(ctx.UserContext(), c.cfg.Hsyq.ApiKey, c.cfg.AiBot.CollectInfo, mes) + if err != nil { + return err + } + fileBaseName := fmt.Sprintf("%s分析报告_%d", collectInfo.Question, time.Now().UnixNano()) + fileName := fmt.Sprintf("%s.md", fileBaseName) + mdAbs := filepath.Join(c.cfg.Sys.MdDir, fileName) + // 创建并写入文件 + file, err := os.Create(mdAbs) + defer os.Remove(mdAbs) + if err != nil { + return fmt.Errorf("创建文件失败: %w", err) + } + defer file.Close() + if _, err := file.WriteString(*content); err != nil { + return fmt.Errorf("写入文件失败: %w", err) + } + + docxPath, err := pkg.Md2wordFix(mdAbs, c.cfg.Sys.MdDir, nil) + defer os.Remove(docxPath) + if err != nil { + return err + } + docxName := fmt.Sprintf("%s.docx", fileBaseName) + docxAbs := filepath.Join(docxPath, docxName) + fileByte, err := pkg.ReadDocxToBytes(docxAbs) + if err != nil { + return err + } + + url, err := c.productBiz.SourceUpload(ctx.UserContext(), fileByte, docxName) + if err != nil { + return fmt.Errorf("上传文件失败: %w", err) + } + err = c.collect.UpdateByKey(ctx.UserContext(), c.collect.PrimaryKey(), req.CollectId, map[string]interface{}{"end_file": url}) + if err != nil { + return err + } + return nil +} diff --git a/plat_cookies/deepseek/deepseek.json b/plat_cookies/deepseek/deepseek.json new file mode 100644 index 0000000..1c06d95 --- /dev/null +++ b/plat_cookies/deepseek/deepseek.json @@ -0,0 +1 @@ +[{"name":"ds_session_id","value":"644b29e2a27d4c528e45ae229fadf68a","domain":"chat.deepseek.com","path":"/","expires":-1,"size":45,"httpOnly":true,"secure":true,"session":true,"sameSite":"Strict","priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"smidV2","value":"20260426163159e83e346d1e3f9f3f696020661e04d7ac002897e2fae729310","domain":"chat.deepseek.com","path":"/","expires":1811752319.803222,"size":69,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"HWWAFSESID","value":"88aaa6580f59abcca71","domain":"chat.deepseek.com","path":"/","expires":-1,"size":29,"httpOnly":true,"secure":true,"session":true,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"HWWAFSESTIME","value":"1777192585249","domain":"chat.deepseek.com","path":"/","expires":-1,"size":25,"httpOnly":true,"secure":true,"session":true,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":".thumbcache_6b2e5483f9d858d7c661c5e276b6a6ae","value":"mMx4TCkinObzkZQYK751bEXCVzaHjqw7amvBHBsPfD6zeLQf+06wldJ1zAYFQwEg2TY6M/dO2FIkwLvEQlfbdA%3D%3D","domain":"chat.deepseek.com","path":"/","expires":1811752320.000822,"size":136,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443}] \ No newline at end of file diff --git a/plat_cookies/deepseek/deepseek_localstorage.json b/plat_cookies/deepseek/deepseek_localstorage.json new file mode 100644 index 0000000..8b21ec8 --- /dev/null +++ b/plat_cookies/deepseek/deepseek_localstorage.json @@ -0,0 +1 @@ +{"debugLiteModelChannel":"{\"value\":\"default\",\"__version\":\"0\"}","__close_idb_signal":"1777192325430.8823","__ds_remote_feature_store_model":"{\"features\":{\"model_configs\":[{\"model_type\":\"default\",\"name\":\"Instant\",\"description\":\"Instant responses for daily conversations\",\"welcome_msg\":\"Start chatting with Instant\",\"is_default\":true,\"enabled\":true,\"switchable\":true,\"show_model_name_in_session\":true,\"input_character_limit\":2621440,\"think_feature\":{},\"search_feature\":{},\"regenerate_options\":null,\"file_feature\":{\"token_limit\":890880,\"token_limit_with_thinking\":890880,\"max_input_file_count\":50,\"max_upload_file_size\":104857600,\"support_file_exts\":[\"pdf\",\"png\",\"jpg\",\"jpeg\",\"svg\",\"svgz\",\"bmp\",\"gif\",\"webp\",\"ico\",\"xbm\",\"dib\",\"pjp\",\"tif\",\"pjpeg\",\"avif\",\"apng\",\"tiff\",\"jfif\",\"txt\",\"md\",\"csv\",\"tsv\",\"html\",\"json\",\"log\",\"dot\",\"go\",\"h\",\"c\",\"cpp\",\"cxx\",\"cc\",\"cs\",\"java\",\"js\",\"css\",\"jsp\",\"php\",\"py\",\"py3\",\"asp\",\"yaml\",\"yml\",\"ini\",\"conf\",\"ts\",\"tsx\",\"doc\",\"docx\",\"ppt\",\"pptx\",\"xls\",\"xlsx\",\"abap\",\"asc\",\"ash\",\"ampl\",\"mod\",\"g4\",\"apib\",\"apl\",\"dyalog\",\"asax\",\"ascx\",\"ashx\",\"asmx\",\"aspx\",\"axd\",\"dats\",\"hats\",\"sats\",\"as\",\"adb\",\"ada\",\"ads\",\"agda\",\"als\",\"apacheconf\",\"vhost\",\"cls\",\"applescript\",\"scpt\",\"arc\",\"ino\",\"asciidoc\",\"adoc\",\"aj\",\"asm\",\"a51\",\"inc\",\"nasm\",\"aug\",\"ahk\",\"ahkl\",\"au3\",\"awk\",\"auk\",\"gawk\",\"mawk\",\"nawk\",\"bat\",\"cmd\",\"befunge\",\"bison\",\"bb\",\"decls\",\"bmx\",\"bsv\",\"boo\",\"b\",\"bf\",\"brs\",\"bro\",\"cats\",\"idc\",\"w\",\"cake\",\"cshtml\",\"csx\",\"c++\",\"cp\",\"h++\",\"hh\",\"hpp\",\"hxx\",\"inl\",\"ipp\",\"tcc\",\"tpp\",\"c-objdump\",\"chs\",\"clp\",\"cmake\",\"in\",\"cob\",\"cbl\",\"ccp\",\"cobol\",\"cpy\",\"capnp\",\"mss\",\"ceylon\",\"chpl\",\"ch\",\"ck\",\"cirru\",\"clw\",\"icl\",\"dcl\",\"click\",\"clj\",\"boot\",\"cl2\",\"cljc\",\"cljs\",\"hl\",\"cljscm\",\"cljx\",\"hic\",\"coffee\",\"_coffee\",\"cjsx\",\"cson\",\"iced\",\"cfm\",\"cfml\",\"cfc\",\"lisp\",\"asd\",\"cl\",\"l\",\"lsp\",\"ny\",\"podsl\",\"sexp\",\"cps\",\"coq\",\"v\",\"cppobjdump\",\"c++-objdump\",\"c++objdump\",\"cpp-objdump\",\"cxx-objdump\",\"creole\",\"cr\",\"feature\",\"cu\",\"cuh\",\"cy\",\"pyx\",\"pxd\",\"pxi\",\"d\",\"di\",\"d-objdump\",\"com\",\"dm\",\"zone\",\"arpa\",\"darcspatch\",\"dpatch\",\"dart\",\"diff\",\"patch\",\"dockerfile\",\"djs\",\"dylan\",\"dyl\",\"intr\",\"lid\",\"E\",\"ecl\",\"eclxml\",\"sch\",\"brd\",\"epj\",\"e\",\"ex\",\"exs\",\"elm\",\"el\",\"emacs\",\"desktop\",\"em\",\"emberscript\",\"erl\",\"es\",\"escript\",\"hrl\",\"xrl\",\"yrl\",\"fs\",\"fsi\",\"fsx\",\"fx\",\"flux\",\"f90\",\"f\",\"f03\",\"f08\",\"f77\",\"f95\",\"for\",\"fpp\",\"factor\",\"fy\",\"fancypack\",\"fan\",\"fth\",\"4th\",\"forth\",\"fr\",\"frt\",\"ftl\",\"g\",\"gco\",\"gcode\",\"gms\",\"gap\",\"gd\",\"gi\",\"tst\",\"s\",\"ms\",\"glsl\",\"fp\",\"frag\",\"frg\",\"fsh\",\"fshader\",\"geo\",\"geom\",\"glslv\",\"gshader\",\"shader\",\"vert\",\"vrx\",\"vsh\",\"vshader\",\"gml\",\"kid\",\"ebuild\",\"eclass\",\"po\",\"pot\",\"glf\",\"gp\",\"gnu\",\"gnuplot\",\"plot\",\"plt\",\"golo\",\"gs\",\"gst\",\"gsx\",\"vark\",\"grace\",\"gradle\",\"gf\",\"graphql\",\"gv\",\"man\",\"1in\",\"1m\",\"1x\",\"3in\",\"3m\",\"3qt\",\"3x\",\"me\",\"n\",\"rno\",\"roff\",\"groovy\",\"grt\",\"gtpl\",\"gvy\",\"gsp\",\"hcl\",\"tf\",\"hlsl\",\"fxh\",\"hlsli\",\"htm\",\"st\",\"xht\",\"xhtml\",\"mustache\",\"jinja\",\"eex\",\"erb\",\"deface\",\"phtml\",\"http\",\"haml\",\"handlebars\",\"hbs\",\"hb\",\"hs\",\"hsc\",\"hx\",\"hxsl\",\"hy\",\"pro\",\"dlm\",\"ipf\",\"cfg\",\"prefs\",\"properties\",\"irclog\",\"weechatlog\",\"idr\",\"lidr\",\"ni\",\"i7x\",\"iss\",\"io\",\"ik\",\"thy\",\"ijs\",\"flex\",\"jflex\",\"geojson\",\"lock\",\"topojson\",\"json5\",\"jsonld\",\"jq\",\"jsx\",\"jade\",\"j\",\"_js\",\"bones\",\"es6\",\"jake\",\"jsb\",\"jscad\",\"jsfl\",\"jsm\",\"jss\",\"njs\",\"pac\",\"sjs\",\"ssjs\",\"sublime-build\",\"sublime-commands\",\"sublime-completions\",\"sublime-keymap\",\"sublime-macro\",\"sublime-menu\",\"sublime-mousemap\",\"sublime-project\",\"sublime-settings\",\"sublime-theme\",\"sublime-workspace\",\"sublime_metrics\",\"sublime_session\",\"xsjs\",\"xsjslib\",\"jl\",\"ipynb\",\"krl\",\"kicad_pcb\",\"kit\",\"kt\",\"ktm\",\"kts\",\"lfe\",\"ll\",\"lol\",\"lsl\",\"lslp\",\"lvproj\",\"lasso\",\"las\",\"lasso8\",\"lasso9\",\"ldml\",\"latte\",\"lean\",\"hlean\",\"less\",\"lex\",\"ly\",\"ily\",\"m\",\"ld\",\"lds\",\"liquid\",\"lagda\",\"litcoffee\",\"lhs\",\"ls\",\"_ls\",\"xm\",\"x\",\"xi\",\"lgt\",\"logtalk\",\"lookml\",\"lua\",\"fcgi\",\"nse\",\"pd_lua\",\"rbxs\",\"wlua\",\"mumps\",\"m4\",\"mcr\",\"mtml\",\"muf\",\"mak\",\"mk\",\"mkfile\",\"mako\",\"mao\",\"markdown\",\"mkd\",\"mkdn\",\"mkdown\",\"ron\",\"mask\",\"mathematica\",\"cdf\",\"ma\",\"map\",\"mt\",\"nb\",\"nbp\",\"wl\",\"wlt\",\"matlab\",\"maxpat\",\"maxhelp\",\"maxproj\",\"mxt\",\"pat\",\"mediawiki\",\"wiki\",\"moo\",\"metal\",\"minid\",\"druby\",\"duby\",\"mir\",\"mirah\",\"mo\",\"mms\",\"mmk\",\"monkey\",\"moon\",\"myt\",\"ncl\",\"nl\",\"nsi\",\"nsh\",\"axs\",\"axi\",\"nlogo\",\"nginxconf\",\"nim\",\"nimrod\",\"ninja\",\"nit\",\"nix\",\"nu\",\"numpy\",\"numpyw\",\"numsc\",\"ml\",\"eliom\",\"eliomi\",\"ml4\",\"mli\",\"mll\",\"mly\",\"objdump\",\"mm\",\"sj\",\"omgrofl\",\"opa\",\"opal\",\"opencl\",\"p\",\"scad\",\"org\",\"ox\",\"oxh\",\"oxo\",\"oxygene\",\"oz\",\"pwn\",\"aw\",\"ctp\",\"php3\",\"php4\",\"php5\",\"php6\",\"php7\",\"php8\",\"phps\",\"phpt\",\"pls\",\"pck\",\"pkb\",\"pks\",\"plb\",\"plsql\",\"sql\",\"pov\",\"pan\",\"psc\",\"parrot\",\"pasm\",\"pir\",\"pas\",\"dfm\",\"dpr\",\"lpr\",\"pp\",\"pl\",\"al\",\"cgi\",\"perl\",\"ph\",\"plx\",\"pm\",\"pod\",\"psgi\",\"t\",\"6pl\",\"6pm\",\"nqp\",\"p6\",\"p6l\",\"p6m\",\"pl6\",\"pm6\",\"pkl\",\"pig\",\"pike\",\"pmod\",\"pogo\",\"pony\",\"ps\",\"eps\",\"ps1\",\"psd1\",\"psm1\",\"pde\",\"prolog\",\"yap\",\"spin\",\"proto\",\"pub\",\"pd\",\"pb\",\"pbi\",\"purs\",\"bzl\",\"gyp\",\"lmi\",\"pyde\",\"pyi\",\"pyp\",\"pyt\",\"pyw\",\"rpy\",\"tac\",\"wsgi\",\"xpy\",\"pytb\",\"qml\",\"qbs\",\"pri\",\"r\",\"rd\",\"rsx\",\"raml\",\"rdoc\",\"rbbas\",\"rbfrm\",\"rbmnu\",\"rbres\",\"rbtbar\",\"rbuistate\",\"rhtml\",\"rmd\",\"rkt\",\"rktd\",\"rktl\",\"scrbl\",\"rl\",\"raw\",\"reb\",\"r2\",\"r3\",\"rebol\",\"red\",\"reds\",\"cw\",\"rs\",\"rsh\",\"robot\",\"rg\",\"rb\",\"builder\",\"gemspec\",\"god\",\"irbrc\",\"jbuilder\",\"mspec\",\"pluginspec\",\"podspec\",\"rabl\",\"rake\",\"rbuild\",\"rbw\",\"rbx\",\"ru\",\"ruby\",\"thor\",\"watchr\",\"sas\",\"scss\",\"smt2\",\"smt\",\"sparql\",\"rq\",\"sqf\",\"hqf\",\"cql\",\"ddl\",\"prc\",\"tab\",\"udf\",\"viw\",\"db2\",\"ston\",\"sage\",\"sagews\",\"sls\",\"sass\",\"scala\",\"sbt\",\"sc\",\"scaml\",\"scm\",\"sld\",\"sps\",\"ss\",\"sci\",\"sce\",\"self\",\"sh\",\"bash\",\"bats\",\"command\",\"ksh\",\"tmux\",\"tool\",\"zsh\",\"sh-session\",\"shen\",\"sl\",\"slim\",\"smali\",\"tpl\",\"sp\",\"sma\",\"nut\",\"stan\",\"ML\",\"fun\",\"sig\",\"sml\",\"do\",\"ado\",\"doh\",\"ihlp\",\"mata\",\"matah\",\"sthlp\",\"styl\",\"scd\",\"swift\",\"sv\",\"svh\",\"vh\",\"toml\",\"txl\",\"tcl\",\"adp\",\"tm\",\"tcsh\",\"csh\",\"tex\",\"aux\",\"bbx\",\"bib\",\"cbx\",\"dtx\",\"ins\",\"lbx\",\"ltx\",\"mkii\",\"mkiv\",\"mkvi\",\"sty\",\"toc\",\"tea\",\"no\",\"textile\",\"thrift\",\"tu\",\"ttl\",\"twig\",\"upc\",\"anim\",\"asset\",\"meta\",\"prefab\",\"unity\",\"uno\",\"uc\",\"ur\",\"urs\",\"vcl\",\"vhdl\",\"vhd\",\"vhf\",\"vhi\",\"vho\",\"vhs\",\"vht\",\"vhw\",\"vala\",\"vapi\",\"veo\",\"vim\",\"vb\",\"bas\",\"frm\",\"frx\",\"vba\",\"vbhtml\",\"vbs\",\"volt\",\"vue\",\"owl\",\"webidl\",\"x10\",\"xc\",\"xml\",\"ant\",\"axml\",\"ccxml\",\"clixml\",\"cproject\",\"csl\",\"csproj\",\"ct\",\"dita\",\"ditamap\",\"ditaval\",\"config\",\"dotsettings\",\"filters\",\"fsproj\",\"fxml\",\"glade\",\"grxml\",\"iml\",\"ivy\",\"jelly\",\"jsproj\",\"kml\",\"launch\",\"mdpolicy\",\"mxml\",\"nproj\",\"nuspec\",\"odd\",\"osm\",\"plist\",\"props\",\"ps1xml\",\"psc1\",\"pt\",\"rdf\",\"rss\",\"scxml\",\"srdf\",\"storyboard\",\"stTheme\",\"sublime-snippet\",\"targets\",\"tmCommand\",\"tml\",\"tmLanguage\",\"tmPreferences\",\"tmSnippet\",\"tmTheme\",\"ui\",\"urdf\",\"ux\",\"vbproj\",\"vcxproj\",\"vssettings\",\"vxml\",\"wsdl\",\"wsf\",\"wxi\",\"wxl\",\"wxs\",\"x3d\",\"xacro\",\"xaml\",\"xib\",\"xlf\",\"xliff\",\"xmi\",\"dist\",\"xproj\",\"xsd\",\"xul\",\"zcml\",\"xsp-config\",\"metadata\",\"xpl\",\"xproc\",\"xquery\",\"xq\",\"xql\",\"xqm\",\"xqy\",\"xs\",\"xslt\",\"xsl\",\"xojo_code\",\"xojo_menu\",\"xojo_report\",\"xojo_script\",\"xojo_toolbar\",\"xojo_window\",\"xtend\",\"reek\",\"rviz\",\"sublime-syntax\",\"syntax\",\"yang\",\"y\",\"yacc\",\"yy\",\"zep\",\"zimpl\",\"zmpl\",\"zpl\",\"ec\",\"eh\",\"edn\",\"fish\",\"mu\",\"nc\",\"ooc\",\"rst\",\"rest\",\"wisp\",\"prg\",\"prw\",\"gitignore\",\"gitkeep\",\"gitmodules\",\"example\",\"avifs\",\"blp\",\"bufr\",\"bw\",\"cur\",\"dcx\",\"dds\",\"emf\",\"fit\",\"fits\",\"flc\",\"fli\",\"ftc\",\"ftu\",\"gbr\",\"grib\",\"h5\",\"hdf\",\"hif\",\"icb\",\"icns\",\"iim\",\"im\",\"j2c\",\"j2k\",\"jp2\",\"jpc\",\"jpe\",\"jpf\",\"jpx\",\"mpeg\",\"mpg\",\"msp\",\"pbm\",\"pcd\",\"pcx\",\"pfm\",\"pgm\",\"pnm\",\"ppm\",\"psd\",\"pxr\",\"qoi\",\"ras\",\"rgb\",\"rgba\",\"sgi\",\"tga\",\"vda\",\"vst\",\"wmf\",\"xpm\",\"epub\",\"mobi\"],\"conflict_with_search\":false,\"enable_thumbnail\":false},\"tips\":{\"upload_panel_hint\":\"Text extraction only\"}},{\"model_type\":\"expert\",\"name\":\"Expert\",\"description\":\"For complex problems, busy at peak times\",\"welcome_msg\":\"Start chatting with Expert\",\"is_default\":false,\"enabled\":true,\"switchable\":true,\"show_model_name_in_session\":true,\"input_character_limit\":163840,\"regenerate_options\":[],\"think_feature\":{},\"search_feature\":{},\"file_feature\":{\"token_limit\":890880,\"token_limit_with_thinking\":890880,\"max_input_file_count\":50,\"max_upload_file_size\":104857600,\"support_file_exts\":[\"pdf\",\"png\",\"jpg\",\"jpeg\",\"svg\",\"svgz\",\"bmp\",\"gif\",\"webp\",\"ico\",\"xbm\",\"dib\",\"pjp\",\"tif\",\"pjpeg\",\"avif\",\"apng\",\"tiff\",\"jfif\",\"txt\",\"md\",\"csv\",\"tsv\",\"html\",\"json\",\"log\",\"dot\",\"go\",\"h\",\"c\",\"cpp\",\"cxx\",\"cc\",\"cs\",\"java\",\"js\",\"css\",\"jsp\",\"php\",\"py\",\"py3\",\"asp\",\"yaml\",\"yml\",\"ini\",\"conf\",\"ts\",\"tsx\",\"doc\",\"docx\",\"ppt\",\"pptx\",\"xls\",\"xlsx\",\"abap\",\"asc\",\"ash\",\"ampl\",\"mod\",\"g4\",\"apib\",\"apl\",\"dyalog\",\"asax\",\"ascx\",\"ashx\",\"asmx\",\"aspx\",\"axd\",\"dats\",\"hats\",\"sats\",\"as\",\"adb\",\"ada\",\"ads\",\"agda\",\"als\",\"apacheconf\",\"vhost\",\"cls\",\"applescript\",\"scpt\",\"arc\",\"ino\",\"asciidoc\",\"adoc\",\"aj\",\"asm\",\"a51\",\"inc\",\"nasm\",\"aug\",\"ahk\",\"ahkl\",\"au3\",\"awk\",\"auk\",\"gawk\",\"mawk\",\"nawk\",\"bat\",\"cmd\",\"befunge\",\"bison\",\"bb\",\"decls\",\"bmx\",\"bsv\",\"boo\",\"b\",\"bf\",\"brs\",\"bro\",\"cats\",\"idc\",\"w\",\"cake\",\"cshtml\",\"csx\",\"c++\",\"cp\",\"h++\",\"hh\",\"hpp\",\"hxx\",\"inl\",\"ipp\",\"tcc\",\"tpp\",\"c-objdump\",\"chs\",\"clp\",\"cmake\",\"in\",\"cob\",\"cbl\",\"ccp\",\"cobol\",\"cpy\",\"capnp\",\"mss\",\"ceylon\",\"chpl\",\"ch\",\"ck\",\"cirru\",\"clw\",\"icl\",\"dcl\",\"click\",\"clj\",\"boot\",\"cl2\",\"cljc\",\"cljs\",\"hl\",\"cljscm\",\"cljx\",\"hic\",\"coffee\",\"_coffee\",\"cjsx\",\"cson\",\"iced\",\"cfm\",\"cfml\",\"cfc\",\"lisp\",\"asd\",\"cl\",\"l\",\"lsp\",\"ny\",\"podsl\",\"sexp\",\"cps\",\"coq\",\"v\",\"cppobjdump\",\"c++-objdump\",\"c++objdump\",\"cpp-objdump\",\"cxx-objdump\",\"creole\",\"cr\",\"feature\",\"cu\",\"cuh\",\"cy\",\"pyx\",\"pxd\",\"pxi\",\"d\",\"di\",\"d-objdump\",\"com\",\"dm\",\"zone\",\"arpa\",\"darcspatch\",\"dpatch\",\"dart\",\"diff\",\"patch\",\"dockerfile\",\"djs\",\"dylan\",\"dyl\",\"intr\",\"lid\",\"E\",\"ecl\",\"eclxml\",\"sch\",\"brd\",\"epj\",\"e\",\"ex\",\"exs\",\"elm\",\"el\",\"emacs\",\"desktop\",\"em\",\"emberscript\",\"erl\",\"es\",\"escript\",\"hrl\",\"xrl\",\"yrl\",\"fs\",\"fsi\",\"fsx\",\"fx\",\"flux\",\"f90\",\"f\",\"f03\",\"f08\",\"f77\",\"f95\",\"for\",\"fpp\",\"factor\",\"fy\",\"fancypack\",\"fan\",\"fth\",\"4th\",\"forth\",\"fr\",\"frt\",\"ftl\",\"g\",\"gco\",\"gcode\",\"gms\",\"gap\",\"gd\",\"gi\",\"tst\",\"s\",\"ms\",\"glsl\",\"fp\",\"frag\",\"frg\",\"fsh\",\"fshader\",\"geo\",\"geom\",\"glslv\",\"gshader\",\"shader\",\"vert\",\"vrx\",\"vsh\",\"vshader\",\"gml\",\"kid\",\"ebuild\",\"eclass\",\"po\",\"pot\",\"glf\",\"gp\",\"gnu\",\"gnuplot\",\"plot\",\"plt\",\"golo\",\"gs\",\"gst\",\"gsx\",\"vark\",\"grace\",\"gradle\",\"gf\",\"graphql\",\"gv\",\"man\",\"1in\",\"1m\",\"1x\",\"3in\",\"3m\",\"3qt\",\"3x\",\"me\",\"n\",\"rno\",\"roff\",\"groovy\",\"grt\",\"gtpl\",\"gvy\",\"gsp\",\"hcl\",\"tf\",\"hlsl\",\"fxh\",\"hlsli\",\"htm\",\"st\",\"xht\",\"xhtml\",\"mustache\",\"jinja\",\"eex\",\"erb\",\"deface\",\"phtml\",\"http\",\"haml\",\"handlebars\",\"hbs\",\"hb\",\"hs\",\"hsc\",\"hx\",\"hxsl\",\"hy\",\"pro\",\"dlm\",\"ipf\",\"cfg\",\"prefs\",\"properties\",\"irclog\",\"weechatlog\",\"idr\",\"lidr\",\"ni\",\"i7x\",\"iss\",\"io\",\"ik\",\"thy\",\"ijs\",\"flex\",\"jflex\",\"geojson\",\"lock\",\"topojson\",\"json5\",\"jsonld\",\"jq\",\"jsx\",\"jade\",\"j\",\"_js\",\"bones\",\"es6\",\"jake\",\"jsb\",\"jscad\",\"jsfl\",\"jsm\",\"jss\",\"njs\",\"pac\",\"sjs\",\"ssjs\",\"sublime-build\",\"sublime-commands\",\"sublime-completions\",\"sublime-keymap\",\"sublime-macro\",\"sublime-menu\",\"sublime-mousemap\",\"sublime-project\",\"sublime-settings\",\"sublime-theme\",\"sublime-workspace\",\"sublime_metrics\",\"sublime_session\",\"xsjs\",\"xsjslib\",\"jl\",\"ipynb\",\"krl\",\"kicad_pcb\",\"kit\",\"kt\",\"ktm\",\"kts\",\"lfe\",\"ll\",\"lol\",\"lsl\",\"lslp\",\"lvproj\",\"lasso\",\"las\",\"lasso8\",\"lasso9\",\"ldml\",\"latte\",\"lean\",\"hlean\",\"less\",\"lex\",\"ly\",\"ily\",\"m\",\"ld\",\"lds\",\"liquid\",\"lagda\",\"litcoffee\",\"lhs\",\"ls\",\"_ls\",\"xm\",\"x\",\"xi\",\"lgt\",\"logtalk\",\"lookml\",\"lua\",\"fcgi\",\"nse\",\"pd_lua\",\"rbxs\",\"wlua\",\"mumps\",\"m4\",\"mcr\",\"mtml\",\"muf\",\"mak\",\"mk\",\"mkfile\",\"mako\",\"mao\",\"markdown\",\"mkd\",\"mkdn\",\"mkdown\",\"ron\",\"mask\",\"mathematica\",\"cdf\",\"ma\",\"map\",\"mt\",\"nb\",\"nbp\",\"wl\",\"wlt\",\"matlab\",\"maxpat\",\"maxhelp\",\"maxproj\",\"mxt\",\"pat\",\"mediawiki\",\"wiki\",\"moo\",\"metal\",\"minid\",\"druby\",\"duby\",\"mir\",\"mirah\",\"mo\",\"mms\",\"mmk\",\"monkey\",\"moon\",\"myt\",\"ncl\",\"nl\",\"nsi\",\"nsh\",\"axs\",\"axi\",\"nlogo\",\"nginxconf\",\"nim\",\"nimrod\",\"ninja\",\"nit\",\"nix\",\"nu\",\"numpy\",\"numpyw\",\"numsc\",\"ml\",\"eliom\",\"eliomi\",\"ml4\",\"mli\",\"mll\",\"mly\",\"objdump\",\"mm\",\"sj\",\"omgrofl\",\"opa\",\"opal\",\"opencl\",\"p\",\"scad\",\"org\",\"ox\",\"oxh\",\"oxo\",\"oxygene\",\"oz\",\"pwn\",\"aw\",\"ctp\",\"php3\",\"php4\",\"php5\",\"php6\",\"php7\",\"php8\",\"phps\",\"phpt\",\"pls\",\"pck\",\"pkb\",\"pks\",\"plb\",\"plsql\",\"sql\",\"pov\",\"pan\",\"psc\",\"parrot\",\"pasm\",\"pir\",\"pas\",\"dfm\",\"dpr\",\"lpr\",\"pp\",\"pl\",\"al\",\"cgi\",\"perl\",\"ph\",\"plx\",\"pm\",\"pod\",\"psgi\",\"t\",\"6pl\",\"6pm\",\"nqp\",\"p6\",\"p6l\",\"p6m\",\"pl6\",\"pm6\",\"pkl\",\"pig\",\"pike\",\"pmod\",\"pogo\",\"pony\",\"ps\",\"eps\",\"ps1\",\"psd1\",\"psm1\",\"pde\",\"prolog\",\"yap\",\"spin\",\"proto\",\"pub\",\"pd\",\"pb\",\"pbi\",\"purs\",\"bzl\",\"gyp\",\"lmi\",\"pyde\",\"pyi\",\"pyp\",\"pyt\",\"pyw\",\"rpy\",\"tac\",\"wsgi\",\"xpy\",\"pytb\",\"qml\",\"qbs\",\"pri\",\"r\",\"rd\",\"rsx\",\"raml\",\"rdoc\",\"rbbas\",\"rbfrm\",\"rbmnu\",\"rbres\",\"rbtbar\",\"rbuistate\",\"rhtml\",\"rmd\",\"rkt\",\"rktd\",\"rktl\",\"scrbl\",\"rl\",\"raw\",\"reb\",\"r2\",\"r3\",\"rebol\",\"red\",\"reds\",\"cw\",\"rs\",\"rsh\",\"robot\",\"rg\",\"rb\",\"builder\",\"gemspec\",\"god\",\"irbrc\",\"jbuilder\",\"mspec\",\"pluginspec\",\"podspec\",\"rabl\",\"rake\",\"rbuild\",\"rbw\",\"rbx\",\"ru\",\"ruby\",\"thor\",\"watchr\",\"sas\",\"scss\",\"smt2\",\"smt\",\"sparql\",\"rq\",\"sqf\",\"hqf\",\"cql\",\"ddl\",\"prc\",\"tab\",\"udf\",\"viw\",\"db2\",\"ston\",\"sage\",\"sagews\",\"sls\",\"sass\",\"scala\",\"sbt\",\"sc\",\"scaml\",\"scm\",\"sld\",\"sps\",\"ss\",\"sci\",\"sce\",\"self\",\"sh\",\"bash\",\"bats\",\"command\",\"ksh\",\"tmux\",\"tool\",\"zsh\",\"sh-session\",\"shen\",\"sl\",\"slim\",\"smali\",\"tpl\",\"sp\",\"sma\",\"nut\",\"stan\",\"ML\",\"fun\",\"sig\",\"sml\",\"do\",\"ado\",\"doh\",\"ihlp\",\"mata\",\"matah\",\"sthlp\",\"styl\",\"scd\",\"swift\",\"sv\",\"svh\",\"vh\",\"toml\",\"txl\",\"tcl\",\"adp\",\"tm\",\"tcsh\",\"csh\",\"tex\",\"aux\",\"bbx\",\"bib\",\"cbx\",\"dtx\",\"ins\",\"lbx\",\"ltx\",\"mkii\",\"mkiv\",\"mkvi\",\"sty\",\"toc\",\"tea\",\"no\",\"textile\",\"thrift\",\"tu\",\"ttl\",\"twig\",\"upc\",\"anim\",\"asset\",\"meta\",\"prefab\",\"unity\",\"uno\",\"uc\",\"ur\",\"urs\",\"vcl\",\"vhdl\",\"vhd\",\"vhf\",\"vhi\",\"vho\",\"vhs\",\"vht\",\"vhw\",\"vala\",\"vapi\",\"veo\",\"vim\",\"vb\",\"bas\",\"frm\",\"frx\",\"vba\",\"vbhtml\",\"vbs\",\"volt\",\"vue\",\"owl\",\"webidl\",\"x10\",\"xc\",\"xml\",\"ant\",\"axml\",\"ccxml\",\"clixml\",\"cproject\",\"csl\",\"csproj\",\"ct\",\"dita\",\"ditamap\",\"ditaval\",\"config\",\"dotsettings\",\"filters\",\"fsproj\",\"fxml\",\"glade\",\"grxml\",\"iml\",\"ivy\",\"jelly\",\"jsproj\",\"kml\",\"launch\",\"mdpolicy\",\"mxml\",\"nproj\",\"nuspec\",\"odd\",\"osm\",\"plist\",\"props\",\"ps1xml\",\"psc1\",\"pt\",\"rdf\",\"rss\",\"scxml\",\"srdf\",\"storyboard\",\"stTheme\",\"sublime-snippet\",\"targets\",\"tmCommand\",\"tml\",\"tmLanguage\",\"tmPreferences\",\"tmSnippet\",\"tmTheme\",\"ui\",\"urdf\",\"ux\",\"vbproj\",\"vcxproj\",\"vssettings\",\"vxml\",\"wsdl\",\"wsf\",\"wxi\",\"wxl\",\"wxs\",\"x3d\",\"xacro\",\"xaml\",\"xib\",\"xlf\",\"xliff\",\"xmi\",\"dist\",\"xproj\",\"xsd\",\"xul\",\"zcml\",\"xsp-config\",\"metadata\",\"xpl\",\"xproc\",\"xquery\",\"xq\",\"xql\",\"xqm\",\"xqy\",\"xs\",\"xslt\",\"xsl\",\"xojo_code\",\"xojo_menu\",\"xojo_report\",\"xojo_script\",\"xojo_toolbar\",\"xojo_window\",\"xtend\",\"reek\",\"rviz\",\"sublime-syntax\",\"syntax\",\"yang\",\"y\",\"yacc\",\"yy\",\"zep\",\"zimpl\",\"zmpl\",\"zpl\",\"ec\",\"eh\",\"edn\",\"fish\",\"mu\",\"nc\",\"ooc\",\"rst\",\"rest\",\"wisp\",\"prg\",\"prw\",\"gitignore\",\"gitkeep\",\"gitmodules\",\"example\",\"avifs\",\"blp\",\"bufr\",\"bw\",\"cur\",\"dcx\",\"dds\",\"emf\",\"fit\",\"fits\",\"flc\",\"fli\",\"ftc\",\"ftu\",\"gbr\",\"grib\",\"h5\",\"hdf\",\"hif\",\"icb\",\"icns\",\"iim\",\"im\",\"j2c\",\"j2k\",\"jp2\",\"jpc\",\"jpe\",\"jpf\",\"jpx\",\"mpeg\",\"mpg\",\"msp\",\"pbm\",\"pcd\",\"pcx\",\"pfm\",\"pgm\",\"pnm\",\"ppm\",\"psd\",\"pxr\",\"qoi\",\"ras\",\"rgb\",\"rgba\",\"sgi\",\"tga\",\"vda\",\"vst\",\"wmf\",\"xpm\",\"epub\",\"mobi\"],\"conflict_with_search\":false,\"enable_thumbnail\":false},\"tips\":{\"upload_panel_hint\":\"Text extraction only\"}}]},\"timestamp\":1777192326054,\"version\":17}","thinkingEnabled":"{\"value\":false,\"__version\":\"2\"}","searchStateTriggerAppliedVersion":"{\"value\":1,\"__version\":\"0\"}",".thumbcache_6b2e5483f9d858d7c661c5e276b6a6ae":"mMx4TCkinObzkZQYK751bEXCVzaHjqw7amvBHBsPfD6zeLQf+06wldJ1zAYFQwEg2TY6M/dO2FIkwLvEQlfbdA==","__tea_cache_tokens_20006317":"{\"web_id\":\"7632984039858664717\",\"user_unique_id\":\"690be93d-58e6-4bd7-8135-04d1904bc08c\",\"timestamp\":1777192325926,\"_type_\":\"default\"}","__appKit_@deepseek/chat_themePreference":"{\"value\":\"system\",\"__version\":\"0\"}","searchEnabled":"{\"value\":true,\"__version\":\"0\"}","__appKit_@deepseek/chat_banner":"{\"value\":{\"closedBannerId\":null,\"cachedBannerOptions\":null},\"__version\":\"0\"}","__appKit_@deepseek/chat_debugPanelEnabled":"{\"value\":false,\"__version\":\"0\"}","__ds_remote_feature_did":"4821bdd1-6e87-4d85-b47a-3a06c5be6349","__appKit_@deepseek/chat_fg_enableHcaptcha":"{\"value\":false,\"__version\":\"0\"}","__ds_remote_feature_store":"{\"features\":{\"sse_auto_resume_timeout\":3000,\"chat_hcaptcha\":true,\"launch_clean_session_interval_seconds\":21600,\"hif_max_retry_interval_secs\":600,\"completion_request_timeout_ms\":60000,\"edit_request_timeout_ms\":60000,\"regenerate_request_timeout_ms\":60000,\"continue_request_timeout_ms\":60000,\"resume_request_timeout_ms\":60000,\"auto_resume_request_timeout_ms\":3000,\"search_state_on_launch\":\"on\",\"search_state_on_manually_created_chat\":\"keep\",\"search_state_on_automatically_created_chat\":\"on\",\"search_state_on_login\":\"on\",\"search_state_trigger\":{\"trigger\":\"on\",\"trigger_version\":1},\"allow_file_with_search\":true,\"normal_history_and_file_token_limit\":890880,\"r1_history_and_file_token_limit\":890880,\"volcengine_enabled\":\"true\",\"pow_prefetch\":true,\"pow_prefetch_count\":1},\"timestamp\":1777192326057,\"version\":17}","__tea_cache_first_20006317":"1","__appKit_@deepseek/chat_debug":"{\"value\":false,\"__version\":\"0\"}","APMPLUS__cache__server__config__675113":"{\"sample\":{\"sample_rate\":1,\"include_users\":[],\"sample_granularity\":\"session\",\"rules\":[{\"name\":\"js_error\",\"enable\":true,\"sample_rate\":0.005,\"conditional_sample_rules\":[]},{\"name\":\"http\",\"enable\":false,\"sample_rate\":0,\"conditional_sample_rules\":[]},{\"name\":\"performance\",\"enable\":false,\"sample_rate\":0,\"conditional_sample_rules\":[]},{\"name\":\"resource_error\",\"enable\":true,\"sample_rate\":0.3,\"conditional_sample_rules\":[]},{\"name\":\"resource\",\"enable\":false,\"sample_rate\":0,\"conditional_sample_rules\":[]},{\"name\":\"custom\",\"enable\":false,\"sample_rate\":0,\"conditional_sample_rules\":[]},{\"name\":\"performance_timing\",\"enable\":false,\"sample_rate\":0,\"conditional_sample_rules\":[]},{\"name\":\"performance_longtask\",\"enable\":false,\"sample_rate\":0,\"conditional_sample_rules\":[]},{\"name\":\"pageview\",\"enable\":true,\"sample_rate\":1,\"conditional_sample_rules\":[]},{\"name\":\"action\",\"enable\":false,\"sample_rate\":0,\"conditional_sample_rules\":[]},{\"name\":\"blank_screen\",\"enable\":true,\"sample_rate\":0.5,\"conditional_sample_rules\":[]}]},\"status\":0,\"timestamp\":1777192592661}","targetBeforeOauthLogin":"{\"value\":\"chat\",\"__version\":\"0\"}","smidV2":"20260426163159e83e346d1e3f9f3f696020661e04d7ac002897e2fae729310","APMPLUS675113":"JTdCJTIydXNlcklkJTIyOiUyMjk0YWFlNjIxLWU5M2YtNDZlYS05M2MzLWY5MzJmZjA5YmM0OCUyMiwlMjJkZXZpY2VJZCUyMjolMjIzZWVlOGJmOC0yNDk2LTRmY2QtYmRhMy0zMWQ2ZmExOGFmOTYlMjIsJTIyciUyMjowLjMyNTEzNDk0MTcyMTM3NTksJTIyZXhwaXJlcyUyMjoxNzg0OTY4MzI1NTg2JTdE","closeUnsafeEnvWarn":"{\"value\":false,\"__version\":\"0\"}","__appKit_@deepseek/chat_lastSessionValue":"{\"value\":{\"userIsMuted\":false,\"userMuteUntil\":0,\"loginMethod\":\"code\",\"siderCollapsed\":false,\"value\":{\"userIsMuted\":false,\"userMuteUntil\":0,\"loginMethod\":\"code\",\"siderCollapsed\":false}},\"__version\":\"0\"}","__appKit_@deepseek/chat_localePreference":"{\"value\":\"system\",\"__version\":\"0\"}","__appKit_@deepseek/chat_fg_enableNoIdbCache":"{\"value\":false,\"__version\":\"0\"}","userToken":"{\"value\":\"CNIAwaqDO+VseyfjQXGEzLA8jP60itXARZMoCHjbIYFk6Crd+1jhjo6sCwy7fUsb\",\"__version\":\"0\"}","debugModelChannel":"{\"value\":\"default\",\"__version\":\"0\"}"} \ No newline at end of file diff --git a/plat_cookies/deepseek/deepseek_sessionstorage.json b/plat_cookies/deepseek/deepseek_sessionstorage.json new file mode 100644 index 0000000..8da4ec5 --- /dev/null +++ b/plat_cookies/deepseek/deepseek_sessionstorage.json @@ -0,0 +1 @@ +{"__tea_session_id_20006317":"{\"sessionId\":\"2c53e899-d750-474f-bd2f-2be348aef3cf\",\"timestamp\":1777192326117}"} \ No newline at end of file diff --git a/plat_cookies/doubao/doubao.json b/plat_cookies/doubao/doubao.json new file mode 100644 index 0000000..6991bec --- /dev/null +++ b/plat_cookies/doubao/doubao.json @@ -0,0 +1 @@ +[{"name":"uid_tt_ss","value":"1946b506b5193b703a364808605adcf7","domain":".doubao.com","path":"/","expires":1779718744.717825,"size":41,"httpOnly":true,"secure":true,"session":false,"sameSite":"None","priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"flow_cur_user_sec_id","value":"Kz9bAGEdJCBsLCAyEXQoOQE8Rh5qMwogVRRXHEtPPDBsKhk2HkcsCCM3Kwt5LFM4BCQsJRtHXQdyQDAjOx0RAzEDEhlcMj86MwA1Ig==","domain":".doubao.com","path":"/","expires":1779810578,"size":124,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"hook_slardar_session_id","value":"2026042623540481E596000CD0CBA40B18","domain":"www.doubao.com","path":"/chat","expires":-1,"size":57,"httpOnly":false,"secure":false,"session":true,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"is_staff_user","value":"false","domain":".doubao.com","path":"/","expires":1779718744.7179,"size":18,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"n_mh","value":"yPWypTUAv0frdkY50QK8Xe8_WpPNWzL2Jmz1dtoR-pQ","domain":".doubao.com","path":"/","expires":1787494744.717775,"size":47,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"passport_csrf_token","value":"ffc21822954b01bd3d69e12fc5426f2e","domain":".doubao.com","path":"/","expires":1782310737.320147,"size":51,"httpOnly":false,"secure":true,"session":false,"sameSite":"None","priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"flow_multi_user_sec_info","value":"Kz9bAGEdJCBsLCAyEXQoOQE8Rh5qMwogVRRXHEtPPDBsKhk2HkcsCCM3Kwt5LFM4BCQsJRtHXQdyQDAjOx0RAzEDEhlcMj86MwA1IhdNVFUaWlBBXRpXR0tDQg==","domain":".doubao.com","path":"/","expires":1779718745,"size":148,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"sessionid_ss","value":"161ee82c0b40359f31ce60d8ee232d83","domain":".doubao.com","path":"/","expires":1779718744.717867,"size":44,"httpOnly":true,"secure":true,"session":false,"sameSite":"None","priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"uid_tt","value":"1946b506b5193b703a364808605adcf7","domain":".doubao.com","path":"/","expires":1779718744.717811,"size":38,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"flow_ssr_sidebar_expand","value":"1","domain":"www.doubao.com","path":"/","expires":-1,"size":24,"httpOnly":false,"secure":false,"session":true,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"passport_csrf_token_default","value":"ffc21822954b01bd3d69e12fc5426f2e","domain":".doubao.com","path":"/","expires":1782310737.320205,"size":59,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"i18next","value":"zh","domain":"www.doubao.com","path":"/","expires":1808754577,"size":9,"httpOnly":false,"secure":false,"session":false,"sameSite":"Strict","priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"sid_ucp_v1","value":"1.0.0-KDZlYjM0YjhhYTE3MjBmOGRlMTY5MDI5ZmE2MTEyZGY4MDVmZTE5MDcKIAik9ZCwkM3vAhDYmrPPBhjCsR4gDDC9w8G8BjgHQPQHGgJscSIgMTYxZWU4MmMwYjQwMzU5ZjMxY2U2MGQ4ZWUyMzJkODM","domain":".doubao.com","path":"/","expires":1779718744.717928,"size":167,"httpOnly":true,"secure":true,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"ssid_ucp_v1","value":"1.0.0-KDZlYjM0YjhhYTE3MjBmOGRlMTY5MDI5ZmE2MTEyZGY4MDVmZTE5MDcKIAik9ZCwkM3vAhDYmrPPBhjCsR4gDDC9w8G8BjgHQPQHGgJscSIgMTYxZWU4MmMwYjQwMzU5ZjMxY2U2MGQ4ZWUyMzJkODM","domain":".doubao.com","path":"/","expires":1779718744.717942,"size":168,"httpOnly":true,"secure":true,"session":false,"sameSite":"None","priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"odin_tt","value":"18be07efbd268cb7a2ee5789c85dc854ebb69a977775e34345aed5ac21c0a4da4fc1dddcc6a0296a6c2e9076546170d3fab342bd522b75f74c94805a7fa5a2a9","domain":".doubao.com","path":"/","expires":1808662744.717738,"size":135,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"sessionid","value":"161ee82c0b40359f31ce60d8ee232d83","domain":".doubao.com","path":"/","expires":1779718744.717853,"size":41,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"s_v_web_id","value":"verify_moefdigw_gNbiGkli_iets_4Uf6_AbCV_ISLjPvavpLiu","domain":"www.doubao.com","path":"/","expires":1782310737,"size":62,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"sid_tt","value":"161ee82c0b40359f31ce60d8ee232d83","domain":".doubao.com","path":"/","expires":1779718744.717839,"size":38,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"ttwid","value":"1%7C2GsOtgDgfStYQCE5SCRU83oti0NgOMwASqR7dyBZxYI%7C1777218846%7C37cefd27c567cadaf51d8ec0436e1504c0efa76828d074e1443c48c447b98650","domain":".doubao.com","path":"/","expires":1808754578.81013,"size":132,"httpOnly":true,"secure":true,"session":false,"sameSite":"None","priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"biz_trace_id","value":"96037ad8","domain":".doubao.com","path":"/","expires":-1,"size":20,"httpOnly":false,"secure":false,"session":true,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"has_biz_token","value":"false","domain":".doubao.com","path":"/","expires":1779718744.717914,"size":18,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"x-tt-multi-sids","value":"V1peQRpEVlMbWFJAXB9WQFxDNV4bWgAcXl4MR09DVVEYVAdAWk4GREkUTApIWVZLAlRc","domain":".doubao.com","path":"/","expires":1779718744,"size":83,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"session_tlb_tag","value":"sttt%7C15%7CFh7oLAtANZ8xzmDY7iMtg__________CCzziOSDqktRj1KhgfOMyHCW2IRMfVlWpzrOYTK_-M1M%3D","domain":".doubao.com","path":"/","expires":1779718744.717886,"size":105,"httpOnly":true,"secure":true,"session":false,"sameSite":"None","priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"multi_sids","value":"1616733165337252%3A161ee82c0b40359f31ce60d8ee232d83","domain":".doubao.com","path":"/","expires":1782310744.717671,"size":61,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"sid_guard","value":"161ee82c0b40359f31ce60d8ee232d83%7C1777126744%7C2592000%7CMon%2C+25-May-2026+14%3A19%3A04+GMT","domain":".doubao.com","path":"/","expires":1808230744.717796,"size":102,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443}] \ No newline at end of file diff --git a/plat_cookies/wenxin/wenxin.json b/plat_cookies/wenxin/wenxin.json new file mode 100644 index 0000000..4ab3655 --- /dev/null +++ b/plat_cookies/wenxin/wenxin.json @@ -0,0 +1 @@ +[{"name":"__bid_n","value":"19dc62e683464163b85575","domain":".baidu.com","path":"/","expires":1811778580.341499,"size":29,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"BDUSS_BFESS","value":"DAtdHNjWFlRY01hWS1VS1NLc2VvRi1ZT0lDemZGNERmWkw3VndQc3BUTTlrUlJxSVFBQUFBJCQAAAAAAAAAAAEAAACmMvqcwuy02M7x1PdEZ0EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD0E7Wk9BO1pT","domain":".baidu.com","path":"/","expires":1811700530.750294,"size":203,"httpOnly":true,"secure":true,"session":false,"sameSite":"None","priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"BAIDUID","value":"CA14CC706AEF6D2BE35B6A7D3217EA30:FG=1","domain":".baidu.com","path":"/","expires":1808676471.79423,"size":44,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"BDUSS","value":"DAtdHNjWFlRY01hWS1VS1NLc2VvRi1ZT0lDemZGNERmWkw3VndQc3BUTTlrUlJxSVFBQUFBJCQAAAAAAAAAAAEAAACmMvqcwuy02M7x1PdEZ0EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD0E7Wk9BO1pT","domain":".baidu.com","path":"/","expires":1811700530.750205,"size":197,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"Hm_lvt_01e907653ac089993ee83ed00ef9c2f3","value":"1777140473","domain":".yiyan.baidu.com","path":"/","expires":1808754577,"size":49,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"BAIDUID_BFESS","value":"CA14CC706AEF6D2BE35B6A7D3217EA30:FG=1","domain":".baidu.com","path":"/","expires":1808676471.794333,"size":50,"httpOnly":false,"secure":true,"session":false,"sameSite":"None","priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"HMACCOUNT","value":"B81968151F5BDC1A","domain":".yiyan.baidu.com","path":"/","expires":-1,"size":25,"httpOnly":false,"secure":false,"session":true,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"ppfuid","value":"FOCoIC3q5fKa8fgJnwzbE0LGziLN3VHbX8wfShDP6RCsfXQp/69CStRUAcn/QmhIlFDxPrAc/s5tJmCocrihdwitHd04Lvs3Nfz26Zt2holplnIKVacidp8Sue4dMTyfg65BJnOFhn1HthtSiwtygiD7piS4vjG/W9dLb1VAdqOHMvLeQtSFU4dpacpz0Y3rO0V6uxgO+hV7+7wZFfXG0MSpuMmh7GsZ4C7fF/kTgmuMnYW0oE2ZxHf7c+cnSwlvxplP4FuSEfucjr6aFQeyUunskqILNo0GM1gbwSG0S93pJIFqcvNsRYzITz5PyOADVc/nSfZwU+XC3JiEtS5ZHaJLwMdIdH+eEAlE3PHwsfIZaGhxes+nljx68Dx7ernRD8/UFc/nN8OqiS2f4kOGCa2zSe/tBcf0Az1OHq+4YwzOjFvjJ8Q16yAMrTdSGO+jRTvWQ0XZ2y+p/Ci8gRyghJkeerLqDamAFPpwvBrHJ7CvK/S9Ff5RtLDcahg8QCqqP/JUZA7BRBFh68uqDQax10gfXgGxCNf3Sx8e4KXUBrqV/g3hEEf9luu8oPziRIwanIJY1XZupqPZgmfh8BLwT9YUuyc0u8RKTitzO23hSwGX7sI4U3M5cfLBwVX5m74NveYUNi7Li87S8ZbXy31eyxBDK4IiDGlt1VFsxDIz0RsVHZudegSJ4zYa95fLOW41YX5RweDqStPwMzdS8AT76ygViv7lnF4xrYkhgSKNlMQwPkP86GfIbGCquGAObJfCfCQsWpf1cbyI+J+oLGgFnUrE+m3VSkymsj2LPtZ+oII+1mlUFNtTvLaHiQemYzO9La7elPNSWU9pSPwamQJULfVlddlyjf2DSNdFaksc04aiPUzHdGkOkrk9Pwc1dtnC1F2mji/F9VQ35tk2qhpFejV/IVJnwOzNVOtnM2yDLLwsWk+/SNzBOP/tSLcHjFmRBmO1eeIQzTADyX401vTVOjzkovG4kdAIyuxdvhnpBOcuRCO3PwUSWeipr32kg7rh+26VQGmUjUFB9qdWM7cM+xhEMNzi6OXbElHkA3erw54tXrxfzO8r5i/hVAMD5lmRW7zauvTts98AVvqIMSVA8vSA4Tm4Z6OA+zAWRuAfqTZ9a1UTTUvqyXLqMyFTl0YlR/5Hpqzck8ARex050OxYQCAmf5kxWsQsjulfPwmSj1dRY67IEuLXPc4GWCjVEmM2sEVUO27UponOPHnWdM3X7FUjxzui04WbenLVj7Cu3YJ8jm/klJ3aRE1kTuzptCsDnIwjfVJ9QODEOux3qU+1RQ==","domain":".baidu.com","path":"/","expires":-1,"size":1310,"httpOnly":false,"secure":true,"session":true,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"ab_sr","value":"1.0.1_ZWU2MTA2MjgyNzBiMGYwMzhkNmUyN2VmYzA3Y2Q3YjY5MjhmN2YxNTVlNDRjMzQwMTU3ZTczM2VjNTEzM2YxYzMwOTg3NjQ1OGQ3NjQ1YjAwZGMwNzEyYjgyMDU2MGM1N2Y2Y2JiMzg1Y2IzNWFiODAwYTkzNGEwNTZiOWFlNjI3OTE4YzRiNzlmOWQ2MGY4OWJmMmVlOWI3OGIwMmJjMzc1Mzk0NTEwZDhiOWE1ZDNjMmQ0NDBhMDEwZTIxN2M4N2M5OGI3ZmZmZDczYjhlMjgyMGM5ZGMyYmMyNzc0YWQxMTYxNDAzZjQzYjVkYTdhNjM3YzhlNzA2OWYwYzFkOQ==","domain":".baidu.com","path":"/","expires":1777225780.507057,"size":355,"httpOnly":true,"secure":true,"session":false,"sameSite":"None","priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"XFI","value":"891a0410-4187-11f1-90e1-e72d8859a548","domain":"yiyan.baidu.com","path":"/","expires":-1,"size":39,"httpOnly":false,"secure":false,"session":true,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"RT","value":"\"z=1\u0026dm=baidu.com\u0026si=35853e6b-3134-4890-a4ac-89d3c3d37505\u0026ss=mofy1y51\u0026sl=1\u0026tt=2yu\u0026bcn=https%3A%2F%2Ffclog.baidu.com%2Flog%2Fweirwood%3Ftype%3Dperf\u0026ld=3r4\"","domain":".baidu.com","path":"/","expires":1777823381,"size":156,"httpOnly":false,"secure":false,"session":false,"sameSite":"Lax","priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"Hm_lpvt_01e907653ac089993ee83ed00ef9c2f3","value":"1777218578","domain":".yiyan.baidu.com","path":"/","expires":-1,"size":50,"httpOnly":false,"secure":false,"session":true,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"XFCS","value":"159D50B1AB4A6DFFAF7C8FC0D3F433AE963C02C6B3CCAF7EA6AC2AD956FC1FB0","domain":"yiyan.baidu.com","path":"/","expires":-1,"size":68,"httpOnly":false,"secure":false,"session":true,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"XFT","value":"PgazBUQva0jGVJRww/ZFfX3u8aQEgUyz6SyE15SZ4QY=","domain":"yiyan.baidu.com","path":"/","expires":-1,"size":47,"httpOnly":false,"secure":false,"session":true,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443}] \ No newline at end of file diff --git a/qianwen_test.go b/qianwen_test.go new file mode 100644 index 0000000..062241b --- /dev/null +++ b/qianwen_test.go @@ -0,0 +1,142 @@ +package collect + +import ( + "context" + "testing" + "time" + + "geo/internal/collect" + "geo/internal/config" + + "github.com/gofiber/fiber/v2/log" +) + +var ( + qianwenCfg, _ = config.LoadConfig() + qianwenManager = collect.NewCollectManager(context.Background(), qianwenCfg, log.DefaultLogger()) +) + +// TestQianwenCollector_WaitLogin 测试千问登录功能 +func TestQianwenCollector_WaitLogin(t *testing.T) { + if testing.Short() { + t.Skip("跳过需要浏览器交互的测试") + } + + params := &collect.CollectParams{ + Platform: "qianwen", + Headless: false, // 登录测试需要显示浏览器 + RequestID: "test_qianwen_login_001", + } + + success, msg := qianwenManager.WaitLogin("qianwen", params) + if !success { + t.Errorf("千问登录失败: %s", msg) + } else { + t.Logf("千问登录成功: %s", msg) + } +} + +// TestQianwenCollector_AskQuestion 测试千问单次提问功能 +func TestQianwenCollector_AskQuestion(t *testing.T) { + if testing.Short() { + t.Skip("跳过需要浏览器交互的测试") + } + + params := &collect.CollectParams{ + Platform: "qianwen", + Headless: false, // 提问测试可以使用无头模式 + RequestID: "test_qianwen_ask_001", + } + + question := "为什么你会说你是扎着丸子头" + + result, err := qianwenManager.AskQuestion("qianwen", params, question) + if err != nil { + t.Fatalf("千问提问失败: %v", err) + } + + if result.Answer == "" { + t.Error("千问返回的答案为空") + } else { + t.Logf("千问回答长度: %d 字符", len(result.Answer)) + // 输出前200个字符作为预览 + previewLen := min(len(result.Answer), 200) + t.Logf("千问回答预览: %s...", result.Answer[:previewLen]) + } + + if result.ShareLink != "" { + t.Logf("千问分享链接: %s", result.ShareLink) + } +} + +// TestQianwenCollector_MultipleQuestions 测试千问多次提问功能 +func TestQianwenCollector_MultipleQuestions(t *testing.T) { + if testing.Short() { + t.Skip("跳过需要浏览器交互的测试") + } + + params := &collect.CollectParams{ + Platform: "qianwen", + Headless: true, + RequestID: "test_qianwen_multi_001", + } + + questions := []string{ + "Python中如何定义一个函数?", + "Go语言的特点是什么?", + "解释一下机器学习的基本概念。", + } + + for i, question := range questions { + t.Logf("第%d次提问: %s", i+1, question) + + result, err := qianwenManager.AskQuestion("qianwen", params, question) + if err != nil { + t.Errorf("第%d次提问失败: %v", i+1, err) + continue + } + + if result.Answer == "" { + t.Errorf("第%d次提问返回的答案为空", i+1) + } else { + t.Logf("第%d次回答长度: %d 字符", i+1, len(result.Answer)) + // 输出前150个字符作为预览 + previewLen := min(len(result.Answer), 150) + t.Logf("第%d次回答预览: %s...", i+1, result.Answer[:previewLen]) + } + + // 每次提问之间间隔2秒,避免过于频繁 + time.Sleep(2 * time.Second) + } +} + +// TestQianwenCollector_SpeedTest 测试千问响应速度 +func TestQianwenCollector_SpeedTest(t *testing.T) { + if testing.Short() { + t.Skip("跳过需要浏览器交互的测试") + } + + params := &collect.CollectParams{ + Platform: "qianwen", + Headless: true, + RequestID: "test_qianwen_speed_001", + } + + question := "1+1等于多少?" + + startTime := time.Now() + result, err := qianwenManager.AskQuestion("qianwen", params, question) + duration := time.Since(startTime) + + if err != nil { + t.Fatalf("千问速度测试失败: %v", err) + } + + if result.Answer == "" { + t.Error("千问速度测试返回的答案为空") + } else { + t.Logf("千问响应时间: %v, 回答长度: %d 字符", duration, len(result.Answer)) + previewLen := min(len(result.Answer), 100) + t.Logf("回答预览: %s...", result.Answer[:previewLen]) + } +} diff --git a/yuanbao_test.go b/yuanbao_test.go new file mode 100644 index 0000000..8891460 --- /dev/null +++ b/yuanbao_test.go @@ -0,0 +1,142 @@ +package collect + +import ( + "context" + "testing" + "time" + + "geo/internal/collect" + "geo/internal/config" + + "github.com/gofiber/fiber/v2/log" +) + +var ( + yuanbaoCfg, _ = config.LoadConfig() + yuanbaoManager = collect.NewCollectManager(context.Background(), yuanbaoCfg, log.DefaultLogger()) +) + +// TestYuanbaoCollector_WaitLogin 测试元宝登录功能 +func TestYuanbaoCollector_WaitLogin(t *testing.T) { + if testing.Short() { + t.Skip("跳过需要浏览器交互的测试") + } + + params := &collect.CollectParams{ + Platform: "yuanbao", + Headless: false, // 登录测试需要显示浏览器 + RequestID: "test_yuanbao_login_001", + } + + success, msg := yuanbaoManager.WaitLogin("yuanbao", params) + if !success { + t.Errorf("元宝登录失败: %s", msg) + } else { + t.Logf("元宝登录成功: %s", msg) + } +} + +// TestYuanbaoCollector_AskQuestion 测试元宝单次提问功能 +func TestYuanbaoCollector_AskQuestion(t *testing.T) { + if testing.Short() { + t.Skip("跳过需要浏览器交互的测试") + } + + params := &collect.CollectParams{ + Platform: "yuanbao", + Headless: false, // 提问测试可以使用无头模式 + RequestID: "test_yuanbao_ask_001", + } + + question := "你好,请介绍一下你自己。" + + result, err := yuanbaoManager.AskQuestion("yuanbao", params, question) + if err != nil { + t.Fatalf("元宝提问失败: %v", err) + } + + if result.Answer == "" { + t.Error("元宝返回的答案为空") + } else { + t.Logf("元宝回答长度: %d 字符", len(result.Answer)) + // 输出前200个字符作为预览 + previewLen := min(len(result.Answer), 200) + t.Logf("元宝回答预览: %s...", result.Answer[:previewLen]) + } + + if result.ShareLink != "" { + t.Logf("元宝分享链接: %s", result.ShareLink) + } +} + +// TestYuanbaoCollector_MultipleQuestions 测试元宝多次提问功能 +func TestYuanbaoCollector_MultipleQuestions(t *testing.T) { + if testing.Short() { + t.Skip("跳过需要浏览器交互的测试") + } + + params := &collect.CollectParams{ + Platform: "yuanbao", + Headless: true, + RequestID: "test_yuanbao_multi_001", + } + + questions := []string{ + "Python中如何定义一个函数?", + "Go语言的特点是什么?", + "解释一下机器学习的基本概念。", + } + + for i, question := range questions { + t.Logf("第%d次提问: %s", i+1, question) + + result, err := yuanbaoManager.AskQuestion("yuanbao", params, question) + if err != nil { + t.Errorf("第%d次提问失败: %v", i+1, err) + continue + } + + if result.Answer == "" { + t.Errorf("第%d次提问返回的答案为空", i+1) + } else { + t.Logf("第%d次回答长度: %d 字符", i+1, len(result.Answer)) + // 输出前150个字符作为预览 + previewLen := min(len(result.Answer), 150) + t.Logf("第%d次回答预览: %s...", i+1, result.Answer[:previewLen]) + } + + // 每次提问之间间隔2秒,避免过于频繁 + time.Sleep(2 * time.Second) + } +} + +// TestYuanbaoCollector_SpeedTest 测试元宝响应速度 +func TestYuanbaoCollector_SpeedTest(t *testing.T) { + if testing.Short() { + t.Skip("跳过需要浏览器交互的测试") + } + + params := &collect.CollectParams{ + Platform: "yuanbao", + Headless: true, + RequestID: "test_yuanbao_speed_001", + } + + question := "1+1等于多少?" + + startTime := time.Now() + result, err := yuanbaoManager.AskQuestion("yuanbao", params, question) + duration := time.Since(startTime) + + if err != nil { + t.Fatalf("元宝速度测试失败: %v", err) + } + + if result.Answer == "" { + t.Error("元宝速度测试返回的答案为空") + } else { + t.Logf("元宝响应时间: %v, 回答长度: %d 字符", duration, len(result.Answer)) + previewLen := min(len(result.Answer), 100) + t.Logf("回答预览: %s...", result.Answer[:previewLen]) + } +}