Files
X-Financial/web/src/components/business/PersonalWorkbench.vue
caoxiaozhu eec4efe207 feat(web): update components
- PersonalWorkbench.vue: update personal workbench component
- SidebarRail.vue: update sidebar rail component
- TopBar.vue: update top bar component
- ConfirmDialog.vue: update confirm dialog component
2026-05-13 13:12:28 +00:00

1134 lines
24 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<section class="workbench">
<PanelHead
v-if="showHeader"
eyebrow="Personal Workspace"
title="个人工作台"
note="把今天要处理的待办、报销进度和制度更新集中到一个入口。"
/>
<article class="panel assistant-hero">
<div class="assistant-visual" aria-hidden="true">
<span class="assistant-glow"></span>
<img class="assistant-image" :src="robotAssistant" alt="" />
</div>
<div class="assistant-copy">
<span class="assistant-tag">AI 报销助手</span>
<h3>描述费用或上传票据AI 直接帮你判断怎么报</h3>
<p>自动识别报销类别核对附件完整性并生成可继续提交的报销草稿</p>
<div class="assistant-input">
<input
ref="fileInputRef"
class="assistant-file-input"
type="file"
multiple
accept=".pdf,.jpg,.jpeg,.png,.webp,.doc,.docx,.xls,.xlsx"
@change="handleWorkbenchFilesChange"
/>
<textarea
v-model="assistantDraft"
rows="1"
placeholder="例如:我昨天请客户吃饭花了 860 元,还打车去了客户公司"
@keydown.enter.prevent="handleWorkbenchEnter"
/>
</div>
<div v-if="selectedFiles.length" class="assistant-file-strip">
<span class="assistant-file-note">已带入 {{ selectedFiles.length }} 份附件</span>
<span v-for="file in selectedFiles" :key="file.name" class="assistant-file-chip">{{ file.name }}</span>
<button type="button" class="assistant-file-clear" @click="clearSelectedFiles">清空</button>
</div>
<div class="assistant-tools">
<button type="button" class="ghost-action" :disabled="Boolean(pendingAction)" @click="triggerFileUpload">
<i class="mdi mdi-upload-outline"></i>
<span>上传票据</span>
</button>
<button
type="button"
class="hero-action"
:disabled="Boolean(pendingAction)"
@click="handleExpenseConversationAction"
>
<i :class="pendingAction === 'expense' ? 'mdi mdi-loading mdi-spin' : expenseActionIcon"></i>
<span>{{ pendingAction === 'expense' ? '处理中...' : expenseActionLabel }}</span>
</button>
</div>
</div>
</article>
<div class="workbench-grid">
<article class="panel list-panel">
<div class="section-head">
<div class="title-with-badge">
<h3>今日待办</h3>
<span class="alert-badge">{{ todoAlertCount }}</span>
</div>
<button type="button" class="link-action">查看全部 <i class="mdi mdi-chevron-right"></i></button>
</div>
<div class="list-body">
<div v-for="item in todoItems" :key="item.title" class="todo-row">
<div class="todo-icon" :style="{ '--icon-color': item.color }">
<i :class="item.icon"></i>
</div>
<div class="todo-copy">
<strong>{{ item.title }}</strong>
<p class="todo-advice">
<span class="todo-advice-label">{{ item.tipLabel }}</span>
<span class="todo-advice-text">{{ item.suggestion }}</span>
</p>
</div>
<button type="button" class="row-action" @click="emit('openAssistant')">{{ item.action }}</button>
</div>
</div>
</article>
<article class="panel list-panel">
<div class="section-head">
<div class="title-with-badge">
<h3>报销进度</h3>
<span class="alert-badge">{{ progressAlertCount }}</span>
</div>
<button type="button" class="link-action">查看全部 <i class="mdi mdi-chevron-right"></i></button>
</div>
<div class="list-body">
<div v-for="item in progressItems" :key="item.id" class="progress-row">
<div class="todo-icon" :style="{ '--icon-color': item.color }">
<i :class="item.icon"></i>
</div>
<div class="todo-copy progress-copy">
<strong>{{ item.title }}</strong>
<p>提交时间{{ item.date }}</p>
</div>
<strong class="progress-amount">{{ item.amount }}</strong>
<span class="progress-status" :class="item.tone">{{ item.status }}</span>
</div>
</div>
</article>
</div>
<article class="panel policy-panel">
<div class="section-head">
<h3>最新报销制度</h3>
<button type="button" class="link-action">查看全部 <i class="mdi mdi-chevron-right"></i></button>
</div>
<div class="policy-table">
<div class="policy-head policy-row">
<span class="policy-title-cell">制度名称</span>
<span class="policy-summary-cell">摘要</span>
<span class="policy-date-cell">发布日期</span>
</div>
<div v-for="item in policyItems" :key="item.name" class="policy-row">
<strong class="policy-title-cell">{{ item.name }}</strong>
<span class="policy-summary-cell">{{ item.summary }}</span>
<span class="policy-date-cell">{{ item.date }}</span>
</div>
</div>
</article>
</section>
</template>
<script setup>
import { computed, onMounted, ref, watch } from 'vue'
import PanelHead from '../shared/PanelHead.vue'
import robotAssistant from '../../assets/robot-helper.png'
import { useSystemState } from '../../composables/useSystemState.js'
import { useToast } from '../../composables/useToast.js'
import { clearUserConversations, fetchLatestConversation } from '../../services/orchestrator.js'
const props = defineProps({
showHeader: { type: Boolean, default: true },
assistantModalOpen: { type: Boolean, default: false }
})
const emit = defineEmits(['openAssistant'])
const { currentUser } = useSystemState()
const { toast } = useToast()
const assistantDraft = ref('')
const fileInputRef = ref(null)
const selectedFiles = ref([])
const pendingAction = ref('')
const latestExpenseConversation = ref(null)
const MAX_ATTACHMENTS = 10
const SESSION_TYPE_EXPENSE = 'expense'
const SESSION_TYPE_KNOWLEDGE = 'knowledge'
const hasExpenseConversation = computed(() => Boolean(latestExpenseConversation.value?.conversation_id || latestExpenseConversation.value?.conversationId))
const expenseActionLabel = computed(() => (hasExpenseConversation.value ? '继续报销' : '新建报销'))
const expenseActionIcon = computed(() => (hasExpenseConversation.value ? 'mdi mdi-history' : 'mdi mdi-magnify-scan'))
function buildSelectedFileKey(file) {
return [file?.name, file?.size, file?.lastModified, file?.type].join('__')
}
function mergeSelectedFiles(existingFiles, incomingFiles) {
const nextFiles = []
const seen = new Set()
for (const file of existingFiles) {
const key = buildSelectedFileKey(file)
if (seen.has(key)) continue
seen.add(key)
nextFiles.push(file)
}
let overflowCount = 0
for (const file of incomingFiles) {
const key = buildSelectedFileKey(file)
if (seen.has(key)) continue
if (nextFiles.length >= MAX_ATTACHMENTS) {
overflowCount += 1
continue
}
seen.add(key)
nextFiles.push(file)
}
return {
files: nextFiles,
overflowCount
}
}
function resolveCurrentUserId() {
const user = currentUser.value || {}
return String(user.username || user.name || 'anonymous').trim() || 'anonymous'
}
function buildAssistantPayload() {
return {
prompt: assistantDraft.value.trim(),
source: 'workbench',
files: Array.from(selectedFiles.value)
}
}
function clearSelectedFiles() {
selectedFiles.value = []
if (fileInputRef.value) {
fileInputRef.value.value = ''
}
}
function resetWorkbenchDraft() {
assistantDraft.value = ''
clearSelectedFiles()
}
function emitAssistant(payload) {
emit('openAssistant', payload)
resetWorkbenchDraft()
}
async function loadLatestConversation() {
const payload = await fetchLatestConversation(resolveCurrentUserId(), SESSION_TYPE_EXPENSE)
return payload?.found ? payload.conversation || null : null
}
function handleWorkbenchEnter(event) {
if (event.isComposing) {
return
}
handleExpenseConversationAction()
}
function triggerFileUpload() {
fileInputRef.value?.click()
}
function handleWorkbenchFilesChange(event) {
const mergeResult = mergeSelectedFiles(selectedFiles.value, Array.from(event.target.files ?? []))
selectedFiles.value = mergeResult.files
if (mergeResult.overflowCount > 0) {
toast(`一次最多上传 ${MAX_ATTACHMENTS} 份附件,已保留前 ${MAX_ATTACHMENTS} 份。`)
}
if (fileInputRef.value) {
fileInputRef.value.value = ''
}
}
async function refreshLatestExpenseConversation() {
try {
latestExpenseConversation.value = await loadLatestConversation()
} catch (error) {
console.warn('Failed to refresh latest expense conversation:', error)
latestExpenseConversation.value = null
}
}
async function clearKnowledgeHistoryBeforeExpense() {
await clearUserConversations(resolveCurrentUserId(), SESSION_TYPE_KNOWLEDGE)
}
async function handleExpenseConversationAction() {
if (pendingAction.value) {
return
}
pendingAction.value = 'expense'
const nextPayload = buildAssistantPayload()
try {
await clearKnowledgeHistoryBeforeExpense()
const conversation = await loadLatestConversation()
latestExpenseConversation.value = conversation
emitAssistant({
...nextPayload,
conversation
})
} catch (error) {
console.warn('Failed to open expense conversation:', error)
toast(error?.message || '打开报销会话失败,请稍后重试。')
} finally {
pendingAction.value = ''
}
}
const todoItems = [
{
title: '业务招待报销建议补参与人员',
tipLabel: 'AI 建议',
suggestion: '补充客户单位、客户人数、我方陪同人员',
action: '去补充',
icon: 'mdi mdi-account-group-outline',
color: '#10b981'
},
{
title: '差旅报销单待提交',
tipLabel: 'AI 建议',
suggestion: '补齐出发交通,可直接生成报销单',
action: '继续填写',
icon: 'mdi mdi-briefcase-outline',
color: '#16a34a'
},
{
title: '有 5 张票据未关联报销单',
tipLabel: 'AI 建议',
suggestion: '其中 3 张疑似交通费,可合并生成交通报销',
action: '去整理',
icon: 'mdi mdi-receipt-text-outline',
color: '#3b82f6'
}
]
const todoAlertCount = todoItems.length
const progressItems = [
{
id: 'travel',
title: '差旅报销',
amount: '¥3,280',
date: '2026-05-03',
status: '主管审批中',
tone: 'success',
icon: 'mdi mdi-airplane',
color: '#10b981'
},
{
id: 'transport',
title: '交通报销',
amount: '¥126',
date: '2026-05-02',
status: '财务复核中',
tone: 'info',
icon: 'mdi mdi-car-outline',
color: '#3b82f6'
},
{
id: 'office',
title: '办公采购',
amount: '¥458',
date: '2026-05-01',
status: '已到账',
tone: 'mint',
icon: 'mdi mdi-cart-outline',
color: '#16a34a'
}
]
const progressAlertCount = progressItems.filter((item) => item.status !== '已到账').length
const policyItems = [
{
name: '差旅报销管理办法2026版',
summary: '更新住宿标准与交通等级规则',
date: '2026-05-04'
},
{
name: '业务招待费用报销规范',
summary: '明确参与人员与事由填写要求',
date: '2026-05-02'
},
{
name: '交通费用报销细则',
summary: '补充网约车与停车费报销说明',
date: '2026-04-28'
},
{
name: '票据与附件提交规范通知',
summary: '统一附件命名与上传要求',
date: '2026-04-25'
}
]
onMounted(() => {
refreshLatestExpenseConversation()
})
watch(
() => props.assistantModalOpen,
(open, previous) => {
if (previous && !open) {
refreshLatestExpenseConversation()
}
}
)
</script>
<style scoped>
.workbench {
min-width: 0;
display: grid;
gap: 16px;
padding-bottom: 10px;
}
.assistant-hero {
position: relative;
overflow: hidden;
display: grid;
grid-template-columns: 228px minmax(0, 1fr);
gap: 18px;
padding: 20px 24px 20px 18px;
border: 1px solid rgba(16, 185, 129, 0.12);
background:
radial-gradient(circle at top left, rgba(16, 185, 129, 0.12), transparent 34%),
radial-gradient(circle at right 20%, rgba(59, 130, 246, 0.07), transparent 28%),
linear-gradient(135deg, #f7fffb 0%, #ffffff 48%, #f5fbff 100%);
}
.assistant-hero::before,
.assistant-hero::after {
content: "";
position: absolute;
border-radius: 999px;
background: rgba(16, 185, 129, 0.06);
pointer-events: none;
}
.assistant-hero::before {
right: -48px;
bottom: -58px;
width: 220px;
height: 220px;
}
.assistant-hero::after {
right: 92px;
top: -44px;
width: 140px;
height: 140px;
}
.assistant-visual {
position: relative;
min-height: 196px;
display: flex;
align-items: flex-end;
justify-content: flex-start;
padding: 0 0 10px 8px;
}
.assistant-visual::before {
content: "";
position: absolute;
inset: auto auto -78px -58px;
width: 264px;
height: 228px;
border-radius: 50%;
background: radial-gradient(circle at 48% 38%, rgba(255, 255, 255, 0.92) 0%, rgba(220, 252, 231, 0.84) 58%, rgba(220, 252, 231, 0) 100%);
pointer-events: none;
}
.assistant-visual::after {
content: "";
position: absolute;
left: 52px;
bottom: 18px;
width: 132px;
height: 18px;
border-radius: 999px;
background: rgba(16, 185, 129, 0.14);
filter: blur(12px);
pointer-events: none;
}
.assistant-glow {
position: absolute;
left: 24px;
bottom: 22px;
width: 176px;
height: 176px;
border-radius: 50%;
background: radial-gradient(circle, rgba(255, 255, 255, 0.98) 0%, rgba(236, 253, 245, 0.9) 58%, rgba(236, 253, 245, 0) 100%);
box-shadow: 0 24px 48px rgba(16, 185, 129, 0.12);
pointer-events: none;
}
.assistant-image {
position: relative;
z-index: 1;
width: 184px;
max-width: 100%;
height: auto;
object-fit: contain;
object-position: left bottom;
filter: drop-shadow(0 22px 28px rgba(15, 23, 42, 0.16));
}
.assistant-copy {
position: relative;
z-index: 1;
display: grid;
gap: 10px;
align-content: center;
}
.assistant-tag {
display: inline-flex;
width: fit-content;
align-items: center;
padding: 6px 12px;
border-radius: 999px;
background: linear-gradient(135deg, rgba(16, 185, 129, 0.14), rgba(59, 130, 246, 0.12));
color: #0f766e;
font-size: 12px;
font-weight: 800;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6);
}
.assistant-copy h3 {
color: #0f172a;
font-size: 26px;
line-height: 1.25;
font-weight: 800;
}
.assistant-copy p {
max-width: 760px;
color: #5b6b83;
font-size: 14px;
line-height: 1.6;
}
.assistant-input {
display: flex;
align-items: center;
min-height: 48px;
padding: 4px 14px;
border: 1px solid rgba(148, 163, 184, 0.28);
border-radius: 12px;
background: rgba(255, 255, 255, 0.92);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
}
.assistant-file-input {
display: none;
}
.assistant-input textarea {
min-width: 0;
flex: 1;
height: 22px;
min-height: 22px;
max-height: 22px;
resize: none;
border: 0;
padding: 1px 0;
background: transparent;
color: #0f172a;
font-size: 15px;
line-height: 22px;
overflow: hidden;
}
.assistant-input textarea::placeholder {
color: #94a3b8;
}
.assistant-input textarea:focus {
outline: none;
}
.hero-action,
.secondary-action,
.ghost-action,
.row-action,
.link-action,
.row-link {
border: 0;
background: transparent;
}
.hero-action {
height: 40px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 0 16px;
border-radius: 10px;
background: linear-gradient(135deg, #10b981, #059669);
color: #fff;
font-size: 14px;
font-weight: 800;
white-space: nowrap;
box-shadow: 0 10px 22px rgba(16, 185, 129, 0.18);
}
.hero-action .mdi,
.secondary-action .mdi,
.ghost-action .mdi {
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 14px;
line-height: 1;
}
.hero-action span,
.secondary-action span,
.ghost-action span {
display: inline-flex;
align-items: center;
line-height: 1;
}
.assistant-tools {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.assistant-file-strip {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.assistant-file-note,
.assistant-file-chip {
display: inline-flex;
align-items: center;
min-height: 30px;
padding: 0 12px;
border-radius: 999px;
font-size: 12px;
font-weight: 700;
}
.assistant-file-note {
background: rgba(16, 185, 129, 0.1);
color: #047857;
}
.assistant-file-chip {
max-width: 220px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
border: 1px solid rgba(148, 163, 184, 0.24);
background: rgba(255, 255, 255, 0.9);
color: #475569;
}
.assistant-file-clear {
border: 0;
background: transparent;
color: #64748b;
font-size: 12px;
font-weight: 700;
cursor: pointer;
}
.ghost-action {
height: 40px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 0 16px;
border: 1px solid rgba(15, 118, 110, 0.18);
border-radius: 10px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.94), rgba(244, 250, 247, 0.88));
color: #0f766e;
font-size: 14px;
font-weight: 700;
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.9),
0 6px 14px rgba(15, 118, 110, 0.06);
}
.ghost-action .mdi {
color: #10b981;
}
.secondary-action {
height: 40px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 0 16px;
border: 1px solid rgba(59, 130, 246, 0.18);
border-radius: 10px;
background: linear-gradient(180deg, rgba(244, 249, 255, 0.96), rgba(234, 244, 255, 0.9));
color: #1d4ed8;
font-size: 14px;
font-weight: 700;
white-space: nowrap;
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.92),
0 6px 14px rgba(37, 99, 235, 0.08);
}
.secondary-action .mdi {
color: #2563eb;
}
.hero-action:disabled,
.secondary-action:disabled,
.ghost-action:disabled {
cursor: not-allowed;
opacity: 0.68;
box-shadow: none;
}
.workbench-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 20px;
}
.list-panel,
.policy-panel {
padding: 20px 22px;
}
.section-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 16px;
}
.section-head h3 {
color: #0f172a;
font-size: 17px;
font-weight: 700;
}
.title-with-badge {
display: inline-flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.alert-badge {
min-width: 22px;
height: 22px;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 7px;
border-radius: 999px;
background: #ef4444;
color: #fff;
font-size: 12px;
font-weight: 800;
line-height: 1;
box-shadow: 0 6px 14px rgba(239, 68, 68, 0.22);
}
.link-action {
display: inline-flex;
align-items: center;
gap: 4px;
color: #10b981;
font-size: 14px;
font-weight: 700;
}
.list-body {
display: grid;
}
.todo-row,
.progress-row {
display: grid;
grid-template-columns: 48px minmax(0, 1fr) auto;
gap: 14px;
align-items: center;
padding: 14px 0;
border-top: 1px solid #edf2f7;
}
.todo-row:first-child,
.progress-row:first-child {
padding-top: 4px;
border-top: 0;
}
.todo-icon {
width: 48px;
height: 48px;
display: grid;
place-items: center;
border-radius: 14px;
background: color-mix(in srgb, var(--icon-color) 12%, white);
color: var(--icon-color);
font-size: 24px;
}
.todo-copy {
min-width: 0;
}
.todo-copy strong {
display: block;
color: #0f172a;
font-size: 15px;
font-weight: 700;
line-height: 1.4;
}
.todo-copy p {
margin-top: 4px;
color: #6b7280;
font-size: 14px;
line-height: 1.5;
}
.todo-advice {
display: flex;
align-items: flex-start;
gap: 8px;
flex-wrap: wrap;
}
.todo-advice-label {
display: inline-flex;
align-items: center;
min-height: 22px;
padding: 0 8px;
border-radius: 999px;
background: #ecfdf5;
color: #059669;
font-size: 12px;
font-weight: 800;
white-space: nowrap;
}
.todo-advice-text {
color: #64748b;
}
.row-action {
height: 38px;
padding: 0 16px;
border: 1px solid rgba(16, 185, 129, 0.36);
border-radius: 10px;
color: #10b981;
font-size: 14px;
font-weight: 700;
white-space: nowrap;
}
.progress-row {
grid-template-columns: 48px minmax(0, 1fr) minmax(84px, auto) minmax(104px, auto);
gap: 14px 16px;
}
.progress-copy strong {
margin-bottom: 2px;
}
.progress-amount {
color: #0f172a;
font-size: 20px;
font-weight: 800;
line-height: 1;
text-align: right;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.progress-status {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 104px;
min-height: 34px;
padding: 6px 14px;
border-radius: 999px;
font-size: 13px;
font-weight: 800;
white-space: nowrap;
justify-self: end;
}
.progress-status.success,
.policy-status.success {
background: #eafaf2;
color: #16935f;
}
.progress-status.info,
.policy-status.info {
background: #eff6ff;
color: #3b82f6;
}
.progress-status.mint {
background: #edfdf5;
color: #10b981;
}
.policy-table {
border: 1px solid #e7edf5;
border-radius: 12px;
overflow: hidden;
}
.policy-row {
display: grid;
grid-template-columns: 2.2fr 2.4fr 1fr;
gap: 16px;
align-items: center;
min-height: 56px;
padding: 0 18px;
border-top: 1px solid #edf2f7;
}
.policy-head {
min-height: 44px;
background: #f8fbff;
color: #64748b;
font-size: 12px;
font-weight: 800;
border-top: 0;
}
.policy-row strong,
.policy-row span {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.policy-row strong {
color: #0f172a;
font-size: 14px;
font-weight: 700;
}
.policy-row span {
color: #64748b;
font-size: 14px;
}
.policy-title-cell,
.policy-summary-cell {
justify-self: stretch;
text-align: left;
}
.policy-date-cell {
justify-self: center;
text-align: center;
}
@media (max-width: 1320px) {
.assistant-copy h3 {
font-size: 24px;
}
.policy-row {
grid-template-columns: 1.8fr 1.8fr 1fr;
}
}
@media (max-width: 1440px) {
.workbench {
gap: 14px;
}
.assistant-hero {
gap: 16px;
padding: 18px 20px 18px 16px;
}
.assistant-copy h3 {
font-size: 24px;
}
.assistant-visual {
min-height: 184px;
}
.assistant-image {
width: 172px;
}
.workbench-grid {
gap: 16px;
}
.list-panel,
.policy-panel {
padding: 18px 20px;
}
.policy-row {
min-height: 52px;
padding: 0 16px;
}
}
@media (max-width: 1080px) {
.assistant-hero {
grid-template-columns: 1fr;
gap: 8px;
}
.assistant-visual {
min-height: 188px;
justify-content: center;
padding: 0 0 8px;
}
.assistant-visual::before,
.assistant-visual::after,
.assistant-glow {
left: 50%;
transform: translateX(-50%);
}
.assistant-visual::before {
inset: auto auto -82px 50%;
}
.assistant-image {
width: 176px;
}
.workbench-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 860px) {
.assistant-hero,
.list-panel,
.policy-panel {
padding: 18px;
}
.assistant-input {
flex-direction: column;
align-items: stretch;
padding: 14px;
}
.assistant-visual {
min-height: 160px;
}
.assistant-glow {
width: 148px;
height: 148px;
}
.assistant-image {
width: 150px;
}
.assistant-input textarea {
height: 40px;
min-height: 40px;
max-height: 40px;
line-height: 1.5;
}
.hero-action,
.secondary-action,
.ghost-action,
.row-action {
width: 100%;
justify-content: center;
}
.assistant-file-chip {
max-width: 100%;
}
.todo-row,
.progress-row {
grid-template-columns: 48px minmax(0, 1fr);
}
.progress-amount {
grid-column: 2;
text-align: left;
font-size: 18px;
}
.row-action,
.progress-status {
grid-column: 2;
justify-self: start;
}
.policy-table {
border: 0;
border-radius: 0;
}
.policy-head {
display: none;
}
.policy-row {
grid-template-columns: 1fr;
gap: 8px;
padding: 16px 0;
border-top: 1px solid #edf2f7;
}
.policy-row strong,
.policy-row span {
white-space: normal;
}
}
</style>