Files
gooseek/backend/webui/src/components/Citation.tsx
home 08bd41e75c feat: travel service with 2GIS routing, POI, hotels + finance providers + UI overhaul
- Add travel-svc microservice (Amadeus, TravelPayouts, 2GIS, OpenRouteService)
- Add travel orchestrator with parallel collectors (events, POI, hotels, flights)
- Add 2GIS road routing with transport cost calculation (car/bus/taxi)
- Add TravelMap (2GIS MapGL) and TravelWidgets components
- Add useTravelChat hook for streaming travel agent responses
- Add finance heatmap providers refactor
- Add SearXNG settings, API proxy routes, Docker compose updates
- Update Dockerfiles, config, types, and all UI pages for consistency

Made-with: Cursor
2026-03-01 21:58:32 +03:00

147 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { ExternalLink } from 'lucide-react';
import * as Tooltip from '@radix-ui/react-tooltip';
import type { Citation as CitationType } from '@/lib/types';
interface CitationProps {
citation: CitationType;
compact?: boolean;
}
export function Citation({ citation, compact }: CitationProps) {
if (compact) {
return (
<a
href={citation.url}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center justify-center w-5 h-5 text-2xs font-medium bg-accent/10 hover:bg-accent/18 text-accent border border-accent/25 rounded transition-colors"
>
{citation.index}
</a>
);
}
return (
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<a
href={citation.url}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-2.5 py-1.5 bg-elevated/80 hover:bg-elevated border border-border hover:border-accent/25 rounded-lg transition-all group"
>
<span className="w-4 h-4 rounded bg-accent/10 text-accent flex items-center justify-center text-2xs font-medium">
{citation.index}
</span>
{citation.favicon && (
<img
src={citation.favicon}
alt=""
className="w-3.5 h-3.5 rounded"
onError={(e) => {
(e.target as HTMLImageElement).style.display = 'none';
}}
/>
)}
<span className="text-xs text-secondary group-hover:text-primary max-w-[120px] truncate transition-colors">
{citation.domain}
</span>
<ExternalLink className="w-2.5 h-2.5 text-muted group-hover:text-secondary transition-colors" />
</a>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content
side="top"
className="max-w-[300px] p-4 bg-elevated backdrop-blur-xl border border-border rounded-xl shadow-dropdown z-50"
sideOffset={8}
>
<p className="font-medium text-sm text-primary line-clamp-2 mb-2">
{citation.title}
</p>
{citation.snippet && (
<p className="text-xs text-secondary line-clamp-3 mb-3">
{citation.snippet}
</p>
)}
<div className="flex items-center gap-2 text-2xs text-muted">
{citation.favicon && (
<img
src={citation.favicon}
alt=""
className="w-3 h-3 rounded"
onError={(e) => {
(e.target as HTMLImageElement).style.display = 'none';
}}
/>
)}
<span className="truncate">{citation.domain}</span>
</div>
<Tooltip.Arrow className="fill-elevated" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</Tooltip.Provider>
);
}
interface CitationListProps {
citations: CitationType[];
maxVisible?: number;
}
export function CitationList({ citations, maxVisible = 6 }: CitationListProps) {
if (!citations || citations.length === 0) return null;
const visible = citations.slice(0, maxVisible);
const remaining = citations.length - maxVisible;
return (
<div className="flex flex-wrap gap-2">
{visible.map((citation) => (
<Citation key={citation.index} citation={citation} />
))}
{remaining > 0 && (
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<button className="text-xs text-muted hover:text-secondary px-2.5 py-1.5 rounded-lg hover:bg-surface/60 transition-colors">
+{remaining} ещё
</button>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content
side="top"
className="max-w-[320px] p-3 bg-elevated backdrop-blur-xl border border-border rounded-xl shadow-dropdown z-50"
sideOffset={8}
>
<div className="space-y-2">
{citations.slice(maxVisible).map((citation) => (
<a
key={citation.index}
href={citation.url}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 p-2 rounded-lg hover:bg-surface/60 transition-colors"
>
<span className="w-4 h-4 rounded bg-accent/10 text-accent flex items-center justify-center text-2xs font-medium flex-shrink-0">
{citation.index}
</span>
<span className="text-xs text-primary truncate">
{citation.title}
</span>
</a>
))}
</div>
<Tooltip.Arrow className="fill-elevated" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</Tooltip.Provider>
)}
</div>
);
}