Add PDF document service with AI extraction and per-app settings
- New `features/doc-service` FastAPI microservice: PDF upload, async text extraction (pdfplumber), AI classification via Anthropic/Ollama/ LM Studio, per-user categories, file download - Alembic migration isolated with `alembic_version_doc_service` table - Main backend: httpx proxy routers for /api/documents/* and /api/documents/categories/*, admin settings API at /api/settings/* - Runtime config in /config/doc_service_config.json (shared Docker volume); api_key masking on reads; atomic write with os.replace() - Frontend: DocumentsPage, DocumentAdminSettingsPage, updated AppsPage launcher hub, simplified Nav (removed Settings link), new routes - docker-compose: doc-service service, doc_data + app_config volumes, removed internal:true from backend-net for outbound AI API calls - Fix pre-commit hook: probe Docker socket path so git subprocess picks up Docker Desktop on macOS - Fix security_check.py: use sys.executable for bandit so venv python is used instead of system python Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,95 @@
|
||||
import { Link } from "react-router-dom";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import Nav from "../components/Nav";
|
||||
import { getMe } from "../api/client";
|
||||
|
||||
interface AppCard {
|
||||
slug: string;
|
||||
name: string;
|
||||
description: string;
|
||||
status: "available" | "coming_soon";
|
||||
path: string;
|
||||
settingsPath?: string;
|
||||
}
|
||||
|
||||
const APPS: AppCard[] = [
|
||||
{
|
||||
slug: "documents",
|
||||
name: "Documents",
|
||||
description: "Upload PDF files, extract data, and organise them with categories.",
|
||||
status: "available",
|
||||
path: "/apps/documents",
|
||||
settingsPath: "/apps/documents/settings/admin",
|
||||
},
|
||||
];
|
||||
|
||||
export default function AppsPage() {
|
||||
const { data: user } = useQuery({ queryKey: ["me"], queryFn: getMe });
|
||||
|
||||
return (
|
||||
<>
|
||||
<Nav />
|
||||
<div style={{ padding: 32 }}>
|
||||
<div style={{ padding: 32, maxWidth: 900, margin: "0 auto" }}>
|
||||
<h1>Apps</h1>
|
||||
<div style={{ display: "flex", gap: 24, flexWrap: "wrap", marginTop: 24 }}>
|
||||
{APPS.map((app) => (
|
||||
<div
|
||||
key={app.slug}
|
||||
style={{
|
||||
border: "1px solid #ddd",
|
||||
borderRadius: 8,
|
||||
padding: 24,
|
||||
width: 280,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 12,
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<h2 style={{ margin: 0, fontSize: 18 }}>{app.name}</h2>
|
||||
{app.status === "available" ? (
|
||||
<span style={{ fontSize: 12, color: "#2a9d8f", fontWeight: 600 }}>Available</span>
|
||||
) : (
|
||||
<span style={{ fontSize: 12, color: "#aaa" }}>Coming soon</span>
|
||||
)}
|
||||
</div>
|
||||
<p style={{ margin: 0, color: "#555", fontSize: 14 }}>{app.description}</p>
|
||||
<div style={{ display: "flex", gap: 8, marginTop: "auto" }}>
|
||||
{app.status === "available" && (
|
||||
<Link
|
||||
to={app.path}
|
||||
style={{
|
||||
padding: "6px 14px",
|
||||
background: "#222",
|
||||
color: "#fff",
|
||||
borderRadius: 4,
|
||||
textDecoration: "none",
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
Open
|
||||
</Link>
|
||||
)}
|
||||
{user?.is_admin && app.settingsPath && app.status === "available" && (
|
||||
<Link
|
||||
to={app.settingsPath}
|
||||
style={{
|
||||
padding: "6px 14px",
|
||||
border: "1px solid #ccc",
|
||||
borderRadius: 4,
|
||||
textDecoration: "none",
|
||||
fontSize: 14,
|
||||
color: "#333",
|
||||
}}
|
||||
title="Settings"
|
||||
>
|
||||
Settings
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user