This commit is contained in:
aaron 2025-04-29 23:18:00 +08:00
parent 9b46bb20c9
commit 6ce4c77e3b
3 changed files with 153 additions and 269 deletions

View File

@ -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>

View File

@ -1,18 +1,29 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref, nextTick } from 'vue'
// APIURL
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,
}) })
// AIAPI //
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>

View File

@ -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>