feat: add AI-powered finance and project risk analysis features with pgvector support for RAG embeddings

This commit is contained in:
poyrazavsever
2026-06-06 21:47:50 +03:00
parent 21af4b7a62
commit 83379a5edc
17 changed files with 1104 additions and 241 deletions
+84 -2
View File
@@ -27,6 +27,8 @@ import {
Plus,
Trash2,
Wallet,
Brain,
Loader2,
} from "lucide-react";
import { useMemo, useState } from "react";
@@ -125,9 +127,10 @@ export function FinanceClient({ transactions, clients, projects }: FinanceClient
Gelir, gider, ödeme durumu ve proje/müşteri bağlantılarını takip et.
</p>
</div>
<div className="flex gap-2">
<AIFinanceDialog />
<FinanceDialog mode="create" clients={clients} projects={projects} />
</div>
<FinanceDialog mode="create" clients={clients} projects={projects} />
</div>
<div className="grid gap-3 md:grid-cols-3 lg:grid-cols-6">
@@ -538,6 +541,85 @@ function calculateSummary(transactions: FinanceTransactionItem[]) {
);
}
function AIFinanceDialog() {
const [open, setOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [result, setResult] = useState<string | null>(null);
const handleAnalyze = async () => {
setLoading(true);
setResult(null);
try {
const res = await fetch("/api/finance-analysis", { method: "POST" });
const data = await res.json();
if (!res.ok) {
throw new Error(data.error || "Bilinmeyen bir hata oluştu.");
}
setResult(data.text);
} catch (err: any) {
setResult("Hata: " + err.message);
} finally {
setLoading(false);
}
};
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="outline" className="gap-2 bg-indigo-50 text-indigo-700 border-indigo-200 hover:bg-indigo-100 hover:text-indigo-800">
<Brain className="h-4 w-4" />
AI Analizi
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-2xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Brain className="h-5 w-5 text-indigo-600" />
Yapay Zeka Finansal Yorumlama
</DialogTitle>
<DialogDescription>
Son 30 günlük finansal kayıtlarınızı analiz edip size önerilerde bulunuyorum.
</DialogDescription>
</DialogHeader>
<div className="py-4">
{!result && !loading && (
<div className="text-center py-10">
<Button onClick={handleAnalyze} className="gap-2 bg-indigo-600 hover:bg-indigo-700 text-white">
<Brain className="h-4 w-4" />
Raporu Oluştur
</Button>
</div>
)}
{loading && (
<div className="flex flex-col items-center justify-center py-10 space-y-4 text-indigo-600">
<Loader2 className="h-8 w-8 animate-spin" />
<p className="text-sm font-medium">Verileriniz analiz ediliyor...</p>
</div>
)}
{result && (
<div className="bg-muted/50 border border-border rounded-lg p-5 text-sm prose prose-sm dark:prose-invert max-w-none whitespace-pre-wrap">
{result}
</div>
)}
</div>
{result && (
<DialogFooter>
<Button variant="outline" onClick={() => setOpen(false)}>Kapat</Button>
<Button variant="default" onClick={handleAnalyze} className="gap-2">
<Brain className="h-4 w-4" />
Yeniden Oluştur
</Button>
</DialogFooter>
)}
</DialogContent>
</Dialog>
);
}
function calculateExpenseCategories(transactions: FinanceTransactionItem[]) {
const totals = new Map<string, number>();
for (const transaction of transactions) {