feat: add AI-powered finance and project risk analysis features with pgvector support for RAG embeddings
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user