Compare commits
9 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
90a55d8d5d | |
|
|
d5d1343d01 | |
|
|
20956731ed | |
|
|
7a71a2147b | |
|
|
9cc280a892 | |
|
|
3d62957fed | |
|
|
38710b89ed | |
|
|
c350fbb38b | |
|
|
20d4048543 |
|
|
@ -1,5 +1,7 @@
|
||||||
from fastapi import FastAPI, Depends, HTTPException, BackgroundTasks
|
from fastapi import FastAPI, Depends, HTTPException, BackgroundTasks
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
from fastapi.responses import FileResponse
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.future import select
|
from sqlalchemy.future import select
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
@ -1140,3 +1142,60 @@ async def update_ai_config(config: LLMConfig):
|
||||||
"provider": config.provider,
|
"provider": config.provider,
|
||||||
"requires_restart": True
|
"requires_restart": True
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# 前端静态文件服务
|
||||||
|
FRONTEND_DIR = Path("/data/neural-sync/frontend/dist")
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
async def serve_frontend():
|
||||||
|
"""服务前端首页"""
|
||||||
|
index_path = FRONTEND_DIR / "index.html"
|
||||||
|
if index_path.exists():
|
||||||
|
return FileResponse(index_path)
|
||||||
|
return {"message": "Frontend not found", "frontend_dir": str(FRONTEND_DIR)}
|
||||||
|
|
||||||
|
@app.get("/{path:path}")
|
||||||
|
async def serve_frontend_paths(path: str):
|
||||||
|
"""服务前端其他路由"""
|
||||||
|
# 先尝试直接文件
|
||||||
|
file_path = FRONTEND_DIR / path
|
||||||
|
if file_path.exists() and file_path.is_file():
|
||||||
|
return FileResponse(file_path)
|
||||||
|
# 回退到 index.html (SPA 路由)
|
||||||
|
return FileResponse(FRONTEND_DIR / "index.html")
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/admin/build-frontend")
|
||||||
|
async def build_frontend(background_tasks: BackgroundTasks):
|
||||||
|
"""构建前端 (仅用于部署)"""
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
frontend_path = FRONTEND_DIR.parent
|
||||||
|
|
||||||
|
async def run_build():
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["npm", "install"],
|
||||||
|
cwd=str(frontend_path),
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=300
|
||||||
|
)
|
||||||
|
result = subprocess.run(
|
||||||
|
["npm", "run", "build"],
|
||||||
|
cwd=str(frontend_path),
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=300
|
||||||
|
)
|
||||||
|
return result.returncode == 0, result.stdout, result.stderr
|
||||||
|
except Exception as e:
|
||||||
|
return False, "", str(e)
|
||||||
|
|
||||||
|
success, stdout, stderr = await run_build()
|
||||||
|
|
||||||
|
if success:
|
||||||
|
return {"status": "success", "message": "前端构建成功"}
|
||||||
|
else:
|
||||||
|
return {"status": "error", "message": stderr or "构建失败"}
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,18 @@ import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from .database import Base
|
from .database import Base
|
||||||
|
|
||||||
# Compatibility layer for SQLite
|
# Compatibility layer for SQLite and PostgreSQL without pgvector
|
||||||
try:
|
try:
|
||||||
from sqlalchemy.dialects.postgresql import UUID, ARRAY
|
from sqlalchemy.dialects.postgresql import UUID, ARRAY
|
||||||
from pgvector.sqlalchemy import Vector
|
|
||||||
# Check if we are using SQLite
|
|
||||||
import os
|
import os
|
||||||
if "sqlite" in os.getenv("DATABASE_URL", ""):
|
if "sqlite" in os.getenv("DATABASE_URL", ""):
|
||||||
raise ImportError("Using SQLite")
|
raise ImportError("Using SQLite")
|
||||||
|
# Try to import pgvector, if fails use JSON
|
||||||
|
try:
|
||||||
|
from pgvector.sqlalchemy import Vector
|
||||||
|
except ImportError:
|
||||||
|
# No pgvector, use JSON instead
|
||||||
|
Vector = JSON
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from sqlalchemy.types import TypeDecorator, CHAR
|
from sqlalchemy.types import TypeDecorator, CHAR
|
||||||
import json
|
import json
|
||||||
|
|
@ -38,7 +42,6 @@ except ImportError:
|
||||||
if value is None: return None
|
if value is None: return None
|
||||||
return json.loads(value)
|
return json.loads(value)
|
||||||
|
|
||||||
# Use JSON for ARRAY in SQLite
|
|
||||||
ARRAY = JSON
|
ARRAY = JSON
|
||||||
|
|
||||||
class ContextNode(Base):
|
class ContextNode(Base):
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ const extractMetadata = (content: string) => {
|
||||||
|
|
||||||
// 1. Extract Title (First H1 or first non-empty line)
|
// 1. Extract Title (First H1 or first non-empty line)
|
||||||
const titleMatch = content.match(/^#\s+(.*)/m)
|
const titleMatch = content.match(/^#\s+(.*)/m)
|
||||||
if (titleMatch) {
|
if (titleMatch && titleMatch[1]) {
|
||||||
title = titleMatch[1].trim()
|
title = titleMatch[1].trim()
|
||||||
} else {
|
} else {
|
||||||
const firstLine = lines.find(l => l.trim().length > 0)
|
const firstLine = lines.find(l => l.trim().length > 0)
|
||||||
|
|
@ -136,12 +136,12 @@ const extractMetadata = (content: string) => {
|
||||||
// Look for "Core Logic", "Architecture", "Introduction" headers
|
// Look for "Core Logic", "Architecture", "Introduction" headers
|
||||||
const logicRegex = /##\s*(Core Logic|Architecture|Introduction|About|Features)([\s\S]*?)(##|$)/i
|
const logicRegex = /##\s*(Core Logic|Architecture|Introduction|About|Features)([\s\S]*?)(##|$)/i
|
||||||
const logicMatch = content.match(logicRegex)
|
const logicMatch = content.match(logicRegex)
|
||||||
if (logicMatch) {
|
if (logicMatch && logicMatch[2]) {
|
||||||
coreLogic = logicMatch[2].trim().substring(0, 300) + (logicMatch[2].length > 300 ? '...' : '')
|
coreLogic = logicMatch[2].trim().substring(0, 300) + (logicMatch[2].length > 300 ? '...' : '')
|
||||||
} else {
|
} else {
|
||||||
// Fallback: First paragraph after title
|
// Fallback: First paragraph after title
|
||||||
const paragraphs = content.split(/\n\s*\n/)
|
const paragraphs = content.split(/\n\s*\n/)
|
||||||
if (paragraphs.length > 1) {
|
if (paragraphs.length > 1 && paragraphs[1]) {
|
||||||
coreLogic = paragraphs[1].trim().substring(0, 200) + '...'
|
coreLogic = paragraphs[1].trim().substring(0, 200) + '...'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -169,7 +169,7 @@ const parsedNodes = computed(() => {
|
||||||
|
|
||||||
const fetchNodes = async () => {
|
const fetchNodes = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await axios.get('http://127.0.0.1:8000/context_nodes/')
|
const res = await axios.get('/context_nodes/')
|
||||||
nodes.value = res.data
|
nodes.value = res.data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('获取认知节点失败')
|
message.error('获取认知节点失败')
|
||||||
|
|
@ -178,7 +178,7 @@ const fetchNodes = async () => {
|
||||||
|
|
||||||
const createNode = async () => {
|
const createNode = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await axios.post('http://127.0.0.1:8000/context_nodes/', formValue.value)
|
const res = await axios.post('/context_nodes/', formValue.value)
|
||||||
message.success('认知已提交!')
|
message.success('认知已提交!')
|
||||||
nodes.value.push(res.data)
|
nodes.value.push(res.data)
|
||||||
showModal.value = false
|
showModal.value = false
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ const getStatusLabel = (status: string) => {
|
||||||
|
|
||||||
const fetchExperiments = async () => {
|
const fetchExperiments = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await axios.get('http://127.0.0.1:8000/experiments/')
|
const res = await axios.get('/experiments/')
|
||||||
experiments.value = res.data
|
experiments.value = res.data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('获取实验列表失败')
|
message.error('获取实验列表失败')
|
||||||
|
|
@ -106,7 +106,7 @@ const fetchExperiments = async () => {
|
||||||
|
|
||||||
const fetchContextNodes = async () => {
|
const fetchContextNodes = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await axios.get('http://127.0.0.1:8000/context_nodes/')
|
const res = await axios.get('/context_nodes/')
|
||||||
contextNodes.value = res.data
|
contextNodes.value = res.data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch context nodes')
|
console.error('Failed to fetch context nodes')
|
||||||
|
|
@ -120,7 +120,7 @@ const openModal = () => {
|
||||||
|
|
||||||
const createExperiment = async () => {
|
const createExperiment = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await axios.post('http://127.0.0.1:8000/experiments/', formValue.value)
|
const res = await axios.post('/experiments/', formValue.value)
|
||||||
message.success('实验已启动!')
|
message.success('实验已启动!')
|
||||||
experiments.value.push(res.data)
|
experiments.value.push(res.data)
|
||||||
showModal.value = false
|
showModal.value = false
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 选中提供商说明 -->
|
<!-- 选中提供商说明 -->
|
||||||
<div class="provider-info">
|
<div class="provider-info" v-if="selectedProvider">
|
||||||
<span class="provider-desc">{{ selectedProvider.desc }}</span>
|
<span class="provider-desc">{{ selectedProvider.desc }}</span>
|
||||||
<span class="provider-pricing">{{ selectedProvider.pricing }}</span>
|
<span class="provider-pricing">{{ selectedProvider.pricing }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -63,7 +63,7 @@
|
||||||
<h2 class="section-title">连接配置</h2>
|
<h2 class="section-title">连接配置</h2>
|
||||||
|
|
||||||
<!-- API 密钥配置 -->
|
<!-- API 密钥配置 -->
|
||||||
<div v-if="showApiKey" class="form-group">
|
<div v-if="showApiKey && selectedProvider" class="form-group">
|
||||||
<label class="form-label">API 密钥</label>
|
<label class="form-label">API 密钥</label>
|
||||||
<input
|
<input
|
||||||
v-model="apiKey"
|
v-model="apiKey"
|
||||||
|
|
@ -161,13 +161,6 @@ const KeyIcon = () => h('svg', { viewBox: '0 0 24 24', fill: 'none', stroke: 'cu
|
||||||
h('path', { d: 'M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4' })
|
h('path', { d: 'M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4' })
|
||||||
])
|
])
|
||||||
|
|
||||||
const ServerIcon = () => h('svg', { viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', 'stroke-width': 1.5 }, [
|
|
||||||
h('rect', { x: '2', y: '2', width: '20', height: '8', rx: '2', ry: '2' }),
|
|
||||||
h('rect', { x: '2', y: '14', width: '20', height: '8', rx: '2', ry: '2' }),
|
|
||||||
h('line', { x1: '6', y1: '6', x2: '6.01', y2: '6' }),
|
|
||||||
h('line', { x1: '6', y1: '18', x2: '6.01', y2: '18' })
|
|
||||||
])
|
|
||||||
|
|
||||||
// 侧边栏导航
|
// 侧边栏导航
|
||||||
const navItems = [
|
const navItems = [
|
||||||
{ key: 'provider', label: '模型服务', icon: CloudIcon },
|
{ key: 'provider', label: '模型服务', icon: CloudIcon },
|
||||||
|
|
@ -239,7 +232,7 @@ const selectProvider = (value: string) => {
|
||||||
const testConnection = async () => {
|
const testConnection = async () => {
|
||||||
testing.value = true
|
testing.value = true
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`${config.ollama_host.replace(':11434', ':8000')}/ai/status`, { timeout: 5000 })
|
const response = await axios.get(`/ai/status`, { timeout: 5000 })
|
||||||
if (response.data.status === 'ready') {
|
if (response.data.status === 'ready') {
|
||||||
isConnected.value = true
|
isConnected.value = true
|
||||||
statusText.value = `${response.data.model_count} 个模型`
|
statusText.value = `${response.data.model_count} 个模型`
|
||||||
|
|
@ -258,7 +251,7 @@ const testConnection = async () => {
|
||||||
|
|
||||||
const loadConfig = async () => {
|
const loadConfig = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get('http://127.0.0.1:8000/ai/config')
|
const response = await axios.get('/ai/config')
|
||||||
config.provider = response.data.provider
|
config.provider = response.data.provider
|
||||||
config.ollama_host = response.data.ollama_host
|
config.ollama_host = response.data.ollama_host
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
@ -267,7 +260,7 @@ const loadConfig = async () => {
|
||||||
const saveConfig = async () => {
|
const saveConfig = async () => {
|
||||||
saving.value = true
|
saving.value = true
|
||||||
try {
|
try {
|
||||||
await axios.post('http://127.0.0.1:8000/ai/config', {
|
await axios.post('/ai/config', {
|
||||||
provider: config.provider,
|
provider: config.provider,
|
||||||
deepseek_api_key: config.deepseek_api_key || undefined,
|
deepseek_api_key: config.deepseek_api_key || undefined,
|
||||||
openai_api_key: config.openai_api_key || undefined,
|
openai_api_key: config.openai_api_key || undefined,
|
||||||
|
|
@ -291,7 +284,7 @@ const resetConfig = () => {
|
||||||
|
|
||||||
const restartBackend = async () => {
|
const restartBackend = async () => {
|
||||||
try {
|
try {
|
||||||
await axios.post('http://127.0.0.1:8000/ai/stop')
|
await axios.post('/ai/stop')
|
||||||
saveStatus.value = '后端已停止,请手动启动'
|
saveStatus.value = '后端已停止,请手动启动'
|
||||||
showRestartModal.value = false
|
showRestartModal.value = false
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,11 @@
|
||||||
<div class="progress-comparison">
|
<div class="progress-comparison">
|
||||||
<div class="progress-row">
|
<div class="progress-row">
|
||||||
<span class="label">我方</span>
|
<span class="label">我方</span>
|
||||||
<n-progress type="line" :percentage="getOurProgress(selectedSignal)" color="#63e2b7" rail-color="rgba(255,255,255,0.1)" height="6" processing />
|
<n-progress type="line" :percentage="Number(getOurProgress(selectedSignal))" color="#63e2b7" rail-color="rgba(255,255,255,0.1)" :height="6" processing />
|
||||||
</div>
|
</div>
|
||||||
<div class="progress-row">
|
<div class="progress-row">
|
||||||
<span class="label">竞品</span>
|
<span class="label">竞品</span>
|
||||||
<n-progress type="line" :percentage="getCompetitorProgress(selectedSignal)" color="#f5222d" rail-color="rgba(255,255,255,0.1)" height="6" />
|
<n-progress type="line" :percentage="Number(getCompetitorProgress(selectedSignal))" color="#f5222d" rail-color="rgba(255,255,255,0.1)" :height="6" />
|
||||||
</div>
|
</div>
|
||||||
<div class="lead-time-text" v-if="selectedSignal">
|
<div class="lead-time-text" v-if="selectedSignal">
|
||||||
{{ getLeadTimeText(selectedSignal) }}
|
{{ getLeadTimeText(selectedSignal) }}
|
||||||
|
|
@ -134,18 +134,16 @@
|
||||||
'new-signal-flash': isNewSignal(signal.latestSignal)
|
'new-signal-flash': isNewSignal(signal.latestSignal)
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template #header>
|
<div class="signal-item-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
||||||
<div style="display: flex; align-items: center; gap: 6px;">
|
<div style="display: flex; align-items: center; gap: 6px;">
|
||||||
<n-tag :type="getPriorityType(signal.latestSignal.priority)" size="small" round>
|
<n-tag :type="getPriorityType(signal.latestSignal?.priority || 'medium')" size="small" round>
|
||||||
{{ getPriorityLabel(signal.latestSignal.priority) }}
|
{{ getPriorityLabel(signal.latestSignal?.priority || 'medium') }}
|
||||||
</n-tag>
|
</n-tag>
|
||||||
<n-tag :type="getImpactColor(signal.impact_score)" size="small" round>{{ signal.source }}</n-tag>
|
<n-tag :type="getImpactColor(signal.impact_score)" size="small" round>{{ signal.source }}</n-tag>
|
||||||
<n-tag v-if="signal.count > 1" type="info" size="tiny" round>{{ signal.count }}条</n-tag>
|
<n-tag v-if="signal.count > 1" type="info" size="tiny" round>{{ signal.count }}条</n-tag>
|
||||||
</div>
|
</div>
|
||||||
<n-text depth="3" style="font-size: 12px;">{{ formatDate(signal.created_at) }}</n-text>
|
<n-text depth="3" style="font-size: 12px;">{{ formatDate(signal.created_at) }}</n-text>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
<n-text class="signal-content" :depth="1">{{ signal.content }}</n-text>
|
<n-text class="signal-content" :depth="1">{{ signal.content }}</n-text>
|
||||||
</n-list-item>
|
</n-list-item>
|
||||||
</n-list>
|
</n-list>
|
||||||
|
|
@ -493,7 +491,7 @@
|
||||||
:percentage="getTaskProgress(task)"
|
:percentage="getTaskProgress(task)"
|
||||||
:color="getTaskTypeColor(task.type, true)"
|
:color="getTaskTypeColor(task.type, true)"
|
||||||
rail-color="rgba(255,255,255,0.1)"
|
rail-color="rgba(255,255,255,0.1)"
|
||||||
height="4"
|
:height="4"
|
||||||
:show-indicator="false"
|
:show-indicator="false"
|
||||||
style="margin-top: 8px;"
|
style="margin-top: 8px;"
|
||||||
:class="{ 'progress-pulse': taskExecuting }"
|
:class="{ 'progress-pulse': taskExecuting }"
|
||||||
|
|
@ -543,7 +541,7 @@
|
||||||
<n-modal v-model:show="showCompetitorModal">
|
<n-modal v-model:show="showCompetitorModal">
|
||||||
<n-card style="width: 400px" title="⚔️ 设定主要竞品(某漫、某画等)" :bordered="false" size="large" role="dialog" aria-modal="true" class="glass-modal">
|
<n-card style="width: 400px" title="⚔️ 设定主要竞品(某漫、某画等)" :bordered="false" size="large" role="dialog" aria-modal="true" class="glass-modal">
|
||||||
<n-space vertical>
|
<n-space vertical>
|
||||||
<n-input v-for="(comp, index) in competitors" :key="index" v-model:value="competitors[index]" placeholder="输入竞品名称..." />
|
<n-input v-for="(_comp, index) in competitors" :key="index" v-model:value="competitors[index]" placeholder="输入竞品名称..." />
|
||||||
</n-space>
|
</n-space>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<n-button type="primary" block @click="saveCompetitors">
|
<n-button type="primary" block @click="saveCompetitors">
|
||||||
|
|
@ -573,11 +571,11 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, reactive, watch, nextTick, computed } from 'vue'
|
import { ref, onMounted, onUnmounted, reactive, watch, computed } from 'vue'
|
||||||
import {
|
import {
|
||||||
NLayout, NLayoutSider, NLayoutContent, NSpace, NCard, NButton, NList, NListItem, NTag, NText,
|
NLayout, NLayoutSider, NLayoutContent, NSpace, NCard, NButton, NList, NListItem, NTag, NText,
|
||||||
NGrid, NGridItem, NDivider, NResult, NCheckbox, NThing, NScrollbar, NSpin, NModal, NForm, NFormItem,
|
NGrid, NGridItem, NScrollbar, NSpin, NModal, NForm, NFormItem,
|
||||||
NInput, useMessage, NEmpty, NProgress, NIcon, NSteps, NStep, NStatistic
|
NInput, useMessage, NProgress, NIcon, NSteps, NStep, NStatistic
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import VChart from 'vue-echarts'
|
import VChart from 'vue-echarts'
|
||||||
|
|
@ -859,12 +857,14 @@ const factSnippets: Record<string, { title: string; source: string; content: str
|
||||||
|
|
||||||
const showFactSnippetModal = (type: string) => {
|
const showFactSnippetModal = (type: string) => {
|
||||||
const snippet = factSnippets[type] || factSnippets['Whitepaper']
|
const snippet = factSnippets[type] || factSnippets['Whitepaper']
|
||||||
|
if (snippet) {
|
||||||
currentFactSnippet.value = {
|
currentFactSnippet.value = {
|
||||||
title: snippet.title,
|
title: snippet.title,
|
||||||
source: snippet.source,
|
source: snippet.source,
|
||||||
content: snippet.content
|
content: snippet.content
|
||||||
}
|
}
|
||||||
showFactSnippet.value = true
|
showFactSnippet.value = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Logic ---
|
// --- Logic ---
|
||||||
|
|
@ -947,8 +947,8 @@ const getRelatedKnowledgeNodes = () => {
|
||||||
return Array.isArray(knowledgeNodes) ? knowledgeNodes : []
|
return Array.isArray(knowledgeNodes) ? knowledgeNodes : []
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTechStackType = (category: string) => {
|
const getTechStackType = (category: string): 'default' | 'error' | 'success' | 'warning' | 'info' | 'primary' => {
|
||||||
const typeMap: Record<string, string> = {
|
const typeMap: Record<string, 'default' | 'error' | 'success' | 'warning' | 'info' | 'primary'> = {
|
||||||
framework: 'success',
|
framework: 'success',
|
||||||
model: 'error',
|
model: 'error',
|
||||||
tool: 'info',
|
tool: 'info',
|
||||||
|
|
@ -994,8 +994,8 @@ const getKnowledgeNodeIcon = (category: string) => {
|
||||||
return iconMap[category] || '🧠'
|
return iconMap[category] || '🧠'
|
||||||
}
|
}
|
||||||
|
|
||||||
const getKnowledgeNodeType = (category: string) => {
|
const getKnowledgeNodeType = (category: string): 'default' | 'error' | 'success' | 'warning' | 'info' | 'primary' => {
|
||||||
const typeMap: Record<string, string> = {
|
const typeMap: Record<string, 'default' | 'error' | 'success' | 'warning' | 'info' | 'primary'> = {
|
||||||
tech_stack: 'success',
|
tech_stack: 'success',
|
||||||
competitor: 'error',
|
competitor: 'error',
|
||||||
market: 'info'
|
market: 'info'
|
||||||
|
|
@ -1163,13 +1163,13 @@ const getPriorityLabel = (priority: string) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Voting helpers
|
// Voting helpers
|
||||||
const getModelVoteType = (modelName: string, strategicInfo: any) => {
|
const getModelVoteType = (_modelName: string, strategicInfo: any) => {
|
||||||
// For now, since we don't have individual model results in the response,
|
// For now, since we don't have individual model results in the response,
|
||||||
// we'll return a default type. This could be enhanced if we send individual results.
|
// we'll return a default type. This could be enhanced if we send individual results.
|
||||||
return strategicInfo.is_strategic ? 'success' : 'default'
|
return strategicInfo.is_strategic ? 'success' : 'default'
|
||||||
}
|
}
|
||||||
|
|
||||||
const getModelVoteLabel = (modelName: string, strategicInfo: any) => {
|
const getModelVoteLabel = (_modelName: string, strategicInfo: any) => {
|
||||||
// Similar to above, placeholder logic
|
// Similar to above, placeholder logic
|
||||||
return strategicInfo.is_strategic ? '战略级' : '非战略'
|
return strategicInfo.is_strategic ? '战略级' : '非战略'
|
||||||
}
|
}
|
||||||
|
|
@ -1186,7 +1186,7 @@ const submitFeedback = async (agreed: boolean) => {
|
||||||
const aiPrediction = currentStrategicInfo.value.is_strategic
|
const aiPrediction = currentStrategicInfo.value.is_strategic
|
||||||
const userFeedback = agreed ? aiPrediction : !aiPrediction
|
const userFeedback = agreed ? aiPrediction : !aiPrediction
|
||||||
|
|
||||||
const response = await axios.post(`http://127.0.0.1:8000/signals/${selectedSignal.value.id}/feedback`, {
|
const response = await axios.post(`/signals/${selectedSignal.value.id}/feedback`, {
|
||||||
signal_id: selectedSignal.value.id,
|
signal_id: selectedSignal.value.id,
|
||||||
was_strategic: userFeedback,
|
was_strategic: userFeedback,
|
||||||
reason: agreed ? 'AI判断准确' : 'AI判断不准确'
|
reason: agreed ? 'AI判断准确' : 'AI判断不准确'
|
||||||
|
|
@ -1447,7 +1447,7 @@ watch(selectedSignal, (newVal, oldVal) => {
|
||||||
// Load existing feedback for a signal
|
// Load existing feedback for a signal
|
||||||
const loadSignalFeedback = async (signalId: string) => {
|
const loadSignalFeedback = async (signalId: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`http://127.0.0.1:8000/signals/${signalId}/feedbacks`)
|
const response = await axios.get(`/signals/${signalId}/feedbacks`)
|
||||||
if (response.data && response.data.length > 0) {
|
if (response.data && response.data.length > 0) {
|
||||||
const latestFeedback = response.data[0]
|
const latestFeedback = response.data[0]
|
||||||
signalFeedback.value = latestFeedback
|
signalFeedback.value = latestFeedback
|
||||||
|
|
@ -1505,7 +1505,7 @@ const isNewSignal = (signal: any) => {
|
||||||
const fetchSignals = async (isPoll = false) => {
|
const fetchSignals = async (isPoll = false) => {
|
||||||
if (!isPoll) loading.value = true
|
if (!isPoll) loading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await axios.get('http://127.0.0.1:8000/signals/')
|
const res = await axios.get('/signals/')
|
||||||
const newSignals = res.data
|
const newSignals = res.data
|
||||||
|
|
||||||
// Check for task status updates if polling
|
// Check for task status updates if polling
|
||||||
|
|
@ -1579,7 +1579,7 @@ const submitSignal = async () => {
|
||||||
if (!formValue.value.content) return
|
if (!formValue.value.content) return
|
||||||
submitting.value = true
|
submitting.value = true
|
||||||
try {
|
try {
|
||||||
const res = await axios.post('http://127.0.0.1:8000/signals/ingest', formValue.value)
|
const res = await axios.post('/signals/ingest', formValue.value)
|
||||||
message.success('信号已注入,战略评估完成!')
|
message.success('信号已注入,战略评估完成!')
|
||||||
showModal.value = false
|
showModal.value = false
|
||||||
await fetchSignals()
|
await fetchSignals()
|
||||||
|
|
@ -1590,7 +1590,7 @@ const submitSignal = async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 停止 Ollama 节省资源
|
// 停止 Ollama 节省资源
|
||||||
await axios.post('http://127.0.0.1:8000/ai/stop')
|
await axios.post('/ai/stop')
|
||||||
ollamaReady.value = false
|
ollamaReady.value = false
|
||||||
message.info('🌱 Ollama 已停止,节省资源')
|
message.info('🌱 Ollama 已停止,节省资源')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -1603,7 +1603,7 @@ const submitSignal = async () => {
|
||||||
// 检查 AI 服务状态
|
// 检查 AI 服务状态
|
||||||
const checkOllamaStatus = async () => {
|
const checkOllamaStatus = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get('http://127.0.0.1:8000/ai/status', { timeout: 3000 })
|
const response = await axios.get('/ai/status', { timeout: 3000 })
|
||||||
|
|
||||||
const data = response.data
|
const data = response.data
|
||||||
providerType.value = data.provider_type || 'cloud'
|
providerType.value = data.provider_type || 'cloud'
|
||||||
|
|
@ -1617,7 +1617,6 @@ const checkOllamaStatus = async () => {
|
||||||
// 本地 Ollama
|
// 本地 Ollama
|
||||||
ollamaReady.value = data.status === 'ready'
|
ollamaReady.value = data.status === 'ready'
|
||||||
const modelCount = data.model_count || 0
|
const modelCount = data.model_count || 0
|
||||||
const models = data.available_models || []
|
|
||||||
|
|
||||||
if (ollamaReady.value) {
|
if (ollamaReady.value) {
|
||||||
message.success(`✅ Ollama 已就绪 (${modelCount} 个模型)`)
|
message.success(`✅ Ollama 已就绪 (${modelCount} 个模型)`)
|
||||||
|
|
@ -1635,7 +1634,7 @@ const checkOllamaStatus = async () => {
|
||||||
const startOllama = async () => {
|
const startOllama = async () => {
|
||||||
startingOllama.value = true
|
startingOllama.value = true
|
||||||
try {
|
try {
|
||||||
const response = await axios.post('http://127.0.0.1:8000/ai/start')
|
const response = await axios.post('/ai/start')
|
||||||
|
|
||||||
if (response.data.status === 'already_running') {
|
if (response.data.status === 'already_running') {
|
||||||
message.info('Ollama 已在运行')
|
message.info('Ollama 已在运行')
|
||||||
|
|
@ -1651,7 +1650,7 @@ const startOllama = async () => {
|
||||||
} else {
|
} else {
|
||||||
message.error(response.data.message)
|
message.error(response.data.message)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
message.error('启动失败: ' + (error.response?.data?.detail || error.message))
|
message.error('启动失败: ' + (error.response?.data?.detail || error.message))
|
||||||
} finally {
|
} finally {
|
||||||
startingOllama.value = false
|
startingOllama.value = false
|
||||||
|
|
@ -1661,7 +1660,7 @@ const startOllama = async () => {
|
||||||
// 停止本地 Ollama
|
// 停止本地 Ollama
|
||||||
const stopOllama = async () => {
|
const stopOllama = async () => {
|
||||||
try {
|
try {
|
||||||
await axios.post('http://127.0.0.1:8000/ai/stop')
|
await axios.post('/ai/stop')
|
||||||
ollamaReady.value = false
|
ollamaReady.value = false
|
||||||
message.success('✅ Ollama 已停止,内存已释放')
|
message.success('✅ Ollama 已停止,内存已释放')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -1692,7 +1691,7 @@ const startAnalysis = async () => {
|
||||||
|
|
||||||
// 调用后端获取并分析信号
|
// 调用后端获取并分析信号
|
||||||
analysisStatus.value = '正在获取 HackerNews 信号...'
|
analysisStatus.value = '正在获取 HackerNews 信号...'
|
||||||
const response = await axios.post('http://127.0.0.1:8000/signals/fetch')
|
const response = await axios.post('/signals/fetch')
|
||||||
|
|
||||||
clearInterval(progressInterval)
|
clearInterval(progressInterval)
|
||||||
analysisProgress.value = 100
|
analysisProgress.value = 100
|
||||||
|
|
@ -1709,7 +1708,7 @@ const startAnalysis = async () => {
|
||||||
// 仅本地 Ollama 需要停止节省资源
|
// 仅本地 Ollama 需要停止节省资源
|
||||||
if (providerType.value === 'local') {
|
if (providerType.value === 'local') {
|
||||||
analysisStatus.value = '正在停止 Ollama...'
|
analysisStatus.value = '正在停止 Ollama...'
|
||||||
await axios.post('http://127.0.0.1:8000/ai/stop')
|
await axios.post('/ai/stop')
|
||||||
ollamaReady.value = false
|
ollamaReady.value = false
|
||||||
analysisStatus.value = `完成!Ollama 已停止`
|
analysisStatus.value = `完成!Ollama 已停止`
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue