989 lines
22 KiB
Vue
989 lines
22 KiB
Vue
<script setup lang="ts">
|
||
import { ref, nextTick, computed, onMounted, watch, onUnmounted } from 'vue'
|
||
import { useUserStore } from '../stores/user'
|
||
import { marked } from 'marked'
|
||
|
||
// 配置 marked
|
||
marked.setOptions({
|
||
async: false,
|
||
})
|
||
|
||
// 渲染 Markdown
|
||
const renderMarkdown = (content: string) => {
|
||
return marked.parse(content)
|
||
}
|
||
|
||
// 定义Agent类型
|
||
interface Agent {
|
||
id: string
|
||
name: string
|
||
description: string
|
||
icon?: string
|
||
hello_prompt?: string
|
||
}
|
||
|
||
// 定义API返回的Agent类型
|
||
interface AgentResponse {
|
||
id: string
|
||
name: string
|
||
description: string
|
||
hello_prompt: string
|
||
}
|
||
|
||
// 获取用户状态
|
||
const userStore = useUserStore()
|
||
const isAuthenticated = computed(() => userStore.isAuthenticated)
|
||
|
||
// 显示访问限制提示
|
||
const showAccessDeniedAlert = ref(false)
|
||
|
||
// Agent列表
|
||
const agents = ref<Agent[]>([])
|
||
const isLoadingAgents = ref(false)
|
||
|
||
// 当前选中的Agent
|
||
const selectedAgent = ref<Agent | null>(null)
|
||
|
||
// 根据环境选择API基础URL
|
||
const apiBaseUrl =
|
||
import.meta.env.MODE === 'development' ? 'http://127.0.0.1:8000' : 'https://api.ibtc.work'
|
||
|
||
// 获取Agent列表
|
||
const fetchAgents = async () => {
|
||
if (!isAuthenticated.value) return
|
||
|
||
isLoadingAgents.value = true
|
||
try {
|
||
const headers: Record<string, string> = {}
|
||
if (userStore.authHeader) {
|
||
headers['Authorization'] = userStore.authHeader
|
||
}
|
||
|
||
const response = await fetch(`${apiBaseUrl}/agent/list`, {
|
||
method: 'GET',
|
||
headers,
|
||
})
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error! status: ${response.status}`)
|
||
}
|
||
|
||
const data = await response.json()
|
||
agents.value = data.map((agent: AgentResponse) => ({
|
||
...agent,
|
||
icon: '📊', // 可以根据agent类型设置不同的图标
|
||
}))
|
||
|
||
// 如果有Agent,默认选中第一个并显示问候语
|
||
if (agents.value.length > 0 && !selectedAgent.value) {
|
||
selectedAgent.value = agents.value[0]
|
||
addInitialGreeting(agents.value[0])
|
||
}
|
||
} catch (error) {
|
||
console.error('获取Agent列表失败:', error)
|
||
} finally {
|
||
isLoadingAgents.value = false
|
||
}
|
||
}
|
||
|
||
// 当用户登录状态改变时获取Agent列表
|
||
onMounted(() => {
|
||
if (isAuthenticated.value) {
|
||
fetchAgents()
|
||
}
|
||
})
|
||
|
||
interface ChatMessage {
|
||
role: 'user' | 'assistant' | 'thought'
|
||
content: string
|
||
files: Array<{
|
||
type: string
|
||
url: string
|
||
}>
|
||
thought?: {
|
||
thought: string
|
||
observation: string
|
||
tool: string
|
||
tool_input: string
|
||
}
|
||
tools?: string[]
|
||
}
|
||
|
||
const userInput = ref('')
|
||
const chatHistory = ref<ChatMessage[]>([])
|
||
const isLoading = ref(false)
|
||
const messagesContainer = ref<HTMLElement | null>(null)
|
||
|
||
// 当用户不是VIP时,显示提示
|
||
onMounted(() => {
|
||
if (isAuthenticated.value) {
|
||
fetchAgents()
|
||
}
|
||
})
|
||
|
||
const closeAlert = () => {
|
||
showAccessDeniedAlert.value = false
|
||
}
|
||
|
||
const scrollToBottom = async () => {
|
||
await nextTick()
|
||
if (messagesContainer.value) {
|
||
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight
|
||
}
|
||
}
|
||
|
||
// 添加初始问候语
|
||
const addInitialGreeting = (agent: Agent) => {
|
||
chatHistory.value = [
|
||
{
|
||
role: 'assistant',
|
||
content:
|
||
agent.hello_prompt ||
|
||
'你好!我是AI Agent,可以回答你的任何关于Web3的问题。请告诉我你想了解什么?',
|
||
files: [],
|
||
},
|
||
]
|
||
}
|
||
|
||
// 监听选中的Agent变化
|
||
watch(selectedAgent, (newAgent) => {
|
||
if (newAgent) {
|
||
addInitialGreeting(newAgent)
|
||
}
|
||
})
|
||
|
||
const sendMessage = async () => {
|
||
if (!userInput.value.trim() || isLoading.value || !isAuthenticated.value || !selectedAgent.value)
|
||
return
|
||
|
||
// 添加用户消息到历史记录
|
||
chatHistory.value.push({
|
||
role: 'user',
|
||
content: userInput.value,
|
||
files: [],
|
||
})
|
||
|
||
// 滚动到底部以显示用户消息
|
||
await scrollToBottom()
|
||
|
||
const currentInput = userInput.value
|
||
userInput.value = ''
|
||
isLoading.value = true
|
||
|
||
// 添加临时助手消息用于流式显示
|
||
const tempMessageIndex = chatHistory.value.length
|
||
chatHistory.value.push({
|
||
role: 'assistant',
|
||
content: 'AI 正在思考...',
|
||
files: [],
|
||
tools: [],
|
||
})
|
||
|
||
await scrollToBottom()
|
||
|
||
try {
|
||
// 准备请求头,包含认证信息
|
||
const headers: Record<string, string> = {
|
||
'Content-Type': 'application/json',
|
||
}
|
||
|
||
// 添加认证头
|
||
if (userStore.authHeader) {
|
||
headers['Authorization'] = userStore.authHeader
|
||
}
|
||
|
||
// 调用流式接口
|
||
const response = await fetch(`${apiBaseUrl}/agent/chat`, {
|
||
method: 'POST',
|
||
headers,
|
||
body: JSON.stringify({
|
||
user_prompt: currentInput,
|
||
agent_id: selectedAgent.value.id,
|
||
}),
|
||
})
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error! status: ${response.status}`)
|
||
}
|
||
|
||
const reader = response.body?.getReader()
|
||
if (!reader) {
|
||
throw new Error('无法获取响应流')
|
||
}
|
||
|
||
const decoder = new TextDecoder()
|
||
let responseText = ''
|
||
|
||
// 处理流式响应
|
||
while (true) {
|
||
const { done, value } = await reader.read()
|
||
if (done) break
|
||
|
||
const chunk = decoder.decode(value, { stream: true })
|
||
const lines = chunk.split('\n')
|
||
|
||
for (const line of lines) {
|
||
if (!line.trim() || !line.startsWith('data: ')) continue
|
||
|
||
try {
|
||
const data = JSON.parse(line.slice(6))
|
||
|
||
switch (data.event) {
|
||
case 'agent_thought':
|
||
// 记录使用的工具
|
||
if (data.tool) {
|
||
if (!chatHistory.value[tempMessageIndex].tools) {
|
||
chatHistory.value[tempMessageIndex].tools = []
|
||
}
|
||
chatHistory.value[tempMessageIndex].tools?.push(data.tool)
|
||
|
||
// 更新显示的工具列表
|
||
let toolsText = ''
|
||
chatHistory.value[tempMessageIndex].tools?.forEach((tool, index) => {
|
||
toolsText += `${index + 1}.调用 ${tool} 工具\n`
|
||
})
|
||
|
||
if (responseText) {
|
||
chatHistory.value[tempMessageIndex].content = toolsText + '\n' + responseText
|
||
} else {
|
||
chatHistory.value[tempMessageIndex].content = toolsText
|
||
}
|
||
await scrollToBottom()
|
||
}
|
||
break
|
||
|
||
case 'agent_message':
|
||
if (data.answer) {
|
||
responseText += data.answer
|
||
|
||
// 更新临时助手消息的内容,保留工具列表
|
||
let toolsText = ''
|
||
if (chatHistory.value[tempMessageIndex].tools?.length) {
|
||
chatHistory.value[tempMessageIndex].tools?.forEach((tool, index) => {
|
||
toolsText += `${index + 1}.调用 ${tool} 工具\n`
|
||
})
|
||
chatHistory.value[tempMessageIndex].content = toolsText + '\n' + responseText
|
||
} else {
|
||
chatHistory.value[tempMessageIndex].content = responseText
|
||
}
|
||
await scrollToBottom()
|
||
}
|
||
break
|
||
|
||
case 'message_file':
|
||
if (data.type === 'image') {
|
||
// 找到最后一条助手消息来添加文件
|
||
const lastIndex = chatHistory.value.length - 1
|
||
if (chatHistory.value[lastIndex].role === 'assistant') {
|
||
chatHistory.value[lastIndex].files.push({
|
||
type: 'image',
|
||
url: data.url,
|
||
})
|
||
}
|
||
}
|
||
break
|
||
|
||
case 'message_end':
|
||
break
|
||
|
||
case 'tts_message':
|
||
// 处理语音合成消息
|
||
break
|
||
}
|
||
} catch (e) {
|
||
console.error('解析响应数据出错:', e)
|
||
}
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('调用API出错:', error)
|
||
// 更新错误消息
|
||
chatHistory.value[tempMessageIndex].content = '抱歉,请求出错了。请稍后再试。'
|
||
await scrollToBottom()
|
||
} finally {
|
||
isLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 添加下拉菜单状态控制
|
||
const showAgentMenu = ref(false)
|
||
|
||
// 切换下拉菜单
|
||
const toggleAgentMenu = () => {
|
||
showAgentMenu.value = !showAgentMenu.value
|
||
}
|
||
|
||
// 选择Agent
|
||
const selectAgent = (agent: Agent) => {
|
||
selectedAgent.value = agent
|
||
showAgentMenu.value = false
|
||
}
|
||
|
||
// 点击其他地方关闭菜单
|
||
const closeAgentMenu = (event: MouseEvent) => {
|
||
const target = event.target as HTMLElement
|
||
if (!target.closest('.agent-selector')) {
|
||
showAgentMenu.value = false
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
if (isAuthenticated.value) {
|
||
fetchAgents()
|
||
}
|
||
document.addEventListener('click', closeAgentMenu)
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
document.removeEventListener('click', closeAgentMenu)
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<div class="ai-agent-view">
|
||
<!-- 访问限制提示 -->
|
||
<div v-if="showAccessDeniedAlert" class="access-denied-alert">
|
||
<div class="alert-content">
|
||
<span>AI Agent功能仅对VIP及超级VIP用户开放</span>
|
||
<button class="close-alert" @click="closeAlert">×</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- <h1 class="page-title">AI Agent</h1>
|
||
<p class="page-description">为您提供全面区块链信息查询和专业分析</p> -->
|
||
|
||
<!-- 未登录状态显示登录提示 -->
|
||
<div v-if="!isAuthenticated" class="login-prompt">
|
||
<div class="login-prompt-content">
|
||
<h2>需要登录</h2>
|
||
<p>请登录或注册账号以使用AI Agent功能</p>
|
||
<div class="login-prompt-actions">
|
||
<router-link to="/login" class="login-button">登录</router-link>
|
||
<router-link to="/register" class="register-button">注册</router-link>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 已登录用户显示聊天界面 -->
|
||
<div v-else class="main-container">
|
||
<!-- 右侧聊天界面 -->
|
||
<div class="chat-container">
|
||
<div class="chat-window">
|
||
<div class="messages-container" ref="messagesContainer">
|
||
<div v-for="(message, index) in chatHistory" :key="index" class="message-wrapper">
|
||
<div :class="['message', message.role === 'user' ? 'user-message' : 'ai-message']">
|
||
<div class="message-content">
|
||
<div class="message-text" v-html="renderMarkdown(message.content)"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="input-container">
|
||
<div class="input-wrapper">
|
||
<textarea
|
||
v-model="userInput"
|
||
class="message-input"
|
||
placeholder="请输入您的问题..."
|
||
@keydown.enter.prevent="sendMessage"
|
||
:disabled="isLoading || !isAuthenticated || !selectedAgent"
|
||
></textarea>
|
||
<button
|
||
class="send-button"
|
||
@click="sendMessage"
|
||
:disabled="isLoading || !isAuthenticated || !selectedAgent"
|
||
>
|
||
<span class="send-icon">➤</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 添加新的 Agent 选择器 -->
|
||
<div class="agent-selector">
|
||
<button class="agent-selector-button" @click="toggleAgentMenu">
|
||
<span class="agent-icon">{{ selectedAgent?.icon || '🤖' }}</span>
|
||
<span class="agent-name">{{ selectedAgent?.name || '选择 Agent' }}</span>
|
||
</button>
|
||
<div v-if="showAgentMenu" class="agent-selector-menu">
|
||
<div v-if="isLoadingAgents" class="agent-loading">加载中...</div>
|
||
<template v-else>
|
||
<div
|
||
v-for="agent in agents"
|
||
:key="agent.id"
|
||
class="agent-menu-item"
|
||
:class="{ active: selectedAgent?.id === agent.id }"
|
||
@click="selectAgent(agent)"
|
||
>
|
||
<div class="agent-icon">{{ agent.icon }}</div>
|
||
<div class="agent-info">
|
||
<div class="agent-name">{{ agent.name }}</div>
|
||
<div class="agent-description">{{ agent.description }}</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.ai-agent-view {
|
||
width: 100%;
|
||
height: calc(100vh - var(--header-height));
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
background-color: #343541;
|
||
}
|
||
|
||
/* 访问限制提示样式 */
|
||
.access-denied-alert {
|
||
position: fixed;
|
||
top: calc(var(--header-height) + 20px);
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
background-color: rgba(255, 152, 0, 0.95);
|
||
color: #000;
|
||
padding: 12px 20px;
|
||
border-radius: var(--border-radius);
|
||
z-index: 1000;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||
animation: slideDown 0.3s ease-out;
|
||
max-width: 90%;
|
||
width: auto;
|
||
}
|
||
|
||
.alert-content {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 16px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.close-alert {
|
||
background: none;
|
||
border: none;
|
||
color: #000;
|
||
font-size: 1.2rem;
|
||
cursor: pointer;
|
||
padding: 0 4px;
|
||
}
|
||
|
||
@keyframes slideDown {
|
||
from {
|
||
transform: translate(-50%, -20px);
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
transform: translate(-50%, 0);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
.page-title {
|
||
font-size: 2.2rem;
|
||
font-weight: 700;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.page-description {
|
||
color: var(--color-text-secondary);
|
||
margin-bottom: 2rem;
|
||
}
|
||
|
||
/* 登录提示样式 */
|
||
.login-prompt {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
padding: 3rem 1rem;
|
||
width: 100%;
|
||
height: calc(80vh - 150px);
|
||
}
|
||
|
||
.login-prompt-content {
|
||
background-color: var(--color-bg-card);
|
||
border-radius: var(--border-radius);
|
||
padding: 2rem;
|
||
text-align: center;
|
||
max-width: 500px;
|
||
width: 100%;
|
||
border: 1px solid var(--color-border);
|
||
}
|
||
|
||
.login-prompt-content h2 {
|
||
font-size: 1.8rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.login-prompt-content p {
|
||
color: var(--color-text-secondary);
|
||
margin-bottom: 2rem;
|
||
}
|
||
|
||
.login-prompt-actions {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.login-button,
|
||
.register-button {
|
||
padding: 0.8rem 2rem;
|
||
border-radius: var(--border-radius);
|
||
font-weight: 500;
|
||
text-decoration: none;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.login-button {
|
||
background-color: transparent;
|
||
border: 1px solid var(--color-border);
|
||
color: var(--color-text-primary);
|
||
}
|
||
|
||
.login-button:hover {
|
||
background-color: var(--color-bg-elevated);
|
||
border-color: var(--color-border-hover);
|
||
}
|
||
|
||
.register-button {
|
||
background-color: #3355ff;
|
||
color: white;
|
||
border: 1px solid #3355ff;
|
||
}
|
||
|
||
.register-button:hover {
|
||
background-color: #2244ee;
|
||
}
|
||
|
||
/* 聊天界面样式 */
|
||
.main-container {
|
||
display: flex;
|
||
width: 100%;
|
||
height: 100%;
|
||
min-height: 0;
|
||
background-color: #343541;
|
||
overflow: hidden;
|
||
position: relative;
|
||
}
|
||
|
||
.chat-container {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-width: 0;
|
||
min-height: 0;
|
||
background-color: #343541;
|
||
}
|
||
|
||
.chat-window {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
background-color: var(--color-bg-primary);
|
||
max-width: 900px;
|
||
margin: 0 auto;
|
||
box-sizing: border-box;
|
||
width: 100%;
|
||
}
|
||
|
||
.messages-container {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 2rem 0.5rem;
|
||
scroll-behavior: smooth;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
.message-wrapper {
|
||
margin: 0.5rem 0;
|
||
display: flex;
|
||
width: 100%;
|
||
}
|
||
|
||
.user-message {
|
||
justify-content: flex-end;
|
||
width: 100%;
|
||
display: flex;
|
||
}
|
||
|
||
.ai-message {
|
||
justify-content: flex-start;
|
||
width: 100%;
|
||
display: flex;
|
||
}
|
||
|
||
.message {
|
||
display: flex;
|
||
padding: 0;
|
||
}
|
||
|
||
.message-content {
|
||
display: inline-block;
|
||
max-width: 800px;
|
||
min-width: 4em;
|
||
padding: 1rem;
|
||
border-radius: 0.75rem;
|
||
word-break: break-word;
|
||
overflow-wrap: break-word;
|
||
white-space: pre-wrap;
|
||
box-sizing: border-box;
|
||
background-clip: padding-box;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.user-message .message-content {
|
||
background-color: #2563eb;
|
||
color: white;
|
||
border-radius: 1rem 1rem 0 1rem;
|
||
}
|
||
|
||
.ai-message .message-content {
|
||
background-color: #1f2937;
|
||
color: var(--color-text-primary);
|
||
border-radius: 1rem 1rem 1rem 0;
|
||
}
|
||
|
||
.message-text {
|
||
font-size: 1rem;
|
||
line-height: 1.6;
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.message-text :deep(ul),
|
||
.message-text :deep(ol) {
|
||
margin: 0.5em 0;
|
||
padding-left: 0;
|
||
list-style-position: inside;
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.message-text :deep(li) {
|
||
margin: 0.25em 0;
|
||
padding-left: 0;
|
||
text-indent: 0;
|
||
list-style-position: inside;
|
||
}
|
||
|
||
.message-text :deep(li > p) {
|
||
display: inline;
|
||
margin: 0;
|
||
}
|
||
|
||
.input-container {
|
||
padding: 1.5rem 2rem;
|
||
background-color: var(--color-bg-primary);
|
||
border-top: 1px solid var(--color-divider);
|
||
}
|
||
|
||
.input-wrapper {
|
||
/* max-width: 48rem; */
|
||
margin: 0 auto;
|
||
position: relative;
|
||
display: flex;
|
||
align-items: flex-end;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.message-input {
|
||
flex: 1;
|
||
height: 3.5rem;
|
||
min-height: 2.5rem;
|
||
max-height: 8rem;
|
||
padding: 0.75rem 1rem;
|
||
padding-right: 3rem;
|
||
background-color: var(--color-bg-elevated);
|
||
border: 1px solid var(--color-border);
|
||
border-radius: var(--border-radius);
|
||
color: var(--color-text-primary);
|
||
font-size: 1rem;
|
||
line-height: 1rem;
|
||
resize: none;
|
||
outline: none;
|
||
transition: border-color 0.2s ease;
|
||
text-align: left;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.message-input::placeholder {
|
||
color: var(--color-text-secondary);
|
||
opacity: 1;
|
||
line-height: 1rem;
|
||
text-align: left;
|
||
}
|
||
|
||
.message-input:focus {
|
||
border-color: var(--color-accent);
|
||
}
|
||
|
||
.message-input:disabled {
|
||
opacity: 0.7;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.send-button {
|
||
position: absolute;
|
||
right: 0.75rem;
|
||
bottom: 0.75rem;
|
||
width: 2rem;
|
||
height: 2rem;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: var(--color-accent);
|
||
border: none;
|
||
border-radius: 50%;
|
||
color: white;
|
||
cursor: pointer;
|
||
transition: background-color 0.2s ease;
|
||
}
|
||
|
||
.send-button:hover:not(:disabled) {
|
||
background-color: var(--color-accent-hover);
|
||
}
|
||
|
||
.send-button:disabled {
|
||
opacity: 0.7;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.send-icon {
|
||
font-size: 1rem;
|
||
transform: rotate(270deg);
|
||
}
|
||
|
||
:deep(.markdown-body) {
|
||
color: inherit;
|
||
font-size: 1rem;
|
||
line-height: 1.6;
|
||
width: 100%;
|
||
overflow-wrap: break-word;
|
||
word-break: break-word;
|
||
}
|
||
|
||
:deep(.markdown-body pre) {
|
||
background-color: rgba(0, 0, 0, 0.2);
|
||
border-radius: 0.5rem;
|
||
padding: 1rem;
|
||
margin: 1rem 0;
|
||
overflow-x: auto;
|
||
}
|
||
|
||
:deep(.markdown-body code) {
|
||
background-color: rgba(0, 0, 0, 0.2);
|
||
padding: 0.2em 0.4em;
|
||
border-radius: 0.25rem;
|
||
font-family: monospace;
|
||
font-size: 0.9em;
|
||
}
|
||
|
||
:deep(.markdown-body pre code) {
|
||
background-color: transparent;
|
||
padding: 0;
|
||
}
|
||
|
||
:deep(.markdown-body p) {
|
||
margin: 0.5em 0;
|
||
overflow-wrap: break-word;
|
||
word-break: break-word;
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
:deep(.markdown-body blockquote) {
|
||
border-left: 4px solid var(--color-accent);
|
||
margin: 0.5rem 0;
|
||
padding: 0.5rem 1rem;
|
||
background-color: rgba(0, 0, 0, 0.2);
|
||
border-radius: 0 0.25rem 0.25rem 0;
|
||
}
|
||
|
||
:deep(.markdown-body table) {
|
||
border-collapse: collapse;
|
||
width: 100%;
|
||
margin: 0.5rem 0;
|
||
}
|
||
|
||
:deep(.markdown-body th),
|
||
:deep(.markdown-body td) {
|
||
border: 1px solid var(--color-border);
|
||
padding: 0.5rem;
|
||
text-align: left;
|
||
}
|
||
|
||
:deep(.markdown-body th) {
|
||
background-color: rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
:deep(.markdown-body img) {
|
||
max-width: 100%;
|
||
height: auto;
|
||
border-radius: 0.5rem;
|
||
}
|
||
|
||
:deep(.markdown-body a) {
|
||
color: var(--color-accent);
|
||
text-decoration: none;
|
||
}
|
||
|
||
:deep(.markdown-body a:hover) {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
/* 添加新的 Agent 选择器样式 */
|
||
.agent-selector {
|
||
position: absolute;
|
||
top: 1rem;
|
||
right: 1rem;
|
||
z-index: 10;
|
||
}
|
||
|
||
.agent-selector-button {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
padding: 0.5rem 1rem;
|
||
background-color: var(--color-bg-elevated);
|
||
border: 1px solid var(--color-border);
|
||
border-radius: var(--border-radius);
|
||
color: var(--color-text-primary);
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.agent-selector-button:hover {
|
||
background-color: var(--color-bg-secondary);
|
||
}
|
||
|
||
.agent-selector-menu {
|
||
position: absolute;
|
||
top: 100%;
|
||
right: 0;
|
||
margin-top: 0.5rem;
|
||
background-color: var(--color-bg-elevated);
|
||
border: 1px solid var(--color-border);
|
||
border-radius: var(--border-radius);
|
||
min-width: 200px;
|
||
max-width: 300px;
|
||
overflow: hidden;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
.agent-menu-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
padding: 0.75rem 1rem;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
border-bottom: 1px solid var(--color-border);
|
||
}
|
||
|
||
.agent-menu-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.agent-menu-item:hover {
|
||
background-color: var(--color-bg-secondary);
|
||
}
|
||
|
||
.agent-menu-item.active {
|
||
background-color: var(--color-bg-secondary);
|
||
}
|
||
|
||
.agent-menu-item .agent-icon {
|
||
font-size: 1.25rem;
|
||
}
|
||
|
||
.agent-menu-item .agent-info {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.agent-menu-item .agent-name {
|
||
font-weight: 500;
|
||
color: var(--color-text-primary);
|
||
margin-bottom: 0.25rem;
|
||
}
|
||
|
||
.agent-menu-item .agent-description {
|
||
font-size: 0.75rem;
|
||
color: var(--color-text-secondary);
|
||
line-height: 1.4;
|
||
}
|
||
|
||
/* 移动端适配 */
|
||
@media (max-width: 768px) {
|
||
.agent-selector {
|
||
top: 0.5rem;
|
||
right: 0.5rem;
|
||
}
|
||
|
||
.agent-selector-menu {
|
||
max-width: calc(100vw - 2rem);
|
||
}
|
||
}
|
||
|
||
@media (max-width: 480px) {
|
||
.agent-selector {
|
||
max-height: 50px;
|
||
}
|
||
|
||
.agent-menu-item {
|
||
padding: 0.4rem;
|
||
min-width: 100px;
|
||
}
|
||
|
||
.agent-name {
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
.message {
|
||
padding: 0.75rem;
|
||
}
|
||
|
||
.input-field {
|
||
font-size: 0.9rem;
|
||
padding: 0.75rem;
|
||
}
|
||
}
|
||
|
||
.message-content {
|
||
overflow: auto;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.message-content pre,
|
||
.message-content table {
|
||
max-width: 100%;
|
||
overflow-x: auto;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.message-content ul,
|
||
.message-content ol {
|
||
margin: 0.5em 0;
|
||
padding-left: 1.5em;
|
||
box-sizing: border-box;
|
||
max-width: 100%;
|
||
overflow-wrap: break-word;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.message-content li {
|
||
margin: 0.25em 0;
|
||
word-break: break-word;
|
||
overflow-wrap: break-word;
|
||
}
|
||
</style>
|