fix code
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
"usingComponents": {
|
||||
"t-icon": "tdesign-miniprogram/icon/icon",
|
||||
"t-skeleton": "tdesign-miniprogram/skeleton/skeleton",
|
||||
"t-cell": "tdesign-miniprogram/cell/cell",
|
||||
"word-dictionary": "../../components/word-dictionary/word-dictionary",
|
||||
"vx-confetti": "/components/vx-confetti/vx-confetti",
|
||||
"cloud-image": "../../components/cloud-image/cloud-image",
|
||||
|
||||
@@ -126,13 +126,31 @@ interface IData {
|
||||
scrollIntoView: string
|
||||
renderPresets: Array<{ name: string; type: string }>
|
||||
sidebar?: any[]
|
||||
}
|
||||
historyList: any[]
|
||||
historyPage: number
|
||||
historyHasMore: boolean
|
||||
historyLoading: boolean
|
||||
chatPlaceholder: string
|
||||
showChatSuggestions: boolean
|
||||
chatSuggestions: Array<{ label: string; text: string }>
|
||||
inputBottom: number
|
||||
}
|
||||
|
||||
interface IPageInstance {
|
||||
pollTimer?: number
|
||||
variationPollTimer?: number
|
||||
conversationPollTimer?: number
|
||||
placeholderTimer?: number
|
||||
suggestionTimer?: number
|
||||
isSuggestionTouching?: boolean
|
||||
audioCtx?: WechatMiniprogram.InnerAudioContext
|
||||
onKeyboardHeightChange: (res: any) => void
|
||||
startPlaceholderTimer: () => void
|
||||
startSuggestionTimer: () => void
|
||||
stopSuggestionTimer: () => void
|
||||
handleSuggestionTap: (e: any) => void
|
||||
handleSuggestionTouchStart: () => void
|
||||
handleSuggestionTouchEnd: () => void
|
||||
fetchQaExercises: (imageId: string, referrerId?: string) => Promise<void>
|
||||
fetchVariationExercises: (imageId: string) => Promise<void>
|
||||
fetchConversationSetting: (imageId: string) => Promise<void>
|
||||
@@ -140,6 +158,8 @@ interface IPageInstance {
|
||||
startVariationPolling: (taskId: string, imageId: string) => void
|
||||
startConversationInitPolling: (taskId: string, imageId: string) => void
|
||||
startConversationPolling: (taskId: string, sessionId: string, showLoadingMask?: boolean, append?: boolean) => void
|
||||
loadHistoryData: () => Promise<void>
|
||||
onHistoryScrollBottom: () => void
|
||||
initExerciseContent: (exercise: any, session?: IQaExerciseSession) => void
|
||||
updateActionButtonsState: () => void
|
||||
shuffleArray: <T>(arr: T[]) => T[]
|
||||
@@ -314,8 +334,28 @@ Page<IData, IPageInstance>({
|
||||
conversationMessages: [],
|
||||
replyLoading: false,
|
||||
chatInputValue: '',
|
||||
renderPresets: [ { name: 'send', type: 'icon'} ]
|
||||
renderPresets: [ { name: 'send', type: 'icon'} ],
|
||||
historyList: [],
|
||||
historyPage: 1,
|
||||
historyHasMore: true,
|
||||
historyLoading: false,
|
||||
chatPlaceholder: '请输入...',
|
||||
showChatSuggestions: false,
|
||||
chatSuggestions: [],
|
||||
inputBottom: 0
|
||||
},
|
||||
|
||||
onKeyboardHeightChange(res: any) {
|
||||
this.setData({ inputBottom: res.height })
|
||||
},
|
||||
|
||||
pollTimer: undefined,
|
||||
variationPollTimer: undefined,
|
||||
conversationPollTimer: undefined,
|
||||
placeholderTimer: undefined,
|
||||
suggestionTimer: undefined,
|
||||
audioCtx: undefined,
|
||||
|
||||
updateProcessDots() {
|
||||
const list = this.data.qaList || []
|
||||
const cache = this.data.qaResultCache || {}
|
||||
@@ -519,6 +559,7 @@ Page<IData, IPageInstance>({
|
||||
},
|
||||
|
||||
async onLoad(options: Record<string, string>) {
|
||||
wx.onKeyboardHeightChange(this.onKeyboardHeightChange)
|
||||
try {
|
||||
const app = getApp<IAppOption>()
|
||||
|
||||
@@ -926,7 +967,7 @@ Page<IData, IPageInstance>({
|
||||
conversationViewMode: 'chat',
|
||||
loadingMaskVisible: false,
|
||||
statusText: '加载完成',
|
||||
conversationLatestSession: { id: sessionId, status: 'ongoing' },
|
||||
conversationLatestSession: { id: sessionId, session_id: sessionId, status: 'ongoing' },
|
||||
replyLoading: false
|
||||
})
|
||||
this.updateConversationMessages(detail, this.data.conversationSceneLang || 'zh', append)
|
||||
@@ -1896,6 +1937,7 @@ Page<IData, IPageInstance>({
|
||||
},
|
||||
|
||||
async onSendMessage(e: any) {
|
||||
this.stopSuggestionTimer()
|
||||
const content = e.detail?.value || this.data.chatInputValue
|
||||
if (!content || !content.trim()) return
|
||||
|
||||
@@ -1933,31 +1975,71 @@ Page<IData, IPageInstance>({
|
||||
},
|
||||
|
||||
onChatInput(e: any) {
|
||||
this.setData({ chatInputValue: e.detail?.value || '' })
|
||||
const val = e.detail?.value || ''
|
||||
this.setData({ chatInputValue: val })
|
||||
|
||||
if (val) {
|
||||
// 有内容,清除计时器,保持默认 placeholder
|
||||
if (this.placeholderTimer) {
|
||||
clearTimeout(this.placeholderTimer)
|
||||
this.placeholderTimer = undefined
|
||||
}
|
||||
this.setData({ chatPlaceholder: '请输入...' })
|
||||
} else {
|
||||
// 内容清空,重新开始计时
|
||||
this.startPlaceholderTimer()
|
||||
}
|
||||
},
|
||||
|
||||
onChatCloseTap(e: any) {
|
||||
this.setData({ isChatInputVisible: false })
|
||||
this.stopSuggestionTimer()
|
||||
this.setData({ isChatInputVisible: false, chatPlaceholder: '请输入...' })
|
||||
if (this.placeholderTimer) {
|
||||
clearTimeout(this.placeholderTimer)
|
||||
this.placeholderTimer = undefined
|
||||
}
|
||||
},
|
||||
|
||||
onChatBlur(e: any) {
|
||||
this.setData({ isChatInputVisible: false, scrollIntoView: '' })
|
||||
if (this.isSuggestionTouching) return
|
||||
this.stopSuggestionTimer()
|
||||
this.setData({ isChatInputVisible: false, scrollIntoView: '', chatPlaceholder: '请输入...' })
|
||||
if (this.placeholderTimer) {
|
||||
clearTimeout(this.placeholderTimer)
|
||||
this.placeholderTimer = undefined
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.setData({ scrollIntoView: 'bottom-anchor' })
|
||||
}, 300)
|
||||
},
|
||||
|
||||
showChatInput() {
|
||||
if (!this.data.conversationLatestSession) {
|
||||
if (!this.data.conversationLatestSession || this.data.conversationViewMode === 'setup') {
|
||||
return
|
||||
}
|
||||
this.setData({ isChatInputVisible: true, scrollIntoView: '' })
|
||||
|
||||
// 开启 placeholder 计时
|
||||
this.startPlaceholderTimer()
|
||||
// 开启建议计时
|
||||
this.startSuggestionTimer()
|
||||
|
||||
setTimeout(() => {
|
||||
this.setData({ scrollIntoView: 'bottom-anchor' })
|
||||
logger.info('Scroll to bottom anchor')
|
||||
}, 2000)
|
||||
},
|
||||
|
||||
handleSuggestionTouchStart() {
|
||||
this.isSuggestionTouching = true
|
||||
},
|
||||
|
||||
handleSuggestionTouchEnd() {
|
||||
setTimeout(() => {
|
||||
this.isSuggestionTouching = false
|
||||
}, 200)
|
||||
},
|
||||
|
||||
updateConversationMessages(detail: any, lang: string, append: boolean = false) {
|
||||
if (!detail || !detail.messages) {
|
||||
if (!append) {
|
||||
@@ -2357,46 +2439,69 @@ Page<IData, IPageInstance>({
|
||||
}
|
||||
},
|
||||
onHistoryTap() {
|
||||
const run = async () => {
|
||||
try {
|
||||
const { imageId } = this.data
|
||||
if (!imageId) {
|
||||
wx.showToast({ title: '缺少图片信息', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.setData({ visible: true })
|
||||
const res = await apiManager.listQaConversations(imageId)
|
||||
logger.info('获取到的历史聊天对话记录:', res)
|
||||
|
||||
// Assuming res is { list: [...], total: ... } or just array
|
||||
const list = Array.isArray(res) ? res : (res.list || [])
|
||||
|
||||
const sidebar = list.map((item: any) => ({
|
||||
title: item.title || item.summary || (item.created_time ? `对话 ${item.created_time}` : `对话 ${item.id}`),
|
||||
...item
|
||||
}))
|
||||
|
||||
this.setData({ sidebar })
|
||||
|
||||
} catch (err) {
|
||||
logger.error('获取历史聊天对话记录失败:', err)
|
||||
wx.showToast({ title: '获取历史记录失败', icon: 'none' })
|
||||
}
|
||||
this.setData({
|
||||
visible: true,
|
||||
historyList: [],
|
||||
historyPage: 1,
|
||||
historyHasMore: true,
|
||||
historyLoading: false
|
||||
}, () => {
|
||||
this.loadHistoryData()
|
||||
})
|
||||
},
|
||||
|
||||
async loadHistoryData() {
|
||||
if (this.data.historyLoading || !this.data.historyHasMore) return
|
||||
|
||||
this.setData({ historyLoading: true })
|
||||
|
||||
try {
|
||||
const { imageId, historyPage } = this.data
|
||||
if (!imageId) return
|
||||
|
||||
const res = await apiManager.listQaConversations(imageId, historyPage, 20)
|
||||
logger.info('获取到的历史聊天对话记录:', res)
|
||||
|
||||
// Ensure we handle both structure { items: [] } and direct array if api changes
|
||||
const items = res.items || (Array.isArray(res) ? res : [])
|
||||
|
||||
const newHistoryList = historyPage === 1 ? items : this.data.historyList.concat(items)
|
||||
|
||||
this.setData({
|
||||
historyList: newHistoryList,
|
||||
historyPage: historyPage + 1,
|
||||
historyHasMore: items.length >= 20,
|
||||
historyLoading: false
|
||||
})
|
||||
} catch (err) {
|
||||
logger.error('获取历史聊天对话记录失败:', err)
|
||||
this.setData({ historyLoading: false })
|
||||
wx.showToast({ title: '加载失败', icon: 'none' })
|
||||
}
|
||||
run()
|
||||
},
|
||||
|
||||
onHistoryScrollBottom() {
|
||||
this.loadHistoryData()
|
||||
},
|
||||
|
||||
chatItemClick(e: any) {
|
||||
const { item } = e.detail
|
||||
if (!item || !item.id) return
|
||||
// Handle both t-drawer item-click (detail.item) and tap on custom view (currentTarget.dataset.item)
|
||||
const item = e.detail?.item || e.currentTarget?.dataset?.item
|
||||
if (!item || !item.session_id) return
|
||||
|
||||
const sessionId = item.id
|
||||
const sessionId = item.session_id
|
||||
|
||||
this.setData({ visible: false })
|
||||
|
||||
// User requirement: "如果点击的 id 和目前正在对话的 id 一致,则什么都不用做"
|
||||
if (this.data.conversationLatestSession && this.data.conversationLatestSession.id === sessionId) {
|
||||
return
|
||||
}
|
||||
|
||||
const run = async () => {
|
||||
try {
|
||||
this.setData({ loadingMaskVisible: true, statusText: '加载对话...' })
|
||||
const detail = await apiManager.getQaConversationLatest(sessionId)
|
||||
const detail = await apiManager.getQaConversationDetail(sessionId)
|
||||
logger.info('Loaded conversation detail:', detail)
|
||||
|
||||
this.setData({
|
||||
@@ -2404,7 +2509,7 @@ Page<IData, IPageInstance>({
|
||||
conversationViewMode: 'chat',
|
||||
loadingMaskVisible: false,
|
||||
statusText: '加载完成',
|
||||
conversationLatestSession: { id: sessionId, status: 'ongoing' },
|
||||
conversationLatestSession: { id: sessionId, session_id: sessionId, status: 'ongoing' },
|
||||
replyLoading: false
|
||||
})
|
||||
this.updateConversationMessages(detail, this.data.conversationSceneLang || 'zh', false)
|
||||
@@ -2417,11 +2522,139 @@ Page<IData, IPageInstance>({
|
||||
run()
|
||||
},
|
||||
|
||||
startPlaceholderTimer() {
|
||||
if (this.placeholderTimer) {
|
||||
clearTimeout(this.placeholderTimer)
|
||||
this.placeholderTimer = undefined
|
||||
}
|
||||
// 重置为默认提示
|
||||
this.setData({ chatPlaceholder: '请输入...' })
|
||||
|
||||
// 3秒后尝试显示 prompt
|
||||
this.placeholderTimer = setTimeout(() => {
|
||||
this.placeholderTimer = undefined
|
||||
// 获取最后一条 assistant 消息
|
||||
const messages = this.data.conversationMessages || []
|
||||
let lastAssistantMsg = null
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
if (messages[i].role === 'assistant') {
|
||||
lastAssistantMsg = messages[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (lastAssistantMsg) {
|
||||
// 根据当前语言设置 placeholder
|
||||
// 注意:messages 里的 content 结构可能已经被 updateConversationMessages 处理过,或者保留了原始结构
|
||||
// 原始结构在 updateConversationMessages 中被 map 成了 { role, content: [{type:'text', data: text}] }
|
||||
// 但是这里我们需要原始的 prompt_en/zh 字段。
|
||||
// 查看 updateConversationMessages,它似乎没有把原始 prompt 存下来?
|
||||
// 让我们检查一下 updateConversationMessages 的实现。
|
||||
// 确实,它只提取了 text。
|
||||
// 所以我们需要从 conversationDetail.messages 里找,而不是 this.data.conversationMessages
|
||||
|
||||
const detailMessages = this.data.conversationDetail?.messages || []
|
||||
let originalMsg = null
|
||||
for (let i = detailMessages.length - 1; i >= 0; i--) {
|
||||
if (detailMessages[i].role === 'assistant') {
|
||||
originalMsg = detailMessages[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (originalMsg && originalMsg.content) {
|
||||
const lang = this.data.conversationSceneLang || 'zh'
|
||||
const prompt = lang === 'zh' ? originalMsg.content.prompt_zh : originalMsg.content.prompt_en
|
||||
if (prompt) {
|
||||
this.setData({ chatPlaceholder: prompt })
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 3000) as any
|
||||
},
|
||||
|
||||
startSuggestionTimer() {
|
||||
if (this.suggestionTimer) {
|
||||
clearTimeout(this.suggestionTimer)
|
||||
this.suggestionTimer = undefined
|
||||
}
|
||||
this.setData({ showChatSuggestions: false })
|
||||
|
||||
this.suggestionTimer = setTimeout(() => {
|
||||
this.suggestionTimer = undefined
|
||||
const detailMessages = this.data.conversationDetail?.messages || []
|
||||
let lastAssistantMsg = null
|
||||
for (let i = detailMessages.length - 1; i >= 0; i--) {
|
||||
if (detailMessages[i].role === 'assistant') {
|
||||
lastAssistantMsg = detailMessages[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (lastAssistantMsg && lastAssistantMsg.content && lastAssistantMsg.content.alternative_responses) {
|
||||
const alts = lastAssistantMsg.content.alternative_responses
|
||||
const lang = this.data.conversationSceneLang || 'zh'
|
||||
const suggestions: Array<{ label: string; text: string }> = []
|
||||
|
||||
// Order: positive, negative, neutral or just iterate keys?
|
||||
// Prompt example shows: positive, negative, neutral.
|
||||
// Let's use a specific order if possible, or just keys.
|
||||
// Prompt example keys: positive, negative, neutral.
|
||||
const keys = ['positive', 'negative', 'neutral']
|
||||
keys.forEach(key => {
|
||||
if (alts[key]) {
|
||||
const item = alts[key]
|
||||
suggestions.push({
|
||||
label: lang === 'zh' ? item.alt_zh : item.alt_en,
|
||||
text: item.alt_en // Always copy en content
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if (suggestions.length > 0) {
|
||||
this.setData({
|
||||
chatSuggestions: suggestions,
|
||||
showChatSuggestions: true
|
||||
})
|
||||
}
|
||||
}
|
||||
}, 15000) as any
|
||||
},
|
||||
|
||||
stopSuggestionTimer() {
|
||||
if (this.suggestionTimer) {
|
||||
clearTimeout(this.suggestionTimer)
|
||||
this.suggestionTimer = undefined
|
||||
}
|
||||
this.setData({ showChatSuggestions: false })
|
||||
},
|
||||
|
||||
handleSuggestionTap(e: any) {
|
||||
const text = e.currentTarget.dataset.text
|
||||
if (text) {
|
||||
this.setData({
|
||||
chatInputValue: text,
|
||||
showChatSuggestions: false
|
||||
})
|
||||
// Should we focus the input? Usually yes.
|
||||
// But simply setting value works.
|
||||
}
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
wx.offKeyboardHeightChange(this.onKeyboardHeightChange)
|
||||
if (this.pollTimer) {
|
||||
clearInterval(this.pollTimer)
|
||||
this.pollTimer = undefined
|
||||
}
|
||||
if (this.placeholderTimer) {
|
||||
clearTimeout(this.placeholderTimer)
|
||||
this.placeholderTimer = undefined
|
||||
}
|
||||
if (this.suggestionTimer) {
|
||||
clearTimeout(this.suggestionTimer)
|
||||
this.suggestionTimer = undefined
|
||||
}
|
||||
if (this.audioCtx) {
|
||||
this.audioCtx.destroy()
|
||||
this.audioCtx = undefined
|
||||
|
||||
@@ -284,14 +284,55 @@
|
||||
<t-drawer
|
||||
visible="{{visible}}"
|
||||
placement="right"
|
||||
items="{{conversationList}}"
|
||||
bind:overlay-click="overlayClick"
|
||||
bind:item-click="chatItemClick"
|
||||
></t-drawer>
|
||||
>
|
||||
<scroll-view scroll-y class="history-drawer-scroll" bindscrolltolower="onHistoryScrollBottom">
|
||||
<view class="history-list">
|
||||
<t-cell
|
||||
wx:for="{{historyList}}"
|
||||
wx:key="session_id"
|
||||
title="{{item.created_at}}"
|
||||
hover
|
||||
class="history-item {{conversationLatestSession && conversationLatestSession.session_id === item.session_id ? 'active' : ''}}"
|
||||
bind:click="chatItemClick"
|
||||
data-item="{{item}}"
|
||||
>
|
||||
<view slot="description" class="history-tags">
|
||||
<block wx:for="{{item.scene}}" wx:for-item="scene" wx:key="index">
|
||||
<view class="history-tag" wx:if="{{scene.en || scene.zh}}">
|
||||
{{conversationSceneLang === 'zh' ? (scene.zh || scene.en) : (scene.en || scene.zh)}}
|
||||
</view>
|
||||
</block>
|
||||
<block wx:for="{{item.event}}" wx:for-item="ev" wx:key="index">
|
||||
<view class="history-tag" wx:if="{{ev.en || ev.zh}}">
|
||||
{{conversationSceneLang === 'zh' ? (ev.zh || ev.en) : (ev.en || ev.zh)}}
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</t-cell>
|
||||
<view class="history-loading" wx:if="{{historyLoading}}">
|
||||
<t-loading theme="spinner" size="40rpx" text="加载中..." />
|
||||
</view>
|
||||
<view class="history-no-more" wx:if="{{!historyHasMore && historyList.length > 0}}">没有更多内容</view>
|
||||
<view class="history-no-more" wx:if="{{!historyHasMore && historyList.length === 0}}">暂无历史记录</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</t-drawer>
|
||||
|
||||
<view class="chat-sender-wrapper {{isChatInputVisible ? 'show' : ''}}" wx:if="{{questionMode === 'conversation' && conversationViewMode === 'chat'}}">
|
||||
<view class="chat-sender-wrapper {{isChatInputVisible ? 'show' : ''}}" style="bottom: {{inputBottom}}px" wx:if="{{questionMode === 'conversation' && conversationViewMode === 'chat'}}">
|
||||
<view class="suggestion-bar {{showChatSuggestions ? 'show' : ''}}"
|
||||
wx:if="{{chatSuggestions.length > 0}}"
|
||||
bind:touchstart="handleSuggestionTouchStart"
|
||||
bind:touchend="handleSuggestionTouchEnd">
|
||||
<scroll-view scroll-x class="suggestion-scroll" enable-flex>
|
||||
<view class="suggestion-item" wx:for="{{chatSuggestions}}" wx:key="index" bindtap="handleSuggestionTap" data-text="{{item.text}}">
|
||||
{{item.label}}
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<t-chat-sender
|
||||
placeholder="请输入..."
|
||||
value="{{chatInputValue}}"
|
||||
placeholder="{{chatPlaceholder}}"
|
||||
loading="{{replyLoading}}"
|
||||
focus="{{isChatInputVisible}}"
|
||||
renderPresets="{{renderPresets}}"
|
||||
@@ -311,8 +352,11 @@
|
||||
</t-chat-sender>
|
||||
</view>
|
||||
<view class="bottom-bar {{contentVisible && !isChatInputVisible ? 'show' : ''}}" wx:if="{{questionMode === 'conversation'}}">
|
||||
<t-icon name="translate" class="bottom-btn" size="48rpx" bind:tap="toggleConversationSceneLang" />
|
||||
<t-icon name="keyboard" class="bottom-btn {{conversationLatestSession ? '' : 'disabled'}}" size="48rpx" bind:tap="showChatInput" />
|
||||
<view class="bottom-btn bottom-button-img-wrap" bind:tap="toggleConversationSceneLang">
|
||||
<t-icon name="translate" class="trans-button left-half {{conversationSceneLang === 'en' ? 'trans-active' : 'trans-deactive'}}" size="48rpx" />
|
||||
<t-icon name="translate" class="trans-button right-half {{conversationSceneLang === 'zh' ? 'trans-active' : 'trans-deactive'}}" size="48rpx" />
|
||||
</view>
|
||||
<t-icon name="keyboard" class="bottom-btn {{conversationLatestSession && conversationViewMode !== 'setup' ? '' : 'disabled'}}" size="48rpx" bind:tap="showChatInput" />
|
||||
<t-icon name="{{conversationLatestSession && conversationViewMode === 'chat' ? 'chat-bubble-add' : 'chat-bubble-1'}}" class="bottom-btn {{conversationLatestSession ? '' : 'disabled'}}" size="48rpx" bind:tap="toggleConversationView" />
|
||||
<t-icon name="fact-check" class="bottom-btn {{resultDisplayed ? '' : 'disabled'}}" size="48rpx" bind:tap="" />
|
||||
<t-icon name="chat-bubble-history" class="bottom-btn" size="48rpx" bind:tap="onHistoryTap" />
|
||||
|
||||
@@ -698,3 +698,142 @@
|
||||
opacity: 0.7;
|
||||
background-color: rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.history-drawer-scroll {
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.history-list {
|
||||
--td-cell-vertical-padding: 12rpx;
|
||||
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.history-item {
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.history-item:active {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.history-item.active {
|
||||
background: #eafaf2;
|
||||
border-color: #21cc80;
|
||||
}
|
||||
|
||||
.history-title {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.history-item.active .history-title {
|
||||
color: #0052d9; /* Highlight active session title in blue */
|
||||
}
|
||||
|
||||
.history-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.history-tag {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
background: #fff;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
border: 1rpx solid #e0e0e0;
|
||||
}
|
||||
|
||||
.history-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 24rpx 0;
|
||||
}
|
||||
|
||||
.history-no-more {
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
padding: 24rpx 0;
|
||||
}
|
||||
|
||||
.suggestion-bar {
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s ease;
|
||||
overflow: hidden;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
.suggestion-bar.show {
|
||||
height: 60rpx;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
padding: 12rpx 0;
|
||||
}
|
||||
.suggestion-scroll {
|
||||
display: flex;
|
||||
white-space: nowrap;
|
||||
padding: 0 24rpx;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.suggestion-item {
|
||||
display: block;
|
||||
background: #fff;
|
||||
border: 1rpx solid #e7e7e7;
|
||||
border-radius: 32rpx;
|
||||
padding: 0 24rpx;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
margin-right: 16rpx;
|
||||
max-width: 600rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
height: 56rpx;
|
||||
line-height: 54rpx;
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.bottom-button-img-wrap {
|
||||
width: 46rpx;
|
||||
height: 46rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.trans-button {
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.trans-button.left-half {
|
||||
left: 20rpx;
|
||||
clip-path: inset(0 50% 0 0);
|
||||
}
|
||||
|
||||
.trans-button.right-half {
|
||||
right: 20rpx;
|
||||
clip-path: inset(0 0 0 50%);
|
||||
}
|
||||
|
||||
.trans-button.trans-active {
|
||||
color: #0096fa;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.trans-button.trans-deactive {
|
||||
color: #666;
|
||||
}
|
||||
Reference in New Issue
Block a user