fix: Web Widget SDK + Auto-Reply AgentBot sender + LLM 真实模型对接

## 核心修复

### 1. Auto-Reply Sender 修复(所有渠道)
- AutoReplyListener.sendAutoReply() 通过 botInboxRepo 查询 inbox 关联的 AgentBot
- 使用正确的 SenderType="AgentBot"(非小写 agent_bot)传递真实 AgentBot ID
- bootstrap 注入 agentBotInboxRepo/agentBotRepo 依赖

### 2. 事件数据 BUG 修复(影响所有 Webhook 渠道)
- incoming_persister.dispatch(): 补全 sender_type/content 到 event.Data
- channel/webhook.go: HandleWebhook 同步分发也补全 sender_type/content
- 未补全前 AutoReplyListener 找不到字段直接跳过

### 3. Web Widget SDK 生产验证修复
- cookie → localStorage token 同步(frontend/index.html)
- 路由双注册修复(router.go)
- Vite SPA 模式 + /widget 重写(vite.config.ts)
- WidgetService 注入 Dispatcher 触发事件分发

### 4. LLM 真实模型对接
- 配置 deepseek-v4-flash @ http://10.58.144.6:2014/v1
- LLM-mode auto-reply 规则创建并验证通过
- Prompt 文档落地: docs/captain-ai-auto-replay-prompt.md

### 5. 新增基础设施
- Helm chart (deploy/helm/)
- Widget SDK 生产测试页面
- QA 报告

Closes: BUG-W2 (auth sync), BUG-W3 (route double-reg),
       BUG-WEBHOOK-EVENT (missing event data fields)
This commit is contained in:
Rogee
2026-07-28 14:03:19 +08:00
parent 05c5752a2b
commit 9816848ca2
32 changed files with 1688 additions and 15 deletions
+26
View File
@@ -49,6 +49,32 @@
);
window.errorLoggingConfig = '';
window.browserConfig = { browser_name: navigator.userAgent };
// BUG-11 mitigation: sync auth tokens from cookie to localStorage on page load
// The backend stores tokens in cw_d_session_info cookie, but the Vue SPA's
// axios interceptor reads from localStorage. This bridge ensures the tokens
// are available in both stores.
(function() {
try {
var cookies = document.cookie.split('; ');
var sessionCookie = cookies.find(function(c) { return c.startsWith('cw_d_session_info='); });
if (sessionCookie) {
var raw = decodeURIComponent(sessionCookie.split('=').slice(1).join('='));
var extract = function(key) {
var match = raw.match(new RegExp('"' + key + '":"([^"]+)"'));
return match ? match[1] : '';
};
var token = extract('access-token');
if (token) {
localStorage.setItem('access-token', token);
localStorage.setItem('client', extract('client'));
localStorage.setItem('uid', extract('uid'));
localStorage.setItem('token-type', extract('token-type') || 'Bearer');
localStorage.setItem('expiry', extract('expiry'));
}
}
} catch(e) { /* ignore cookie parse errors */ }
})();
})();
</script>
</head>
+7 -1
View File
@@ -80,10 +80,16 @@ export default defineConfig({
},
},
},
// Dev server proxies API + WebSocket to the GoChat Go backend
// Dev server: SPA mode for dashboard + widget.html route
server: {
host: '0.0.0.0',
port: 3036,
// Serve widget.html for /widget path (widget iframe), index.html for SPA routes
historyApiFallback: {
rewrites: [
{ from: /^\/widget/, to: '/widget.html' },
],
},
proxy: {
'/api': {
target: process.env.VITE_API_HOST || 'http://127.0.0.1:3000',
+102
View File
@@ -0,0 +1,102 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=0" />
<title>GoChat Widget</title>
<script>
(function () {
var cfg = window.__GOCHAT_CONFIG__ || {};
window.chatwootConfig = Object.assign(
{
hostURL: '',
helpCenterURL: '',
fbAppId: '',
instagramAppId: '',
tiktokAppId: '',
googleOAuthClientId: '',
googleOAuthCallbackUrl: '',
allowedLoginMethods: ['email'],
fbApiVersion: '',
whatsappAppId: '',
whatsappConfigurationId: '',
whatsappApiVersion: '',
signupEnabled: 'false',
isMfaEnabled: 'false',
inboxEventsEnabled: 'false',
selectedLocale: 'zh_CN',
enabledLanguages: [
{ name: '中文', iso_639_1_code: 'zh_CN' },
{ name: 'English', iso_639_1_code: 'en' },
],
helpUrls: {},
},
cfg
);
window.globalConfig = Object.assign(
{
INSTALLATION_NAME: 'GoChat',
CREATE_NEW_ACCOUNT_FROM_DASHBOARD: false,
DISPLAY_MANIFEST: false,
LOGO_THUMBNAIL: '/favicon-32x32.png',
},
cfg.globalConfig || {}
);
window.errorLoggingConfig = '';
window.browserConfig = { browser_name: navigator.userAgent };
// Initialize widget channel config from URL params
(function() {
var params = new URLSearchParams(window.location.search);
var websiteToken = params.get('website_token');
if (!websiteToken) return;
window.chatwootWebChannel = {
websiteToken: websiteToken,
locale: params.get('locale') || 'zh_CN',
widgetColor: '#1f93ff',
enabledLanguages: window.chatwootConfig.enabledLanguages || [],
workingHours: [],
workingHoursEnabled: false,
utcOffset: '+08:00',
replyTime: 'in_a_few_minutes',
enabledFeatures: ['attachments', 'emoji', 'end_conversation'],
allowMessageAfterResolved: true,
preChatFormEnabled: false,
preChatFormOptions: {},
businessHoursEnabled: false,
offlineMessageEnabled: true,
hmacEnabled: false,
portal: null,
};
var xhr = new XMLHttpRequest();
xhr.open('GET', '/api/v1/widget/config?website_token=' + websiteToken, true);
xhr.onload = function() {
if (xhr.status === 200) {
try {
var config = JSON.parse(xhr.responseText);
if (config.widgetColor) window.chatwootWebChannel.widgetColor = config.widgetColor;
if (config.enabledLanguages) window.chatwootWebChannel.enabledLanguages = config.enabledLanguages;
if (config.workingHours) window.chatwootWebChannel.workingHours = config.workingHours;
if (config.workingHoursEnabled !== undefined) window.chatwootWebChannel.workingHoursEnabled = config.workingHoursEnabled;
if (config.replyTime) window.chatwootWebChannel.replyTime = config.replyTime;
if (config.preChatFormEnabled !== undefined) window.chatwootWebChannel.preChatFormEnabled = config.preChatFormEnabled;
if (config.businessHoursEnabled !== undefined) window.chatwootWebChannel.businessHoursEnabled = config.businessHoursEnabled;
if (config.offlineMessageEnabled !== undefined) window.chatwootWebChannel.offlineMessageEnabled = config.offlineMessageEnabled;
if (config.portal) window.chatwootWebChannel.portal = config.portal;
if (config.enabledFeatures) window.chatwootWebChannel.enabledFeatures = config.enabledFeatures;
if (config.allowMessageAfterResolved !== undefined) window.chatwootWebChannel.allowMessageAfterResolved = config.allowMessageAfterResolved;
if (config.hmacEnabled !== undefined) window.chatwootWebChannel.hmacEnabled = config.hmacEnabled;
} catch(e) {}
}
};
xhr.send();
})();
})();
</script>
</head>
<body>
<div id="app"></div>
<noscript id="noscript">This app works best with JavaScript enabled.</noscript>
<script type="module" src="/app/javascript/entrypoints/widget.js"></script>
</body>
</html>