feat(chat-ui): 增加聊天 UI 配置的本地持久化和预加载功能,优化首帧渲染体验

This commit is contained in:
2026-09-24 11:10:21 +08:00
parent 86b87eeb9d
commit 70f8fb867d
3 changed files with 78 additions and 0 deletions

View File

@@ -1004,6 +1004,9 @@ export default {
const merged = mergeChatUiConfig(remote);
_chatUiConfigCache = merged;
_chatUiConfigCacheAt = Date.now();
// PR-CHAT-UI-CONFIG-PRELOAD (2026-09-24): 持久化到本地, 供下次进入页面时
// getChatUiConfigSync() 同步取用, 实现首帧即用真实配置 (消除默认值闪烁)
try { uni.setStorageSync('AI_CHAT_UI_CONFIG', merged); } catch (e) { /* 存储失败不影响主流程 */ }
return merged;
} catch (e) {
console.warn('[AI Service] getChatUiConfig 失败, 使用前端默认值:', (e && e.message) || e);
@@ -1012,6 +1015,23 @@ export default {
}
},
/**
* PR-CHAT-UI-CONFIG-PRELOAD (2026-09-24): 同步获取聊天 UI 配置 (本地持久缓存)
* 场景: 页面 onLoad 首帧渲染前调用, 上次会话成功拉取过的配置直接可用,
* 避免"先按默认值渲染 → 异步配置到达后跳变"的闪烁。
* 返回: 配置对象 (缓存命中, 已 merge 兜底) 或 null (从未成功拉取过)
* 注意: 只读缓存不发起网络请求; 最新值仍由 getChatUiConfig() 异步刷新
*/
getChatUiConfigSync() {
try {
const cached = uni.getStorageSync('AI_CHAT_UI_CONFIG');
if (cached && typeof cached === 'object' && Object.keys(cached).length) {
return mergeChatUiConfig(cached);
}
} catch (e) { /* 存储读取失败按无缓存处理 */ }
return null;
},
/**
* 清除会话历史(调用后端接口)
* PR-7: URL 按 agent_id 路由

View File

@@ -615,6 +615,13 @@ export default {
type: Array,
default: () => []
},
// PR-CHAT-UI-CONFIG-PRELOAD (2026-09-24): 父页面渲染前预取的聊天 UI 配置
// 传入后 created() 立即用作首帧值, 消除"默认值渲染 → 异步配置到达跳变"的闪烁;
// 之后 _loadChatUiConfig() 仍会拉最新值覆盖 (与父页面共享 60s TTL 缓存)
preloadedUiConfig: {
type: Object,
default: null
},
// 初始用户头像(外部传入,可选)
initUserAvatar: {
type: String,
@@ -880,6 +887,21 @@ export default {
this.currentConversationId = aiService.getConversationId();
console.log('[AI Chat] created, currentConversationId:', this.currentConversationId, 'messages.length:', this.messages.length);
// PR-CHAT-UI-CONFIG-PRELOAD (2026-09-24): 父页面预取配置优先作为首帧值
// 父页面已保证渲染本组件时配置就绪 (缓存或预取), 此处同步落位;
// _loadChatUiConfig() 随后仍拉最新值 (TTL 缓存命中则同值, 无视觉跳变)
if (this.preloadedUiConfig && typeof this.preloadedUiConfig === 'object') {
this.chatUiConfig = Object.assign({}, this.chatUiConfig, this.preloadedUiConfig)
if (Array.isArray(this.preloadedUiConfig.ui_available_models) && this.preloadedUiConfig.ui_available_models.length) {
this.availableModels = this.preloadedUiConfig.ui_available_models.slice()
if (!this.availableModels.includes(this.selectedModel)) {
this.selectedModel = this.availableModels[0] || 'Auto'
}
}
this.chatUiConfigLoaded = true
console.log('[AI Chat] chatUiConfig 使用父页面预取值 (首帧即真实配置)')
}
// 异步获取服务配置(含欢迎语)
this._loadWelcomeMessage()
// PR-CHAT-UI-CONFIG (2026-09-20): 异步加载 UI 开关 (与欢迎语并行, 不阻塞 created)

View File

@@ -16,7 +16,9 @@
<!-- AI 智能页始终全屏,不让位给底部 tabBar,聊天区直接占满到底 -->
<view class="chat-content" :style="{ bottom: '0rpx' }">
<ai-chat-message
v-if="uiCfgReady"
ref="chat"
:preloaded-ui-config="preloadedUiCfg"
:initial-messages="initialMessages"
:fallback-welcome="fallbackWelcome"
:user-avatar="userAvatar"
@@ -96,6 +98,12 @@ export default {
agentId: 'agent_aikefu',
pageTitle: '',
// PR-CHAT-UI-CONFIG-PRELOAD (2026-09-24): 聊天 UI 配置预取
// uiCfgReady 控制聊天组件渲染时机 — 配置就绪(缓存命中或预取完成)才挂载,
// 避免"首帧用默认值渲染 → 异步配置到达后名称/头像/时间跳变"的闪烁
uiCfgReady: false,
preloadedUiCfg: null,
// --- 系统导航栏/状态栏相关 ---
navBarHeight: 44, // uni-page-head 系统导航栏,默认高度为44px
statusBarHeight: 0, // 状态栏高度,默认0
@@ -140,6 +148,34 @@ export default {
async onLoad(options) {
this.fallbackWelcome = this.$lang('ai-chat.index.fallbackWelcome');
// ====== PR-CHAT-UI-CONFIG-PRELOAD (2026-09-24): 配置先行,再渲染聊天组件 ======
// 1) 本地持久缓存命中 → 同步就绪, 立即渲染 (零延迟, 用上次会话的真实配置)
// 2) 未命中(首次访问) → await 强制拉取 (race 2.5s 超时兜底), 拿到后再渲染
// 3) 拉取失败/超时 → 也放行渲染, 组件内部仍有默认值兜底, 不白屏
// force 拉取同时预热 /info 缓存, 欢迎语/模型列表等后续请求直接复用
try {
const cached = aiService.getChatUiConfigSync();
if (cached) {
this.preloadedUiCfg = cached;
this.uiCfgReady = true;
console.log('[AI Chat] chatUiConfig 命中本地缓存, 首帧即用真实配置');
}
} catch (e) { /* 缓存读取失败走预取 */ }
if (!this.uiCfgReady) {
try {
const cfg = await Promise.race([
aiService.getChatUiConfig(true),
new Promise(resolve => setTimeout(() => resolve(null), 6000))
]);
if (cfg) this.preloadedUiCfg = cfg;
else console.warn('[AI Chat] chatUiConfig 预取超时(6s), 先渲染后由组件异步刷新');
} catch (e) { /* 失败兜底默认值 */ }
this.uiCfgReady = true;
console.log('[AI Chat] chatUiConfig 预取完成, 渲染聊天组件:', !!this.preloadedUiCfg);
}
// 最新值刷新: 组件 created 的 _loadChatUiConfig() 会异步拉取,
// 与此处 force 拉取共享 60s TTL 内存缓存, 不会产生二次 /info 请求
// ====== AI Agent 通用入参(链接系统 -> 通用 agent_id/title) ======
// 老客服入口不传 options 也能跑(智能客服是首个 agent,默认 agent_aikefu)。
// 新增 AI 销售/HR/...智能体只需在 link.page_params.agent_id 传不同值,