feat: 2026-04-15~05-02 累积变更基线 — AI 重构 + Runtime Context + DWS 修复
涵盖(每条对应已存的审计记录): - AI 模块拆分:apps/backend/app/ai/apps -> prompts/(8 个 APP + app2a 派生) audit: 2026-04-20__ai-module-complete.md - admin-web AI 管理套件:AIDashboard / AIOperations / AIRunLogs / AITriggers / TriggerManager audit: 2026-04-21__admin-web-ai-management-suite.md - App2 财务洞察 prompt v3 -> v5.1 + 小程序 AI 接入(chat / board-finance) audit: 2026-04-22__app2_prompt_v5_1_and_miniprogram_ai_insight.md - App2 prewarm 全过滤器 + AI 触发器 cron reschedule audit: 2026-04-21__app2-finance-prewarm-all-filters.md migration: 20260420_ai_trigger_jobs_and_app2_prewarm.sql / 20260421_app2_prewarm_cron_reschedule.sql - AppType 联合类型对齐 + adminAiAppTypes.test.ts audit: 2026-04-30__admin_web_ai_app_type_alignment.md - DashScope tokens_used 提取修复 audit: 2026-04-30__backend_dashscope_tokens_used_extraction.md - App3 线索完整详情 prompt audit: 2026-05-01__backend_app3_full_detail_prompt.md - Runtime Context 沙箱(5-1~5-2 主线): - 后端 schema/service + admin_runtime_context / xcx_runtime_clock 两个 router - admin-web RuntimeContext.tsx + miniprogram runtime-clock.ts - migration: 20260501__runtime_context_sandbox.sql - tools/db/verify_admin_web_sandbox.py + verify_sandbox_end_to_end.py - database/changes: 7 份 sandbox_* 验证报告 - 飞球 DWS 修复:finance_area_daily 区域汇总 + task_engine 调整 + RLS 视图业务日上界(migration 20260502 + scripts/ops/gen_rls_business_date_migration.py) 合规: - .gitignore 启用 tmp/ 排除 - 不入仓:apps/etl/connectors/feiqiu/.env(API_TOKEN secret,本地修改保留) 待验证清单: - docs/audit/changes/2026-05-04__cumulative_baseline_pending_verification.md 每个主题的功能完整性 / 上线验证几乎都未收口,按优先级 P0~P3 逐一处理
This commit is contained in:
@@ -198,6 +198,12 @@ Page({
|
||||
/** 最后一次发送的用户消息内容(用于重试) */
|
||||
_lastUserContent: '',
|
||||
|
||||
/** SSE 断线重试次数 */
|
||||
_sseRetryCount: 0,
|
||||
|
||||
/** SSE 最大自动重试次数 */
|
||||
_SSE_MAX_RETRY: 2,
|
||||
|
||||
onShow() {
|
||||
// 权限守卫:检查登录状态、账号禁用、角色权限
|
||||
checkPageAccess('pages/chat/chat')
|
||||
@@ -227,10 +233,27 @@ Page({
|
||||
this.loadMessagesByContext('coach', options.coachId)
|
||||
} else if (options?.sourcePage) {
|
||||
// 看板类入口:保存来源页面和筛选参数
|
||||
const filterKeys = ['timeDimension', 'areaFilter', 'dimension', 'typeFilter', 'projectFilter']
|
||||
// Phase 2.3:优先解析 options.pageFilters(ai-float-button 传入的 JSON 字符串),
|
||||
// 回退到单独键(旧入口兼容:timeDimension / areaFilter 等)
|
||||
const pageFilters: Record<string, string> = {}
|
||||
for (const key of filterKeys) {
|
||||
if (options[key]) pageFilters[key] = options[key]
|
||||
if (options.pageFilters) {
|
||||
try {
|
||||
const parsed = JSON.parse(decodeURIComponent(options.pageFilters))
|
||||
if (parsed && typeof parsed === 'object') {
|
||||
for (const k of Object.keys(parsed)) {
|
||||
const v = parsed[k]
|
||||
if (v != null) pageFilters[k] = String(v)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// JSON 解析失败忽略,回退到单键读取
|
||||
}
|
||||
}
|
||||
if (Object.keys(pageFilters).length === 0) {
|
||||
const filterKeys = ['timeDimension', 'areaFilter', 'dimension', 'typeFilter', 'projectFilter']
|
||||
for (const key of filterKeys) {
|
||||
if (options[key]) pageFilters[key] = options[key]
|
||||
}
|
||||
}
|
||||
this.setData({ sourcePage: options.sourcePage, pageFilters })
|
||||
this.loadMessagesByContext(options.sourcePage, '')
|
||||
@@ -418,6 +441,7 @@ Page({
|
||||
},
|
||||
// onDone: 流结束,更新消息 ID 和时间
|
||||
(messageId: number, createdAt: string) => {
|
||||
this._sseRetryCount = 0
|
||||
this.setData({
|
||||
[`messages[${aiIndex}].id`]: String(messageId),
|
||||
[`messages[${aiIndex}].timestamp`]: createdAt,
|
||||
@@ -477,8 +501,20 @@ Page({
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
// 网络错误或连接中断
|
||||
if (this.data.isStreaming) {
|
||||
// 网络错误或连接中断:无内容时指数退避重连
|
||||
this._sseTask = null
|
||||
if (!this.data.isStreaming) return
|
||||
if (fullContent === '' && this._sseRetryCount < this._SSE_MAX_RETRY) {
|
||||
this._sseRetryCount++
|
||||
const delay = (2 ** this._sseRetryCount) * 1000
|
||||
wx.showToast({ title: `重连中 ${this._sseRetryCount}/${this._SSE_MAX_RETRY}...`, icon: 'loading', duration: delay })
|
||||
this.setData({
|
||||
messages: this.data.messages.slice(0, aiIndex),
|
||||
isStreaming: false,
|
||||
streamingContent: '',
|
||||
})
|
||||
setTimeout(() => { this.triggerAIReply(chatId, content) }, delay)
|
||||
} else {
|
||||
const errorContent = fullContent || '连接中断,请重试'
|
||||
this.setData({
|
||||
[`messages[${aiIndex}].content`]: errorContent,
|
||||
@@ -487,7 +523,6 @@ Page({
|
||||
})
|
||||
wx.showToast({ title: '连接中断', icon: 'none', duration: 3000 })
|
||||
}
|
||||
this._sseTask = null
|
||||
},
|
||||
} as WechatMiniprogram.RequestOption)
|
||||
|
||||
@@ -509,4 +544,19 @@ Page({
|
||||
}, 50)
|
||||
}, 50)
|
||||
},
|
||||
|
||||
/** 点击引用卡片:跳转到对应详情页(Phase 2.1) */
|
||||
onRefCardTap(e: WechatMiniprogram.BaseEvent & { currentTarget: { dataset: { link?: string } } }) {
|
||||
const link = e.currentTarget?.dataset?.link
|
||||
if (!link || typeof link !== 'string') {
|
||||
return
|
||||
}
|
||||
wx.navigateTo({
|
||||
url: link,
|
||||
fail: (err) => {
|
||||
console.error('跳转引用详情失败', err)
|
||||
wx.showToast({ title: '跳转失败', icon: 'none' })
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user