mirror of
https://github.com/jihe520/MathModelAgent.git
synced 2026-09-19 08:09:48 +08:00
简化配置
添加文件夹显示
This commit is contained in:
@@ -1,138 +0,0 @@
|
||||
# name: Build and Push Docker Image
|
||||
|
||||
# on:
|
||||
# push:
|
||||
# branches:
|
||||
# - main
|
||||
# paths:
|
||||
# - 'backend/**'
|
||||
# - 'frontend/**'
|
||||
# - 'docker-compose.yml'
|
||||
# - '.github/workflows/docker-build-push.yml'
|
||||
# pull_request:
|
||||
# branches:
|
||||
# - main
|
||||
# paths:
|
||||
# - 'backend/**'
|
||||
# - 'frontend/**'
|
||||
# - 'docker-compose.yml'
|
||||
# - '.github/workflows/docker-build-push.yml'
|
||||
|
||||
# env:
|
||||
# REGISTRY: docker.io
|
||||
# BACKEND_IMAGE_NAME: mathmodelagent-backend
|
||||
# FRONTEND_IMAGE_NAME: mathmodelagent-frontend
|
||||
|
||||
# jobs:
|
||||
# build-backend:
|
||||
# runs-on: ubuntu-latest
|
||||
# outputs:
|
||||
# image-tag: ${{ steps.meta.outputs.tags }}
|
||||
# image-digest: ${{ steps.build.outputs.digest }}
|
||||
|
||||
# steps:
|
||||
# - name: Checkout code
|
||||
# uses: actions/checkout@v4
|
||||
|
||||
# - name: Set up Docker Buildx
|
||||
# uses: docker/setup-buildx-action@v3
|
||||
|
||||
# - name: Log in to Docker Hub
|
||||
# if: github.event_name != 'pull_request'
|
||||
# uses: docker/login-action@v3
|
||||
# with:
|
||||
# registry: ${{ env.REGISTRY }}
|
||||
# username: ${{ secrets.DOCKER_USERNAME }}
|
||||
# password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
# - name: Extract metadata for backend
|
||||
# id: meta
|
||||
# uses: docker/metadata-action@v5
|
||||
# with:
|
||||
# images: ${{ env.REGISTRY }}/${{ secrets.DOCKER_USERNAME }}/${{ env.BACKEND_IMAGE_NAME }}
|
||||
# tags: |
|
||||
# type=ref,event=branch
|
||||
# type=ref,event=pr
|
||||
# type=sha,prefix={{branch}}-
|
||||
# type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
# - name: Build and push backend Docker image
|
||||
# id: build
|
||||
# uses: docker/build-push-action@v5
|
||||
# with:
|
||||
# context: ./backend
|
||||
# file: ./backend/Dockerfile
|
||||
# push: ${{ github.event_name != 'pull_request' }}
|
||||
# tags: ${{ steps.meta.outputs.tags }}
|
||||
# labels: ${{ steps.meta.outputs.labels }}
|
||||
# cache-from: type=gha
|
||||
# cache-to: type=gha,mode=max
|
||||
# platforms: linux/amd64,linux/arm64
|
||||
|
||||
# build-frontend:
|
||||
# runs-on: ubuntu-latest
|
||||
# outputs:
|
||||
# image-tag: ${{ steps.meta.outputs.tags }}
|
||||
# image-digest: ${{ steps.build.outputs.digest }}
|
||||
|
||||
# steps:
|
||||
# - name: Checkout code
|
||||
# uses: actions/checkout@v4
|
||||
|
||||
# - name: Set up Docker Buildx
|
||||
# uses: docker/setup-buildx-action@v3
|
||||
|
||||
# - name: Log in to Docker Hub
|
||||
# if: github.event_name != 'pull_request'
|
||||
# uses: docker/login-action@v3
|
||||
# with:
|
||||
# registry: ${{ env.REGISTRY }}
|
||||
# username: ${{ secrets.DOCKER_USERNAME }}
|
||||
# password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
# - name: Extract metadata for frontend
|
||||
# id: meta
|
||||
# uses: docker/metadata-action@v5
|
||||
# with:
|
||||
# images: ${{ env.REGISTRY }}/${{ secrets.DOCKER_USERNAME }}/${{ env.FRONTEND_IMAGE_NAME }}
|
||||
# tags: |
|
||||
# type=ref,event=branch
|
||||
# type=ref,event=pr
|
||||
# type=sha,prefix={{branch}}-
|
||||
# type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
# - name: Build and push frontend Docker image
|
||||
# id: build
|
||||
# uses: docker/build-push-action@v5
|
||||
# with:
|
||||
# context: ./frontend
|
||||
# file: ./frontend/Dockerfile
|
||||
# push: ${{ github.event_name != 'pull_request' }}
|
||||
# tags: ${{ steps.meta.outputs.tags }}
|
||||
# labels: ${{ steps.meta.outputs.labels }}
|
||||
# cache-from: type=gha
|
||||
# cache-to: type=gha,mode=max
|
||||
# platforms: linux/amd64,linux/arm64
|
||||
|
||||
# security-scan:
|
||||
# runs-on: ubuntu-latest
|
||||
# needs: [build-backend, build-frontend]
|
||||
# if: github.event_name != 'pull_request'
|
||||
|
||||
# strategy:
|
||||
# matrix:
|
||||
# component: [backend, frontend]
|
||||
|
||||
# steps:
|
||||
# - name: Run Trivy vulnerability scanner
|
||||
# uses: aquasecurity/trivy-action@master
|
||||
# with:
|
||||
# image-ref: ${{ needs[format('build-{0}', matrix.component)].outputs.image-tag }}
|
||||
# format: 'sarif'
|
||||
# output: 'trivy-results-${{ matrix.component }}.sarif'
|
||||
|
||||
# - name: Upload Trivy scan results to GitHub Security tab
|
||||
# uses: github/codeql-action/upload-sarif@v3
|
||||
# if: always()
|
||||
# with:
|
||||
# sarif_file: 'trivy-results-${{ matrix.component }}.sarif'
|
||||
@@ -29,7 +29,6 @@ MAX_RETRIES=5
|
||||
# E2B_API_KEY=
|
||||
SERVER_HOST=http://localhost:8000
|
||||
# 使用 email 注册账号从 https://openalex.org/ 文献
|
||||
# OPENALEX_EMAIL=example@example.com
|
||||
OPENALEX_EMAIL=
|
||||
|
||||
LOG_LEVEL=DEBUG
|
||||
@@ -2,8 +2,6 @@ venv/
|
||||
_pycache_/
|
||||
*.pyc
|
||||
.venv/
|
||||
.env.dev
|
||||
.env.prod
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ app.include_router(files_router.router)
|
||||
# 跨域 CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=settings.CORS_ALLOW_ORIGINS,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
|
||||
@@ -8,12 +8,27 @@ from fastapi import HTTPException
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/download_url")
|
||||
async def get_download_url(task_id: str, filename: str):
|
||||
return {"download_url": f"http://localhost:8000/static/{task_id}/{filename}"}
|
||||
|
||||
|
||||
@router.get("/download_all_url")
|
||||
async def get_download_all_url(task_id: str):
|
||||
return {"download_url": f"http://localhost:8000/static/{task_id}/all.zip"}
|
||||
|
||||
|
||||
@router.get("/files")
|
||||
async def get_files(task_id: str):
|
||||
work_dir = get_work_dir(task_id)
|
||||
files = get_current_files(work_dir, "all")
|
||||
file_all = []
|
||||
|
||||
return {"files": files}
|
||||
for i in files:
|
||||
file_type = i.split(".")[-1]
|
||||
file_all.append({"filename": i, "file_type": file_type})
|
||||
|
||||
return file_all
|
||||
|
||||
|
||||
@router.get("/open_folder")
|
||||
|
||||
@@ -19,6 +19,7 @@ from app.schemas.request import ExampleRequest
|
||||
from pydantic import BaseModel
|
||||
import litellm
|
||||
from app.config.setting import settings
|
||||
import requests
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -29,6 +30,15 @@ class ValidateApiKeyRequest(BaseModel):
|
||||
model_id: str
|
||||
|
||||
|
||||
class ValidateOpenalexEmailRequest(BaseModel):
|
||||
email: str
|
||||
|
||||
|
||||
class ValidateOpenalexEmailResponse(BaseModel):
|
||||
valid: bool
|
||||
message: str
|
||||
|
||||
|
||||
class ValidateApiKeyResponse(BaseModel):
|
||||
valid: bool
|
||||
message: str
|
||||
@@ -39,6 +49,7 @@ class SaveApiConfigRequest(BaseModel):
|
||||
modeler: dict
|
||||
coder: dict
|
||||
writer: dict
|
||||
openalex_email: str
|
||||
|
||||
|
||||
@router.post("/save-api-config")
|
||||
@@ -49,25 +60,28 @@ async def save_api_config(request: SaveApiConfigRequest):
|
||||
try:
|
||||
# 更新各个模块的设置
|
||||
if request.coordinator:
|
||||
settings.COORDINATOR_API_KEY = request.coordinator.get('apiKey', '')
|
||||
settings.COORDINATOR_MODEL = request.coordinator.get('modelId', '')
|
||||
settings.COORDINATOR_BASE_URL = request.coordinator.get('baseUrl', '')
|
||||
|
||||
settings.COORDINATOR_API_KEY = request.coordinator.get("apiKey", "")
|
||||
settings.COORDINATOR_MODEL = request.coordinator.get("modelId", "")
|
||||
settings.COORDINATOR_BASE_URL = request.coordinator.get("baseUrl", "")
|
||||
|
||||
if request.modeler:
|
||||
settings.MODELER_API_KEY = request.modeler.get('apiKey', '')
|
||||
settings.MODELER_MODEL = request.modeler.get('modelId', '')
|
||||
settings.MODELER_BASE_URL = request.modeler.get('baseUrl', '')
|
||||
|
||||
settings.MODELER_API_KEY = request.modeler.get("apiKey", "")
|
||||
settings.MODELER_MODEL = request.modeler.get("modelId", "")
|
||||
settings.MODELER_BASE_URL = request.modeler.get("baseUrl", "")
|
||||
|
||||
if request.coder:
|
||||
settings.CODER_API_KEY = request.coder.get('apiKey', '')
|
||||
settings.CODER_MODEL = request.coder.get('modelId', '')
|
||||
settings.CODER_BASE_URL = request.coder.get('baseUrl', '')
|
||||
|
||||
settings.CODER_API_KEY = request.coder.get("apiKey", "")
|
||||
settings.CODER_MODEL = request.coder.get("modelId", "")
|
||||
settings.CODER_BASE_URL = request.coder.get("baseUrl", "")
|
||||
|
||||
if request.writer:
|
||||
settings.WRITER_API_KEY = request.writer.get('apiKey', '')
|
||||
settings.WRITER_MODEL = request.writer.get('modelId', '')
|
||||
settings.WRITER_BASE_URL = request.writer.get('baseUrl', '')
|
||||
|
||||
settings.WRITER_API_KEY = request.writer.get("apiKey", "")
|
||||
settings.WRITER_MODEL = request.writer.get("modelId", "")
|
||||
settings.WRITER_BASE_URL = request.writer.get("baseUrl", "")
|
||||
|
||||
if request.openalex_email:
|
||||
settings.OPENALEX_EMAIL = request.openalex_email
|
||||
|
||||
return {"success": True, "message": "配置保存成功"}
|
||||
except Exception as e:
|
||||
logger.error(f"保存配置失败: {str(e)}")
|
||||
@@ -86,44 +100,56 @@ async def validate_api_key(request: ValidateApiKeyRequest):
|
||||
messages=[{"role": "user", "content": "Hi"}],
|
||||
max_tokens=1,
|
||||
api_key=request.api_key,
|
||||
base_url=request.base_url if request.base_url != "https://api.openai.com/v1" else None,
|
||||
)
|
||||
|
||||
return ValidateApiKeyResponse(
|
||||
valid=True,
|
||||
message="✓ 模型 API 验证成功"
|
||||
base_url=request.base_url
|
||||
if request.base_url != "https://api.openai.com/v1"
|
||||
else None,
|
||||
)
|
||||
|
||||
return ValidateApiKeyResponse(valid=True, message="✓ 模型 API 验证成功")
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
|
||||
|
||||
# 解析不同类型的错误
|
||||
if "401" in error_msg or "Unauthorized" in error_msg:
|
||||
return ValidateApiKeyResponse(
|
||||
valid=False,
|
||||
message="✗ API Key 无效或已过期"
|
||||
)
|
||||
return ValidateApiKeyResponse(valid=False, message="✗ API Key 无效或已过期")
|
||||
elif "404" in error_msg or "Not Found" in error_msg:
|
||||
return ValidateApiKeyResponse(
|
||||
valid=False,
|
||||
message="✗ 模型 ID 不存在或 Base URL 错误"
|
||||
valid=False, message="✗ 模型 ID 不存在或 Base URL 错误"
|
||||
)
|
||||
elif "429" in error_msg or "rate limit" in error_msg.lower():
|
||||
return ValidateApiKeyResponse(
|
||||
valid=False,
|
||||
message="✗ 请求过于频繁,请稍后再试"
|
||||
valid=False, message="✗ 请求过于频繁,请稍后再试"
|
||||
)
|
||||
elif "403" in error_msg or "Forbidden" in error_msg:
|
||||
return ValidateApiKeyResponse(
|
||||
valid=False,
|
||||
message="✗ API 权限不足或账户余额不足"
|
||||
valid=False, message="✗ API 权限不足或账户余额不足"
|
||||
)
|
||||
else:
|
||||
return ValidateApiKeyResponse(
|
||||
valid=False,
|
||||
message=f"✗ 验证失败: {error_msg[:50]}..."
|
||||
valid=False, message=f"✗ 验证失败: {error_msg[:50]}..."
|
||||
)
|
||||
|
||||
|
||||
@router.post("/validate-openalex-email", response_model=ValidateOpenalexEmailResponse)
|
||||
async def validate_openalex_email(request: ValidateOpenalexEmailRequest):
|
||||
"""
|
||||
验证 OpenAlex Email 的有效性
|
||||
"""
|
||||
try:
|
||||
response = requests.get(
|
||||
f"https://api.openalex.org/works?mailto={request.email}"
|
||||
)
|
||||
logger.debug(f"OpenAlex Email 验证响应: {response}")
|
||||
response.raise_for_status()
|
||||
return ValidateOpenalexEmailResponse(
|
||||
valid=True, message="✓ OpenAlex Email 验证成功"
|
||||
)
|
||||
except Exception as e:
|
||||
return ValidateOpenalexEmailResponse(
|
||||
valid=False, message=f"✗ OpenAlex Email 验证失败: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.post("/example")
|
||||
async def exampleModeling(
|
||||
example_request: ExampleRequest,
|
||||
@@ -235,8 +261,8 @@ async def run_modeling_task_async(
|
||||
|
||||
# 创建任务并等待它完成
|
||||
task = asyncio.create_task(MathModelWorkFlow().execute(problem))
|
||||
# 设置超时时间(比如 60 分钟)
|
||||
await asyncio.wait_for(task, timeout=3600)
|
||||
# 设置超时时间(比如 300 分钟)
|
||||
await asyncio.wait_for(task, timeout=3600 * 5)
|
||||
|
||||
# 发送任务完成状态
|
||||
await redis_manager.publish_message(
|
||||
|
||||
@@ -25,7 +25,6 @@ dist-ssr
|
||||
|
||||
.env
|
||||
.env.local
|
||||
.env.development
|
||||
.env.production
|
||||
.env.test
|
||||
|
||||
|
||||
@@ -19,22 +19,36 @@ export interface SaveApiConfigRequest {
|
||||
apiKey: string;
|
||||
baseUrl: string;
|
||||
modelId: string;
|
||||
provider: string;
|
||||
};
|
||||
modeler: {
|
||||
apiKey: string;
|
||||
baseUrl: string;
|
||||
modelId: string;
|
||||
provider: string;
|
||||
};
|
||||
coder: {
|
||||
apiKey: string;
|
||||
baseUrl: string;
|
||||
modelId: string;
|
||||
provider: string;
|
||||
};
|
||||
writer: {
|
||||
apiKey: string;
|
||||
baseUrl: string;
|
||||
modelId: string;
|
||||
provider: string;
|
||||
};
|
||||
openalex_email: string;
|
||||
}
|
||||
|
||||
export interface ValidateOpenalexEmailRequest {
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface ValidateOpenalexEmailResponse {
|
||||
valid: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
||||
// 验证 API Key
|
||||
@@ -42,6 +56,10 @@ export function validateApiKey(params: ValidateApiKeyRequest) {
|
||||
return request.post<ValidateApiKeyResponse>("/validate-api-key", params);
|
||||
}
|
||||
|
||||
export function validateOpenalexEmail(params: ValidateOpenalexEmailRequest) {
|
||||
return request.post<ValidateOpenalexEmailResponse>("/validate-openalex-email", params);
|
||||
}
|
||||
|
||||
// 保存 API 配置
|
||||
export function saveApiConfig(params: SaveApiConfigRequest) {
|
||||
return request.post<{ success: boolean; message: string }>("/save-api-config", params);
|
||||
|
||||
@@ -1,7 +1,39 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
export function getFiles(task_id: string) {
|
||||
return request.get<{ message: string }>("/files", {
|
||||
return request.get<{
|
||||
files: {
|
||||
filename: string;
|
||||
file_type: string;
|
||||
}[]
|
||||
}>("/files", {
|
||||
params: { task_id },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取单个文件下载链接
|
||||
* @param task_id 任务ID
|
||||
* @param filename 文件名
|
||||
*/
|
||||
export async function getFileDownloadUrl(task_id: string, filename: string) {
|
||||
return await request.get<{ download_url: string }>(`/download_url`, {
|
||||
params: {
|
||||
task_id,
|
||||
filename,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有文件压缩包下载链接
|
||||
* @param task_id 任务ID
|
||||
*/
|
||||
export async function getAllFilesDownloadUrl(task_id: string) {
|
||||
return await request.get<{ download_url: string }>(`/download_all_url`, {
|
||||
params: {
|
||||
task_id,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -36,43 +36,7 @@ const data = {
|
||||
title: '历史任务',
|
||||
url: '#',
|
||||
items: [
|
||||
{
|
||||
title: 'Routing',
|
||||
url: '#',
|
||||
},
|
||||
{
|
||||
title: 'Data Fetching',
|
||||
url: '#',
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
title: 'Rendering',
|
||||
url: '#',
|
||||
},
|
||||
{
|
||||
title: 'Caching',
|
||||
url: '#',
|
||||
},
|
||||
{
|
||||
title: 'Styling',
|
||||
url: '#',
|
||||
},
|
||||
{
|
||||
title: 'Optimizing',
|
||||
url: '#',
|
||||
},
|
||||
{
|
||||
title: 'Configuring',
|
||||
url: '#',
|
||||
},
|
||||
{
|
||||
title: 'Testing',
|
||||
url: '#',
|
||||
},
|
||||
{
|
||||
title: 'Authentication',
|
||||
url: '#',
|
||||
},
|
||||
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
BadgeCheck,
|
||||
Bell,
|
||||
ChevronsUpDown,
|
||||
CreditCard,
|
||||
LogOut,
|
||||
KeyRound,
|
||||
} from 'lucide-vue-next'
|
||||
@@ -103,10 +102,6 @@ const openApiKeyDialog = () => {
|
||||
<BadgeCheck />
|
||||
Account
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<CreditCard />
|
||||
Billing
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Bell />
|
||||
Notifications
|
||||
|
||||
@@ -7,42 +7,57 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { useApiKeyStore } from '@/stores/apiKeys'
|
||||
import { CheckCircle, XCircle } from 'lucide-vue-next'
|
||||
import { validateApiKey, saveApiConfig } from '@/apis/apiKeyApi'
|
||||
import { validateApiKey, saveApiConfig, validateOpenalexEmail } from '@/apis/apiKeyApi'
|
||||
|
||||
const apiKeyStore = useApiKeyStore()
|
||||
|
||||
// 本地表单数据
|
||||
const form = ref<{
|
||||
coordinator: { apiKey: string; baseUrl: string; modelId: string };
|
||||
modeler: { apiKey: string; baseUrl: string; modelId: string };
|
||||
coder: { apiKey: string; baseUrl: string; modelId: string };
|
||||
writer: { apiKey: string; baseUrl: string; modelId: string };
|
||||
coordinator: { apiKey: string; baseUrl: string; modelId: string; provider: string };
|
||||
modeler: { apiKey: string; baseUrl: string; modelId: string; provider: string };
|
||||
coder: { apiKey: string; baseUrl: string; modelId: string; provider: string };
|
||||
writer: { apiKey: string; baseUrl: string; modelId: string; provider: string };
|
||||
openalex_email: string;
|
||||
}>({
|
||||
coordinator: {
|
||||
apiKey: '',
|
||||
baseUrl: '',
|
||||
modelId: ''
|
||||
modelId: '',
|
||||
provider: ''
|
||||
},
|
||||
modeler: {
|
||||
apiKey: '',
|
||||
baseUrl: '',
|
||||
modelId: ''
|
||||
modelId: '',
|
||||
provider: ''
|
||||
},
|
||||
coder: {
|
||||
apiKey: '',
|
||||
baseUrl: '',
|
||||
modelId: ''
|
||||
modelId: '',
|
||||
provider: ''
|
||||
},
|
||||
writer: {
|
||||
apiKey: '',
|
||||
baseUrl: '',
|
||||
modelId: ''
|
||||
}
|
||||
modelId: '',
|
||||
provider: ''
|
||||
},
|
||||
openalex_email: ''
|
||||
})
|
||||
|
||||
// 验证状态
|
||||
@@ -51,7 +66,8 @@ const validationResults = ref({
|
||||
coordinator: { valid: false, message: '' },
|
||||
modeler: { valid: false, message: '' },
|
||||
coder: { valid: false, message: '' },
|
||||
writer: { valid: false, message: '' }
|
||||
writer: { valid: false, message: '' },
|
||||
openalex_email: { valid: false, message: '' }
|
||||
})
|
||||
|
||||
// 计算所有验证是否都通过
|
||||
@@ -73,6 +89,7 @@ const loadFromStore = () => {
|
||||
form.value.modeler = { ...apiKeyStore.modelerConfig }
|
||||
form.value.coder = { ...apiKeyStore.coderConfig }
|
||||
form.value.writer = { ...apiKeyStore.writerConfig }
|
||||
form.value.openalex_email = apiKeyStore.openalexEmail
|
||||
}
|
||||
|
||||
// 保存表单数据到 store
|
||||
@@ -82,7 +99,7 @@ const saveToStore = async () => {
|
||||
apiKeyStore.setModelerConfig(form.value.modeler)
|
||||
apiKeyStore.setCoderConfig(form.value.coder)
|
||||
apiKeyStore.setWriterConfig(form.value.writer)
|
||||
|
||||
apiKeyStore.setOpenalexEmail(form.value.openalex_email)
|
||||
// 如果验证成功,也保存到后端设置
|
||||
if (allValid.value) {
|
||||
try {
|
||||
@@ -90,7 +107,8 @@ const saveToStore = async () => {
|
||||
coordinator: form.value.coordinator,
|
||||
modeler: form.value.modeler,
|
||||
coder: form.value.coder,
|
||||
writer: form.value.writer
|
||||
writer: form.value.writer,
|
||||
openalex_email: form.value.openalex_email
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('保存配置到后端失败:', error)
|
||||
@@ -159,15 +177,11 @@ const validateAllApiKeys = async () => {
|
||||
coordinator: { valid: false, message: '' },
|
||||
modeler: { valid: false, message: '' },
|
||||
coder: { valid: false, message: '' },
|
||||
writer: { valid: false, message: '' }
|
||||
writer: { valid: false, message: '' },
|
||||
openalex_email: { valid: false, message: '' }
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
|
||||
// 在 E2B 验证后等待 500ms
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
|
||||
// 逐个验证各模型 API Keys,避免并发请求
|
||||
for (const config of modelConfigs.value) {
|
||||
const key = config.key as keyof typeof validationResults.value
|
||||
@@ -182,6 +196,10 @@ const validateAllApiKeys = async () => {
|
||||
// 每次验证后等待 1 秒,避免触发速率限制
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
}
|
||||
|
||||
// 验证 OpenAlex Email
|
||||
validationResults.value.openalex_email = await validateOpenalexEmail({ email: form.value.openalex_email }).then(res => res.data)
|
||||
|
||||
} catch (error) {
|
||||
console.error('验证过程中发生错误:', error)
|
||||
// 显示全局错误
|
||||
@@ -198,30 +216,70 @@ const validateAllApiKeys = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const links = {
|
||||
const resetAll = () => {
|
||||
form.value = {
|
||||
coordinator: { apiKey: '', baseUrl: '', modelId: '', provider: '' },
|
||||
modeler: { apiKey: '', baseUrl: '', modelId: '', provider: '' },
|
||||
coder: { apiKey: '', baseUrl: '', modelId: '', provider: '' },
|
||||
writer: { apiKey: '', baseUrl: '', modelId: '', provider: '' },
|
||||
openalex_email: ''
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const providers = {
|
||||
"DeepSeek": {
|
||||
"url": "https://platform.deepseek.com/api_keys",
|
||||
"key": "DeepSeek",
|
||||
"BaseURL": "https://api.deepseek.com",
|
||||
"ModelID": "deepseek/deepseek-chat"
|
||||
"baseUrl": "https://api.deepseek.com",
|
||||
"modelId": "deepseek/deepseek-chat"
|
||||
},
|
||||
"硅基流动": {
|
||||
"url": "https://cloud.siliconflow.cn/i/UIb4Enf4",
|
||||
"key": "硅基流动",
|
||||
"BaseURL": "https://api.siliconflow.cn",
|
||||
"ModelID": "openai/deepseek-ai/DeepSeek-V3"
|
||||
"baseUrl": "https://api.siliconflow.cn",
|
||||
"modelId": "openai/deepseek-ai/DeepSeek-V3"
|
||||
},
|
||||
"Sophnet": {
|
||||
"url": "https://www.sophnet.com/#?code=AZBSFG",
|
||||
"key": "Sophnet",
|
||||
"BaseURL": "https://www.sophnet.com/api/open-apis",
|
||||
"ModelID": "openai/DeepSeek-V3-Fast"
|
||||
"baseUrl": "https://www.sophnet.com/api/open-apis",
|
||||
"modelId": "openai/DeepSeek-V3-Fast"
|
||||
},
|
||||
"OpenAI": {
|
||||
"url": "https://platform.openai.com/api-keys",
|
||||
"key": "OpenAI",
|
||||
"BaseURL": "https://api.openai.com",
|
||||
"ModelID": "openai/gpt-4o"
|
||||
"baseUrl": "https://api.openai.com",
|
||||
"modelId": "openai/gpt-5"
|
||||
},
|
||||
"302.AI": {
|
||||
"url": "https://302.ai/",
|
||||
"key": "302.AI",
|
||||
"baseUrl": "https://api.302.ai",
|
||||
"modelId": "openai/deepseek-chat"
|
||||
},
|
||||
"OpenAI 兼容": {
|
||||
"url": "/",
|
||||
"key": "OpenAI 兼容",
|
||||
"baseUrl": "basurl",
|
||||
"modelId": "provider/model_id"
|
||||
}
|
||||
}
|
||||
|
||||
// 当供应商选择改变时,自动填写配置
|
||||
const onProviderChange = (configKey: string, providerKey: string) => {
|
||||
const provider = providers[providerKey as keyof typeof providers]
|
||||
if (provider) {
|
||||
const formConfig = (form.value as any)[configKey]
|
||||
formConfig.provider = providerKey
|
||||
formConfig.baseUrl = provider.baseUrl
|
||||
formConfig.modelId = provider.modelId
|
||||
|
||||
// 清除之前的验证结果
|
||||
validationResults.value[configKey as keyof typeof validationResults.value] = {
|
||||
valid: false,
|
||||
message: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,25 +289,10 @@ const links = {
|
||||
<Dialog :open="props.open" @update:open="updateOpen">
|
||||
<DialogContent class="max-w-xl max-h-[85vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>API Key 设置</DialogTitle>
|
||||
<DialogTitle>设置</DialogTitle>
|
||||
<DialogDescription>
|
||||
为每个 Agent 配置合适模型
|
||||
<br>
|
||||
<div class="">
|
||||
<div v-for="link in links" :key="link.key">
|
||||
<div class="flex flex-col gap-1">
|
||||
<a :href="link.url" target="_blank" class="text-blue-600 hover:text-blue-800 underline text-xs">
|
||||
{{ link.key }}
|
||||
</a>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
Base URL: {{ link.BaseURL }}
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
Model ID: {{ link.ModelID }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div><a href="https://docs.litellm.ai/docs/providers" target="_blank"
|
||||
class="text-blue-600 hover:text-blue-800 underline text-xs">
|
||||
more details
|
||||
@@ -263,51 +306,105 @@ const links = {
|
||||
<!-- Models Configurations -->
|
||||
<div v-for="config in modelConfigs" :key="config.key" class="space-y-2">
|
||||
<h3 class="text-sm font-medium">{{ config.label }}</h3>
|
||||
<div class="grid grid-cols-1 gap-2">
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="space-y-1">
|
||||
<Label :for="`${config.key}-api-key`" class="text-xs text-muted-foreground">API Key</Label>
|
||||
<div class="flex items-center gap-2">
|
||||
<Input :id="`${config.key}-api-key`" v-model.trim="(form as any)[config.key].apiKey" type="password"
|
||||
placeholder="请输入 API Key" class="h-7 text-xs flex-1" />
|
||||
<div v-if="validationResults[config.key as keyof typeof validationResults].message"
|
||||
class="flex items-center">
|
||||
<CheckCircle v-if="validationResults[config.key as keyof typeof validationResults].valid"
|
||||
class="h-4 w-4 text-green-500" />
|
||||
<XCircle v-else class="h-4 w-4 text-red-500" />
|
||||
<Label :for="`${config.key}-provider`" class="text-xs text-muted-foreground">提供商</Label>
|
||||
|
||||
<div class="flex gap-2 items-center">
|
||||
<Select :model-value="(form as any)[config.key].provider"
|
||||
@update:model-value="(value: any) => value && onProviderChange(config.key, String(value))">
|
||||
<SelectTrigger class="w-[120px] h-7 text-xs">
|
||||
<SelectValue placeholder="选择提供商" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>提供商</SelectLabel>
|
||||
<SelectItem v-for="(provider, key) in providers" :key="key" :value="key">
|
||||
{{ provider.key }}
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div v-if="(form as any)[config.key].provider">
|
||||
<a :href="providers[(form as any)[config.key].provider as keyof typeof providers]?.url"
|
||||
target="_blank" class="text-blue-600 hover:text-blue-800 underline text-xs">
|
||||
{{ providers[(form as any)[config.key].provider as keyof typeof providers]?.key }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="space-y-1">
|
||||
<Label :for="`${config.key}-base-url`" class="text-xs text-muted-foreground">Base URL</Label>
|
||||
<Input :id="`${config.key}-base-url`" v-model.trim="(form as any)[config.key].baseUrl"
|
||||
placeholder="https://api.deepseek.com" class="h-7 text-xs" />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Label :for="`${config.key}-model-id`" class="text-xs text-muted-foreground">Model ID</Label>
|
||||
<Input :id="`${config.key}-model-id`" v-model.trim="(form as any)[config.key].modelId"
|
||||
placeholder="provider/model_id" class="h-7 text-xs" />
|
||||
|
||||
<div class="space-y-1">
|
||||
|
||||
<Label :for="`${config.key}-api-key`" class="text-xs text-muted-foreground">API Key</Label>
|
||||
|
||||
<Input :id="`${config.key}-api-key`" v-model.trim="(form as any)[config.key].apiKey" type="password"
|
||||
placeholder="请输入 API Key" class="h-7 text-xs flex-1" />
|
||||
|
||||
<div v-if="validationResults[config.key as keyof typeof validationResults].message"
|
||||
class="flex items-center">
|
||||
<CheckCircle v-if="validationResults[config.key as keyof typeof validationResults].valid"
|
||||
class="h-4 w-4 text-green-500" />
|
||||
<XCircle v-else class="h-4 w-4 text-red-500" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="validationResults[config.key as keyof typeof validationResults].message" :class="[
|
||||
'text-xs px-2 py-1 rounded text-left border',
|
||||
validationResults[config.key as keyof typeof validationResults].valid ? 'bg-green-50 text-green-700 border-green-200' : 'bg-red-50 text-red-700 border-red-200'
|
||||
]">
|
||||
{{ validationResults[config.key as keyof typeof validationResults].message }}
|
||||
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="space-y-1">
|
||||
<Label :for="`${config.key}-base-url`" class="text-xs text-muted-foreground">Base URL</Label>
|
||||
<Input :id="`${config.key}-base-url`" v-model.trim="(form as any)[config.key].baseUrl"
|
||||
placeholder="baseUrl" class="h-7 text-xs" />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Label :for="`${config.key}-model-id`" class="text-xs text-muted-foreground">Model ID</Label>
|
||||
<Input :id="`${config.key}-model-id`" v-model.trim="(form as any)[config.key].modelId"
|
||||
placeholder="provider/model_id" class="h-7 text-xs" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="validationResults[config.key as keyof typeof validationResults].message" :class="[
|
||||
'text-xs px-2 py-1 rounded text-left border',
|
||||
validationResults[config.key as keyof typeof validationResults].valid ? 'bg-green-50 text-green-700 border-green-200' : 'bg-red-50 text-red-700 border-red-200'
|
||||
]">
|
||||
{{ validationResults[config.key as keyof typeof validationResults].message }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="space-y-2">
|
||||
<h3 class="text-sm font-medium">其他</h3>
|
||||
<Label :for="`openalex-email`" class="text-xs text-muted-foreground">OpenAlex Email</Label>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
使用 email 注册账号从 <a href="https://openalex.org/" target="_blank"
|
||||
class="text-blue-600 hover:text-blue-800 underline text-xs">OpenAlex</a> 获取访问文献权利
|
||||
</div>
|
||||
<Input :id="`openalex-email`" v-model.trim="form.openalex_email" placeholder="请输入 OpenAlex Email"
|
||||
class="h-7 text-xs flex-1" />
|
||||
<div v-if="validationResults.openalex_email.message" :class="[
|
||||
'text-xs px-2 py-1 rounded text-left border',
|
||||
validationResults.openalex_email.valid ? 'bg-green-50 text-green-700 border-green-200' : 'bg-red-50 text-red-700 border-red-200'
|
||||
]">
|
||||
{{ validationResults.openalex_email.message }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center pt-3 border-t">
|
||||
<Button @click="validateAllApiKeys" :disabled="validating" class="h-7 text-xs px-3" variant="secondary">
|
||||
{{ validating ? '验证中...' : '一键验证' }}
|
||||
</Button>
|
||||
<div class="flex justify-between items-center gap-2">
|
||||
<Button @click="validateAllApiKeys" :disabled="validating" class="h-7 text-xs px-3" variant="secondary">
|
||||
{{ validating ? '验证中...' : '一键验证' }}
|
||||
</Button>
|
||||
<Button @click="resetAll" class="h-7 text-xs px-3" variant="secondary">
|
||||
重置
|
||||
</Button>
|
||||
</div>
|
||||
<div class="flex space-x-2">
|
||||
<Button variant="outline" @click="updateOpen(false)" class="h-7 text-xs px-3">
|
||||
取消
|
||||
</Button>
|
||||
<Button v-if="allValid" @click="saveAndClose" class="h-7 text-xs px-3">
|
||||
<Button @click="saveAndClose" class="h-7 text-xs px-3">
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Folder, RefreshCw, File, FileText, Download, Archive, Files } from 'lucide-vue-next'
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { getFileDownloadUrl, getAllFilesDownloadUrl, getFiles } from '@/apis/filesApi';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from '@/components/ui/sheet'
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip'
|
||||
import { useToast } from '@/components/ui/toast/use-toast'
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
const route = useRoute()
|
||||
const taskId = route.params.task_id;
|
||||
|
||||
|
||||
const { toast } = useToast()
|
||||
|
||||
|
||||
// 文件列表相关
|
||||
const fileListVisible = ref(false)
|
||||
const fileList = ref<any[]>([])
|
||||
const loadingFiles = ref(false)
|
||||
const downloadingFile = ref<string | null>(null)
|
||||
const downloadingAll = ref(false)
|
||||
|
||||
|
||||
const openFolder = async () => {
|
||||
console.log('openFolder', taskId)
|
||||
try {
|
||||
loadingFiles.value = true
|
||||
const res = await getFiles(taskId as string);
|
||||
console.log(res);
|
||||
|
||||
if (res.data) {
|
||||
fileList.value = Array.isArray(res.data) ? res.data : [res.data]
|
||||
fileListVisible.value = true
|
||||
} else {
|
||||
toast({
|
||||
title: "获取文件列表失败",
|
||||
description: "无法获取工作区文件列表",
|
||||
variant: "destructive"
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取文件列表失败:', error)
|
||||
toast({
|
||||
title: "错误",
|
||||
description: "获取文件列表时出现错误",
|
||||
variant: "destructive"
|
||||
})
|
||||
} finally {
|
||||
loadingFiles.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取文件图标
|
||||
const getFileIcon = (fileName: string) => {
|
||||
const ext = fileName.split('.').pop()?.toLowerCase()
|
||||
const textExts = ['txt', 'md', 'json', 'csv', 'xml', 'yml', 'yaml']
|
||||
|
||||
if (textExts.includes(ext || '')) {
|
||||
return FileText
|
||||
}
|
||||
return File
|
||||
}
|
||||
|
||||
// 获取文件大小格式化
|
||||
const formatFileSize = (size: number | undefined) => {
|
||||
if (!size) return ''
|
||||
|
||||
const units = ['B', 'KB', 'MB', 'GB']
|
||||
let unitIndex = 0
|
||||
let fileSize = size
|
||||
|
||||
while (fileSize >= 1024 && unitIndex < units.length - 1) {
|
||||
fileSize /= 1024
|
||||
unitIndex++
|
||||
}
|
||||
|
||||
return `${fileSize.toFixed(1)} ${units[unitIndex]}`
|
||||
}
|
||||
|
||||
// 下载单个文件
|
||||
const downloadSingleFile = async (filename: string) => {
|
||||
try {
|
||||
downloadingFile.value = filename
|
||||
const res = await getFileDownloadUrl(taskId as string, filename)
|
||||
if (res.data?.download_url) {
|
||||
// 创建隐藏的链接元素并触发下载
|
||||
const link = document.createElement('a')
|
||||
link.href = res.data.download_url
|
||||
link.download = filename
|
||||
link.target = '_blank'
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
|
||||
toast({
|
||||
title: "下载成功",
|
||||
description: `文件 ${filename} 开始下载`
|
||||
})
|
||||
} else {
|
||||
throw new Error('获取下载链接失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('下载文件失败:', error)
|
||||
toast({
|
||||
title: "下载失败",
|
||||
description: `下载文件 ${filename} 时出现错误`,
|
||||
variant: "destructive"
|
||||
})
|
||||
} finally {
|
||||
downloadingFile.value = null
|
||||
}
|
||||
}
|
||||
|
||||
// 下载所有文件
|
||||
const downloadAll = async () => {
|
||||
try {
|
||||
downloadingAll.value = true
|
||||
const res = await getAllFilesDownloadUrl(taskId as string)
|
||||
if (res.data?.download_url) {
|
||||
// 创建隐藏的链接元素并触发下载
|
||||
const link = document.createElement('a')
|
||||
link.href = res.data.download_url
|
||||
link.download = `task_${taskId}_files.zip`
|
||||
link.target = '_blank'
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
|
||||
toast({
|
||||
title: "下载成功",
|
||||
description: "所有文件压缩包开始下载"
|
||||
})
|
||||
} else {
|
||||
throw new Error('获取下载链接失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('下载所有文件失败:', error)
|
||||
toast({
|
||||
title: "下载失败",
|
||||
description: "下载所有文件时出现错误",
|
||||
variant: "destructive"
|
||||
})
|
||||
} finally {
|
||||
downloadingAll.value = false
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Sheet v-model:open="fileListVisible">
|
||||
<SheetTrigger asChild>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button @click="openFolder()" :disabled="loadingFiles" class="flex gap-2" size="icon">
|
||||
<RefreshCw v-if="loadingFiles" class="w-4 h-4 animate-spin" />
|
||||
<Files v-else class="w-4 h-4" />
|
||||
<Folder v-else class="w-4 h-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>工作区文件</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
</SheetTrigger>
|
||||
<SheetContent side="right" class="w-[400px] sm:w-[540px]">
|
||||
<SheetHeader>
|
||||
<SheetTitle class="flex items-center justify-between mr-5">
|
||||
工作区文件
|
||||
|
||||
</SheetTitle>
|
||||
<SheetDescription>
|
||||
当前会话的工作区文件列表
|
||||
<br>
|
||||
运行的结果和产生在<span class="font-mono">backend/project/work_dir/{{ taskId }}/*</span> 目录下
|
||||
</SheetDescription>
|
||||
</SheetHeader>
|
||||
|
||||
<div class="mt-6">
|
||||
<ScrollArea class="h-[calc(100vh-120px)]">
|
||||
<div v-if="fileList.length === 0" class="text-center py-8 text-gray-500">
|
||||
暂无文件
|
||||
</div>
|
||||
<div v-else class="space-y-2">
|
||||
<div v-for="(file, index) in fileList" :key="index"
|
||||
class="flex items-center gap-3 p-3 rounded-lg border hover:bg-gray-50 transition-colors">
|
||||
<component :is="getFileIcon(file.name || file.filename || '')"
|
||||
class="w-5 h-5 text-gray-600 flex-shrink-0" />
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="font-medium text-sm truncate">
|
||||
{{ file.name || file.filename || 'Unknown' }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 flex gap-2">
|
||||
<span v-if="file.size">{{ formatFileSize(file.size) }}</span>
|
||||
<span v-if="file.modified_time">{{ new Date(file.modified_time).toLocaleDateString()
|
||||
}}</span>
|
||||
<span v-if="file.type">{{ file.type }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button @click="downloadSingleFile(file.name || file.filename || '')"
|
||||
:disabled="downloadingFile === (file.name || file.filename || '')" size="sm" variant="ghost"
|
||||
class="flex-shrink-0">
|
||||
<RefreshCw v-if="downloadingFile === (file.name || file.filename || '')"
|
||||
class="w-4 h-4 animate-spin" />
|
||||
<Download v-else class="w-4 h-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>下载文件</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
</div>
|
||||
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</template>
|
||||
@@ -18,9 +18,8 @@ import { onMounted, onBeforeUnmount, ref } from 'vue'
|
||||
import { useTaskStore } from '@/stores/task'
|
||||
import { getWriterSeque } from '@/apis/commonApi';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { openFolderAPI } from '@/apis/commonApi';
|
||||
import { useToast } from '@/components/ui/toast/use-toast'
|
||||
import { Folder } from 'lucide-vue-next'
|
||||
import FilesSheet from '@/pages/task/components/FileSheet.vue'
|
||||
const { toast } = useToast()
|
||||
|
||||
|
||||
@@ -69,15 +68,6 @@ onMounted(async () => {
|
||||
updateDuration() // 立即更新一次
|
||||
})
|
||||
|
||||
const openFolder = async () => {
|
||||
const res = await openFolderAPI(props.task_id);
|
||||
console.log(res);
|
||||
toast({
|
||||
title: '打开工作目录成功',
|
||||
description: res.data.message,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
taskStore.closeWebSocket()
|
||||
@@ -125,9 +115,8 @@ onBeforeUnmount(() => {
|
||||
下载消息
|
||||
</Button>
|
||||
|
||||
<Button @click="openFolder" class="flex">
|
||||
<Folder class="w-5 h-5" /> workspace
|
||||
</Button>
|
||||
<FilesSheet />
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -9,31 +9,37 @@ export const useApiKeyStore = defineStore('apiKeys', () => {
|
||||
const coordinatorConfig = ref<ModelConfig>({
|
||||
apiKey: '',
|
||||
baseUrl: '',
|
||||
modelId: ''
|
||||
modelId: '',
|
||||
provider: ''
|
||||
});
|
||||
|
||||
|
||||
const modelerConfig = ref<ModelConfig>({
|
||||
apiKey: '',
|
||||
baseUrl: '',
|
||||
modelId: ''
|
||||
modelId: '',
|
||||
provider: ''
|
||||
});
|
||||
|
||||
|
||||
const coderConfig = ref<ModelConfig>({
|
||||
apiKey: '',
|
||||
baseUrl: '',
|
||||
modelId: ''
|
||||
modelId: '',
|
||||
provider: ''
|
||||
});
|
||||
|
||||
|
||||
const writerConfig = ref<ModelConfig>({
|
||||
apiKey: '',
|
||||
baseUrl: '',
|
||||
modelId: ''
|
||||
modelId: '',
|
||||
provider: ''
|
||||
});
|
||||
|
||||
const openalexEmail = ref<string>('');
|
||||
|
||||
const isEmpty = computed(() => {
|
||||
return Object.values(getAllAgentConfigs()).every(config => config.apiKey === '')
|
||||
})
|
||||
|
||||
|
||||
// 设置协调者模型配置
|
||||
function setCoordinatorConfig(config: ModelConfig) {
|
||||
coordinatorConfig.value = { ...config };
|
||||
@@ -54,6 +60,11 @@ export const useApiKeyStore = defineStore('apiKeys', () => {
|
||||
writerConfig.value = { ...config };
|
||||
}
|
||||
|
||||
function setOpenalexEmail(email: string) {
|
||||
console.log('setOpenalexEmail', email)
|
||||
openalexEmail.value = email;
|
||||
}
|
||||
|
||||
// 获取所有 agent 配置
|
||||
function getAllAgentConfigs() {
|
||||
return {
|
||||
@@ -66,10 +77,11 @@ export const useApiKeyStore = defineStore('apiKeys', () => {
|
||||
|
||||
// 重置所有配置
|
||||
function resetAll() {
|
||||
coordinatorConfig.value = { apiKey: '', baseUrl: '', modelId: '' };
|
||||
modelerConfig.value = { apiKey: '', baseUrl: '', modelId: '' };
|
||||
coderConfig.value = { apiKey: '', baseUrl: '', modelId: '' };
|
||||
writerConfig.value = { apiKey: '', baseUrl: '', modelId: '' };
|
||||
coordinatorConfig.value = { apiKey: '', baseUrl: '', modelId: '', provider: '' };
|
||||
modelerConfig.value = { apiKey: '', baseUrl: '', modelId: '', provider: '' };
|
||||
coderConfig.value = { apiKey: '', baseUrl: '', modelId: '', provider: '' };
|
||||
writerConfig.value = { apiKey: '', baseUrl: '', modelId: '', provider: '' };
|
||||
openalexEmail.value = '';
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -78,13 +90,15 @@ export const useApiKeyStore = defineStore('apiKeys', () => {
|
||||
modelerConfig,
|
||||
coderConfig,
|
||||
writerConfig,
|
||||
openalexEmail,
|
||||
isEmpty,
|
||||
|
||||
|
||||
// 方法
|
||||
setCoordinatorConfig,
|
||||
setModelerConfig,
|
||||
setCoderConfig,
|
||||
setWriterConfig,
|
||||
setOpenalexEmail,
|
||||
getAllAgentConfigs,
|
||||
resetAll
|
||||
}
|
||||
|
||||
@@ -20,4 +20,5 @@ export interface ModelConfig {
|
||||
apiKey: string;
|
||||
baseUrl: string;
|
||||
modelId: string;
|
||||
provider: string;
|
||||
}
|
||||
Vendored
+6
@@ -1 +1,7 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
#!/bin/bash
|
||||
# push-to-dockerhub.sh
|
||||
|
||||
set -e
|
||||
|
||||
USERNAME="sanjin66"
|
||||
BACKEND_TAG="${USERNAME}/mathmodelagent-backend-github:latest"
|
||||
FRONTEND_TAG="${USERNAME}/mathmodelagent-frontend-github:latest"
|
||||
|
||||
echo "开始构建和推送 MathModelAgent..."
|
||||
|
||||
# 构建并推送后端
|
||||
echo "构建后端镜像..."
|
||||
docker build -t ${BACKEND_TAG} ./backend
|
||||
echo "推送后端镜像..."
|
||||
docker push ${BACKEND_TAG}
|
||||
|
||||
# 构建并推送前端
|
||||
echo "构建前端镜像..."
|
||||
docker build -t ${FRONTEND_TAG} ./frontend
|
||||
echo "推送前端镜像..."
|
||||
docker push ${FRONTEND_TAG}
|
||||
|
||||
echo "推送完成!"
|
||||
Reference in New Issue
Block a user