Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,31 @@ FROM python:3.11-slim

WORKDIR /app

# Install system dependencies
# Install system dependencies (fonts for matplotlib charts in Docker)
RUN apt-get update && apt-get install -y \
gcc \
libpq-dev \
fonts-wqy-zenhei \
&& rm -rf /var/lib/apt/lists/*

# Install Python dependencies
COPY requirements.txt .
COPY backend/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy source
COPY . .
# Backend source
COPY backend/ /app/

# Create uploads directory
RUN mkdir -p /app/uploads
# Repo assets required by Skill runtime
COPY skills/ /repo/skills/
COPY config/ /repo/config/
COPY static/ /repo/static/

ENV AGENTFLOW_REPO_ROOT=/repo
ENV AGENTFLOW_UPLOADS_DIR=/repo/uploads
ENV PYTHONPATH=/app

RUN mkdir -p /repo/uploads /repo/static/generated

# Expose port
EXPOSE 8000

# Run
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
55 changes: 55 additions & 0 deletions backend/app/core/paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""Repository path resolution for local dev and Docker."""
from __future__ import annotations

import os
from functools import lru_cache
from pathlib import Path


@lru_cache
def repo_root() -> Path:
"""AgentFlow repo root (contains skills/, config/, static/)."""
env = (os.environ.get("AGENTFLOW_REPO_ROOT") or "").strip()
if env:
return Path(env).resolve()

# backend/app/core/paths.py -> parents[2] == backend/
backend_dir = Path(__file__).resolve().parents[2]
candidate = backend_dir.parent
if (candidate / "skills").is_dir():
return candidate

# Docker: skills/config/static mounted under /repo
docker_repo = Path("/repo")
if docker_repo.is_dir():
return docker_repo

return candidate


def skills_root() -> Path:
return repo_root() / "skills"


def static_root() -> Path:
return repo_root() / "static"


def generated_root() -> Path:
path = static_root() / "generated"
path.mkdir(parents=True, exist_ok=True)
return path


def uploads_root() -> Path:
env = (os.environ.get("AGENTFLOW_UPLOADS_DIR") or "").strip()
if env:
path = Path(env).resolve()
else:
path = repo_root() / "uploads"
path.mkdir(parents=True, exist_ok=True)
return path


def models_catalog_path() -> Path:
return repo_root() / "config" / "models.json"
9 changes: 3 additions & 6 deletions backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
AgentFlow - AI Agent 部署平台
生产级架构,支持 WebSocket、缓存、监控、日志
"""
from pathlib import Path

from fastapi import FastAPI, Request
from app.core.paths import generated_root, static_root
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
Expand Down Expand Up @@ -116,9 +114,8 @@ async def log_requests(request: Request, call_next):
app.include_router(skills.router, prefix="/api/v1/skills", tags=["Skill 广场"])

# Skill 工具产出(Excel/PDF/PPT 等)
_static_root = Path(__file__).resolve().parents[3] / "static"
_static_root.mkdir(parents=True, exist_ok=True)
(_static_root / "generated").mkdir(parents=True, exist_ok=True)
_static_root = static_root()
generated_root()
app.mount("/static", StaticFiles(directory=str(_static_root)), name="static")

# WebSocket 路由
Expand Down
4 changes: 3 additions & 1 deletion backend/app/services/model_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
from pathlib import Path
from typing import Any

CATALOG_PATH = Path(__file__).resolve().parents[4] / "config" / "models.json"
from app.core.paths import models_catalog_path

CATALOG_PATH = models_catalog_path()


@lru_cache
Expand Down
5 changes: 3 additions & 2 deletions backend/app/services/skill/doc_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@

logger = get_logger("docparse")

ROOT = Path(__file__).resolve().parents[4]
GENERATED_DIR = ROOT / "static" / "generated"
from app.core.paths import generated_root

GENERATED_DIR = generated_root()
DEFAULT_JAR = ROOT / "demo-1.1-SNAPSHOT.jar"

PARSEABLE_SUFFIX = {
Expand Down
5 changes: 3 additions & 2 deletions backend/app/services/skill/excel_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@

logger = get_logger("excel")

ROOT = Path(__file__).resolve().parents[4]
GENERATED_DIR = ROOT / "static" / "generated"
from app.core.paths import generated_root

GENERATED_DIR = generated_root()
TABULAR_SUFFIX = {".xlsx", ".xls", ".csv", ".tsv"}
_SQL_FORBIDDEN = re.compile(
r"\b(insert|update|delete|drop|alter|attach|copy|pragma|install|export|create\s+or)\b",
Expand Down
5 changes: 3 additions & 2 deletions backend/app/services/skill/pdf_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@

logger = get_logger("pdf")

ROOT = Path(__file__).resolve().parents[4]
GENERATED_DIR = ROOT / "static" / "generated"
from app.core.paths import generated_root

GENERATED_DIR = generated_root()
_WM_FONT: str | None = None
_CJK_FONT_CANDIDATES = (
Path(r"C:\Windows\Fonts\msyh.ttc"),
Expand Down
5 changes: 3 additions & 2 deletions backend/app/services/skill/pdf_word.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@

logger = get_logger("pdf-word")

ROOT = Path(__file__).resolve().parents[4]
GENERATED_DIR = ROOT / "static" / "generated"
from app.core.paths import generated_root

GENERATED_DIR = generated_root()

_IMG_RE = re.compile(r"!\[([^\]]*)\]\(([^)]+)\)")
_HEADING_RE = re.compile(r"^(#{1,6})\s+(.*)$")
Expand Down
5 changes: 3 additions & 2 deletions backend/app/services/skill/ppt_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@

logger = get_logger("ppt")

ROOT = Path(__file__).resolve().parents[4]
GENERATED_DIR = ROOT / "static" / "generated"
from app.core.paths import generated_root

GENERATED_DIR = generated_root()
PPT_FONT = "Microsoft YaHei"


Expand Down
9 changes: 6 additions & 3 deletions backend/app/services/skill/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@

logger = get_logger("skillstore")

SKILLS_ROOT = Path(__file__).resolve().parents[4] / "skills"
from app.core.paths import skills_root as _skills_root_path

SKILL_FILENAME = "SKILL.md"

_CACHE: dict[str, tuple[float, "FileSkill"]] = {}
Expand Down Expand Up @@ -75,8 +76,10 @@ def scripts_dir(self) -> Path | None:


def skills_root() -> Path:
SKILLS_ROOT.mkdir(parents=True, exist_ok=True)
return SKILLS_ROOT
root = _skills_root_path()
root.mkdir(parents=True, exist_ok=True)
return root



def slugify(name: str, *, fallback: str = "skill") -> str:
Expand Down
5 changes: 3 additions & 2 deletions backend/app/services/uploads.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@

from fastapi import UploadFile

ROOT = Path(__file__).resolve().parents[4]
UPLOAD_ROOT = ROOT / "uploads"
from app.core.paths import uploads_root

UPLOAD_ROOT = uploads_root()
ALLOWED_SUFFIX = {
".csv",
".json",
Expand Down
46 changes: 34 additions & 12 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: '3.8'

services:
postgres:
image: pgvector/pgvector:pg16
Expand Down Expand Up @@ -29,24 +27,34 @@ services:

backend:
build:
context: ./backend
dockerfile: Dockerfile
context: .
dockerfile: backend/Dockerfile
environment:
AGENTFLOW_REPO_ROOT: /repo
AGENTFLOW_UPLOADS_DIR: /repo/uploads
DATABASE_URL: postgresql+asyncpg://postgres:postgres@postgres:5432/agentflow
REDIS_URL: redis://redis:6379/0
JWT_SECRET_KEY: ${JWT_SECRET_KEY:-change-me-in-production}
OPENAI_API_KEY: ${OPENAI_API_KEY}
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY}
DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY}
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}
CHATZOC_API_KEY: ${CHATZOC_API_KEY:-}
CHATZOC_BASE_URL: ${CHATZOC_BASE_URL:-}
DASHSCOPE_API_KEY: ${DASHSCOPE_API_KEY:-}
MODEL_BASE_URL: ${MODEL_BASE_URL:-}
CORS_ORIGINS: http://localhost:3000,http://localhost:5173,http://localhost:3001
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
ports:
- "8000:8000"
- "8001:8000"
volumes:
- ./uploads:/app/uploads
- ./skills:/repo/skills
- ./config:/repo/config
- ./static:/repo/static
- ./uploads:/repo/uploads

frontend:
build:
Expand All @@ -55,19 +63,33 @@ services:
depends_on:
- backend
ports:
- "3001:3000"
- "3000:3000"

celery-worker:
build:
context: ./backend
dockerfile: Dockerfile
context: .
dockerfile: backend/Dockerfile
command: celery -A app.celery_app worker --loglevel=info
environment:
AGENTFLOW_REPO_ROOT: /repo
AGENTFLOW_UPLOADS_DIR: /repo/uploads
DATABASE_URL: postgresql+asyncpg://postgres:postgres@postgres:5432/agentflow
REDIS_URL: redis://redis:6379/0
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}
CHATZOC_API_KEY: ${CHATZOC_API_KEY:-}
CHATZOC_BASE_URL: ${CHATZOC_BASE_URL:-}
DASHSCOPE_API_KEY: ${DASHSCOPE_API_KEY:-}
MODEL_BASE_URL: ${MODEL_BASE_URL:-}
depends_on:
- postgres
- redis
volumes:
- ./skills:/repo/skills
- ./config:/repo/config
- ./static:/repo/static
- ./uploads:/repo/uploads

volumes:
postgres_data:
16 changes: 16 additions & 0 deletions frontend/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ server {
root /usr/share/nginx/html;
index index.html;

client_max_body_size 50m;

location / {
try_files $uri $uri/ /index.html;
}
Expand All @@ -14,4 +16,18 @@ server {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}

location /static {
proxy_pass http://backend:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}

location /ws {
proxy_pass http://backend:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}
6 changes: 6 additions & 0 deletions frontend/src/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import HelpPage from './pages/HelpPage'
import KnowledgePage from './pages/KnowledgePage'
import WorkflowPage from './pages/WorkflowPage'
import TemplateMarket from './pages/TemplateMarket'
import SkillPlazaPage from './pages/SkillPlazaPage'
import SkillChatPage from './pages/SkillChatPage'

// Pages wrapper components
function LandingPage() {
Expand Down Expand Up @@ -45,9 +47,13 @@ export default function Router() {
<Route path="/agents/:id/edit" element={<DashboardLayout><AgentCreatePage /></DashboardLayout>} />
<Route path="/conversations" element={<DashboardLayout><AgentListPage /></DashboardLayout>} />
<Route path="/knowledge" element={<DashboardLayout><KnowledgePage /></DashboardLayout>} />
<Route path="/skills" element={<DashboardLayout><SkillPlazaPage /></DashboardLayout>} />
<Route path="/settings" element={<DashboardLayout><SettingsPage /></DashboardLayout>} />
<Route path="/help" element={<DashboardLayout><HelpPage /></DashboardLayout>} />

{/* Skill Chat - Full Screen */}
<Route path="/skills/:id" element={<SkillChatPage />} />

{/* Chat Page - Full Screen */}
<Route path="/agents/:id/chat" element={<ChatPage />} />

Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/layout/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { Button } from '../ui/Button'
const navItems = [
{ icon: LayoutDashboard, label: '控制台', href: '/dashboard' },
{ icon: MessageSquare, label: 'Agent', href: '/agents' },
{ icon: Sparkles, label: 'Skill 广场', href: '/skills' },
{ icon: Workflow, label: '工作流', href: '/workflow/new' },
{ icon: FileText, label: '知识库', href: '/knowledge' },
]
Expand Down
Loading
Loading