166 lines
5.7 KiB
Plaintext
166 lines
5.7 KiB
Plaintext
<!-- pages/chat/chat.wxml — AI 对话页 -->
|
|
<wxs src="../../utils/time.wxs" module="timefmt" />
|
|
|
|
<!-- 加载态 -->
|
|
<view class="g-toast-loading" wx:if="{{pageState === 'loading'}}">
|
|
<view class="g-toast-loading-inner">
|
|
<t-loading theme="circular" size="40rpx" />
|
|
<text class="g-toast-loading-text">加载中...</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 错误态 -->
|
|
<view class="page-error" wx:elif="{{pageState === 'error'}}">
|
|
<view class="error-content">
|
|
<text class="error-text">加载失败,请重试</text>
|
|
<view class="retry-btn" hover-class="retry-btn--hover" bindtap="onRetry">
|
|
<text class="retry-btn-text">重新加载</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 正常态 -->
|
|
<view class="chat-page" wx:elif="{{pageState === 'normal' || pageState === 'empty'}}">
|
|
|
|
<!-- 消息列表 -->
|
|
<scroll-view
|
|
class="message-list"
|
|
scroll-y
|
|
scroll-into-view="{{scrollToId}}"
|
|
scroll-with-animation
|
|
enhanced
|
|
show-scrollbar="{{false}}"
|
|
style="bottom: {{inputBarBottom}}px;"
|
|
>
|
|
|
|
<!-- 引用内容卡片(从其他页面跳转时显示)-->
|
|
<view class="reference-card" wx:if="{{referenceCard}}">
|
|
<view class="reference-label-row">
|
|
<text class="reference-tag">引用内容</text>
|
|
<text class="reference-source">{{referenceCard.title}}</text>
|
|
</view>
|
|
<text class="reference-summary">{{referenceCard.summary}}</text>
|
|
</view>
|
|
|
|
<!-- 空对话提示 -->
|
|
<view class="empty-hint" wx:if="{{pageState === 'empty' && messages.length === 0}}">
|
|
<view class="empty-ai-avatar">
|
|
<image src="/assets/icons/ai-robot.svg" class="empty-ai-img" mode="aspectFit" />
|
|
</view>
|
|
<text class="empty-text">你好,我是 AI 助手</text>
|
|
<text class="empty-sub">有什么可以帮你的?</text>
|
|
</view>
|
|
|
|
<!-- 消息气泡列表 -->
|
|
<block wx:for="{{messages}}" wx:key="id">
|
|
|
|
<!--
|
|
IM 时间分割线
|
|
· 首条消息始终显示
|
|
· 相邻消息间隔 ≥ 5 分钟时显示
|
|
· 格式:今天 HH:mm / 今年 MM-DD HH:mm / 跨年 YYYY-MM-DD HH:mm
|
|
-->
|
|
<view class="time-divider" wx:if="{{item.showTimeDivider}}">
|
|
<view class="time-divider-inner">
|
|
<text class="time-divider-text">{{timefmt.imTime(item.timestamp)}}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 用户消息:右对齐蓝色 -->
|
|
<view class="message-row message-user" wx:if="{{item.role === 'user'}}" id="msg-{{item.id}}">
|
|
<view class="user-bubble-col">
|
|
<view class="bubble bubble-user">
|
|
<text class="bubble-text">{{item.content}}</text>
|
|
</view>
|
|
<!-- 用户侧引用卡片(用户发给 AI 的上下文卡片)-->
|
|
<view class="inline-ref-card inline-ref-card--user" wx:if="{{item.referenceCard}}">
|
|
<view class="inline-ref-header">
|
|
<text class="inline-ref-type">{{item.referenceCard.type === 'customer' ? '👤 客户' : '📋 记录'}}</text>
|
|
<text class="inline-ref-title">{{item.referenceCard.title}}</text>
|
|
</view>
|
|
<text class="inline-ref-summary">{{item.referenceCard.summary}}</text>
|
|
<view class="inline-ref-data">
|
|
<view class="ref-data-item" wx:for="{{item.referenceCard.dataList}}" wx:for-item="entry" wx:key="key">
|
|
<text class="ref-data-key">{{entry.key}}</text>
|
|
<text class="ref-data-value">{{entry.value}}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- AI 消息:左对齐白色 -->
|
|
<view class="message-row message-assistant" wx:else id="msg-{{item.id}}">
|
|
<view class="ai-avatar">
|
|
<image src="/assets/icons/ai-robot.svg" class="ai-avatar-img" mode="aspectFit" />
|
|
</view>
|
|
<view class="bubble-col">
|
|
<view class="bubble bubble-assistant">
|
|
<text class="bubble-text">{{item.content}}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
</block>
|
|
|
|
<!-- AI 正在输入指示器 -->
|
|
<view class="message-row message-assistant" wx:if="{{isStreaming && !streamingContent}}" id="msg-typing">
|
|
<view class="ai-avatar">
|
|
<image src="/assets/icons/ai-robot.svg" class="ai-avatar-img" mode="aspectFit" />
|
|
</view>
|
|
<view class="bubble bubble-assistant typing-bubble">
|
|
<view class="typing-dots">
|
|
<view class="dot dot-1"></view>
|
|
<view class="dot dot-2"></view>
|
|
<view class="dot dot-3"></view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 底部占位 -->
|
|
<view class="scroll-bottom-spacer" id="scroll-bottom"></view>
|
|
|
|
</scroll-view>
|
|
|
|
<!-- 底部输入区域 -->
|
|
<view class="input-bar" style="bottom: {{inputBarBottom}}px;">
|
|
<view class="input-wrapper">
|
|
<input
|
|
class="chat-input"
|
|
value="{{inputText}}"
|
|
placeholder="输入消息..."
|
|
placeholder-class="input-placeholder"
|
|
confirm-type="send"
|
|
bindinput="onInputChange"
|
|
bindconfirm="onSendMessage"
|
|
bindfocus="onInputFocus"
|
|
bindblur="onInputBlur"
|
|
adjust-position="{{false}}"
|
|
disabled="{{isStreaming}}"
|
|
cursor-spacing="16"
|
|
/>
|
|
</view>
|
|
<view
|
|
class="send-btn {{inputText.length > 0 && !isStreaming ? 'send-btn-active' : 'send-btn-disabled'}}"
|
|
hover-class="send-btn--hover"
|
|
bindtap="onSendMessage"
|
|
>
|
|
<image
|
|
wx:if="{{inputText.length > 0 && !isStreaming}}"
|
|
src="/assets/icons/send-arrow-white.svg"
|
|
class="send-icon"
|
|
mode="aspectFit"
|
|
/>
|
|
<image
|
|
wx:else
|
|
src="/assets/icons/send-arrow-gray.svg"
|
|
class="send-icon"
|
|
mode="aspectFit"
|
|
/>
|
|
</view>
|
|
</view>
|
|
|
|
</view>
|
|
|
|
<dev-fab />
|