corrected FRONTEND, docker compose, mcp server

This commit is contained in:
G0DSEND016 2026-04-20 11:43:14 +02:00
parent f2123a5779
commit d400215596
16 changed files with 64 additions and 99 deletions

View File

@ -40,7 +40,7 @@ async def chat(request: Request):
async def stream(): async def stream():
async for token in stream_response(agent, messages): async for token in stream_response(agent, messages):
chunk = json.dumps({"type": "text", "delta": token}) chunk = json.dumps({"type": "text-delta", "textDelta": token})
yield f"data: {chunk}\n\n" yield f"data: {chunk}\n\n"
yield "data: [DONE]\n\n" yield "data: [DONE]\n\n"

View File

@ -3,7 +3,6 @@ import httpx
import json import json
from fastmcp import FastMCP from fastmcp import FastMCP
from typing import Optional from typing import Optional
from pydantic import Field
JUSTICE_API = "https://obcan.justice.sk/pilot/api/ress-isu-service" JUSTICE_API = "https://obcan.justice.sk/pilot/api/ress-isu-service"
HEADERS = { HEADERS = {
@ -29,7 +28,7 @@ def register_judge_tools(mcp: FastMCP):
async def judge_autocomplete( async def judge_autocomplete(
query: str, query: str,
court_id: Optional[str] = None, court_id: Optional[str] = None,
limit: int = Field(default=10, description="Maximálny počet výsledkov"), limit: int = 10,
) -> str: ) -> str:
""" """
Autocomplete pre mená sudcov použiť AKO PRVÝ krok Autocomplete pre mená sudcov použiť AKO PRVÝ krok
@ -48,8 +47,8 @@ def register_judge_tools(mcp: FastMCP):
kraj: Optional[str] = None, kraj: Optional[str] = None,
court_id: Optional[str] = None, court_id: Optional[str] = None,
status: Optional[str] = None, status: Optional[str] = None,
page: int = Field(default=0, description="Číslo stránky (začína od 0)"), page: int = 0,
size: int = Field(default=20, description="Počet výsledkov na stránku"), size: int = 20,
) -> str: ) -> str:
""" """
Vyhľadávanie sudcov s filtrami. Vyhľadávanie sudcov s filtrami.

View File

@ -16,7 +16,7 @@ services:
backend: backend:
build: build:
context: . context: .
dockerfile: /backend/Dockerfile dockerfile: backend/Dockerfile
restart: unless-stopped restart: unless-stopped
ports: ports:
- "8000:8000" - "8000:8000"
@ -40,9 +40,6 @@ services:
- "4000:4000" - "4000:4000"
env_file: env_file:
- backend/.env - backend/.env
environment:
- GROQ_API_KEY=${GROQ_API_KEY}
- GEMINI_API_KEY=${GEMINI_API_KEY}
volumes: volumes:
- ./backend/config.yaml:/app/config.yaml:ro - ./backend/config.yaml:/app/config.yaml:ro
command: ['--config', '/app/config.yaml', '--port', '4000'] command: ['--config', '/app/config.yaml', '--port', '4000']
@ -50,7 +47,7 @@ services:
mcp: mcp:
build: build:
context: . context: .
dockerfile: /backend/mcp_server/Dockerfile dockerfile: backend/mcp_server/Dockerfile
restart: unless-stopped restart: unless-stopped
ports: ports:
- "8001:8001" - "8001:8001"

View File

@ -1,19 +0,0 @@
export async function POST(req: Request) {
const body = await req.json();
const response = await fetch("http://backend:8000/api/chat", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
return new Response(response.body, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"X-Accel-Buffering": "no",
},
});
}

View File

@ -67,31 +67,7 @@
--ring: oklch(0.5 0.15 240); --ring: oklch(0.5 0.15 240);
} }
.dark {
--background: oklch(0.12 0.04 240);
--foreground: oklch(0.95 0.01 240);
--card: oklch(0.17 0.05 240);
--card-foreground: oklch(0.95 0.01 240);
--popover: oklch(0.17 0.05 240);
--popover-foreground: oklch(0.95 0.01 240);
--primary: oklch(0.6 0.18 240);
--primary-foreground: oklch(0.98 0 0);
--secondary: oklch(0.2 0.05 240);
--secondary-foreground: oklch(0.95 0.01 240);
--muted: oklch(0.2 0.05 240);
--muted-foreground: oklch(0.65 0.05 240);
--accent: oklch(0.22 0.06 240);
--accent-foreground: oklch(0.95 0.01 240);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 8%);
--input: oklch(1 0 0 / 12%);
--ring: oklch(0.5 0.15 240);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
}
@layer base { @layer base {
* { * {

View File

@ -14,8 +14,7 @@ const geistMono = Geist_Mono({
}); });
export const metadata: Metadata = { export const metadata: Metadata = {
title: "assistant-ui starter app", title: "Legal AI Assistant",
description: "Generated by create-assistant-ui",
}; };
export default function RootLayout({ export default function RootLayout({
@ -24,7 +23,7 @@ export default function RootLayout({
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
return ( return (
<html lang="en" className="dark"> <html lang="en" className="root">
<body <body
className={`${geistSans.variable} ${geistMono.variable} antialiased`} className={`${geistSans.variable} ${geistMono.variable} antialiased`}
> >

View File

@ -16,10 +16,6 @@ export const ThreadHeader: FC = () => {
</span> </span>
<ChevronDownIcon className="size-4 text-muted-foreground" /> <ChevronDownIcon className="size-4 text-muted-foreground" />
</div> </div>
<div className="ml-auto p-1.5">
<ScaleIcon className="size-5 text-blue-400" />
</div>
</div> </div>
); );
}; };

View File

@ -57,9 +57,6 @@ export const Thread: FC = () => {
<ThreadPrimitive.ViewportFooter className="sticky bottom-0 mx-auto mt-auto flex w-full max-w-(--thread-max-width) flex-col gap-4 overflow-visible rounded-t-2xl bg-background pb-4 md:pb-6 pt-2"> <ThreadPrimitive.ViewportFooter className="sticky bottom-0 mx-auto mt-auto flex w-full max-w-(--thread-max-width) flex-col gap-4 overflow-visible rounded-t-2xl bg-background pb-4 md:pb-6 pt-2">
<ThreadScrollToBottom /> <ThreadScrollToBottom />
<ThreadComposer /> <ThreadComposer />
<p className="text-center text-xs text-muted-foreground/50 pb-1">
Právny AI Asistent môže robiť chyby. Overte dôležité informácie.
</p>
</ThreadPrimitive.ViewportFooter> </ThreadPrimitive.ViewportFooter>
</ThreadPrimitive.Viewport> </ThreadPrimitive.Viewport>
</ThreadPrimitive.Root> </ThreadPrimitive.Root>

View File

@ -2,30 +2,22 @@
import { type FC } from "react"; import { type FC } from "react";
const SUGGESTIONS = [ const STARTERS = [
{ {
icon: "🔍", icon: "/icons/magnifying-glass.svg",
title: "Aké právne dáta", description: "What legal data can the agent find?",
description: "môže agent nájsť?",
prompt: "Aké právne dáta môžeš vyhľadať?",
}, },
{ {
icon: "🚫", icon: "/icons/ban.svg",
title: "Čo agent nesmie", description: "What are the agent's limitations?",
description: "robiť alebo používať?",
prompt: "Čo nie si oprávnený robiť alebo použiť?",
}, },
{ {
icon: "🤖", icon: "/icons/hexagon.svg",
title: "Detaily AI modelu", description: "What are the details of your AI model?",
description: "aký model používaš?",
prompt: "Aké sú detaily tvojho AI modelu?",
}, },
{ {
icon: "📊", icon: "/icons/database.svg",
title: "Zdroje dát", description: "What are your data sources?",
description: "odkiaľ čerpáš informácie?",
prompt: "Aké dátové zdroje agent využíva?",
}, },
]; ];
@ -47,16 +39,22 @@ const ThreadSuggestions: FC = () => {
return ( return (
<div className="grid w-full grid-cols-2 @lg:grid-cols-4 gap-2 pb-4 px-2"> <div className="grid w-full grid-cols-2 @lg:grid-cols-4 gap-2 pb-4 px-2">
{SUGGESTIONS.map((s) => ( {STARTERS.map((s) => (
<button <button
key={s.title} key={s.description}
onClick={() => handleClick(s.prompt)} onClick={() => handleClick(s.description)}
className="flex flex-col items-start gap-1 rounded-xl border border-blue-500/20 bg-card px-4 py-3 text-left text-sm transition-colors hover:bg-blue-600/10 hover:border-blue-500/40 cursor-pointer" className="flex flex-row items-center gap-1 rounded-xl border border-blue-500/20 bg-card px-4 py-3 text-left text-sm transition-colors hover:bg-blue-600/10 hover:border-blue-500/40 cursor-pointer"
> >
<span className="font-medium text-foreground"> <div className="size-5 flex-shrink-0">
{s.icon} {s.title} <img
src={s.icon}
alt=""
className="size-full object-contain group-hover:scale-110 transition-transform"
/>
</div>
<span className="text-muted-foreground group-hover:text-foreground transition-colors leading-tight">
{s.description}
</span> </span>
<span className="text-muted-foreground text-xs">{s.description}</span>
</button> </button>
))} ))}
</div> </div>
@ -65,17 +63,13 @@ const ThreadSuggestions: FC = () => {
export const ThreadWelcome: FC = () => { export const ThreadWelcome: FC = () => {
return ( return (
<div className="mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col items-center justify-center gap-6"> <div className="mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col items-center justify-end gap-6">
<div className="flex flex-col items-center gap-4"> <div className="flex flex-col items-center gap-4">
<div className="size-24 rounded-full overflow-hidden ring-2 ring-blue-500/30 shadow-lg shadow-blue-500/20"> <div className=" rounded-full overflow-hidden ring-2 ring-blue-500/30 shadow-lg shadow-blue-500/20">
<img <img
src="/logo.png" src="/logo.png"
alt="Legal AI" alt="Legal AI"
className="size-full object-cover" className="w-50 h-50 object-cover"
onError={(e) => {
e.currentTarget.style.display = "none";
e.currentTarget.parentElement!.innerHTML = `<div class="size-full bg-blue-600/20 flex items-center justify-center text-4xl">⚖️</div>`;
}}
/> />
</div> </div>
<div className="flex flex-col items-center gap-1 text-center"> <div className="flex flex-col items-center gap-1 text-center">

View File

@ -9,8 +9,9 @@
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@ai-sdk/openai": "^3.0.52", "@ai-sdk/openai": "^3.0.52",
"@assistant-ui/react": "latest", "@assistant-ui/react": "^0.12.25",
"@assistant-ui/react-ai-sdk": "latest", "@assistant-ui/react-ai-sdk": "latest",
"@assistant-ui/react-data-stream": "^0.12.11",
"@assistant-ui/react-markdown": "latest", "@assistant-ui/react-markdown": "latest",
"@radix-ui/react-avatar": "^1.1.11", "@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-collapsible": "^1.1.12",
@ -227,6 +228,27 @@
} }
} }
}, },
"node_modules/@assistant-ui/react-data-stream": {
"version": "0.12.11",
"resolved": "https://registry.npmjs.org/@assistant-ui/react-data-stream/-/react-data-stream-0.12.11.tgz",
"integrity": "sha512-DuCV1ILVJsJjl746Ly50jsyt1gUVp43E02Fbq3uau4LU9p3MhUP5CeCV0LLkdS80Hlu9X6xXc34tsL5BCyVzow==",
"license": "MIT",
"dependencies": {
"@ai-sdk/provider": "^3.0.8",
"@assistant-ui/core": "^0.1.14",
"assistant-cloud": "*",
"assistant-stream": "^0.3.11"
},
"peerDependencies": {
"@types/react": "*",
"react": "^18 || ^19"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@assistant-ui/react-markdown": { "node_modules/@assistant-ui/react-markdown": {
"version": "0.12.9", "version": "0.12.9",
"resolved": "https://registry.npmjs.org/@assistant-ui/react-markdown/-/react-markdown-0.12.9.tgz", "resolved": "https://registry.npmjs.org/@assistant-ui/react-markdown/-/react-markdown-0.12.9.tgz",
@ -7716,7 +7738,6 @@
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz", "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz",
"integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==", "integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12.20.0" "node": ">=12.20.0"
}, },

View File

@ -13,8 +13,9 @@
}, },
"dependencies": { "dependencies": {
"@ai-sdk/openai": "^3.0.52", "@ai-sdk/openai": "^3.0.52",
"@assistant-ui/react": "latest", "@assistant-ui/react": "^0.12.25",
"@assistant-ui/react-ai-sdk": "latest", "@assistant-ui/react-ai-sdk": "latest",
"@assistant-ui/react-data-stream": "^0.12.11",
"@assistant-ui/react-markdown": "latest", "@assistant-ui/react-markdown": "latest",
"@radix-ui/react-avatar": "^1.1.11", "@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-collapsible": "^1.1.12",

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path fill="rgb(255, 5, 30)" d="M431.2 476.5L163.5 208.8C141.1 240.2 128 278.6 128 320C128 426 214 512 320 512C361.5 512 399.9 498.9 431.2 476.5zM476.5 431.2C498.9 399.8 512 361.4 512 320C512 214 426 128 320 128C278.5 128 240.1 141.1 208.8 163.5L476.5 431.2zM64 320C64 178.6 178.6 64 320 64C461.4 64 576 178.6 576 320C576 461.4 461.4 576 320 576C178.6 576 64 461.4 64 320z"/></svg>

After

Width:  |  Height:  |  Size: 594 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path fill="rgb(45, 53, 196)" d="M544 269.8C529.2 279.6 512.2 287.5 494.5 293.8C447.5 310.6 385.8 320 320 320C254.2 320 192.4 310.5 145.5 293.8C127.9 287.5 110.8 279.6 96 269.8L96 352C96 396.2 196.3 432 320 432C443.7 432 544 396.2 544 352L544 269.8zM544 192L544 144C544 99.8 443.7 64 320 64C196.3 64 96 99.8 96 144L96 192C96 236.2 196.3 272 320 272C443.7 272 544 236.2 544 192zM494.5 453.8C447.6 470.5 385.9 480 320 480C254.1 480 192.4 470.5 145.5 453.8C127.9 447.5 110.8 439.6 96 429.8L96 496C96 540.2 196.3 576 320 576C443.7 576 544 540.2 544 496L544 429.8C529.2 439.6 512.2 447.5 494.5 453.8z"/></svg>

After

Width:  |  Height:  |  Size: 817 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path fill="rgb(177, 151, 252)" d="M344 170.6C362.9 161.6 376 142.3 376 120C376 89.1 350.9 64 320 64C289.1 64 264 89.1 264 120C264 142.3 277.1 161.6 296 170.6L296 269.4C293.2 270.7 290.5 272.3 288 274.1L207.9 228.3C209.5 207.5 199.3 186.7 180 175.5C153.2 160 119 169.2 103.5 196C88 222.8 97.2 257 124 272.5C125.3 273.3 126.6 274 128 274.6L128 365.4C126.7 366 125.3 366.7 124 367.5C97.2 383 88 417.2 103.5 444C119 470.8 153.2 480 180 464.5C199.3 453.4 209.4 432.5 207.8 411.7L258.3 382.8C246.8 371.6 238.4 357.2 234.5 341.1L184 370.1C181.4 368.3 178.8 366.8 176 365.4L176 274.6C178.8 273.3 181.5 271.7 184 269.9L264.1 315.7C264 317.1 263.9 318.5 263.9 320C263.9 342.3 277 361.6 295.9 370.6L295.9 469.4C277 478.4 263.9 497.7 263.9 520C263.9 550.9 289 576 319.9 576C350.8 576 375.9 550.9 375.9 520C375.9 497.7 362.8 478.4 343.9 469.4L343.9 370.6C346.7 369.3 349.4 367.7 351.9 365.9L432 411.7C430.4 432.5 440.6 453.3 459.8 464.5C486.6 480 520.8 470.8 536.3 444C551.8 417.2 542.6 383 515.8 367.5C514.5 366.7 513.1 366 511.8 365.4L511.8 274.6C513.2 274 514.5 273.3 515.8 272.5C542.6 257 551.8 222.8 536.3 196C520.8 169.2 486.8 160 460 175.5C440.7 186.6 430.6 207.5 432.2 228.3L381.6 257.2C393.1 268.4 401.5 282.8 405.4 298.9L456 269.9C458.6 271.7 461.2 273.2 464 274.6L464 365.4C461.2 366.7 458.5 368.3 456 370L375.9 324.2C376 322.8 376.1 321.4 376.1 319.9C376.1 297.6 363 278.3 344.1 269.3L344.1 170.5z"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path fill="rgb(116, 192, 252)" d="M480 272C480 317.9 465.1 360.3 440 394.7L566.6 521.4C579.1 533.9 579.1 554.2 566.6 566.7C554.1 579.2 533.8 579.2 521.3 566.7L394.7 440C360.3 465.1 317.9 480 272 480C157.1 480 64 386.9 64 272C64 157.1 157.1 64 272 64C386.9 64 480 157.1 480 272zM272 416C351.5 416 416 351.5 416 272C416 192.5 351.5 128 272 128C192.5 128 128 192.5 128 272C128 351.5 192.5 416 272 416z"/></svg>

After

Width:  |  Height:  |  Size: 621 B