Compare commits
No commits in common. "main" and "master" have entirely different histories.
|
|
@ -1,7 +1,5 @@
|
|||
from fastapi import FastAPI, Depends, HTTPException, BackgroundTasks
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.responses import FileResponse
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.future import select
|
||||
from typing import List
|
||||
|
|
@ -1142,60 +1140,3 @@ async def update_ai_config(config: LLMConfig):
|
|||
"provider": config.provider,
|
||||
"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,18 +4,14 @@ import uuid
|
|||
from datetime import datetime
|
||||
from .database import Base
|
||||
|
||||
# Compatibility layer for SQLite and PostgreSQL without pgvector
|
||||
# Compatibility layer for SQLite
|
||||
try:
|
||||
from sqlalchemy.dialects.postgresql import UUID, ARRAY
|
||||
from pgvector.sqlalchemy import Vector
|
||||
# Check if we are using SQLite
|
||||
import os
|
||||
if "sqlite" in os.getenv("DATABASE_URL", ""):
|
||||
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:
|
||||
from sqlalchemy.types import TypeDecorator, CHAR
|
||||
import json
|
||||
|
|
@ -42,6 +38,7 @@ except ImportError:
|
|||
if value is None: return None
|
||||
return json.loads(value)
|
||||
|
||||
# Use JSON for ARRAY in SQLite
|
||||
ARRAY = JSON
|
||||
|
||||
class ContextNode(Base):
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ const extractMetadata = (content: string) => {
|
|||
|
||||
// 1. Extract Title (First H1 or first non-empty line)
|
||||
const titleMatch = content.match(/^#\s+(.*)/m)
|
||||
if (titleMatch && titleMatch[1]) {
|
||||
if (titleMatch) {
|
||||
title = titleMatch[1].trim()
|
||||
} else {
|
||||
const firstLine = lines.find(l => l.trim().length > 0)
|
||||
|
|
@ -136,12 +136,12 @@ const extractMetadata = (content: string) => {
|
|||
// Look for "Core Logic", "Architecture", "Introduction" headers
|
||||
const logicRegex = /##\s*(Core Logic|Architecture|Introduction|About|Features)([\s\S]*?)(##|$)/i
|
||||
const logicMatch = content.match(logicRegex)
|
||||
if (logicMatch && logicMatch[2]) {
|
||||
if (logicMatch) {
|
||||
coreLogic = logicMatch[2].trim().substring(0, 300) + (logicMatch[2].length > 300 ? '...' : '')
|
||||
} else {
|
||||
// Fallback: First paragraph after title
|
||||
const paragraphs = content.split(/\n\s*\n/)
|
||||
if (paragraphs.length > 1 && paragraphs[1]) {
|
||||
if (paragraphs.length > 1) {
|
||||
coreLogic = paragraphs[1].trim().substring(0, 200) + '...'
|
||||
}
|
||||
}
|
||||
|
|
@ -169,7 +169,7 @@ const parsedNodes = computed(() => {
|
|||
|
||||
const fetchNodes = async () => {
|
||||
try {
|
||||
const res = await axios.get('/context_nodes/')
|
||||
const res = await axios.get('http://127.0.0.1:8000/context_nodes/')
|
||||
nodes.value = res.data
|
||||
} catch (error) {
|
||||
message.error('获取认知节点失败')
|
||||
|
|
@ -178,7 +178,7 @@ const fetchNodes = async () => {
|
|||
|
||||
const createNode = async () => {
|
||||
try {
|
||||
const res = await axios.post('/context_nodes/', formValue.value)
|
||||
const res = await axios.post('http://127.0.0.1:8000/context_nodes/', formValue.value)
|
||||
message.success('认知已提交!')
|
||||
nodes.value.push(res.data)
|
||||
showModal.value = false
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ const getStatusLabel = (status: string) => {
|
|||
|
||||
const fetchExperiments = async () => {
|
||||
try {
|
||||
const res = await axios.get('/experiments/')
|
||||
const res = await axios.get('http://127.0.0.1:8000/experiments/')
|
||||
experiments.value = res.data
|
||||
} catch (error) {
|
||||
message.error('获取实验列表失败')
|
||||
|
|
@ -106,7 +106,7 @@ const fetchExperiments = async () => {
|
|||
|
||||
const fetchContextNodes = async () => {
|
||||
try {
|
||||
const res = await axios.get('/context_nodes/')
|
||||
const res = await axios.get('http://127.0.0.1:8000/context_nodes/')
|
||||
contextNodes.value = res.data
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch context nodes')
|
||||
|
|
@ -120,7 +120,7 @@ const openModal = () => {
|
|||
|
||||
const createExperiment = async () => {
|
||||
try {
|
||||
const res = await axios.post('/experiments/', formValue.value)
|
||||
const res = await axios.post('http://127.0.0.1:8000/experiments/', formValue.value)
|
||||
message.success('实验已启动!')
|
||||
experiments.value.push(res.data)
|
||||
showModal.value = false
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
</div>
|
||||
|
||||
<!-- 选中提供商说明 -->
|
||||
<div class="provider-info" v-if="selectedProvider">
|
||||
<div class="provider-info">
|
||||
<span class="provider-desc">{{ selectedProvider.desc }}</span>
|
||||
<span class="provider-pricing">{{ selectedProvider.pricing }}</span>
|
||||
</div>
|
||||
|
|
@ -63,7 +63,7 @@
|
|||
<h2 class="section-title">连接配置</h2>
|
||||
|
||||
<!-- API 密钥配置 -->
|
||||
<div v-if="showApiKey && selectedProvider" class="form-group">
|
||||
<div v-if="showApiKey" class="form-group">
|
||||
<label class="form-label">API 密钥</label>
|
||||
<input
|
||||
v-model="apiKey"
|
||||
|
|
@ -161,6 +161,13 @@ 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' })
|
||||
])
|
||||
|
||||
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 = [
|
||||
{ key: 'provider', label: '模型服务', icon: CloudIcon },
|
||||
|
|
@ -232,7 +239,7 @@ const selectProvider = (value: string) => {
|
|||
const testConnection = async () => {
|
||||
testing.value = true
|
||||
try {
|
||||
const response = await axios.get(`/ai/status`, { timeout: 5000 })
|
||||
const response = await axios.get(`${config.ollama_host.replace(':11434', ':8000')}/ai/status`, { timeout: 5000 })
|
||||
if (response.data.status === 'ready') {
|
||||
isConnected.value = true
|
||||
statusText.value = `${response.data.model_count} 个模型`
|
||||
|
|
@ -251,7 +258,7 @@ const testConnection = async () => {
|
|||
|
||||
const loadConfig = async () => {
|
||||
try {
|
||||
const response = await axios.get('/ai/config')
|
||||
const response = await axios.get('http://127.0.0.1:8000/ai/config')
|
||||
config.provider = response.data.provider
|
||||
config.ollama_host = response.data.ollama_host
|
||||
} catch {}
|
||||
|
|
@ -260,7 +267,7 @@ const loadConfig = async () => {
|
|||
const saveConfig = async () => {
|
||||
saving.value = true
|
||||
try {
|
||||
await axios.post('/ai/config', {
|
||||
await axios.post('http://127.0.0.1:8000/ai/config', {
|
||||
provider: config.provider,
|
||||
deepseek_api_key: config.deepseek_api_key || undefined,
|
||||
openai_api_key: config.openai_api_key || undefined,
|
||||
|
|
@ -284,7 +291,7 @@ const resetConfig = () => {
|
|||
|
||||
const restartBackend = async () => {
|
||||
try {
|
||||
await axios.post('/ai/stop')
|
||||
await axios.post('http://127.0.0.1:8000/ai/stop')
|
||||
saveStatus.value = '后端已停止,请手动启动'
|
||||
showRestartModal.value = false
|
||||
} catch {
|
||||
|
|
|
|||
|
|
@ -21,11 +21,11 @@
|
|||
<div class="progress-comparison">
|
||||
<div class="progress-row">
|
||||
<span class="label">我方</span>
|
||||
<n-progress type="line" :percentage="Number(getOurProgress(selectedSignal))" color="#63e2b7" rail-color="rgba(255,255,255,0.1)" :height="6" processing />
|
||||
<n-progress type="line" :percentage="getOurProgress(selectedSignal)" color="#63e2b7" rail-color="rgba(255,255,255,0.1)" height="6" processing />
|
||||
</div>
|
||||
<div class="progress-row">
|
||||
<span class="label">竞品</span>
|
||||
<n-progress type="line" :percentage="Number(getCompetitorProgress(selectedSignal))" color="#f5222d" rail-color="rgba(255,255,255,0.1)" :height="6" />
|
||||
<n-progress type="line" :percentage="getCompetitorProgress(selectedSignal)" color="#f5222d" rail-color="rgba(255,255,255,0.1)" height="6" />
|
||||
</div>
|
||||
<div class="lead-time-text" v-if="selectedSignal">
|
||||
{{ getLeadTimeText(selectedSignal) }}
|
||||
|
|
@ -134,16 +134,18 @@
|
|||
'new-signal-flash': isNewSignal(signal.latestSignal)
|
||||
}"
|
||||
>
|
||||
<div class="signal-item-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
||||
<template #header>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="display: flex; align-items: center; gap: 6px;">
|
||||
<n-tag :type="getPriorityType(signal.latestSignal?.priority || 'medium')" size="small" round>
|
||||
{{ getPriorityLabel(signal.latestSignal?.priority || 'medium') }}
|
||||
<n-tag :type="getPriorityType(signal.latestSignal.priority)" size="small" round>
|
||||
{{ getPriorityLabel(signal.latestSignal.priority) }}
|
||||
</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>
|
||||
</div>
|
||||
<n-text depth="3" style="font-size: 12px;">{{ formatDate(signal.created_at) }}</n-text>
|
||||
</div>
|
||||
</template>
|
||||
<n-text class="signal-content" :depth="1">{{ signal.content }}</n-text>
|
||||
</n-list-item>
|
||||
</n-list>
|
||||
|
|
@ -491,7 +493,7 @@
|
|||
:percentage="getTaskProgress(task)"
|
||||
:color="getTaskTypeColor(task.type, true)"
|
||||
rail-color="rgba(255,255,255,0.1)"
|
||||
:height="4"
|
||||
height="4"
|
||||
:show-indicator="false"
|
||||
style="margin-top: 8px;"
|
||||
:class="{ 'progress-pulse': taskExecuting }"
|
||||
|
|
@ -541,7 +543,7 @@
|
|||
<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-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>
|
||||
<template #footer>
|
||||
<n-button type="primary" block @click="saveCompetitors">
|
||||
|
|
@ -571,11 +573,11 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, reactive, watch, computed } from 'vue'
|
||||
import { ref, onMounted, onUnmounted, reactive, watch, nextTick, computed } from 'vue'
|
||||
import {
|
||||
NLayout, NLayoutSider, NLayoutContent, NSpace, NCard, NButton, NList, NListItem, NTag, NText,
|
||||
NGrid, NGridItem, NScrollbar, NSpin, NModal, NForm, NFormItem,
|
||||
NInput, useMessage, NProgress, NIcon, NSteps, NStep, NStatistic
|
||||
NGrid, NGridItem, NDivider, NResult, NCheckbox, NThing, NScrollbar, NSpin, NModal, NForm, NFormItem,
|
||||
NInput, useMessage, NEmpty, NProgress, NIcon, NSteps, NStep, NStatistic
|
||||
} from 'naive-ui'
|
||||
import axios from 'axios'
|
||||
import VChart from 'vue-echarts'
|
||||
|
|
@ -857,7 +859,6 @@ const factSnippets: Record<string, { title: string; source: string; content: str
|
|||
|
||||
const showFactSnippetModal = (type: string) => {
|
||||
const snippet = factSnippets[type] || factSnippets['Whitepaper']
|
||||
if (snippet) {
|
||||
currentFactSnippet.value = {
|
||||
title: snippet.title,
|
||||
source: snippet.source,
|
||||
|
|
@ -865,7 +866,6 @@ const showFactSnippetModal = (type: string) => {
|
|||
}
|
||||
showFactSnippet.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// --- Logic ---
|
||||
|
||||
|
|
@ -947,8 +947,8 @@ const getRelatedKnowledgeNodes = () => {
|
|||
return Array.isArray(knowledgeNodes) ? knowledgeNodes : []
|
||||
}
|
||||
|
||||
const getTechStackType = (category: string): 'default' | 'error' | 'success' | 'warning' | 'info' | 'primary' => {
|
||||
const typeMap: Record<string, 'default' | 'error' | 'success' | 'warning' | 'info' | 'primary'> = {
|
||||
const getTechStackType = (category: string) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
framework: 'success',
|
||||
model: 'error',
|
||||
tool: 'info',
|
||||
|
|
@ -994,8 +994,8 @@ const getKnowledgeNodeIcon = (category: string) => {
|
|||
return iconMap[category] || '🧠'
|
||||
}
|
||||
|
||||
const getKnowledgeNodeType = (category: string): 'default' | 'error' | 'success' | 'warning' | 'info' | 'primary' => {
|
||||
const typeMap: Record<string, 'default' | 'error' | 'success' | 'warning' | 'info' | 'primary'> = {
|
||||
const getKnowledgeNodeType = (category: string) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
tech_stack: 'success',
|
||||
competitor: 'error',
|
||||
market: 'info'
|
||||
|
|
@ -1163,13 +1163,13 @@ const getPriorityLabel = (priority: string) => {
|
|||
}
|
||||
|
||||
// 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,
|
||||
// we'll return a default type. This could be enhanced if we send individual results.
|
||||
return strategicInfo.is_strategic ? 'success' : 'default'
|
||||
}
|
||||
|
||||
const getModelVoteLabel = (_modelName: string, strategicInfo: any) => {
|
||||
const getModelVoteLabel = (modelName: string, strategicInfo: any) => {
|
||||
// Similar to above, placeholder logic
|
||||
return strategicInfo.is_strategic ? '战略级' : '非战略'
|
||||
}
|
||||
|
|
@ -1186,7 +1186,7 @@ const submitFeedback = async (agreed: boolean) => {
|
|||
const aiPrediction = currentStrategicInfo.value.is_strategic
|
||||
const userFeedback = agreed ? aiPrediction : !aiPrediction
|
||||
|
||||
const response = await axios.post(`/signals/${selectedSignal.value.id}/feedback`, {
|
||||
const response = await axios.post(`http://127.0.0.1:8000/signals/${selectedSignal.value.id}/feedback`, {
|
||||
signal_id: selectedSignal.value.id,
|
||||
was_strategic: userFeedback,
|
||||
reason: agreed ? 'AI判断准确' : 'AI判断不准确'
|
||||
|
|
@ -1447,7 +1447,7 @@ watch(selectedSignal, (newVal, oldVal) => {
|
|||
// Load existing feedback for a signal
|
||||
const loadSignalFeedback = async (signalId: string) => {
|
||||
try {
|
||||
const response = await axios.get(`/signals/${signalId}/feedbacks`)
|
||||
const response = await axios.get(`http://127.0.0.1:8000/signals/${signalId}/feedbacks`)
|
||||
if (response.data && response.data.length > 0) {
|
||||
const latestFeedback = response.data[0]
|
||||
signalFeedback.value = latestFeedback
|
||||
|
|
@ -1505,7 +1505,7 @@ const isNewSignal = (signal: any) => {
|
|||
const fetchSignals = async (isPoll = false) => {
|
||||
if (!isPoll) loading.value = true
|
||||
try {
|
||||
const res = await axios.get('/signals/')
|
||||
const res = await axios.get('http://127.0.0.1:8000/signals/')
|
||||
const newSignals = res.data
|
||||
|
||||
// Check for task status updates if polling
|
||||
|
|
@ -1579,7 +1579,7 @@ const submitSignal = async () => {
|
|||
if (!formValue.value.content) return
|
||||
submitting.value = true
|
||||
try {
|
||||
const res = await axios.post('/signals/ingest', formValue.value)
|
||||
const res = await axios.post('http://127.0.0.1:8000/signals/ingest', formValue.value)
|
||||
message.success('信号已注入,战略评估完成!')
|
||||
showModal.value = false
|
||||
await fetchSignals()
|
||||
|
|
@ -1590,7 +1590,7 @@ const submitSignal = async () => {
|
|||
}
|
||||
|
||||
// 停止 Ollama 节省资源
|
||||
await axios.post('/ai/stop')
|
||||
await axios.post('http://127.0.0.1:8000/ai/stop')
|
||||
ollamaReady.value = false
|
||||
message.info('🌱 Ollama 已停止,节省资源')
|
||||
} catch (error) {
|
||||
|
|
@ -1603,7 +1603,7 @@ const submitSignal = async () => {
|
|||
// 检查 AI 服务状态
|
||||
const checkOllamaStatus = async () => {
|
||||
try {
|
||||
const response = await axios.get('/ai/status', { timeout: 3000 })
|
||||
const response = await axios.get('http://127.0.0.1:8000/ai/status', { timeout: 3000 })
|
||||
|
||||
const data = response.data
|
||||
providerType.value = data.provider_type || 'cloud'
|
||||
|
|
@ -1617,6 +1617,7 @@ const checkOllamaStatus = async () => {
|
|||
// 本地 Ollama
|
||||
ollamaReady.value = data.status === 'ready'
|
||||
const modelCount = data.model_count || 0
|
||||
const models = data.available_models || []
|
||||
|
||||
if (ollamaReady.value) {
|
||||
message.success(`✅ Ollama 已就绪 (${modelCount} 个模型)`)
|
||||
|
|
@ -1634,7 +1635,7 @@ const checkOllamaStatus = async () => {
|
|||
const startOllama = async () => {
|
||||
startingOllama.value = true
|
||||
try {
|
||||
const response = await axios.post('/ai/start')
|
||||
const response = await axios.post('http://127.0.0.1:8000/ai/start')
|
||||
|
||||
if (response.data.status === 'already_running') {
|
||||
message.info('Ollama 已在运行')
|
||||
|
|
@ -1650,7 +1651,7 @@ const startOllama = async () => {
|
|||
} else {
|
||||
message.error(response.data.message)
|
||||
}
|
||||
} catch (error: any) {
|
||||
} catch (error) {
|
||||
message.error('启动失败: ' + (error.response?.data?.detail || error.message))
|
||||
} finally {
|
||||
startingOllama.value = false
|
||||
|
|
@ -1660,7 +1661,7 @@ const startOllama = async () => {
|
|||
// 停止本地 Ollama
|
||||
const stopOllama = async () => {
|
||||
try {
|
||||
await axios.post('/ai/stop')
|
||||
await axios.post('http://127.0.0.1:8000/ai/stop')
|
||||
ollamaReady.value = false
|
||||
message.success('✅ Ollama 已停止,内存已释放')
|
||||
} catch (error) {
|
||||
|
|
@ -1691,7 +1692,7 @@ const startAnalysis = async () => {
|
|||
|
||||
// 调用后端获取并分析信号
|
||||
analysisStatus.value = '正在获取 HackerNews 信号...'
|
||||
const response = await axios.post('/signals/fetch')
|
||||
const response = await axios.post('http://127.0.0.1:8000/signals/fetch')
|
||||
|
||||
clearInterval(progressInterval)
|
||||
analysisProgress.value = 100
|
||||
|
|
@ -1708,7 +1709,7 @@ const startAnalysis = async () => {
|
|||
// 仅本地 Ollama 需要停止节省资源
|
||||
if (providerType.value === 'local') {
|
||||
analysisStatus.value = '正在停止 Ollama...'
|
||||
await axios.post('/ai/stop')
|
||||
await axios.post('http://127.0.0.1:8000/ai/stop')
|
||||
ollamaReady.value = false
|
||||
analysisStatus.value = `完成!Ollama 已停止`
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Reference in New Issue