2025-05-15 22:34:32 +08:00
|
|
|
|
<template>
|
2026-01-15 06:01:34 +08:00
|
|
|
|
<div
|
|
|
|
|
|
v-if="message.message_type === 'multimodal_image' && message.image_content"
|
|
|
|
|
|
class="message-image"
|
|
|
|
|
|
>
|
2025-11-12 14:04:34 +08:00
|
|
|
|
<img :src="`data:image/jpeg;base64,${message.image_content}`" alt="用户上传的图片" />
|
|
|
|
|
|
</div>
|
2025-05-15 22:34:32 +08:00
|
|
|
|
<div class="message-box" :class="[message.type, customClasses]">
|
|
|
|
|
|
<!-- 用户消息 -->
|
2026-01-15 06:01:34 +08:00
|
|
|
|
<div
|
|
|
|
|
|
v-if="message.type === 'human'"
|
|
|
|
|
|
class="message-copy-btn human-copy"
|
|
|
|
|
|
@click="copyToClipboard(message.content)"
|
|
|
|
|
|
:class="{ 'is-copied': isCopied }"
|
|
|
|
|
|
>
|
2025-12-23 01:51:05 +08:00
|
|
|
|
<Check v-if="isCopied" size="14" />
|
|
|
|
|
|
<Copy v-else size="14" />
|
|
|
|
|
|
</div>
|
2025-05-15 22:34:32 +08:00
|
|
|
|
<p v-if="message.type === 'human'" class="message-text">{{ message.content }}</p>
|
|
|
|
|
|
|
2025-11-01 21:34:16 +08:00
|
|
|
|
<p v-else-if="message.type === 'system'" class="message-text-system">{{ message.content }}</p>
|
|
|
|
|
|
|
2025-05-15 22:34:32 +08:00
|
|
|
|
<!-- 助手消息 -->
|
|
|
|
|
|
<div v-else-if="message.type === 'ai'" class="assistant-message">
|
2025-08-25 13:57:47 +08:00
|
|
|
|
<div v-if="parsedData.reasoning_content" class="reasoning-box">
|
2025-05-15 22:34:32 +08:00
|
|
|
|
<a-collapse v-model:activeKey="reasoningActiveKey" :bordered="false">
|
|
|
|
|
|
<template #expandIcon="{ isActive }">
|
|
|
|
|
|
<caret-right-outlined :rotate="isActive ? 90 : 0" />
|
|
|
|
|
|
</template>
|
2026-01-15 06:01:34 +08:00
|
|
|
|
<a-collapse-panel
|
|
|
|
|
|
key="show"
|
|
|
|
|
|
:header="message.status == 'reasoning' ? '正在思考...' : '推理过程'"
|
|
|
|
|
|
class="reasoning-header"
|
|
|
|
|
|
>
|
2025-08-25 13:57:47 +08:00
|
|
|
|
<p class="reasoning-content">{{ parsedData.reasoning_content }}</p>
|
2025-05-15 22:34:32 +08:00
|
|
|
|
</a-collapse-panel>
|
|
|
|
|
|
</a-collapse>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 消息内容 -->
|
2026-01-15 06:01:34 +08:00
|
|
|
|
<MdPreview
|
|
|
|
|
|
v-if="parsedData.content"
|
|
|
|
|
|
ref="editorRef"
|
2025-05-15 22:34:32 +08:00
|
|
|
|
editorId="preview-only"
|
2025-11-23 01:39:44 +08:00
|
|
|
|
:theme="theme"
|
2025-05-15 22:34:32 +08:00
|
|
|
|
previewTheme="github"
|
|
|
|
|
|
:showCodeRowNumber="false"
|
2025-08-25 13:57:47 +08:00
|
|
|
|
:modelValue="parsedData.content"
|
2025-05-15 22:34:32 +08:00
|
|
|
|
:key="message.id"
|
2026-04-26 00:47:30 +08:00
|
|
|
|
class="message-md flat-md-preview"
|
2026-01-15 06:01:34 +08:00
|
|
|
|
/>
|
2025-05-15 22:34:32 +08:00
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
<div v-else-if="parsedData.reasoning_content" class="empty-block"></div>
|
2025-05-15 22:34:32 +08:00
|
|
|
|
|
2025-10-12 23:45:17 +08:00
|
|
|
|
<!-- 错误提示块 -->
|
2025-11-25 22:34:44 +08:00
|
|
|
|
<div v-if="displayError" class="error-hint">
|
|
|
|
|
|
<span v-if="getErrorMessage">{{ getErrorMessage }}</span>
|
|
|
|
|
|
<span v-else-if="message.error_type === 'interrupted'">回答生成已中断</span>
|
2025-10-12 23:45:17 +08:00
|
|
|
|
<span v-else-if="message.error_type === 'unexpect'">生成过程中出现异常</span>
|
2026-01-15 06:01:34 +08:00
|
|
|
|
<span v-else-if="message.error_type === 'content_guard_blocked'"
|
|
|
|
|
|
>检测到敏感内容,已中断输出</span
|
|
|
|
|
|
>
|
2025-11-25 22:34:44 +08:00
|
|
|
|
<span v-else>{{ message.error_type || '未知错误' }}</span>
|
2025-10-12 23:45:17 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-04-13 18:06:25 +08:00
|
|
|
|
<ToolCallsGroupComponent
|
|
|
|
|
|
v-if="!hideToolCalls && validToolCalls.length > 0"
|
|
|
|
|
|
:tool-calls="validToolCalls"
|
|
|
|
|
|
/>
|
2025-05-15 22:34:32 +08:00
|
|
|
|
|
|
|
|
|
|
<div v-if="message.isStoppedByUser" class="retry-hint">
|
|
|
|
|
|
你停止生成了本次回答
|
2026-01-15 06:01:34 +08:00
|
|
|
|
<span class="retry-link" @click="emit('retryStoppedMessage', message.id)"
|
|
|
|
|
|
>重新编辑问题</span
|
|
|
|
|
|
>
|
2025-05-15 22:34:32 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
<div
|
|
|
|
|
|
v-if="
|
|
|
|
|
|
(message.role == 'received' || message.role == 'assistant') &&
|
|
|
|
|
|
message.status == 'finished' &&
|
|
|
|
|
|
showRefs
|
|
|
|
|
|
"
|
|
|
|
|
|
>
|
|
|
|
|
|
<RefsComponent
|
|
|
|
|
|
:message="message"
|
|
|
|
|
|
:show-refs="showRefs"
|
|
|
|
|
|
:is-latest-message="isLatestMessage"
|
2026-03-02 21:01:37 +08:00
|
|
|
|
:sources="messageSources"
|
2026-01-15 06:01:34 +08:00
|
|
|
|
@retry="emit('retry')"
|
|
|
|
|
|
@openRefs="emit('openRefs', $event)"
|
|
|
|
|
|
/>
|
2025-05-15 22:34:32 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<!-- 错误消息 -->
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-09-11 03:09:17 +08:00
|
|
|
|
<div v-if="infoStore.debugMode" class="status-info">{{ message }}</div>
|
2025-05-15 22:34:32 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 自定义内容 -->
|
|
|
|
|
|
<slot></slot>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2026-01-15 06:01:34 +08:00
|
|
|
|
import { computed, ref } from 'vue'
|
2026-03-26 13:53:35 +08:00
|
|
|
|
import { CaretRightOutlined } from '@ant-design/icons-vue'
|
2025-05-15 22:34:32 +08:00
|
|
|
|
import RefsComponent from '@/components/RefsComponent.vue'
|
2026-01-15 06:01:34 +08:00
|
|
|
|
import { Copy, Check } from 'lucide-vue-next'
|
2026-04-13 18:06:25 +08:00
|
|
|
|
import ToolCallsGroupComponent from '@/components/ToolCallsGroupComponent.vue'
|
2025-08-25 01:46:31 +08:00
|
|
|
|
import { useAgentStore } from '@/stores/agent'
|
2025-09-11 03:09:17 +08:00
|
|
|
|
import { useInfoStore } from '@/stores/info'
|
2025-11-23 01:39:44 +08:00
|
|
|
|
import { useThemeStore } from '@/stores/theme'
|
2025-08-25 01:46:31 +08:00
|
|
|
|
import { storeToRefs } from 'pinia'
|
2026-03-02 21:01:37 +08:00
|
|
|
|
import { MessageProcessor } from '@/utils/messageProcessor'
|
2025-05-15 22:34:32 +08:00
|
|
|
|
|
|
|
|
|
|
import { MdPreview } from 'md-editor-v3'
|
2026-01-15 06:01:34 +08:00
|
|
|
|
import 'md-editor-v3/lib/preview.css'
|
2025-05-15 22:34:32 +08:00
|
|
|
|
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
|
|
// 消息角色:'user'|'assistant'|'sent'|'received'
|
|
|
|
|
|
message: {
|
|
|
|
|
|
type: Object,
|
|
|
|
|
|
required: true
|
|
|
|
|
|
},
|
|
|
|
|
|
// 是否正在处理中
|
|
|
|
|
|
isProcessing: {
|
|
|
|
|
|
type: Boolean,
|
|
|
|
|
|
default: false
|
|
|
|
|
|
},
|
|
|
|
|
|
// 自定义类
|
|
|
|
|
|
customClasses: {
|
|
|
|
|
|
type: Object,
|
|
|
|
|
|
default: () => ({})
|
|
|
|
|
|
},
|
|
|
|
|
|
// 是否显示推理过程
|
|
|
|
|
|
showRefs: {
|
|
|
|
|
|
type: [Array, Boolean],
|
|
|
|
|
|
default: () => false
|
|
|
|
|
|
},
|
2025-09-11 03:09:17 +08:00
|
|
|
|
// 是否为最新消息
|
|
|
|
|
|
isLatestMessage: {
|
2025-05-15 22:34:32 +08:00
|
|
|
|
type: Boolean,
|
|
|
|
|
|
default: false
|
|
|
|
|
|
},
|
2026-04-13 18:06:25 +08:00
|
|
|
|
hideToolCalls: {
|
|
|
|
|
|
type: Boolean,
|
|
|
|
|
|
default: false
|
|
|
|
|
|
},
|
2025-09-11 03:09:17 +08:00
|
|
|
|
// 是否显示调试信息 (已废弃,使用 infoStore.debugMode)
|
|
|
|
|
|
debugMode: {
|
2025-05-15 22:34:32 +08:00
|
|
|
|
type: Boolean,
|
|
|
|
|
|
default: false
|
|
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
})
|
2025-05-15 22:34:32 +08:00
|
|
|
|
|
|
|
|
|
|
const editorRef = ref()
|
|
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const emit = defineEmits(['retry', 'retryStoppedMessage', 'openRefs'])
|
2025-05-15 22:34:32 +08:00
|
|
|
|
|
2025-12-23 01:51:05 +08:00
|
|
|
|
// 复制状态
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const isCopied = ref(false)
|
2025-12-23 01:51:05 +08:00
|
|
|
|
|
|
|
|
|
|
const copyToClipboard = async (text) => {
|
|
|
|
|
|
try {
|
2025-12-29 19:52:40 +08:00
|
|
|
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
await navigator.clipboard.writeText(text)
|
2025-12-29 19:52:40 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// 降级处理:使用传统的 execCommand 方法
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const textArea = document.createElement('textarea')
|
|
|
|
|
|
textArea.value = text
|
|
|
|
|
|
textArea.style.position = 'fixed'
|
|
|
|
|
|
textArea.style.left = '-999999px'
|
|
|
|
|
|
textArea.style.top = '-999999px'
|
|
|
|
|
|
document.body.appendChild(textArea)
|
|
|
|
|
|
textArea.focus()
|
|
|
|
|
|
textArea.select()
|
|
|
|
|
|
const successful = document.execCommand('copy')
|
|
|
|
|
|
document.body.removeChild(textArea)
|
|
|
|
|
|
if (!successful) throw new Error('execCommand failed')
|
2025-12-29 19:52:40 +08:00
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
isCopied.value = true
|
2025-12-23 01:51:05 +08:00
|
|
|
|
setTimeout(() => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
isCopied.value = false
|
|
|
|
|
|
}, 2000)
|
2025-12-23 01:51:05 +08:00
|
|
|
|
} catch (err) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
console.error('Failed to copy: ', err)
|
2025-12-23 01:51:05 +08:00
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
}
|
2025-12-23 01:51:05 +08:00
|
|
|
|
|
2025-05-15 22:34:32 +08:00
|
|
|
|
// 推理面板展开状态
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const reasoningActiveKey = ref(['hide'])
|
2025-05-15 22:34:32 +08:00
|
|
|
|
|
2025-11-25 22:34:44 +08:00
|
|
|
|
// 错误消息处理
|
|
|
|
|
|
const displayError = computed(() => {
|
|
|
|
|
|
// 简化错误判断:只检查明确的错误类型标识
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return !!(props.message.error_type || props.message.extra_metadata?.error_type)
|
|
|
|
|
|
})
|
2025-11-25 22:34:44 +08:00
|
|
|
|
|
|
|
|
|
|
const getErrorMessage = computed(() => {
|
|
|
|
|
|
// 优先使用直接的 error_message 字段
|
|
|
|
|
|
if (props.message.error_message) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return props.message.error_message
|
2025-11-25 22:34:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 其次从 extra_metadata 中获取具体的错误信息
|
|
|
|
|
|
if (props.message.extra_metadata?.error_message) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return props.message.extra_metadata.error_message
|
2025-11-25 22:34:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 对于已知的错误类型,返回默认提示
|
|
|
|
|
|
switch (props.message.error_type) {
|
|
|
|
|
|
case 'interrupted':
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return '回答生成已中断'
|
2025-11-25 22:34:44 +08:00
|
|
|
|
case 'content_guard_blocked':
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return '检测到敏感内容,已中断输出'
|
2025-11-25 22:34:44 +08:00
|
|
|
|
case 'unexpect':
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return '生成过程中出现异常'
|
2025-11-25 22:34:44 +08:00
|
|
|
|
case 'agent_error':
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return '智能体获取失败'
|
2025-11-25 22:34:44 +08:00
|
|
|
|
default:
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return null
|
2025-11-25 22:34:44 +08:00
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
})
|
2025-11-25 22:34:44 +08:00
|
|
|
|
|
2025-08-25 01:46:31 +08:00
|
|
|
|
// 引入智能体 store
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const agentStore = useAgentStore()
|
2026-03-02 21:01:37 +08:00
|
|
|
|
const { availableKnowledgeBases } = storeToRefs(agentStore)
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const infoStore = useInfoStore()
|
|
|
|
|
|
const themeStore = useThemeStore()
|
2025-08-25 01:46:31 +08:00
|
|
|
|
|
2026-03-02 21:01:37 +08:00
|
|
|
|
// 提取消息来源
|
|
|
|
|
|
const messageSources = computed(() => {
|
|
|
|
|
|
if (props.message.type === 'ai') {
|
|
|
|
|
|
return MessageProcessor.extractSourcesFromMessage(props.message, availableKnowledgeBases.value)
|
|
|
|
|
|
}
|
|
|
|
|
|
return { knowledgeChunks: [], webSources: [] }
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-11-23 01:39:44 +08:00
|
|
|
|
// 主题设置 - 根据系统主题动态切换
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const theme = computed(() => (themeStore.isDark ? 'dark' : 'light'))
|
2025-11-23 01:39:44 +08:00
|
|
|
|
|
2025-10-27 18:41:35 +08:00
|
|
|
|
// 过滤有效的工具调用
|
|
|
|
|
|
const validToolCalls = computed(() => {
|
|
|
|
|
|
if (!props.message.tool_calls || !Array.isArray(props.message.tool_calls)) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return []
|
2025-10-27 18:41:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return props.message.tool_calls.filter((toolCall) => {
|
2025-10-27 18:41:35 +08:00
|
|
|
|
// 过滤掉无效的工具调用
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return (
|
|
|
|
|
|
toolCall &&
|
2026-04-13 18:06:25 +08:00
|
|
|
|
(toolCall.id || toolCall.name || toolCall.function?.name) &&
|
2026-01-15 06:01:34 +08:00
|
|
|
|
(toolCall.args !== undefined ||
|
|
|
|
|
|
toolCall.function?.arguments !== undefined ||
|
|
|
|
|
|
toolCall.tool_call_result !== undefined)
|
|
|
|
|
|
)
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
2025-10-27 18:41:35 +08:00
|
|
|
|
|
2025-08-25 13:57:47 +08:00
|
|
|
|
const parsedData = computed(() => {
|
|
|
|
|
|
// Start with default values from the prop to avoid mutation.
|
2026-01-15 06:01:34 +08:00
|
|
|
|
let content = props.message.content.trim() || ''
|
|
|
|
|
|
let reasoning_content = props.message.additional_kwargs?.reasoning_content || ''
|
2025-08-25 13:57:47 +08:00
|
|
|
|
|
2025-09-11 02:11:06 +08:00
|
|
|
|
if (reasoning_content) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
content,
|
2026-01-15 06:01:34 +08:00
|
|
|
|
reasoning_content
|
2025-09-11 02:11:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-25 13:57:47 +08:00
|
|
|
|
// Regex to find <think>...</think> or an unclosed <think>... at the end of the string.
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const thinkRegex = /<think>(.*?)<\/think>|<think>(.*?)$/s
|
|
|
|
|
|
const thinkMatch = content.match(thinkRegex)
|
2025-08-25 13:57:47 +08:00
|
|
|
|
|
|
|
|
|
|
if (thinkMatch) {
|
|
|
|
|
|
// The captured reasoning is in either group 1 (closed tag) or 2 (unclosed tag).
|
2026-01-15 06:01:34 +08:00
|
|
|
|
reasoning_content = (thinkMatch[1] || thinkMatch[2] || '').trim()
|
2025-08-25 13:57:47 +08:00
|
|
|
|
// Remove the entire matched <think> block from the original content.
|
2026-01-15 06:01:34 +08:00
|
|
|
|
content = content.replace(thinkMatch[0], '').trim()
|
2025-06-22 23:56:12 +08:00
|
|
|
|
}
|
2025-08-25 13:57:47 +08:00
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
content,
|
2026-01-15 06:01:34 +08:00
|
|
|
|
reasoning_content
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2025-05-15 22:34:32 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="less" scoped>
|
|
|
|
|
|
.message-box {
|
|
|
|
|
|
display: inline-block;
|
|
|
|
|
|
border-radius: 1.5rem;
|
|
|
|
|
|
margin: 0.8rem 0;
|
|
|
|
|
|
padding: 0.625rem 1.25rem;
|
|
|
|
|
|
user-select: text;
|
|
|
|
|
|
word-break: break-word;
|
|
|
|
|
|
word-wrap: break-word;
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
line-height: 24px;
|
|
|
|
|
|
box-sizing: border-box;
|
2025-11-23 01:39:44 +08:00
|
|
|
|
color: var(--gray-10000);
|
2025-05-15 22:34:32 +08:00
|
|
|
|
max-width: 100%;
|
|
|
|
|
|
position: relative;
|
2026-01-15 06:01:34 +08:00
|
|
|
|
letter-spacing: 0.25px;
|
2025-05-15 22:34:32 +08:00
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
&.human,
|
|
|
|
|
|
&.sent {
|
2025-05-15 22:34:32 +08:00
|
|
|
|
max-width: 95%;
|
2025-11-12 14:04:34 +08:00
|
|
|
|
color: var(--gray-1000);
|
|
|
|
|
|
background-color: var(--main-50);
|
2025-05-15 22:34:32 +08:00
|
|
|
|
align-self: flex-end;
|
2026-01-15 06:01:34 +08:00
|
|
|
|
border-radius: 0.5rem;
|
2025-05-15 22:34:32 +08:00
|
|
|
|
padding: 0.5rem 1rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
&.assistant,
|
|
|
|
|
|
&.received,
|
|
|
|
|
|
&.ai {
|
2025-05-15 22:34:32 +08:00
|
|
|
|
color: initial;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 0px;
|
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
|
border-radius: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.message-text {
|
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
|
white-space: pre-line;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-23 01:51:05 +08:00
|
|
|
|
.message-copy-btn {
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
color: var(--gray-400);
|
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
color: var(--main-color);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.is-copied {
|
|
|
|
|
|
color: var(--color-success-500);
|
|
|
|
|
|
opacity: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.human-copy {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
left: -28px;
|
|
|
|
|
|
bottom: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
.message-copy-btn {
|
|
|
|
|
|
opacity: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-01 21:34:16 +08:00
|
|
|
|
.message-text-system {
|
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
|
white-space: pre-line;
|
|
|
|
|
|
color: var(--gray-600);
|
|
|
|
|
|
font-style: italic;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
padding: 8px 12px;
|
|
|
|
|
|
background-color: var(--gray-50);
|
|
|
|
|
|
border-left: 3px solid var(--gray-300);
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-15 22:34:32 +08:00
|
|
|
|
.err-msg {
|
2025-11-25 22:34:44 +08:00
|
|
|
|
color: var(--color-error-500);
|
2025-11-23 01:39:44 +08:00
|
|
|
|
border: 1px solid currentColor;
|
2025-05-15 22:34:32 +08:00
|
|
|
|
padding: 0.5rem 1rem;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
text-align: left;
|
2025-11-25 22:34:44 +08:00
|
|
|
|
background: var(--color-error-50);
|
2025-05-15 22:34:32 +08:00
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.searching-msg {
|
|
|
|
|
|
color: var(--gray-700);
|
|
|
|
|
|
animation: colorPulse 1s infinite ease-in-out;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.reasoning-box {
|
|
|
|
|
|
margin-top: 10px;
|
|
|
|
|
|
margin-bottom: 15px;
|
2025-08-27 00:13:38 +08:00
|
|
|
|
border-radius: 8px;
|
2025-11-23 01:39:44 +08:00
|
|
|
|
border: 1px solid var(--gray-150);
|
2025-06-24 15:44:05 +08:00
|
|
|
|
background-color: var(--gray-25);
|
2025-06-22 23:56:12 +08:00
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-collapse) {
|
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
|
|
|
|
|
|
.ant-collapse-item {
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
|
|
|
|
|
|
.ant-collapse-header {
|
2025-06-24 15:44:05 +08:00
|
|
|
|
padding: 8px 12px;
|
2025-06-22 23:56:12 +08:00
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 500;
|
2025-08-27 00:13:38 +08:00
|
|
|
|
color: var(--gray-700);
|
2025-06-22 23:56:12 +08:00
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
|
|
|
|
|
|
|
.ant-collapse-expand-icon {
|
2025-08-27 00:13:38 +08:00
|
|
|
|
color: var(--gray-400);
|
2025-06-22 23:56:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ant-collapse-content {
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
|
|
|
|
|
|
|
.ant-collapse-content-box {
|
|
|
|
|
|
padding: 16px;
|
2025-08-27 09:57:29 +08:00
|
|
|
|
background-color: var(--gray-25);
|
2025-06-22 23:56:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-05-15 22:34:32 +08:00
|
|
|
|
|
|
|
|
|
|
.reasoning-content {
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
color: var(--gray-800);
|
|
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
|
margin: 0;
|
2025-06-22 23:56:12 +08:00
|
|
|
|
line-height: 1.6;
|
2025-05-15 22:34:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.assistant-message {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-12 23:45:17 +08:00
|
|
|
|
.error-hint {
|
|
|
|
|
|
margin: 10px 0;
|
|
|
|
|
|
padding: 8px 16px;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
2025-11-25 22:34:44 +08:00
|
|
|
|
background-color: var(--color-error-50);
|
|
|
|
|
|
color: var(--color-error-500);
|
2025-10-12 23:45:17 +08:00
|
|
|
|
span {
|
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-15 22:34:32 +08:00
|
|
|
|
.status-info {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
background-color: var(--gray-50);
|
|
|
|
|
|
color: var(--gray-700);
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
font-family: monospace;
|
|
|
|
|
|
max-height: 200px;
|
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.retry-hint {
|
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
|
padding: 8px 16px;
|
2025-11-23 01:39:44 +08:00
|
|
|
|
color: var(--gray-600);
|
2025-05-15 22:34:32 +08:00
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.retry-link {
|
2025-11-25 22:34:44 +08:00
|
|
|
|
color: var(--color-info-500);
|
2025-05-15 22:34:32 +08:00
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
margin-left: 4px;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
text-decoration: underline;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ant-btn-icon-only {
|
|
|
|
|
|
&:has(.anticon-stop) {
|
2025-11-25 22:34:44 +08:00
|
|
|
|
background-color: var(--color-error-500) !important;
|
2025-05-15 22:34:32 +08:00
|
|
|
|
|
|
|
|
|
|
&:hover {
|
2025-11-25 22:34:44 +08:00
|
|
|
|
background-color: var(--color-error-100) !important;
|
2025-05-15 22:34:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@keyframes colorPulse {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
0% {
|
|
|
|
|
|
color: var(--gray-700);
|
|
|
|
|
|
}
|
|
|
|
|
|
50% {
|
|
|
|
|
|
color: var(--gray-300);
|
|
|
|
|
|
}
|
|
|
|
|
|
100% {
|
|
|
|
|
|
color: var(--gray-700);
|
|
|
|
|
|
}
|
2025-05-15 22:34:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@keyframes fadeInUp {
|
|
|
|
|
|
from {
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
transform: translateY(10px);
|
|
|
|
|
|
}
|
|
|
|
|
|
to {
|
|
|
|
|
|
opacity: 1;
|
|
|
|
|
|
transform: translateY(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-27 01:53:45 +08:00
|
|
|
|
|
|
|
|
|
|
@keyframes rotate {
|
|
|
|
|
|
from {
|
|
|
|
|
|
transform: rotate(0deg);
|
|
|
|
|
|
}
|
|
|
|
|
|
to {
|
|
|
|
|
|
transform: rotate(360deg);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-12 14:04:34 +08:00
|
|
|
|
|
|
|
|
|
|
// 多模态消息样式
|
|
|
|
|
|
.message-image {
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
margin-left: auto;
|
2026-01-29 22:49:04 +08:00
|
|
|
|
/* max-height: 200px; */
|
2025-11-12 14:04:34 +08:00
|
|
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
|
|
|
|
|
|
|
|
|
|
img {
|
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
|
max-height: 200px;
|
|
|
|
|
|
object-fit: contain;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-05-15 22:34:32 +08:00
|
|
|
|
</style>
|
|
|
|
|
|
|
2025-08-25 13:57:47 +08:00
|
|
|
|
<style lang="less" scoped>
|
|
|
|
|
|
:deep(.message-md) {
|
2025-05-15 22:34:32 +08:00
|
|
|
|
margin: 8px 0;
|
|
|
|
|
|
}
|
2025-11-25 22:34:44 +08:00
|
|
|
|
</style>
|