update
This commit is contained in:
parent
9b46bb20c9
commit
6ce4c77e3b
@ -10,11 +10,11 @@ import { RouterView } from 'vue-router'
|
||||
<img src="@/assets/logo.png" alt="Crypto.AI Logo" class="logo-image" />
|
||||
Crypto.AI
|
||||
</div>
|
||||
<!-- <nav class="main-nav">
|
||||
<nav class="main-nav">
|
||||
<RouterLink to="/" class="nav-link">首页</RouterLink>
|
||||
<RouterLink to="/ai-agent" class="nav-link">AI Agent</RouterLink>
|
||||
<RouterLink to="/tools" class="nav-link">工具集合</RouterLink>
|
||||
</nav> -->
|
||||
<!-- <RouterLink to="/tools" class="nav-link">工具集合</RouterLink> -->
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
@ -1,18 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, nextTick } from 'vue'
|
||||
|
||||
// 根据环境选择API基础URL
|
||||
const apiBaseUrl =
|
||||
import.meta.env.MODE === 'development' ? 'http://127.0.0.1:8000' : 'https://api.ibtc.work'
|
||||
|
||||
const activeAgent = ref('crypto-doctor')
|
||||
const userInput = ref('')
|
||||
const chatHistory = ref([
|
||||
{
|
||||
role: 'assistant',
|
||||
content:
|
||||
'你好!我是加密项目医生,可以帮你分析任何加密项目的健康状况。请告诉我你想了解的项目名称或合约地址。',
|
||||
content: '你好!我是区块链智能AI助手,可以回答你的任何关于区块链的问题。请告诉我你想了解什么?',
|
||||
},
|
||||
])
|
||||
const isLoading = ref(false)
|
||||
const messagesContainer = ref<HTMLElement | null>(null)
|
||||
|
||||
const sendMessage = () => {
|
||||
if (!userInput.value.trim()) return
|
||||
const scrollToBottom = async () => {
|
||||
await nextTick()
|
||||
if (messagesContainer.value) {
|
||||
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight
|
||||
}
|
||||
}
|
||||
|
||||
const sendMessage = async () => {
|
||||
if (!userInput.value.trim() || isLoading.value) return
|
||||
|
||||
// 添加用户消息到历史记录
|
||||
chatHistory.value.push({
|
||||
@ -20,115 +31,117 @@ const sendMessage = () => {
|
||||
content: userInput.value,
|
||||
})
|
||||
|
||||
// 模拟AI回复(实际项目中这里应该调用后端API)
|
||||
setTimeout(() => {
|
||||
if (activeAgent.value === 'crypto-doctor') {
|
||||
chatHistory.value.push({
|
||||
role: 'assistant',
|
||||
content:
|
||||
'我正在分析这个项目,这可能需要一些时间。基于初步分析,我建议关注以下几个方面:\n\n1. 团队背景和透明度\n2. 代码审计情况\n3. 项目活跃度和发展\n4. 代币分配机制\n5. 市场流动性\n\n您想了解哪方面的详细信息?',
|
||||
})
|
||||
} else {
|
||||
chatHistory.value.push({
|
||||
role: 'assistant',
|
||||
content:
|
||||
'根据最近的市场数据和技术指标分析,我注意到该代币的趋势如下:\n\n- 价格走势:呈现上升通道,但接近阻力位\n- 交易量:逐渐增加,表明兴趣上升\n- RSI指标:当前处于60左右,未进入超买区域\n- MACD:短期均线刚刚穿过长期均线,形成黄金交叉\n\n需要注意的是,市场可能受到宏观经济因素的影响。您希望了解更多具体的技术指标分析吗?',
|
||||
})
|
||||
}
|
||||
userInput.value = ''
|
||||
}, 1000)
|
||||
}
|
||||
// 滚动到底部以显示用户消息
|
||||
await scrollToBottom()
|
||||
|
||||
const switchAgent = (agent: string) => {
|
||||
activeAgent.value = agent
|
||||
chatHistory.value = [
|
||||
{
|
||||
role: 'assistant',
|
||||
content:
|
||||
agent === 'crypto-doctor'
|
||||
? '你好!我是加密项目医生,可以帮你分析任何加密项目的健康状况。请告诉我你想了解的项目名称或合约地址。'
|
||||
: '你好!我是币种技术分析师,可以为你提供币种的技术面分析和趋势预测。请告诉我你想分析的币种名称或代号。',
|
||||
},
|
||||
]
|
||||
const currentInput = userInput.value
|
||||
userInput.value = ''
|
||||
isLoading.value = true
|
||||
|
||||
// 添加临时助手消息用于流式显示
|
||||
chatHistory.value.push({
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
})
|
||||
|
||||
await scrollToBottom()
|
||||
|
||||
try {
|
||||
// 调用流式接口
|
||||
const response = await fetch(`${apiBaseUrl}/agent/chat`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user_prompt: currentInput,
|
||||
}),
|
||||
})
|
||||
|
||||
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 })
|
||||
responseText += chunk
|
||||
|
||||
// 更新最后一条消息(助手的回复)
|
||||
const lastIndex = chatHistory.value.length - 1
|
||||
chatHistory.value[lastIndex].content = responseText
|
||||
|
||||
// 滚动到底部以跟随新内容
|
||||
await scrollToBottom()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('调用API出错:', error)
|
||||
// 更新最后一条消息显示错误
|
||||
const lastIndex = chatHistory.value.length - 1
|
||||
chatHistory.value[lastIndex].content = '抱歉,请求出错了。请稍后再试。'
|
||||
await scrollToBottom()
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="ai-agent-view">
|
||||
<h1 class="page-title">AI Agent</h1>
|
||||
<p class="page-description">智能AI助手,为您提供加密项目分析和技术指标解读</p>
|
||||
<p class="page-description">为您提供全面区块链信息查询和专业分析</p>
|
||||
|
||||
<div class="agent-container">
|
||||
<div class="agent-sidebar">
|
||||
<div class="agent-selector">
|
||||
<button
|
||||
class="agent-option"
|
||||
:class="{ active: activeAgent === 'crypto-doctor' }"
|
||||
@click="switchAgent('crypto-doctor')"
|
||||
>
|
||||
<span class="agent-icon">🔍</span>
|
||||
<div class="agent-info">
|
||||
<h3>加密项目医生</h3>
|
||||
<p>项目风险分析与评估</p>
|
||||
</div>
|
||||
</button>
|
||||
<div class="chat-container">
|
||||
<div class="chat-header">
|
||||
<h2>区块链智能AI助手</h2>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="agent-option"
|
||||
:class="{ active: activeAgent === 'technical-analysis' }"
|
||||
@click="switchAgent('technical-analysis')"
|
||||
>
|
||||
<span class="agent-icon">📊</span>
|
||||
<div class="agent-info">
|
||||
<h3>币种技术分析</h3>
|
||||
<p>技术指标与趋势预测</p>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-info">
|
||||
<h3>使用提示</h3>
|
||||
<ul class="tips-list">
|
||||
<li>提供详细的项目信息以获得更准确的分析</li>
|
||||
<li>可以直接输入合约地址进行分析</li>
|
||||
<li>分析结果仅供参考,不构成投资建议</li>
|
||||
</ul>
|
||||
<div ref="messagesContainer" class="chat-messages">
|
||||
<div
|
||||
v-for="(message, index) in chatHistory"
|
||||
:key="index"
|
||||
class="message"
|
||||
:class="{
|
||||
'user-message': message.role === 'user',
|
||||
'ai-message': message.role === 'assistant',
|
||||
}"
|
||||
>
|
||||
<div class="message-content">
|
||||
<p v-for="(line, i) in message.content.split('\n')" :key="i">
|
||||
{{
|
||||
line ||
|
||||
(message.role === 'assistant' && isLoading && index === chatHistory.length - 1
|
||||
? '...'
|
||||
: line)
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chat-container">
|
||||
<div class="chat-header">
|
||||
<h2>{{ activeAgent === 'crypto-doctor' ? '加密项目医生' : '币种技术分析' }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="chat-messages">
|
||||
<div
|
||||
v-for="(message, index) in chatHistory"
|
||||
:key="index"
|
||||
class="message"
|
||||
:class="{
|
||||
'user-message': message.role === 'user',
|
||||
'ai-message': message.role === 'assistant',
|
||||
}"
|
||||
>
|
||||
<div class="message-content">
|
||||
<p v-for="(line, i) in message.content.split('\n')" :key="i">
|
||||
{{ line }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chat-input">
|
||||
<input
|
||||
type="text"
|
||||
v-model="userInput"
|
||||
@keyup.enter="sendMessage"
|
||||
placeholder="输入项目名称、合约地址或问题..."
|
||||
class="input-field"
|
||||
/>
|
||||
<button class="send-button" @click="sendMessage">发送</button>
|
||||
</div>
|
||||
<div class="chat-input">
|
||||
<input
|
||||
type="text"
|
||||
v-model="userInput"
|
||||
@keyup.enter="sendMessage"
|
||||
placeholder="输入您的问题..."
|
||||
class="input-field"
|
||||
:disabled="isLoading"
|
||||
/>
|
||||
<button class="send-button" @click="sendMessage" :disabled="isLoading">
|
||||
{{ isLoading ? '加载中...' : '发送' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -151,112 +164,16 @@ const switchAgent = (agent: string) => {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.agent-container {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
height: calc(80vh - 100px);
|
||||
min-height: 500px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.agent-sidebar {
|
||||
width: 300px;
|
||||
background-color: var(--color-bg-card);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.agent-selector {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.agent-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.8rem;
|
||||
padding: 1rem;
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--color-bg-primary);
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.agent-option:hover {
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
.agent-option.active {
|
||||
border-color: var(--color-accent);
|
||||
background-color: var(--color-accent-light);
|
||||
}
|
||||
|
||||
.agent-icon {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.agent-info h3 {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.2rem;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.agent-info p {
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.agent-option.active .agent-info h3 {
|
||||
color: var(--color-accent-hover);
|
||||
}
|
||||
|
||||
.sidebar-info {
|
||||
margin-top: auto;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.sidebar-info h3 {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
.tips-list {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tips-list li {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.5rem;
|
||||
padding-left: 1rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tips-list li::before {
|
||||
content: '•';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--color-bg-card);
|
||||
border-radius: var(--border-radius);
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-border);
|
||||
height: calc(80vh - 150px);
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
@ -276,6 +193,26 @@ const switchAgent = (agent: string) => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* 自定义滚动条样式 - 深色系 */
|
||||
.chat-messages::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.chat-messages::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.chat-messages::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.chat-messages::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.message {
|
||||
@ -287,8 +224,9 @@ const switchAgent = (agent: string) => {
|
||||
|
||||
.user-message {
|
||||
align-self: flex-end;
|
||||
background-color: var(--color-accent);
|
||||
color: white;
|
||||
background-color: #3355ff;
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.ai-message {
|
||||
@ -328,8 +266,8 @@ const switchAgent = (agent: string) => {
|
||||
}
|
||||
|
||||
.send-button {
|
||||
background-color: var(--color-accent);
|
||||
color: var(--color-bg-primary);
|
||||
background-color: #3355ff;
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: var(--border-radius);
|
||||
padding: 0 1.2rem;
|
||||
@ -340,71 +278,17 @@ const switchAgent = (agent: string) => {
|
||||
}
|
||||
|
||||
.send-button:hover {
|
||||
background-color: var(--color-accent-hover);
|
||||
background-color: #2233cc;
|
||||
}
|
||||
|
||||
.btn-connect,
|
||||
.btn-primary,
|
||||
.btn-secondary {
|
||||
color: var(--color-bg-primary);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.agent-container {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.agent-sidebar {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
flex-basis: calc(100% - 270px);
|
||||
}
|
||||
.send-button:disabled {
|
||||
background-color: #8899dd;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.agent-container {
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.agent-sidebar {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.agent-selector {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.agent-option {
|
||||
flex: 1;
|
||||
min-width: 140px;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.agent-info h3 {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.agent-info p {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar-info {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
height: 70vh;
|
||||
flex-basis: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="stats-section">
|
||||
<!-- <section class="stats-section">
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card card">
|
||||
<div class="stat-value">1000+</div>
|
||||
@ -35,7 +35,7 @@
|
||||
<div class="stat-label">正常运行时间</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user