feat(chat-ui): 增加聊天 UI 配置的本地持久化和预加载功能,优化首帧渲染体验
This commit is contained in:
@@ -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 路由
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 传不同值,
|
||||
|
||||
Reference in New Issue
Block a user