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" />
|
<img src="@/assets/logo.png" alt="Crypto.AI Logo" class="logo-image" />
|
||||||
Crypto.AI
|
Crypto.AI
|
||||||
</div>
|
</div>
|
||||||
<!-- <nav class="main-nav">
|
<nav class="main-nav">
|
||||||
<RouterLink to="/" class="nav-link">首页</RouterLink>
|
<RouterLink to="/" class="nav-link">首页</RouterLink>
|
||||||
<RouterLink to="/ai-agent" class="nav-link">AI Agent</RouterLink>
|
<RouterLink to="/ai-agent" class="nav-link">AI Agent</RouterLink>
|
||||||
<RouterLink to="/tools" class="nav-link">工具集合</RouterLink>
|
<!-- <RouterLink to="/tools" class="nav-link">工具集合</RouterLink> -->
|
||||||
</nav> -->
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
@ -1,18 +1,29 @@
|
|||||||
<script setup lang="ts">
|
<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 userInput = ref('')
|
||||||
const chatHistory = ref([
|
const chatHistory = ref([
|
||||||
{
|
{
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
content:
|
content: '你好!我是区块链智能AI助手,可以回答你的任何关于区块链的问题。请告诉我你想了解什么?',
|
||||||
'你好!我是加密项目医生,可以帮你分析任何加密项目的健康状况。请告诉我你想了解的项目名称或合约地址。',
|
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const messagesContainer = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
const sendMessage = () => {
|
const scrollToBottom = async () => {
|
||||||
if (!userInput.value.trim()) return
|
await nextTick()
|
||||||
|
if (messagesContainer.value) {
|
||||||
|
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendMessage = async () => {
|
||||||
|
if (!userInput.value.trim() || isLoading.value) return
|
||||||
|
|
||||||
// 添加用户消息到历史记录
|
// 添加用户消息到历史记录
|
||||||
chatHistory.value.push({
|
chatHistory.value.push({
|
||||||
@ -20,115 +31,117 @@ const sendMessage = () => {
|
|||||||
content: userInput.value,
|
content: userInput.value,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 模拟AI回复(实际项目中这里应该调用后端API)
|
// 滚动到底部以显示用户消息
|
||||||
setTimeout(() => {
|
await scrollToBottom()
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
const switchAgent = (agent: string) => {
|
const currentInput = userInput.value
|
||||||
activeAgent.value = agent
|
userInput.value = ''
|
||||||
chatHistory.value = [
|
isLoading.value = true
|
||||||
{
|
|
||||||
role: 'assistant',
|
// 添加临时助手消息用于流式显示
|
||||||
content:
|
chatHistory.value.push({
|
||||||
agent === 'crypto-doctor'
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="ai-agent-view">
|
<div class="ai-agent-view">
|
||||||
<h1 class="page-title">AI Agent</h1>
|
<h1 class="page-title">AI Agent</h1>
|
||||||
<p class="page-description">智能AI助手,为您提供加密项目分析和技术指标解读</p>
|
<p class="page-description">为您提供全面区块链信息查询和专业分析</p>
|
||||||
|
|
||||||
<div class="agent-container">
|
<div class="chat-container">
|
||||||
<div class="agent-sidebar">
|
<div class="chat-header">
|
||||||
<div class="agent-selector">
|
<h2>区块链智能AI助手</h2>
|
||||||
<button
|
</div>
|
||||||
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>
|
|
||||||
|
|
||||||
<button
|
<div ref="messagesContainer" class="chat-messages">
|
||||||
class="agent-option"
|
<div
|
||||||
:class="{ active: activeAgent === 'technical-analysis' }"
|
v-for="(message, index) in chatHistory"
|
||||||
@click="switchAgent('technical-analysis')"
|
:key="index"
|
||||||
>
|
class="message"
|
||||||
<span class="agent-icon">📊</span>
|
:class="{
|
||||||
<div class="agent-info">
|
'user-message': message.role === 'user',
|
||||||
<h3>币种技术分析</h3>
|
'ai-message': message.role === 'assistant',
|
||||||
<p>技术指标与趋势预测</p>
|
}"
|
||||||
</div>
|
>
|
||||||
</button>
|
<div class="message-content">
|
||||||
</div>
|
<p v-for="(line, i) in message.content.split('\n')" :key="i">
|
||||||
|
{{
|
||||||
<div class="sidebar-info">
|
line ||
|
||||||
<h3>使用提示</h3>
|
(message.role === 'assistant' && isLoading && index === chatHistory.length - 1
|
||||||
<ul class="tips-list">
|
? '...'
|
||||||
<li>提供详细的项目信息以获得更准确的分析</li>
|
: line)
|
||||||
<li>可以直接输入合约地址进行分析</li>
|
}}
|
||||||
<li>分析结果仅供参考,不构成投资建议</li>
|
</p>
|
||||||
</ul>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chat-container">
|
<div class="chat-input">
|
||||||
<div class="chat-header">
|
<input
|
||||||
<h2>{{ activeAgent === 'crypto-doctor' ? '加密项目医生' : '币种技术分析' }}</h2>
|
type="text"
|
||||||
</div>
|
v-model="userInput"
|
||||||
|
@keyup.enter="sendMessage"
|
||||||
<div class="chat-messages">
|
placeholder="输入您的问题..."
|
||||||
<div
|
class="input-field"
|
||||||
v-for="(message, index) in chatHistory"
|
:disabled="isLoading"
|
||||||
:key="index"
|
/>
|
||||||
class="message"
|
<button class="send-button" @click="sendMessage" :disabled="isLoading">
|
||||||
:class="{
|
{{ isLoading ? '加载中...' : '发送' }}
|
||||||
'user-message': message.role === 'user',
|
</button>
|
||||||
'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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -151,112 +164,16 @@ const switchAgent = (agent: string) => {
|
|||||||
margin-bottom: 2rem;
|
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 {
|
.chat-container {
|
||||||
flex: 1;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background-color: var(--color-bg-card);
|
background-color: var(--color-bg-card);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
|
height: calc(80vh - 150px);
|
||||||
|
min-height: 500px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-header {
|
.chat-header {
|
||||||
@ -276,6 +193,26 @@ const switchAgent = (agent: string) => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
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 {
|
.message {
|
||||||
@ -287,8 +224,9 @@ const switchAgent = (agent: string) => {
|
|||||||
|
|
||||||
.user-message {
|
.user-message {
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
background-color: var(--color-accent);
|
background-color: #3355ff;
|
||||||
color: white;
|
color: #ffffff;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-message {
|
.ai-message {
|
||||||
@ -328,8 +266,8 @@ const switchAgent = (agent: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.send-button {
|
.send-button {
|
||||||
background-color: var(--color-accent);
|
background-color: #3355ff;
|
||||||
color: var(--color-bg-primary);
|
color: #ffffff;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
padding: 0 1.2rem;
|
padding: 0 1.2rem;
|
||||||
@ -340,71 +278,17 @@ const switchAgent = (agent: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.send-button:hover {
|
.send-button:hover {
|
||||||
background-color: var(--color-accent-hover);
|
background-color: #2233cc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-connect,
|
.send-button:disabled {
|
||||||
.btn-primary,
|
background-color: #8899dd;
|
||||||
.btn-secondary {
|
cursor: not-allowed;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@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 {
|
.chat-container {
|
||||||
height: 70vh;
|
height: 70vh;
|
||||||
flex-basis: 100%;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="stats-section">
|
<!-- <section class="stats-section">
|
||||||
<div class="stats-grid">
|
<div class="stats-grid">
|
||||||
<div class="stat-card card">
|
<div class="stat-card card">
|
||||||
<div class="stat-value">1000+</div>
|
<div class="stat-value">1000+</div>
|
||||||
@ -35,7 +35,7 @@
|
|||||||
<div class="stat-label">正常运行时间</div>
|
<div class="stat-label">正常运行时间</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section> -->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user