'use client'
import { useState } from 'react'
import { BookOpen, ExternalLink, Scale, ChevronDown, ChevronUp, Info } from 'lucide-react'
import {
DSFALicenseCode,
DSFA_LICENSE_LABELS,
SourceAttributionProps
} from '@/lib/sdk/types'
/**
* Get license badge color based on license type
*/
function getLicenseBadgeColor(licenseCode: DSFALicenseCode): string {
switch (licenseCode) {
case 'DL-DE-BY-2.0':
case 'DL-DE-ZERO-2.0':
return 'bg-blue-100 text-blue-700 border-blue-200'
case 'CC-BY-4.0':
return 'bg-green-100 text-green-700 border-green-200'
case 'EDPB-LICENSE':
return 'bg-purple-100 text-purple-700 border-purple-200'
case 'PUBLIC_DOMAIN':
return 'bg-gray-100 text-gray-700 border-gray-200'
case 'PROPRIETARY':
return 'bg-amber-100 text-amber-700 border-amber-200'
default:
return 'bg-slate-100 text-slate-700 border-slate-200'
}
}
/**
* Get license URL based on license code
*/
function getLicenseUrl(licenseCode: DSFALicenseCode): string | null {
switch (licenseCode) {
case 'DL-DE-BY-2.0':
return 'https://www.govdata.de/dl-de/by-2-0'
case 'DL-DE-ZERO-2.0':
return 'https://www.govdata.de/dl-de/zero-2-0'
case 'CC-BY-4.0':
return 'https://creativecommons.org/licenses/by/4.0/'
case 'EDPB-LICENSE':
return 'https://edpb.europa.eu/about-edpb/legal-notice_en'
default:
return null
}
}
/**
* License badge component
*/
function LicenseBadge({ licenseCode }: { licenseCode: DSFALicenseCode }) {
const licenseUrl = getLicenseUrl(licenseCode)
const colorClass = getLicenseBadgeColor(licenseCode)
const label = DSFA_LICENSE_LABELS[licenseCode] || licenseCode
if (licenseUrl) {
return (
{label}
)
}
return (
{label}
)
}
/**
* Single source item in the attribution list
*/
function SourceItem({
source,
index,
showScore
}: {
source: SourceAttributionProps['sources'][0]
index: number
showScore: boolean
}) {
return (
{index + 1}.
{source.sourceUrl ? (
{source.sourceName}
) : (
{source.sourceName}
)}
{showScore && source.score !== undefined && (
({(source.score * 100).toFixed(0)}%)
)}
{source.attributionText}
)
}
/**
* Compact source badge for inline display
*/
function CompactSourceBadge({
source
}: {
source: SourceAttributionProps['sources'][0]
}) {
return (
{source.sourceCode}
)
}
/**
* SourceAttribution component - displays source/license information for DSFA RAG results
*
* @example
* ```tsx
*
* ```
*/
export function SourceAttribution({
sources,
compact = false,
showScores = false
}: SourceAttributionProps) {
const [isExpanded, setIsExpanded] = useState(!compact)
if (!sources || sources.length === 0) {
return null
}
// Compact mode - just show badges
if (compact && !isExpanded) {
return (
setIsExpanded(true)}
className="inline-flex items-center gap-1 text-xs text-slate-500 hover:text-slate-700"
>
Quellen ({sources.length})
{sources.slice(0, 3).map((source, i) => (
))}
{sources.length > 3 && (
+{sources.length - 3}
)}
)
}
return (
Quellen & Lizenzen
{compact && (
setIsExpanded(false)}
className="text-xs text-slate-400 hover:text-slate-600 flex items-center gap-1"
>
Einklappen
)}
{sources.map((source, i) => (
))}
{/* Aggregated license notice */}
{sources.length > 1 && (
Hinweis: Die angezeigten Inhalte stammen aus {sources.length} verschiedenen Quellen
mit unterschiedlichen Lizenzen. Bitte beachten Sie die jeweiligen Attributionsanforderungen.
)}
)
}
/**
* Inline source reference for use within text
*/
export function InlineSourceRef({
sourceCode,
sourceName,
sourceUrl
}: {
sourceCode: string
sourceName: string
sourceUrl?: string
}) {
if (sourceUrl) {
return (
[{sourceCode}]
)
}
return (
[{sourceCode}]
)
}
/**
* Attribution footer for generated documents
*/
export function AttributionFooter({
sources,
generatedAt
}: {
sources: SourceAttributionProps['sources']
generatedAt?: Date
}) {
if (!sources || sources.length === 0) {
return null
}
// Group by license
const byLicense = sources.reduce((acc, source) => {
const key = source.licenseCode
if (!acc[key]) acc[key] = []
acc[key].push(source)
return acc
}, {} as Record)
return (
)
}
export default SourceAttribution