feat: enhance ChatView with improved layout and conversation interactions
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -1,5 +1,24 @@
|
|||||||
import { nextTick, ref } from 'vue'
|
import { nextTick, ref } from 'vue'
|
||||||
import { initialMessages, prompts } from '../data/requests.js'
|
|
||||||
|
const initialMessages = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
role: 'agent',
|
||||||
|
text: '我已读取当前报销、发票、行程和制度命中情况。当前建议:优先处理即将超时与高风险单据。'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
role: 'user',
|
||||||
|
text: '请列出今天最需要关注的风险。'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
role: 'agent',
|
||||||
|
text: '主要风险包括:3 笔单据将在 30 分钟内超时,市场部存在 2 笔高风险差旅报销,另有 1 笔报销缺少酒店入住清单。'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const prompts = ['生成审批意见', '列出补件清单', '解释风险原因', '生成运营简报']
|
||||||
|
|
||||||
export function useChat(activeView) {
|
export function useChat(activeView) {
|
||||||
const messages = ref([...initialMessages])
|
const messages = ref([...initialMessages])
|
||||||
@@ -10,21 +29,27 @@ export function useChat(activeView) {
|
|||||||
|
|
||||||
function agentReply(text) {
|
function agentReply(text) {
|
||||||
const c = activeCase.value
|
const c = activeCase.value
|
||||||
if (text.includes('出差申请') || text.includes('出差'))
|
if (text.includes('超时') || text.includes('SLA')) {
|
||||||
return '好的,我来帮您处理出差申请。请提供以下信息:\n1. 出发城市和目的地\n2. 出差日期和天数\n3. 出差事由\n4. 预计费用预算'
|
return '当前最紧急的是 3 笔即将超时单据,建议先按剩余处理时长排序,并把缺附件单据转给申请人补齐。'
|
||||||
if (text.includes('机票') || text.includes('飞机'))
|
}
|
||||||
return '我来帮您查询机票信息。请告诉我:\n1. 出发城市 → 目的城市\n2. 出发日期\n3. 偏好的时间段(上午/下午/晚间)\n4. 舱位要求(经济舱/商务舱)'
|
if (text.includes('高风险') || text.includes('风险')) {
|
||||||
if (text.includes('酒店') || text.includes('住宿'))
|
return '高风险主要集中在市场部差旅报销,风险点包括住宿超标、重复发票疑似命中、行程说明缺失。建议人工复核后再通过。'
|
||||||
return '好的,帮您查找合适的酒店。请提供:\n1. 入住城市和区域偏好\n2. 入住和退房日期\n3. 星级/价位要求\n4. 是否需要含早餐'
|
}
|
||||||
if (text.includes('火车票') || text.includes('高铁'))
|
if (text.includes('部门')) {
|
||||||
return '帮您查询火车票。请告诉我:\n1. 出发站 → 到达站\n2. 出行日期\n3. 座位偏好(二等座/一等座/商务座)\n4. 偏好的出发时间段'
|
return '从待处理金额看,销售部与研发中心占比最高;从异常占比看,市场部更需要优先关注。'
|
||||||
if (text.includes('审批意见'))
|
}
|
||||||
return c ? `${c.id} 建议审批意见:费用归属与预算中心匹配,建议保留业务说明后通过。` : '请先选择一份单据再生成审批意见。'
|
if (text.includes('简报')) {
|
||||||
if (text.includes('补件'))
|
return '运营简报:今日待审批 12 单,高风险 4 单,平均审批 5.6h,SLA 达成率 96%。建议优先处理差旅报销和即将超时单据。'
|
||||||
return '补件优先级:业务目的说明、行程或客户名单、直属经理确认记录。'
|
}
|
||||||
if (text.includes('差旅政策') || text.includes('政策'))
|
if (text.includes('补件') || text.includes('附件')) {
|
||||||
return '当前差旅政策要点:\n• 住宿标准:一线城市 600 元/晚,二线城市 400 元/晚\n• 机票:优先经济舱,3 小时以上可申请商务舱\n• 高铁:优先二等座,4 小时以上可申请一等座\n• 每日餐饮补贴:120 元'
|
return '建议补件清单:酒店入住水单、完整行程单、发票原件或验真结果、直属经理确认记录。'
|
||||||
return '好的,我已记录您的需求。请问还需要什么帮助?我还可以帮您查询差旅政策、预订机票酒店火车票等。'
|
}
|
||||||
|
if (text.includes('审批意见')) {
|
||||||
|
return c
|
||||||
|
? `${c.id} 建议审批意见:费用归属与预算中心基本匹配,请补充必要说明后通过。`
|
||||||
|
: '建议审批意见:当前单据存在待确认项,请先完成风险核查和附件补齐后再审批。'
|
||||||
|
}
|
||||||
|
return '收到。我可以继续帮你拆解异常原因、比较部门趋势、生成审批意见,或整理一份今日报销运营简报。'
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollToBottom() {
|
function scrollToBottom() {
|
||||||
@@ -53,7 +78,7 @@ export function useChat(activeView) {
|
|||||||
messages.value.push({
|
messages.value.push({
|
||||||
id: Date.now(),
|
id: Date.now(),
|
||||||
role: 'agent',
|
role: 'agent',
|
||||||
text: `已接收 ${uploadedFiles.value.length} 个附件:${names}。我会优先核对发票验真、费用标准、预算归属和必备审批材料。`
|
text: `已接收 ${uploadedFiles.value.length} 个附件:${names}。我会优先核对发票验真、费用标准、预算归属和必要审批材料。`
|
||||||
})
|
})
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,153 +1,162 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="view full">
|
<section class="assistant-view">
|
||||||
<div class="queue-panel">
|
<div class="assistant-grid">
|
||||||
<DataTable
|
<article class="conversation-panel panel">
|
||||||
:value="documents"
|
<header class="panel-head">
|
||||||
:paginator="true"
|
<h2>今日你可以这样问我</h2>
|
||||||
:rows="pageSize"
|
<button class="text-action" type="button" @click="rotatePrompts">
|
||||||
:rowsPerPageOptions="[5, 10, 20, 50]"
|
<i class="pi pi-refresh"></i>
|
||||||
:totalRecords="documents.length"
|
<span>换一换</span>
|
||||||
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown JumpToPageInput"
|
</button>
|
||||||
currentPageReportTemplate="共 {totalRecords} 条"
|
</header>
|
||||||
stripedRows
|
|
||||||
size="small"
|
<div class="prompt-grid">
|
||||||
:rowHover="false"
|
<button
|
||||||
tableStyle="min-width: 960px"
|
v-for="prompt in visiblePrompts"
|
||||||
|
:key="prompt.text"
|
||||||
|
class="prompt-card"
|
||||||
|
type="button"
|
||||||
|
@click="applyPrompt(prompt.text)"
|
||||||
>
|
>
|
||||||
<Column field="id" header="单据编号">
|
<i :class="prompt.icon"></i>
|
||||||
<template #body="{ data }">
|
<span>{{ prompt.text }}</span>
|
||||||
<strong>{{ data.id }}</strong>
|
|
||||||
</template>
|
|
||||||
</Column>
|
|
||||||
|
|
||||||
<Column field="type" header="申请类型">
|
|
||||||
<template #body="{ data }">
|
|
||||||
<span class="type-tag" :class="data.typeTag">{{ data.type }}</span>
|
|
||||||
</template>
|
|
||||||
</Column>
|
|
||||||
|
|
||||||
<Column field="applicant" header="申请人">
|
|
||||||
<template #body="{ data }">
|
|
||||||
<strong>{{ data.applicant }}</strong>
|
|
||||||
<p class="sub-text">{{ data.dept }}</p>
|
|
||||||
</template>
|
|
||||||
</Column>
|
|
||||||
|
|
||||||
<Column field="destination" header="目的地 / 行程" />
|
|
||||||
|
|
||||||
<Column field="date" header="申请日期" sortable />
|
|
||||||
|
|
||||||
<Column field="amount" header="金额" sortable>
|
|
||||||
<template #body="{ data }">
|
|
||||||
<strong>{{ data.amount }}</strong>
|
|
||||||
</template>
|
|
||||||
</Column>
|
|
||||||
|
|
||||||
<Column field="status" header="状态">
|
|
||||||
<template #body="{ data }">
|
|
||||||
<Tag :value="data.status" :severity="statusSeverity(data.statusClass)" />
|
|
||||||
</template>
|
|
||||||
</Column>
|
|
||||||
|
|
||||||
<Column field="conclusion" header="AI 结论">
|
|
||||||
<template #body="{ data }">
|
|
||||||
<span class="conclusion" :class="data.statusClass">{{ data.conclusion }}</span>
|
|
||||||
</template>
|
|
||||||
</Column>
|
|
||||||
|
|
||||||
<Column header="操作" style="width: 120px">
|
|
||||||
<template #body="{ data }">
|
|
||||||
<div class="row-actions">
|
|
||||||
<Button label="对话" icon="pi pi-comment" size="small" severity="secondary" rounded @click="openCaseDialog(data)" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Column>
|
|
||||||
</DataTable>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- AI Travel Assistant Dialog -->
|
|
||||||
<Dialog
|
|
||||||
v-model:visible="dialogOpen"
|
|
||||||
:modal="true"
|
|
||||||
:draggable="false"
|
|
||||||
:closable="true"
|
|
||||||
:header="dialogCase ? dialogCase.type + ' · ' + dialogCase.id : '智能差旅助手'"
|
|
||||||
:style="{ width: '720px' }"
|
|
||||||
:breakpoints="{ '760px': '95vw' }"
|
|
||||||
>
|
|
||||||
<template #header>
|
|
||||||
<div class="dialog-header-custom">
|
|
||||||
<div>
|
|
||||||
<span class="eyebrow">AI Travel Assistant</span>
|
|
||||||
<h3>{{ dialogCase ? dialogCase.type + ' · ' + dialogCase.id : '智能差旅助手' }}</h3>
|
|
||||||
<p v-if="dialogCase">{{ dialogCase.applicant }} · {{ dialogCase.destination }} · {{ dialogCase.days }}天</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Quick Service Cards (for new request) -->
|
|
||||||
<div v-if="!dialogCase" class="service-cards">
|
|
||||||
<button v-for="svc in services" :key="svc.label" class="service-card" @click="applyService(svc)">
|
|
||||||
<i :class="svc.piIcon" class="svc-icon"></i>
|
|
||||||
<strong>{{ svc.label }}</strong>
|
|
||||||
<small>{{ svc.desc }}</small>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Case Summary -->
|
<div ref="localMessageList" class="message-stream" aria-live="polite">
|
||||||
<div v-if="dialogCase" class="review-summary">
|
<div class="message-row user">
|
||||||
<div class="risk-ring">
|
<div class="message-bubble">
|
||||||
<strong>{{ dialogCase.status === '已通过' || dialogCase.status === '已完成' ? '96' : '78' }}</strong>
|
<p>今天我最应该关注哪些问题?</p>
|
||||||
<span>合规分</span>
|
<time>09:41</time>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<span class="chat-avatar user-avatar"><i class="pi pi-user"></i></span>
|
||||||
<h4>{{ dialogCase.conclusion }}</h4>
|
|
||||||
<p>{{ dialogCase.type }} · {{ dialogCase.destination }} · {{ dialogCase.amount }} · {{ dialogCase.days }}天行程</p>
|
|
||||||
<div class="summary-pills">
|
|
||||||
<span>{{ dialogCase.date }}</span>
|
|
||||||
<span>{{ dialogCase.status }}</span>
|
|
||||||
<span>{{ dialogCase.applicant }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="message-row assistant">
|
||||||
|
<span class="chat-avatar assistant-avatar"><i class="pi pi-sparkles"></i></span>
|
||||||
|
<div class="message-bubble">
|
||||||
|
<p>基于当前数据,你优先关注以下 3 项:</p>
|
||||||
|
<ol>
|
||||||
|
<li>3 笔单据将在 30 分钟内超时;</li>
|
||||||
|
<li>市场部有 2 笔高风险差旅报销;</li>
|
||||||
|
<li>1 笔报销缺少酒店入住清单,建议优先补充。</li>
|
||||||
|
</ol>
|
||||||
|
<time>09:41</time>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Messages -->
|
<div class="message-row assistant">
|
||||||
<div class="messages" ref="messageList" aria-live="polite">
|
<span class="chat-avatar assistant-avatar"><i class="pi pi-sparkles"></i></span>
|
||||||
<TransitionGroup name="msg-list">
|
<div class="message-bubble">
|
||||||
<div v-for="message in messages" :key="message.id" class="message" :class="message.role">
|
<p>为你生成行动建议:</p>
|
||||||
<span>{{ message.role === 'user' ? '你' : '差旅助手' }}</span>
|
<ul class="action-list">
|
||||||
|
<li><i class="pi pi-exclamation-circle danger"></i><strong>紧急处理:</strong>处理即将超时的 3 笔单据,避免 SLA 逾期。</li>
|
||||||
|
<li><i class="pi pi-info-circle warning"></i><strong>风险关注:</strong>审核市场部高风险差旅报销,重点关注差旅与超标费用。</li>
|
||||||
|
<li><i class="pi pi-arrow-circle-up info"></i><strong>信息补齐:</strong>提醒申请人补齐酒店入住清单,加快审批进度。</li>
|
||||||
|
<li><i class="pi pi-check-circle success"></i><strong>效率优化:</strong>当前审批瓶颈在财务审批,建议分配或优先处理。</li>
|
||||||
|
</ul>
|
||||||
|
<time>09:42</time>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-for="message in messages" :key="message.id" class="message-row" :class="message.role === 'user' ? 'user' : 'assistant'">
|
||||||
|
<span v-if="message.role !== 'user'" class="chat-avatar assistant-avatar"><i class="pi pi-sparkles"></i></span>
|
||||||
|
<div class="message-bubble">
|
||||||
<p>{{ message.text }}</p>
|
<p>{{ message.text }}</p>
|
||||||
</div>
|
</div>
|
||||||
</TransitionGroup>
|
<span v-if="message.role === 'user'" class="chat-avatar user-avatar"><i class="pi pi-user"></i></span>
|
||||||
<div v-if="!messages.length" class="messages-empty">
|
|
||||||
<p>告诉我您的差旅需求,我来帮您安排 ✈️</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Quick Prompts -->
|
<div class="composer">
|
||||||
<div class="quick-bar">
|
<textarea
|
||||||
<Button v-for="p in quickPrompts" :key="p" :label="p" size="small" severity="secondary" rounded outlined @click="applyPrompt(p)" />
|
:value="draft"
|
||||||
|
rows="3"
|
||||||
|
placeholder="输入问题,例如:今天的异常报销主要原因是什么?"
|
||||||
|
@input="emit('draft', $event.target.value)"
|
||||||
|
@keydown.ctrl.enter.prevent="emit('send')"
|
||||||
|
></textarea>
|
||||||
|
<div class="composer-actions">
|
||||||
|
<div class="input-tools">
|
||||||
|
<button type="button" aria-label="上传附件" @click="uploadInput?.click()">
|
||||||
|
<i class="pi pi-paperclip"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" aria-label="上传图片" @click="uploadInput?.click()">
|
||||||
|
<i class="pi pi-image"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" aria-label="语音输入">
|
||||||
|
<i class="pi pi-microphone"></i>
|
||||||
|
</button>
|
||||||
|
<input ref="uploadInput" class="sr-only" type="file" multiple @change="emit('upload', $event)" />
|
||||||
</div>
|
</div>
|
||||||
|
<span class="counter">{{ draft.length }}/2000</span>
|
||||||
|
<button class="send-btn" type="button" aria-label="发送消息" @click="emit('send')">
|
||||||
|
<i class="pi pi-send"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
<!-- Input -->
|
<aside class="insight-column">
|
||||||
<div class="dialog-input">
|
<article class="insight-card panel">
|
||||||
<Textarea v-model="localDraft" rows="2" autoResize placeholder="描述您的差旅需求…" @keydown.ctrl.enter.prevent="emit('send')" />
|
<header>
|
||||||
<div class="input-actions">
|
<h3>A. AI 重点关注</h3>
|
||||||
<Button label="发送" icon="pi pi-send" size="small" @click="emit('send')" />
|
<button type="button">查看全部</button>
|
||||||
|
</header>
|
||||||
|
<div class="focus-list">
|
||||||
|
<div v-for="item in focusItems" :key="item.label" class="focus-row">
|
||||||
|
<span :class="['dot-icon', item.tone]"><i :class="item.icon"></i></span>
|
||||||
|
<strong>{{ item.label }}</strong>
|
||||||
|
<span :class="item.tone">{{ item.value }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</article>
|
||||||
|
|
||||||
|
<article class="insight-card panel">
|
||||||
|
<header>
|
||||||
|
<h3>B. 场景建议</h3>
|
||||||
|
<button type="button">查看全部 <i class="pi pi-angle-right"></i></button>
|
||||||
|
</header>
|
||||||
|
<div class="suggestion-list">
|
||||||
|
<button v-for="item in suggestions" :key="item.text" type="button" @click="applyPrompt(item.text)">
|
||||||
|
<i :class="item.icon"></i>
|
||||||
|
<span>{{ item.text }}</span>
|
||||||
|
<i class="pi pi-angle-right"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="insight-card panel">
|
||||||
|
<header>
|
||||||
|
<h3>C. 快捷分析</h3>
|
||||||
|
</header>
|
||||||
|
<div class="analysis-grid">
|
||||||
|
<button v-for="item in analysisActions" :key="item.text" type="button" @click="applyPrompt(item.text)">
|
||||||
|
<i :class="item.icon"></i>
|
||||||
|
<span>{{ item.text }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="insight-card panel">
|
||||||
|
<header>
|
||||||
|
<h3>D. 最近提问 / 常用问题</h3>
|
||||||
|
<button type="button" @click="rotatePrompts">
|
||||||
|
<i class="pi pi-refresh"></i>
|
||||||
|
<span>换一换</span>
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
<ul class="recent-list">
|
||||||
|
<li v-for="item in recentQuestions" :key="item">{{ item }}</li>
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed } from 'vue'
|
import { computed, nextTick, ref, watch } from 'vue'
|
||||||
import DataTable from 'primevue/datatable'
|
|
||||||
import Column from 'primevue/column'
|
|
||||||
import Button from 'primevue/button'
|
|
||||||
import Tag from 'primevue/tag'
|
|
||||||
import Dialog from 'primevue/dialog'
|
|
||||||
import Textarea from 'primevue/textarea'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
documents: { type: Array, required: true },
|
documents: { type: Array, required: true },
|
||||||
@@ -160,156 +169,577 @@ const props = defineProps({
|
|||||||
messageList: { type: Object, default: null }
|
messageList: { type: Object, default: null }
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['send', 'upload', 'draft', 'approveCase', 'rejectCase', 'update:draft', 'selectCase'])
|
const emit = defineEmits(['send', 'upload', 'draft', 'approveCase', 'rejectCase', 'selectCase'])
|
||||||
|
|
||||||
const dialogOpen = ref(false)
|
const localMessageList = ref(null)
|
||||||
const dialogCase = ref(null)
|
const uploadInput = ref(null)
|
||||||
const pageSize = ref(10)
|
const promptPage = ref(0)
|
||||||
|
|
||||||
const services = [
|
const prompts = [
|
||||||
{ label: '出差申请', desc: '提交出差申请单', piIcon: 'pi pi-file', prompt: '我需要提交一份出差申请' },
|
{ icon: 'pi pi-chart-line', text: '今天有哪些关键指标异常?' },
|
||||||
{ label: '预订机票', desc: '查询并预订机票', piIcon: 'pi pi-send', prompt: '帮我预订机票' },
|
{ icon: 'pi pi-file-check', text: '哪些单据最需要优先处理?' },
|
||||||
{ label: '预订酒店', desc: '查询并预订酒店', piIcon: 'pi pi-building', prompt: '帮我预订酒店' },
|
{ icon: 'pi pi-shield', text: '高风险报销主要集中在哪些部门?' },
|
||||||
{ label: '预订火车票', desc: '查询并预订火车票', piIcon: 'pi pi-map-marker', prompt: '帮我预订火车票' }
|
{ icon: 'pi pi-clock', text: '本周审批效率相比昨天如何?' },
|
||||||
|
{ icon: 'pi pi-lightbulb', text: '给我当前报销场景的处理建议' },
|
||||||
|
{ icon: 'pi pi-building', text: '生成一份运营简报' },
|
||||||
|
{ icon: 'pi pi-filter', text: '找出即将超时的单据' },
|
||||||
|
{ icon: 'pi pi-wallet', text: '分析本月预算执行压力' }
|
||||||
]
|
]
|
||||||
|
|
||||||
const localDraft = computed({
|
const visiblePrompts = computed(() => {
|
||||||
get: () => props.draft,
|
const start = (promptPage.value % 2) * 6
|
||||||
set: (val) => emit('update:draft', val)
|
return prompts.slice(start, start + 6).length === 6 ? prompts.slice(start, start + 6) : prompts.slice(0, 6)
|
||||||
})
|
})
|
||||||
|
|
||||||
function statusSeverity(cls) {
|
const focusItems = [
|
||||||
if (cls === 'success') return 'success'
|
{ icon: 'pi pi-star-fill', tone: 'danger', label: '3 单即将超时', value: '30 分钟内超时' },
|
||||||
if (cls === 'danger') return 'danger'
|
{ icon: 'pi pi-exclamation-triangle', tone: 'warning', label: '市场部高风险占比最高', value: '高风险 2 单' },
|
||||||
return 'warn'
|
{ icon: 'pi pi-arrow-right-arrow-left', tone: 'info', label: '重复报销风险 1 笔', value: '待核查' },
|
||||||
|
{ icon: 'pi pi-check-circle', tone: 'success', label: '1 笔缺失附件', value: '待补充' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const suggestions = [
|
||||||
|
{ icon: 'pi pi-send', text: '优先处理差旅报销(占待审 62%)' },
|
||||||
|
{ icon: 'pi pi-file-plus', text: '先补齐缺失附件再提交审批' },
|
||||||
|
{ icon: 'pi pi-car', text: '对超标出租车费用要求补充说明' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const analysisActions = [
|
||||||
|
{ icon: 'pi pi-wave-pulse', text: '异常原因分析' },
|
||||||
|
{ icon: 'pi pi-building', text: '部门对比' },
|
||||||
|
{ icon: 'pi pi-shield', text: '风险趋势' },
|
||||||
|
{ icon: 'pi pi-filter', text: '审批瓶颈' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const recentQuestions = [
|
||||||
|
'最近 7 天哪些部门的审批时长最长?',
|
||||||
|
'本月报销金额环比增长最快的是哪个部门?',
|
||||||
|
'有哪些报销类型的超标率最高?',
|
||||||
|
'SLA 未达成的主要原因是什么?'
|
||||||
|
]
|
||||||
|
|
||||||
|
function rotatePrompts() {
|
||||||
|
promptPage.value += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
function openCaseDialog(doc) {
|
function applyPrompt(text) {
|
||||||
dialogCase.value = doc
|
emit('draft', text)
|
||||||
emit('selectCase', doc)
|
|
||||||
dialogOpen.value = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyService(svc) {
|
watch(
|
||||||
emit('draft', svc.prompt)
|
() => props.messages.length,
|
||||||
}
|
() => {
|
||||||
|
nextTick(() => {
|
||||||
function applyPrompt(p) {
|
localMessageList.value?.scrollTo({ top: localMessageList.value.scrollHeight, behavior: 'smooth' })
|
||||||
emit('draft', p)
|
})
|
||||||
}
|
}
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* ── List View ──────────────────────────────────── */
|
.assistant-view {
|
||||||
.view { display: grid; gap: 22px; animation: fadeUp 220ms var(--ease) both; }
|
height: 100%;
|
||||||
.view.full { width: 100%; }
|
min-height: 0;
|
||||||
.queue-panel {
|
display: grid;
|
||||||
border: 1px solid var(--line); border-radius: var(--radius);
|
animation: fadeUp 240ms var(--ease) both;
|
||||||
background: var(--surface); overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub-text { margin-top: 3px; color: var(--muted); font-size: 11px; }
|
.assistant-grid {
|
||||||
|
min-height: 0;
|
||||||
/* Type tags */
|
display: grid;
|
||||||
.type-tag {
|
grid-template-columns: minmax(0, 1.5fr) minmax(360px, .95fr);
|
||||||
display: inline-flex; align-items: center; gap: 4px;
|
gap: 16px;
|
||||||
padding: 3px 10px; border-radius: 999px;
|
align-items: stretch;
|
||||||
font-size: 12px; font-weight: 750; white-space: nowrap;
|
|
||||||
}
|
|
||||||
.type-tag.travel { background: var(--primary-soft); color: var(--primary); }
|
|
||||||
.type-tag.flight { background: #eef3ff; color: #335cff; }
|
|
||||||
.type-tag.hotel { background: var(--warning-soft); color: var(--warning); }
|
|
||||||
.type-tag.train { background: var(--success-soft); color: var(--success); }
|
|
||||||
|
|
||||||
.conclusion { color: var(--muted); font-size: 12px; }
|
|
||||||
.conclusion.success { color: var(--success); }
|
|
||||||
.conclusion.warning { color: var(--warning); }
|
|
||||||
.conclusion.danger { color: var(--danger); }
|
|
||||||
|
|
||||||
.row-actions { display: flex; gap: 6px; }
|
|
||||||
|
|
||||||
/* ── Dialog Custom Header ───────────────────────── */
|
|
||||||
.dialog-header-custom h3 { margin: 4px 0 0; color: var(--ink); font-size: 20px; }
|
|
||||||
.dialog-header-custom p { margin-top: 4px; color: var(--muted); font-size: 12px; }
|
|
||||||
|
|
||||||
/* ── Service Cards ──────────────────────────────── */
|
|
||||||
.service-cards {
|
|
||||||
display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
.service-card {
|
|
||||||
display: grid; gap: 6px; place-items: center; text-align: center;
|
|
||||||
padding: 16px 8px; border: 1px solid var(--line); border-radius: var(--radius);
|
|
||||||
background: #fff; cursor: pointer;
|
|
||||||
transition: border-color 160ms ease, box-shadow 160ms ease;
|
|
||||||
}
|
|
||||||
.service-card:hover { border-color: rgba(51,92,255,.28); box-shadow: 0 8px 24px rgba(51,92,255,.08); }
|
|
||||||
.svc-icon { font-size: 22px; color: var(--primary); }
|
|
||||||
.service-card strong { color: var(--ink); font-size: 13px; }
|
|
||||||
.service-card small { color: var(--muted); font-size: 11px; }
|
|
||||||
|
|
||||||
/* ── Summary ────────────────────────────────────── */
|
|
||||||
.review-summary {
|
|
||||||
display: grid; grid-template-columns: auto 1fr; align-items: center; gap: 16px;
|
|
||||||
padding: 14px 0; margin-bottom: 12px;
|
|
||||||
border-bottom: 1px solid var(--line);
|
|
||||||
}
|
|
||||||
.risk-ring {
|
|
||||||
width: 68px; aspect-ratio: 1; display: grid; place-items: center;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: radial-gradient(circle,#fff 0 55%,transparent 56%), conic-gradient(var(--success) 0 82%, #e4e7ec 82% 100%);
|
|
||||||
}
|
|
||||||
.risk-ring strong { color: var(--ink); font-size: 20px; line-height: 1; }
|
|
||||||
.risk-ring span { color: var(--muted); font-size: 10px; }
|
|
||||||
.review-summary h4 { color: var(--ink); font-size: 14px; }
|
|
||||||
.review-summary p { margin-top: 3px; color: var(--muted); font-size: 13px; }
|
|
||||||
.summary-pills { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
|
|
||||||
.summary-pills span {
|
|
||||||
min-height: 24px; display: inline-flex; align-items: center;
|
|
||||||
padding: 0 8px; border-radius: 999px;
|
|
||||||
background: var(--primary-soft); color: var(--primary); font-size: 11px; font-weight: 750;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Messages ───────────────────────────────────── */
|
.conversation-panel {
|
||||||
.messages {
|
min-height: 0;
|
||||||
min-height: 160px; max-height: 360px; overflow-y: auto;
|
display: grid;
|
||||||
display: grid; align-content: start; gap: 12px;
|
grid-template-rows: auto auto minmax(0, 1fr) auto;
|
||||||
padding: 14px 0; background: linear-gradient(180deg, #fbfcff, #f6f8fb);
|
padding: 20px;
|
||||||
border-radius: var(--radius); margin-bottom: 12px;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.message { max-width: 82%; display: grid; gap: 4px; }
|
|
||||||
.message.user { justify-self: end; }
|
.panel-head,
|
||||||
.message span { color: var(--muted); font-size: 10px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; }
|
.insight-card header {
|
||||||
.message p {
|
display: flex;
|
||||||
padding: 12px 14px; border: 1px solid var(--line);
|
align-items: center;
|
||||||
border-radius: 14px 14px 14px 4px; background: #fff;
|
justify-content: space-between;
|
||||||
font-size: 13px; line-height: 1.6; white-space: pre-line;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
.message.user p {
|
|
||||||
border-color: transparent; border-radius: 14px 14px 4px 14px;
|
.panel-head h2,
|
||||||
background: linear-gradient(135deg, var(--primary), #2446d8);
|
.insight-card h3 {
|
||||||
|
color: #0f172a;
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-action,
|
||||||
|
.insight-card header button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-action:hover,
|
||||||
|
.insight-card header button:hover {
|
||||||
|
color: #10b981;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prompt-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prompt-card {
|
||||||
|
min-height: 52px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 24px minmax(0, 1fr);
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 8px 14px;
|
||||||
|
border: 1px solid #dce5ef;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fff;
|
||||||
|
color: #334155;
|
||||||
|
text-align: left;
|
||||||
|
transition: border-color 180ms ease, box-shadow 180ms ease, color 180ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prompt-card:hover {
|
||||||
|
border-color: rgba(16,185,129,.36);
|
||||||
|
box-shadow: 0 8px 20px rgba(15,23,42,.06);
|
||||||
|
color: #0f9f78;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prompt-card i {
|
||||||
|
color: #10b981;
|
||||||
|
font-size: 19px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prompt-card:nth-child(4n) i,
|
||||||
|
.prompt-card:nth-child(5n) i {
|
||||||
|
color: #f59e0b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prompt-card:nth-child(3n) i {
|
||||||
|
color: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prompt-card span {
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 650;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-stream {
|
||||||
|
min-height: 0;
|
||||||
|
display: grid;
|
||||||
|
align-content: start;
|
||||||
|
gap: 14px;
|
||||||
|
margin-top: 18px;
|
||||||
|
padding: 2px 8px 12px 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
scrollbar-gutter: stable;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #b6c4d5 #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-stream::-webkit-scrollbar,
|
||||||
|
.insight-column::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-stream::-webkit-scrollbar-track,
|
||||||
|
.insight-column::-webkit-scrollbar-track {
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-stream::-webkit-scrollbar-thumb,
|
||||||
|
.insight-column::-webkit-scrollbar-thumb {
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #b6c4d5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-row {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-row.assistant {
|
||||||
|
grid-template-columns: 38px minmax(0, .74fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-row.user {
|
||||||
|
grid-template-columns: minmax(0, .4fr) 38px;
|
||||||
|
justify-content: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-avatar {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assistant-avatar {
|
||||||
|
background: #dff7ee;
|
||||||
|
color: #0f9f78;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar {
|
||||||
|
background: #dff7ee;
|
||||||
|
color: #0f9f78;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-bubble {
|
||||||
|
position: relative;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border: 1px solid #dce5ef;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #fff;
|
||||||
|
color: #334155;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-row.user .message-bubble {
|
||||||
|
background: linear-gradient(135deg, rgba(16,185,129,.14), rgba(16,185,129,.07));
|
||||||
|
border-color: rgba(16,185,129,.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-bubble p,
|
||||||
|
.message-bubble ol,
|
||||||
|
.message-bubble ul {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-bubble ol {
|
||||||
|
padding-left: 18px;
|
||||||
|
color: #0f9f78;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 4px;
|
||||||
|
padding-left: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-list li {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-list i {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger { color: #ef4444; }
|
||||||
|
.warning { color: #f59e0b; }
|
||||||
|
.info { color: #3b82f6; }
|
||||||
|
.success { color: #10b981; }
|
||||||
|
|
||||||
|
.message-bubble time {
|
||||||
|
float: right;
|
||||||
|
margin-left: 14px;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.composer {
|
||||||
|
min-height: 94px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: minmax(48px, 1fr) auto;
|
||||||
|
border: 1px solid #d7e0ea;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #fff;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.composer textarea {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 52px;
|
||||||
|
resize: none;
|
||||||
|
border: 0;
|
||||||
|
padding: 14px 16px 6px;
|
||||||
|
color: #0f172a;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.composer textarea::placeholder {
|
||||||
|
color: #91a2b5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.composer textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.composer-actions {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 0 10px 10px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-tools {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-tools button,
|
||||||
|
.send-btn {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-tools button {
|
||||||
|
background: transparent;
|
||||||
|
color: #42526b;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-tools button:hover {
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: #0f9f78;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counter {
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-btn {
|
||||||
|
background: #10b981;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
font-size: 17px;
|
||||||
}
|
}
|
||||||
.messages-empty { display: grid; place-items: center; padding: 32px; }
|
|
||||||
.messages-empty p { color: var(--muted); font-size: 13px; }
|
|
||||||
.msg-list-enter-active { transition: opacity 200ms var(--ease), transform 200ms var(--ease); }
|
|
||||||
.msg-list-leave-active { transition: opacity 120ms ease; }
|
|
||||||
.msg-list-enter-from { opacity: 0; transform: translateY(6px); }
|
|
||||||
.msg-list-leave-to { opacity: 0; }
|
|
||||||
|
|
||||||
/* ── Quick Prompts ──────────────────────────────── */
|
.send-btn:hover {
|
||||||
.quick-bar { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
|
background: #0ea672;
|
||||||
|
}
|
||||||
/* ── Input ──────────────────────────────────────── */
|
|
||||||
.dialog-input {
|
.insight-column {
|
||||||
display: grid; grid-template-columns: minmax(0, 1fr) auto;
|
min-height: 0;
|
||||||
gap: 10px; align-items: start;
|
max-height: 100%;
|
||||||
|
display: grid;
|
||||||
|
align-content: start;
|
||||||
|
gap: 12px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
padding-right: 4px;
|
||||||
|
scrollbar-gutter: stable;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #b6c4d5 #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insight-card {
|
||||||
|
padding: 18px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.focus-list {
|
||||||
|
display: grid;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.focus-row {
|
||||||
|
min-height: 34px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 24px minmax(0, 1fr) auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
border-bottom: 1px solid #eef2f7;
|
||||||
|
color: #334155;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.focus-row:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
border-radius: 999px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot-icon.danger { background: #ef4444; color: #fff; }
|
||||||
|
.dot-icon.warning { background: #f59e0b; color: #fff; }
|
||||||
|
.dot-icon.info { background: #3b82f6; color: #fff; }
|
||||||
|
.dot-icon.success { background: #10b981; color: #fff; }
|
||||||
|
|
||||||
|
.focus-row strong {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.focus-row > span:last-child {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-list button {
|
||||||
|
min-height: 42px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 22px minmax(0, 1fr) 16px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 0 10px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fff;
|
||||||
|
color: #334155;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-list button:hover,
|
||||||
|
.analysis-grid button:hover {
|
||||||
|
border-color: rgba(16,185,129,.32);
|
||||||
|
color: #0f9f78;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-list i:first-child {
|
||||||
|
color: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-list button:nth-child(2) i:first-child {
|
||||||
|
color: #10b981;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-list button:nth-child(3) i:first-child {
|
||||||
|
color: #f59e0b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysis-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysis-grid button {
|
||||||
|
min-height: 44px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 0 10px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fff;
|
||||||
|
color: #334155;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 650;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysis-grid i {
|
||||||
|
color: #10b981;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recent-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
margin: 14px 0 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recent-list li {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 16px;
|
||||||
|
color: #334155;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recent-list li::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: .62em;
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #10b981;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sr-only {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1280px) {
|
||||||
|
.assistant-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insight-column {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
max-height: none;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.input-actions { display: grid; gap: 6px; padding-top: 2px; }
|
|
||||||
|
|
||||||
@media (max-width: 760px) {
|
@media (max-width: 760px) {
|
||||||
.dialog-input { grid-template-columns: 1fr; }
|
.prompt-grid,
|
||||||
.input-actions { display: flex; }
|
.insight-column,
|
||||||
.service-cards { grid-template-columns: repeat(2, 1fr); }
|
.analysis-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.conversation-panel,
|
||||||
|
.insight-card {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-row.assistant,
|
||||||
|
.message-row.user {
|
||||||
|
grid-template-columns: 34px minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-row.user {
|
||||||
|
grid-template-columns: minmax(0, 1fr) 34px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user