Initial commit: 国土空间规划课程智能体 v1.0
单容器 Docker 架构的国土空间规划课程智能问答系统,集成 FastAPI 后端与 Next.js 前端。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
"use client";
|
||||
|
||||
import { FileText, ExternalLink, Star, Database, Globe } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
|
||||
interface SourceReference {
|
||||
title: string;
|
||||
filename?: string;
|
||||
page?: number;
|
||||
score?: number;
|
||||
preview: string;
|
||||
url?: string;
|
||||
source_type?: "web" | "rag";
|
||||
}
|
||||
|
||||
interface SourceReferencesProps {
|
||||
sources: SourceReference[];
|
||||
maxSources?: number;
|
||||
}
|
||||
|
||||
export default function SourceReferences({ sources, maxSources = 5 }: SourceReferencesProps) {
|
||||
if (!sources || sources.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const displaySources = sources.slice(0, maxSources);
|
||||
const ragSources = displaySources.filter(s => s.source_type !== "web");
|
||||
const webSources = displaySources.filter(s => s.source_type === "web");
|
||||
|
||||
return (
|
||||
<div className="mt-4 space-y-3">
|
||||
{ragSources.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2 text-sm text-blue-600">
|
||||
<Database className="h-4 w-4" />
|
||||
<span>知识库来源 ({ragSources.length})</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{ragSources.map((source, index) => (
|
||||
<Card key={index} className="border border-blue-200 bg-blue-50/30 hover:border-blue-300 transition-colors">
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex items-start justify-between">
|
||||
<CardTitle className="text-sm font-medium line-clamp-2">
|
||||
{source.title}
|
||||
</CardTitle>
|
||||
{source.score != null && source.score > 0 && source.score < 1 && (
|
||||
<div className="flex items-center gap-1 ml-2">
|
||||
<Star className="h-3 w-3 text-yellow-500" />
|
||||
<span className="text-xs text-gray-500">
|
||||
{(source.score * 100).toFixed(1)}%
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
{source.filename}
|
||||
{source.page && ` • 第 ${source.page} 页`}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-xs text-gray-600 line-clamp-2">
|
||||
{source.preview}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{webSources.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2 text-sm text-green-600">
|
||||
<Globe className="h-4 w-4" />
|
||||
<span>网络来源 ({webSources.length})</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{webSources.map((source, index) => (
|
||||
<Card key={index} className="border border-green-200 bg-green-50/30 hover:border-green-300 transition-colors">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium line-clamp-2">
|
||||
{source.title}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-xs text-gray-600 line-clamp-2 mb-2">
|
||||
{source.preview}
|
||||
</p>
|
||||
{source.url && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="h-6 text-xs"
|
||||
onClick={() => window.open(source.url, '_blank')}
|
||||
>
|
||||
<ExternalLink className="h-3 w-3 mr-1" />
|
||||
查看原文
|
||||
</Button>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{sources.length > maxSources && (
|
||||
<div className="text-xs text-gray-500 text-center">
|
||||
还有 {sources.length - maxSources} 个相关来源
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user