Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 18 additions & 5 deletions src/components/features/ExportToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
import { FileText, FileDown, MessageSquare } from 'lucide-react';
import { Button } from '@/components/ui/button';
import GoogleDocsIcon from '@/components/icons/GoogleDocsIcon';
import { cn } from '@/lib/utils';

interface ExportToolbarProps {
onPdf: () => void;
Expand All @@ -16,8 +17,16 @@ interface ExportToolbarProps {
extraExports?: ReactNode;
/** Buttons that appear on the top row (Save, Discuss, Flowcharts, Undo, etc.). */
secondaryActions?: ReactNode;
/** On a phone, both rows stretch their buttons into even pairs and the EXPORT tag takes its own line. */
evenRows?: boolean;
}

// On a phone each button grows from half the row, so two share a line and a
// label too long for half takes the whole line; nothing is left ragged. Buttons
// set their own touch min-width, which would let a long label shrink into half
// a row, so the width of the label wins.
const EVEN_ROW = 'max-sm:[&>*]:grow max-sm:[&>*]:basis-[calc(50%-0.25rem)] max-sm:[&>*]:whitespace-nowrap max-sm:[&>*]:min-w-fit!';

// Unified export bar used by every feature's results view.
// Two explicit rows, both left-aligned:
// Row 1 (top): secondary actions — Save, Discuss, Flowcharts, Undo
Expand All @@ -32,16 +41,22 @@ export function ExportToolbar({
discussLabel,
extraExports,
secondaryActions,
evenRows = false,
}: ExportToolbarProps) {
const { t } = useTranslation();

const hasSecondaryActions = Children.toArray(secondaryActions).filter(Boolean).length > 0;
const hasTopRow = hasSecondaryActions || !!onDiscuss;
const exportTag = (
<span className="rounded-full border border-primary/20 bg-primary/10 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-primary">
{t('common.export', 'Export')}
</span>
);

return (
<div className="flex w-full flex-col items-start gap-2">
{hasTopRow && (
<div className="flex w-full flex-wrap items-center justify-start gap-2">
<div className={cn('flex w-full flex-wrap items-center justify-start gap-2', evenRows && EVEN_ROW)}>
{secondaryActions}
{onDiscuss && (
<Button variant="outline" size="sm" className="whitespace-nowrap" onClick={onDiscuss}>
Expand All @@ -51,10 +66,8 @@ export function ExportToolbar({
)}
</div>
)}
<div className="flex w-full flex-wrap items-center justify-start gap-2">
<span className="rounded-full border border-primary/20 bg-primary/10 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-primary">
{t('common.export', 'Export')}
</span>
<div className={cn('flex w-full flex-wrap items-center justify-start gap-2', evenRows && EVEN_ROW)}>
{evenRows ? <div className="flex max-sm:basis-full!">{exportTag}</div> : exportTag}
{extraExports}
<Button variant="outline" size="sm" className="whitespace-nowrap" onClick={onPdf}>
<FileText className="mr-2 h-4 w-4" />
Expand Down
94 changes: 48 additions & 46 deletions src/components/features/ask/AskChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -644,8 +644,10 @@ export function AskChat({ messages, streamingMessage, isLoading, error, onDismis
</CardContent>

<div className="shrink-0 border-t border-border p-4 space-y-2">
<div className="flex items-center gap-3 flex-wrap">
<div className="flex gap-1 overflow-x-auto scrollbar-hide">
{/* Options share the mode row, so only one row of controls sits above the message box.
On a phone it shows its icon only: the Hungarian label does not fit beside both modes. */}
<div className="flex items-center gap-2">
<div className="flex min-w-0 gap-1 overflow-x-auto scrollbar-hide">
{modeOptions.map((option) => (
<Button
key={option.id}
Expand All @@ -659,51 +661,51 @@ export function AskChat({ messages, streamingMessage, isLoading, error, onDismis
</Button>
))}
</div>
</div>
<Popover>
<PopoverTrigger asChild>
<Button type="button" variant="outline" size="sm" className="gap-2">
<SlidersHorizontal className="h-4 w-4" aria-hidden="true" />
{t('askChat.options', 'Options')}
{[citationMode, webSearch, prosConsMode, argumentMapMode].filter(Boolean).length > 0 && (
<span className="rounded-full bg-primary/10 px-1.5 text-xs font-medium text-primary tabular-nums">{[citationMode, webSearch, prosConsMode, argumentMapMode].filter(Boolean).length}</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent align="start" className="w-72 space-y-1 p-3">
<div className="flex items-center justify-between gap-3 rounded-md px-1 py-1.5 pointer-coarse:min-h-11">
<Label htmlFor="citation-mode" className="flex items-center gap-2 text-sm font-normal">
<Quote className="h-4 w-4 text-muted-foreground" aria-hidden="true" />
{t('askChat.citations')}
</Label>
<Switch id="citation-mode" checked={citationMode} onCheckedChange={setCitationMode} />
</div>
<div className="flex items-center justify-between gap-3 rounded-md px-1 py-1.5 pointer-coarse:min-h-11">
<Label htmlFor="web-search-mode" className="flex items-center gap-2 text-sm font-normal">
<Search className="h-4 w-4 text-muted-foreground" aria-hidden="true" />
{t('askChat.webSearch')}
</Label>
<Switch id="web-search-mode" checked={webSearch} onCheckedChange={setWebSearch} />
</div>
<div className="flex items-center justify-between gap-3 rounded-md px-1 py-1.5 pointer-coarse:min-h-11">
<Label htmlFor="pros-cons-mode" className="flex items-center gap-2 text-sm font-normal">
<List className="h-4 w-4 text-muted-foreground" aria-hidden="true" />
{t('askChat.modeProsCons')}
</Label>
<Switch id="pros-cons-mode" checked={prosConsMode} onCheckedChange={setProsConsMode} />
</div>
<div className="flex items-center justify-between gap-3 rounded-md px-1 py-1.5 pointer-coarse:min-h-11">
<Label htmlFor="argument-map-mode" className="flex items-center gap-2 text-sm font-normal">
<Map className="h-4 w-4 text-muted-foreground" aria-hidden="true" />
{t('askChat.modeArgMap')}
</Label>
<Switch id="argument-map-mode" checked={argumentMapMode} onCheckedChange={setArgumentMapMode} />
<Popover>
<PopoverTrigger asChild>
<Button type="button" variant="outline" size="sm" className="ml-auto shrink-0 gap-2">
<SlidersHorizontal className="h-4 w-4" aria-hidden="true" />
<span className="sr-only sm:not-sr-only">{t('askChat.options', 'Options')}</span>
{[citationMode, webSearch, prosConsMode, argumentMapMode].filter(Boolean).length > 0 && (
<span className="rounded-full bg-primary/10 px-1.5 text-xs font-medium text-primary tabular-nums">{[citationMode, webSearch, prosConsMode, argumentMapMode].filter(Boolean).length}</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent align="end" className="w-72 space-y-1 p-3">
<div className="flex items-center justify-between gap-3 rounded-md px-1 py-1.5 pointer-coarse:min-h-11">
<Label htmlFor="citation-mode" className="flex items-center gap-2 text-sm font-normal">
<Quote className="h-4 w-4 text-muted-foreground" aria-hidden="true" />
{t('askChat.citations')}
</Label>
<Switch id="citation-mode" checked={citationMode} onCheckedChange={setCitationMode} />
</div>
<div className="flex items-center justify-between gap-3 rounded-md px-1 py-1.5 pointer-coarse:min-h-11">
<Label htmlFor="web-search-mode" className="flex items-center gap-2 text-sm font-normal">
<Search className="h-4 w-4 text-muted-foreground" aria-hidden="true" />
{t('askChat.webSearch')}
</Label>
<Switch id="web-search-mode" checked={webSearch} onCheckedChange={setWebSearch} />
</div>
<div className="flex items-center justify-between gap-3 rounded-md px-1 py-1.5 pointer-coarse:min-h-11">
<Label htmlFor="pros-cons-mode" className="flex items-center gap-2 text-sm font-normal">
<List className="h-4 w-4 text-muted-foreground" aria-hidden="true" />
{t('askChat.modeProsCons')}
</Label>
<Switch id="pros-cons-mode" checked={prosConsMode} onCheckedChange={setProsConsMode} />
</div>
<div className="flex items-center justify-between gap-3 rounded-md px-1 py-1.5 pointer-coarse:min-h-11">
<Label htmlFor="argument-map-mode" className="flex items-center gap-2 text-sm font-normal">
<Map className="h-4 w-4 text-muted-foreground" aria-hidden="true" />
{t('askChat.modeArgMap')}
</Label>
<Switch id="argument-map-mode" checked={argumentMapMode} onCheckedChange={setArgumentMapMode} />
</div>
<div className="border-t pt-2">
<ModelSelector feature="ask" className="w-full" />
</div>
<div className="border-t pt-2">
<ModelSelector feature="ask" className="w-full" />
</div>
</PopoverContent>
</Popover>
</PopoverContent>
</Popover>
</div>

{files.length > 0 && (
<div className="flex flex-wrap gap-2">
Expand Down
20 changes: 11 additions & 9 deletions src/components/features/generate/GenerateResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,15 +183,17 @@ export function GenerateResults({ result, discoveredSources, onRegenerateSection
onGoogleDocs={handleGoogleDocsExport}
isExportingGDocs={isExportingGDocs}
extraExports={<CopyResultButton getText={() => caseText(result)} />}
evenRows
secondaryActions={(onSave || saved || onDiscuss || onImportFlowchart || onUndo || onPracticeCrossfire) ? (
// What happens to this case first (keep it, undo a change), then where to take it next.
<>
{onPracticeCrossfire && (
<Button variant="outline" size="sm" onClick={onPracticeCrossfire}>
<Flame className="mr-2 h-4 w-4" />
{t('crossfire.openFromResult', 'Open in Crossfire')}
<SaveResultAction saved={saved} onSave={onSave} />
{onUndo && (
<Button variant="outline" size="sm" onClick={onUndo} disabled={regeneratingSection !== null}>
<Undo2 className="mr-2 h-4 w-4" />
{t('generateResults.undo', 'Undo')}
</Button>
)}
<SaveResultAction saved={saved} onSave={onSave} />
{onDiscuss && (
<Button variant="outline" size="sm" onClick={onDiscuss}>
<MessageSquare className="mr-2 h-4 w-4" />
Expand All @@ -204,10 +206,10 @@ export function GenerateResults({ result, discoveredSources, onRegenerateSection
{t('generateResults.flowcharts')}
</Button>
)}
{onUndo && (
<Button variant="outline" size="sm" onClick={onUndo} disabled={regeneratingSection !== null}>
<Undo2 className="mr-2 h-4 w-4" />
{t('generateResults.undo', 'Undo')}
{onPracticeCrossfire && (
<Button variant="outline" size="sm" onClick={onPracticeCrossfire}>
<Flame className="mr-2 h-4 w-4" />
{t('crossfire.openFromResult', 'Open in Crossfire')}
</Button>
)}
</>
Expand Down
Loading