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" />
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>

View File

@ -1,18 +1,29 @@
<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 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,88 +31,83 @@ const sendMessage = () => {
content: userInput.value,
})
// AIAPI
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 = [
{
const currentInput = userInput.value
userInput.value = ''
isLoading.value = true
//
chatHistory.value.push({
role: 'assistant',
content:
agent === 'crypto-doctor'
? '你好!我是加密项目医生,可以帮你分析任何加密项目的健康状况。请告诉我你想了解的项目名称或合约地址。'
: '你好!我是币种技术分析师,可以为你提供币种的技术面分析和趋势预测。请告诉我你想分析的币种名称或代号。',
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>
<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>
<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>
</div>
<p class="page-description">为您提供全面区块链信息查询和专业分析</p>
<div class="chat-container">
<div class="chat-header">
<h2>{{ activeAgent === 'crypto-doctor' ? '加密项目医生' : '币种技术分析' }}</h2>
<h2>区块链智能AI助手</h2>
</div>
<div class="chat-messages">
<div ref="messagesContainer" class="chat-messages">
<div
v-for="(message, index) in chatHistory"
:key="index"
@ -113,7 +119,12 @@ const switchAgent = (agent: string) => {
>
<div class="message-content">
<p v-for="(line, i) in message.content.split('\n')" :key="i">
{{ line }}
{{
line ||
(message.role === 'assistant' && isLoading && index === chatHistory.length - 1
? '...'
: line)
}}
</p>
</div>
</div>
@ -124,11 +135,13 @@ const switchAgent = (agent: string) => {
type="text"
v-model="userInput"
@keyup.enter="sendMessage"
placeholder="输入项目名称、合约地址或问题..."
placeholder="输入您的问题..."
class="input-field"
:disabled="isLoading"
/>
<button class="send-button" @click="sendMessage">发送</button>
</div>
<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>

View File

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