- 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
147 lines
5.1 KiB
TypeScript
147 lines
5.1 KiB
TypeScript
'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>
|
||
);
|
||
}
|