feat: add fusion for multi-run sessions

Adds Run fusion for multi-run-like sessions
Combines sibling outputs into a new fusion session
Adds configurable Fusion prompts in Magic Prompts settings
This commit is contained in:
Bohdan Triapitsyn
2026-05-15 13:26:46 +03:00
parent 713bb6dfb1
commit 3e53d7e071
24 changed files with 548 additions and 13 deletions
@@ -0,0 +1,31 @@
import type { SVGProps } from 'react';
export function FusionIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
aria-hidden="true"
{...props}
>
<path
d="M12 4.5v6.25M12 10.75v3.5M12 10.75H7.25A3.25 3.25 0 0 0 4 14v1.25M12 10.75h4.75A3.25 3.25 0 0 1 20 14v1.25"
stroke="currentColor"
strokeWidth="1.65"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M9.25 7.25 12 4.5l2.75 2.75"
stroke="currentColor"
strokeWidth="1.65"
strokeLinecap="round"
strokeLinejoin="round"
/>
<circle cx="4" cy="17.25" r="1.55" stroke="currentColor" strokeWidth="1.65" />
<circle cx="12" cy="17.25" r="1.55" stroke="currentColor" strokeWidth="1.65" />
<circle cx="20" cy="17.25" r="1.55" stroke="currentColor" strokeWidth="1.65" />
</svg>
);
}
@@ -22,6 +22,8 @@ export interface AgentSelectorProps {
disabled?: boolean;
/** ID for accessibility */
id?: string;
/** Portal menu to body instead of nearest dialog. */
portalToBody?: boolean;
}
/**
@@ -34,6 +36,7 @@ export const AgentSelector: React.FC<AgentSelectorProps> = ({
className,
disabled,
id,
portalToBody,
}) => {
const { t } = useI18n();
const getVisibleAgents = useConfigStore((state) => state.getVisibleAgents);
@@ -95,7 +98,7 @@ export const AgentSelector: React.FC<AgentSelectorProps> = ({
>
<SelectValue placeholder={t('multirun.agentSelector.placeholder')} />
</SelectTrigger>
<SelectContent fitContent>
<SelectContent fitContent portalToBody={portalToBody}>
{selectableAgents.length > 0 && (
<SelectGroup>
{selectableAgents.map((agent) => (
@@ -100,6 +100,14 @@ export interface ModelMultiSelectProps {
maxModels?: number;
/** Optional className for add model trigger button */
addButtonClassName?: string;
/** Direction for the model picker popup. Multi-run launcher opens upward near the footer. */
dropdownSide?: 'top' | 'bottom';
/** Optional className for the picker popup. */
dropdownClassName?: string;
/** Optional className for the trigger/dropdown positioning container. */
containerClassName?: string;
/** Optional trigger icon override. */
triggerIcon?: React.ReactNode;
}
/**
@@ -115,6 +123,10 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
showChips = true,
maxModels,
addButtonClassName,
dropdownSide = 'top',
dropdownClassName,
containerClassName,
triggerIcon,
}) => {
const { t } = useI18n();
const providers = useConfigStore((state) => state.providers);
@@ -128,7 +140,8 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
const dropdownRef = React.useRef<HTMLDivElement>(null);
const triggerRef = React.useRef<HTMLButtonElement>(null);
const itemRefs = React.useRef<(HTMLButtonElement | null)[]>([]);
const canAddModel = maxModels === undefined || selectedModels.length < maxModels;
const isSingleSelect = maxModels === 1;
const canAddModel = maxModels === undefined || selectedModels.length < maxModels || isSingleSelect;
// Count occurrences of each model for display purposes
const modelCounts = React.useMemo(() => {
@@ -208,12 +221,21 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
const hasResults = filteredFavorites.length > 0 || filteredRecents.length > 0 || filteredProviders.length > 0;
// Calculate available height: space above trigger within visible area
// Calculate available height: multi-run opens upward inside a scroller; fusion opens downward and may extend past the dialog.
React.useEffect(() => {
if (!isOpen || !triggerRef.current) return;
const triggerRect = triggerRef.current.getBoundingClientRect();
if (dropdownSide === 'bottom') {
const viewportHeight = window.visualViewport?.height ?? document.documentElement.clientHeight ?? window.innerHeight;
const spaceBelow = viewportHeight - triggerRect.bottom - 16;
// availableHeight is only the scrollable model list; reserve room for search + keyboard hint chrome.
const listSpaceBelow = spaceBelow - 112;
setAvailableHeight(Math.max(160, Math.min(320, listSpaceBelow)));
return;
}
// Find the nearest dialog or overflow ancestor to constrain within
let container: HTMLElement | null = triggerRef.current.parentElement;
while (container) {
@@ -231,7 +253,7 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
const spaceAbove = triggerRect.top - topBound - 16;
// Cap: min 150, max 300
setAvailableHeight(Math.max(150, Math.min(300, spaceAbove)));
}, [isOpen]);
}, [dropdownSide, isOpen]);
// Focus search input when opened
React.useEffect(() => {
@@ -292,12 +314,27 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
type="button"
disabled={!canAddModel}
onClick={() => {
onAdd({
const nextModel = {
providerID,
modelID,
displayName: (model.name as string) || modelID,
instanceId: generateInstanceId(),
};
if (isSingleSelect && selectedModels.length > 0 && onUpdate) {
onUpdate(0, nextModel);
setIsOpen(false);
setSearchQuery('');
setSelectedIndex(0);
return;
}
onAdd({
...nextModel,
});
if (isSingleSelect) {
setIsOpen(false);
setSearchQuery('');
setSelectedIndex(0);
}
// Don't close dropdown - allow selecting multiple
}}
onMouseEnter={() => setSelectedIndex(flatIndex)}
@@ -333,7 +370,7 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
<div className="space-y-2">
<div className="flex flex-wrap gap-1.5 items-center">
{/* Add model button (dropdown trigger) */}
<div className="relative" ref={dropdownRef}>
<div className={cn('relative', containerClassName)} ref={dropdownRef}>
<Button
ref={triggerRef}
type="button"
@@ -349,7 +386,7 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
setIsOpen(!isOpen);
}}
>
<Icon name="add" className="h-3.5 w-3.5 mr-1" />
{triggerIcon ?? <Icon name="add" className="h-3.5 w-3.5 mr-1" />}
{addButtonLabel ?? t('multirun.modelMultiSelect.actions.addModel')}
</Button>
@@ -398,12 +435,22 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
e.stopPropagation();
const selectedItem = flatModelList[selectedIndex];
if (selectedItem && canAddModel) {
onAdd({
const nextModel = {
providerID: selectedItem.providerID,
modelID: selectedItem.modelID,
displayName: (selectedItem.model.name as string) || selectedItem.modelID,
instanceId: generateInstanceId(),
});
};
if (isSingleSelect && selectedModels.length > 0 && onUpdate) {
onUpdate(0, nextModel);
} else {
onAdd(nextModel);
}
if (isSingleSelect) {
setIsOpen(false);
setSearchQuery('');
setSelectedIndex(0);
}
}
} else if (e.key === 'Escape') {
e.preventDefault();
@@ -418,7 +465,11 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
return (
<div
className="absolute bottom-full left-0 mb-1 z-50 w-[min(380px,calc(100vw-2rem))] max-w-[calc(100vw-2rem)] flex flex-col overflow-hidden rounded-xl border border-border/50 shadow-lg"
className={cn(
'absolute left-0 z-50 w-[min(420px,calc(100vw-2rem))] max-w-[calc(100vw-2rem)] flex flex-col overflow-hidden rounded-xl border border-border/50 shadow-lg',
dropdownSide === 'top' ? 'bottom-full mb-1' : 'top-full mt-1',
dropdownClassName,
)}
style={{
background: 'linear-gradient(var(--surface-elevated),var(--surface-elevated)),linear-gradient(var(--surface-background),var(--surface-background))',
}}
@@ -0,0 +1,259 @@
import React from 'react';
import type { Session } from '@opencode-ai/sdk/v2/client';
import { toast } from '@/components/ui';
import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Icon } from '@/components/icon/Icon';
import { ProviderLogo } from '@/components/ui/ProviderLogo';
import { useI18n } from '@/lib/i18n';
import { opencodeClient } from '@/lib/opencode/client';
import { useConfigStore } from '@/stores/useConfigStore';
import { resolveGlobalSessionDirectory, useGlobalSessionsStore } from '@/stores/useGlobalSessionsStore';
import { useSessionUIStore } from '@/sync/session-ui-store';
import { useAllLiveSessions } from '@/sync/sync-context';
import { getSyncMessages, getSyncParts } from '@/sync/sync-refs';
import { flattenAssistantTextParts } from '@/lib/messages/messageText';
import { getFusionSessionTitle, parseMultiRunSessionTitle } from '@/lib/multirun/title';
import { renderMagicPrompt } from '@/lib/magicPrompts';
import { AgentSelector } from './AgentSelector';
import { ModelMultiSelect, generateInstanceId, type ModelSelectionWithId } from './ModelMultiSelect';
type FusionSource = {
session: Session;
directory: string | null;
projectDirectory: string | null;
};
const buildSourcePart = (source: FusionSource, text: string, index: number): string => {
const title = source.session.title?.trim() || source.session.id;
return `\n\n--- RESULT ${index + 1}: ${title} ---\n${text.trim()}\n--- END RESULT ${index + 1} ---`;
};
const getSessionProjectDirectory = (sessionId: string, directory: string | null): string | null => {
const metadata = useSessionUIStore.getState().getWorktreeMetadata(sessionId);
return metadata?.projectDirectory ?? directory;
};
const getLastAssistantText = async (source: FusionSource): Promise<string> => {
const directory = source.directory ?? undefined;
const messages = getSyncMessages(source.session.id, directory);
if (messages.length === 0 && source.directory) {
const result = await opencodeClient.withDirectory(source.directory, () =>
opencodeClient.getSdkClient().session.messages({
sessionID: source.session.id,
directory: source.directory ?? undefined,
limit: 50,
})
);
const records = result.data ?? [];
for (let index = records.length - 1; index >= 0; index -= 1) {
const record = records[index] as { info?: { role?: string }; parts?: unknown[] };
if (record.info?.role !== 'assistant') continue;
return flattenAssistantTextParts((record.parts ?? []) as Parameters<typeof flattenAssistantTextParts>[0]).trim();
}
return '';
}
for (let index = messages.length - 1; index >= 0; index -= 1) {
const message = messages[index];
if (message.role !== 'assistant') continue;
return flattenAssistantTextParts(getSyncParts(message.id, directory)).trim();
}
return '';
};
export function MultiRunFusionDialog({
session,
open,
onOpenChange,
}: {
session: Session;
open: boolean;
onOpenChange: (open: boolean) => void;
}) {
const { t } = useI18n();
const liveSessions = useAllLiveSessions();
const activeSessions = useGlobalSessionsStore((state) => state.activeSessions);
const archivedSessions = useGlobalSessionsStore((state) => state.archivedSessions);
const providers = useConfigStore((state) => state.providers);
const currentProviderId = useConfigStore((state) => state.currentProviderId);
const currentModelId = useConfigStore((state) => state.currentModelId);
const currentAgentName = useConfigStore((state) => state.currentAgentName);
const [providerID, setProviderID] = React.useState(currentProviderId ?? '');
const [modelID, setModelID] = React.useState(currentModelId ?? '');
const [selectedModelSelection, setSelectedModelSelection] = React.useState<ModelSelectionWithId[]>(() => (
currentProviderId && currentModelId
? [{ providerID: currentProviderId, modelID: currentModelId, instanceId: generateInstanceId() }]
: []
));
const [variant, setVariant] = React.useState<string>('');
const [agent, setAgent] = React.useState(currentAgentName ?? '');
const [sources, setSources] = React.useState<FusionSource[]>([]);
const [isStarting, setIsStarting] = React.useState(false);
const parsed = React.useMemo(() => parseMultiRunSessionTitle(session.title), [session.title]);
const allSessions = React.useMemo(() => {
const byId = new Map<string, Session>();
for (const candidate of liveSessions) byId.set(candidate.id, candidate);
for (const candidate of activeSessions) byId.set(candidate.id, candidate);
for (const candidate of archivedSessions) byId.set(candidate.id, candidate);
if (session.id) byId.set(session.id, session);
return Array.from(byId.values());
}, [activeSessions, archivedSessions, liveSessions, session]);
React.useEffect(() => {
if (!open || !parsed) return;
const currentDirectory = useSessionUIStore.getState().getDirectoryForSession(session.id);
const currentProjectDirectory = getSessionProjectDirectory(session.id, currentDirectory);
const nextSources = allSessions
.map((candidate): FusionSource | null => {
const candidateParsed = parseMultiRunSessionTitle(candidate.title);
if (!candidateParsed || candidateParsed.groupSlug !== parsed.groupSlug || candidateParsed.fusion) return null;
const directory = useSessionUIStore.getState().getDirectoryForSession(candidate.id)
?? resolveGlobalSessionDirectory(candidate);
const projectDirectory = getSessionProjectDirectory(candidate.id, directory);
if (currentProjectDirectory && projectDirectory && currentProjectDirectory !== projectDirectory) return null;
return { session: candidate, directory, projectDirectory };
})
.filter((source): source is FusionSource => source !== null)
.sort((a, b) => (a.session.time?.created ?? 0) - (b.session.time?.created ?? 0));
setSources(nextSources);
}, [allSessions, open, parsed, session.id]);
const selectedProvider = providers.find((provider) => provider.id === providerID);
const selectedProviderModel = selectedProvider?.models.find((model) => model.id === modelID) as { variants?: Record<string, unknown> } | undefined;
const variantKeys = selectedProviderModel?.variants ? Object.keys(selectedProviderModel.variants) : [];
const canStart = Boolean(parsed && providerID && modelID && sources.length > 0 && !isStarting);
const handleModelSelect = React.useCallback((model: ModelSelectionWithId) => {
setSelectedModelSelection([model]);
setProviderID(model.providerID);
setModelID(model.modelID);
setVariant('');
}, []);
const selectedModelLabel = selectedModelSelection[0]?.displayName || selectedModelSelection[0]?.modelID || t('multirun.fusion.model.placeholder');
const handleStart = async () => {
if (!parsed || !providerID || !modelID) return;
setIsStarting(true);
try {
const sourceTexts = await Promise.all(sources.map((source) => getLastAssistantText(source)));
const usableSources = sources
.map((source, index) => ({ source, text: sourceTexts[index] ?? '' }))
.filter((item) => item.text.trim().length > 0);
if (usableSources.length === 0) {
toast.error(t('multirun.fusion.toast.noOutputs'));
return;
}
const directory = sources[0]?.projectDirectory ?? sources[0]?.directory ?? null;
const fusionTitle = getFusionSessionTitle(parsed.groupSlug, providerID, modelID);
const [visiblePrompt, instructionsPrompt] = await Promise.all([
renderMagicPrompt('session.fusion.visible'),
renderMagicPrompt('session.fusion.instructions'),
]);
const fusionSession = await useSessionUIStore.getState().createSession(fusionTitle, directory, null);
if (!fusionSession) throw new Error('Failed to create fusion session');
useSessionUIStore.getState().setCurrentSession(fusionSession.id, directory);
onOpenChange(false);
await opencodeClient.withDirectory(directory ?? opencodeClient.getDirectory(), () =>
opencodeClient.sendMessage({
id: fusionSession.id,
providerID,
modelID,
variant: variant || undefined,
agent: agent || undefined,
text: visiblePrompt,
additionalParts: [
{ text: instructionsPrompt, synthetic: true },
...usableSources.map((item, index) => ({ text: buildSourcePart(item.source, item.text, index), synthetic: true })),
{ text: '\n\n--- FUSION INPUTS END ---\nNow write the final fused answer.', synthetic: true },
],
})
);
} catch (error) {
console.error('[MultiRunFusion] Failed to start fusion', error);
toast.error(t('multirun.fusion.toast.failed'));
} finally {
setIsStarting(false);
}
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-xl overflow-visible">
<DialogHeader>
<DialogTitle>{t('multirun.fusion.title')}</DialogTitle>
<DialogDescription>{t('multirun.fusion.description')}</DialogDescription>
</DialogHeader>
<div className="flex flex-wrap items-center gap-2">
<div className="max-w-full">
<ModelMultiSelect
selectedModels={selectedModelSelection}
onAdd={handleModelSelect}
onUpdate={(_, model) => handleModelSelect(model)}
onRemove={() => {
setSelectedModelSelection([]);
setProviderID('');
setModelID('');
setVariant('');
}}
maxModels={1}
addButtonLabel={selectedModelLabel}
showChips={false}
addButtonClassName="h-8 w-fit max-w-[min(28rem,calc(100vw-8rem))] justify-start rounded-[9px] [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-[50px] px-3 py-1.5"
dropdownSide="bottom"
dropdownClassName="w-[min(28rem,calc(100vw-8rem))]"
triggerIcon={providerID ? <ProviderLogo providerId={providerID} className="h-3.5 w-3.5 mr-1" /> : undefined}
/>
</div>
{variantKeys.length > 0 ? (
<Select value={variant || '__default__'} onValueChange={(value) => setVariant(value === '__default__' ? '' : value)}>
<SelectTrigger size="lg" className="h-8 w-fit rounded-[9px] [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-[50px] !border-border/80 !bg-[var(--surface-subtle)] hover:!bg-[var(--interactive-hover)]/70 typography-meta font-medium text-foreground px-3 py-1.5">
<Icon name="brain-ai-3" className="h-3.5 w-3.5 text-muted-foreground" />
<SelectValue>{(value) => value === '__default__' ? t('multirun.modelMultiSelect.variant.default') : value}</SelectValue>
</SelectTrigger>
<SelectContent fitContent portalToBody>
<SelectItem value="__default__">{t('multirun.modelMultiSelect.variant.default')}</SelectItem>
{variantKeys.map((key) => <SelectItem key={key} value={key}>{key}</SelectItem>)}
</SelectContent>
</Select>
) : null}
<AgentSelector value={agent} onChange={setAgent} portalToBody className="h-8 rounded-[9px] [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-[50px] px-3 py-1.5" />
</div>
<div className="space-y-2">
<div className="typography-meta font-medium text-foreground">{t('multirun.fusion.sources.label', { count: sources.length })}</div>
<div className="max-h-56 space-y-1 overflow-auto rounded-lg border border-[var(--interactive-border)] p-1">
{sources.map((source) => (
<div key={source.session.id} className="flex items-center gap-2 rounded-md px-2 py-1.5 typography-meta">
<ProviderLogo providerId={parseMultiRunSessionTitle(source.session.title)?.providerID ?? ''} className="h-4 w-4" />
<span className="min-w-0 flex-1 truncate">{source.session.title || source.session.id}</span>
<button type="button" onClick={() => setSources((prev) => prev.filter((item) => item.session.id !== source.session.id))} className="text-muted-foreground hover:text-foreground">
<Icon name="close" className="h-4 w-4" />
</button>
</div>
))}
</div>
</div>
<DialogFooter>
<Button variant="ghost" onClick={() => onOpenChange(false)}>{t('multirun.fusion.actions.cancel')}</Button>
<Button onClick={handleStart} disabled={!canStart}>{isStarting ? t('multirun.fusion.actions.starting') : t('multirun.fusion.actions.start')}</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
@@ -140,6 +140,14 @@ const PROMPT_PAGE_MAP: Record<string, PromptPageConfig> = {
{ id: 'session.review.instructions', titleKey: 'settings.magicPrompts.page.block.instructions' },
],
},
'session.fusion': {
titleKey: 'settings.magicPrompts.page.group.sessionFusion.title',
descriptionKey: 'settings.magicPrompts.page.group.sessionFusion.description',
blocks: [
{ id: 'session.fusion.visible', titleKey: 'settings.magicPrompts.page.block.visiblePrompt' },
{ id: 'session.fusion.instructions', titleKey: 'settings.magicPrompts.page.block.instructions' },
],
},
};
const hasOwn = (input: Record<string, string>, key: string) => Object.prototype.hasOwnProperty.call(input, key);
@@ -47,6 +47,7 @@ export const MagicPromptsSidebar: React.FC<MagicPromptsSidebarProps> = ({ onItem
items: [
{ id: 'session.summary', titleKey: 'settings.magicPrompts.sidebar.item.sessionSummary' },
{ id: 'session.review', titleKey: 'settings.magicPrompts.sidebar.item.sessionWorkspaceReview' },
{ id: 'session.fusion', titleKey: 'settings.magicPrompts.sidebar.item.sessionFusion' },
],
},
] as const;
@@ -29,6 +29,9 @@ import { useSessionDisplayStore } from '@/stores/useSessionDisplayStore';
import { useSessionUnseenCount } from '@/sync/notification-store';
import { useSessionMultiSelectStore } from '@/stores/useSessionMultiSelectStore';
import { useI18n } from '@/lib/i18n';
import { parseMultiRunSessionTitle } from '@/lib/multirun/title';
import { MultiRunFusionDialog } from '@/components/multirun/MultiRunFusionDialog';
import { FusionIcon } from '@/components/icons/FusionIcon';
type Folder = { id: string; name: string; sessionIds: string[] };
@@ -319,6 +322,8 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
const sessionUpdatedLabel = formatSessionDateLabel(sessionTimestamp);
const sessionCompactUpdatedLabel = formatSessionCompactDateLabel(sessionTimestamp);
const isMenuOpen = openSidebarMenuKey === menuInstanceKey;
const isMultiRunLikeSession = React.useMemo(() => parseMultiRunSessionTitle(resolvedSession.title) !== null, [resolvedSession.title]);
const [fusionDialogOpen, setFusionDialogOpen] = React.useState(false);
const descendantCount = React.useMemo(() => collectNodeDescendantIds(node).length, [collectNodeDescendantIds, node]);
@@ -670,6 +675,12 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
<Icon name="download" className="mr-1 h-4 w-4" />
{t('sessions.sidebar.session.menu.exportMarkdown')}
</DropdownMenuItem>
{isMultiRunLikeSession ? (
<DropdownMenuItem onClick={() => setFusionDialogOpen(true)} className="[&>svg]:mr-1">
<FusionIcon className="mr-1 h-4 w-4" />
{t('sessions.sidebar.session.menu.runFusion')}
</DropdownMenuItem>
) : null}
{sessionDirectory && !archivedBucket ? (() => {
const scopeFolders = getFoldersForScope(sessionDirectory);
@@ -980,6 +991,13 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
</DialogFooter>
</DialogContent>
</Dialog>
{isMultiRunLikeSession ? (
<MultiRunFusionDialog
session={resolvedSession}
open={fusionDialogOpen}
onOpenChange={setFusionDialogOpen}
/>
) : null}
</React.Fragment>
);
}
+4 -2
View File
@@ -155,6 +155,7 @@ function SelectTrigger({
type SelectContentExtra = {
position?: "popper" | "item-aligned";
fitContent?: boolean;
portalToBody?: boolean;
sideOffset?: number;
side?: "top" | "right" | "bottom" | "left";
align?: "start" | "center" | "end";
@@ -165,6 +166,7 @@ function SelectContent({
children,
position = "popper",
fitContent = false,
portalToBody = false,
sideOffset,
side,
align,
@@ -175,13 +177,13 @@ function SelectContent({
const portalContainer = portalContext?.portalContainer ?? null;
return (
<BaseSelect.Portal container={portalContainer || undefined}>
<BaseSelect.Portal container={portalToBody ? undefined : portalContainer || undefined}>
<BaseSelect.Positioner
alignItemWithTrigger={alignItemWithTrigger}
sideOffset={sideOffset}
side={side}
align={align}
className="z-[120] pointer-events-auto"
className="absolute z-[120] pointer-events-auto"
>
<BaseSelect.Popup
data-slot="select-content"
@@ -164,6 +164,7 @@ export const settingsDict = {
'settings.magicPrompts.sidebar.item.planImplement': 'Implement Plan',
'settings.magicPrompts.sidebar.item.sessionSummary': 'Session Summary',
'settings.magicPrompts.sidebar.item.sessionWorkspaceReview': 'Workspace Review',
'settings.magicPrompts.sidebar.item.sessionFusion': 'Fusion',
'settings.remoteInstances.sidebar.title': 'Remote Instances',
'settings.remoteInstances.sidebar.total': 'Total {count}',
'settings.remoteInstances.sidebar.newSshInstanceName': 'New SSH Instance',
@@ -1541,6 +1542,8 @@ export const settingsDict = {
'settings.magicPrompts.page.group.sessionSummary.description': 'Prompts used by the /summary slash command: visible user message + hidden instructions. Non-destructive - does not compact session history.',
'settings.magicPrompts.page.group.sessionWorkspaceReview.title': 'Workspace Review',
'settings.magicPrompts.page.group.sessionWorkspaceReview.description': 'Prompts used by the /workspace-review slash command: visible user message + hidden instructions. Reviews current workspace changes for high-signal issues only.',
'settings.magicPrompts.page.group.sessionFusion.title': 'Fusion',
'settings.magicPrompts.page.group.sessionFusion.description': 'Prompts used when combining multi-run outputs into one final answer: visible user message + hidden instructions before source results.',
'settings.magicPrompts.page.actions.resetting': 'Resetting...',
'settings.magicPrompts.page.actions.resetAllOverrides': 'Reset All Overrides',
'settings.magicPrompts.page.actions.resetToDefault': 'Reset to Default',
+11
View File
@@ -180,6 +180,16 @@ export const dict = {
'multirun.modelMultiSelect.validation.minOnly': 'Select at least {min} models.',
'multirun.modelMultiSelect.validation.minToMax': 'Select from {min} to {max} models.',
'multirun.agentSelector.placeholder': 'Select an agent',
'multirun.fusion.title': 'Run fusion',
'multirun.fusion.description': 'Combine the selected multi-run outputs into one stronger answer.',
'multirun.fusion.provider.placeholder': 'Provider',
'multirun.fusion.model.placeholder': 'Model',
'multirun.fusion.sources.label': 'Sources ({count})',
'multirun.fusion.actions.cancel': 'Cancel',
'multirun.fusion.actions.start': 'Start fusion',
'multirun.fusion.actions.starting': 'Starting...',
'multirun.fusion.toast.noOutputs': 'No assistant outputs found to fuse.',
'multirun.fusion.toast.failed': 'Failed to start fusion.',
'sessions.sidebar.header.actions.searchSessions': 'Search sessions',
'sessions.sidebar.header.actions.exitSelection': 'Exit selection',
'sessions.sidebar.header.actions.selectSessions': 'Select sessions',
@@ -242,6 +252,7 @@ export const dict = {
'sessions.sidebar.session.menu.copyLink': 'Copy link',
'sessions.sidebar.session.menu.unshare': 'Unshare',
'sessions.sidebar.session.menu.exportMarkdown': 'Export Markdown',
'sessions.sidebar.session.menu.runFusion': 'Run fusion',
'sessions.sidebar.session.menu.openInSidePanel': 'Open in Side Panel',
'sessions.sidebar.session.menu.betaBadge': 'beta',
'sessions.sidebar.session.menu.label': 'Session menu',
@@ -164,6 +164,7 @@ export const settingsDict = {
"settings.magicPrompts.sidebar.item.planImplement": "Implementar plan",
"settings.magicPrompts.sidebar.item.sessionSummary": "Resumen de sesión",
"settings.magicPrompts.sidebar.item.sessionWorkspaceReview": "Revisión del espacio de trabajo",
"settings.magicPrompts.sidebar.item.sessionFusion": "Fusion",
"settings.remoteInstances.sidebar.title": "Instancias remotas",
"settings.remoteInstances.sidebar.total": "Total {count}",
"settings.remoteInstances.sidebar.newSshInstanceName": "Nueva instancia SSH",
@@ -1541,6 +1542,8 @@ export const settingsDict = {
"settings.magicPrompts.page.group.sessionSummary.description": "Prompts usados por el comando /summary: mensaje visible del usuario + instrucciones ocultas. No destructivo: no compacta el historial de la sesión.",
"settings.magicPrompts.page.group.sessionWorkspaceReview.title": "Revisión del espacio de trabajo",
"settings.magicPrompts.page.group.sessionWorkspaceReview.description": "Prompts usados por el comando /workspace-review: mensaje visible del usuario + instrucciones ocultas. Revisa los cambios actuales del espacio de trabajo solo para problemas de alta señal.",
"settings.magicPrompts.page.group.sessionFusion.title": "Fusion",
"settings.magicPrompts.page.group.sessionFusion.description": "Prompts usados para combinar salidas de multi-run en una respuesta final: mensaje visible del usuario + instrucciones ocultas antes de los resultados fuente.",
"settings.magicPrompts.page.actions.resetting": "Restableciendo...",
"settings.magicPrompts.page.actions.resetAllOverrides": "Restablecer todas las sobrescrituras",
"settings.magicPrompts.page.actions.resetToDefault": "Restablecer valores predeterminados",
+11
View File
@@ -181,6 +181,16 @@ export const dict: Record<I18nKey, string> = {
"multirun.modelMultiSelect.validation.minOnly": "Selecciona al menos {min} modelos.",
"multirun.modelMultiSelect.validation.minToMax": "Selecciona entre {min} y {max} modelos.",
"multirun.agentSelector.placeholder": "Selecciona un agente",
"multirun.fusion.title": "Ejecutar fusion",
"multirun.fusion.description": "Combina los resultados seleccionados del multi-run en una respuesta más sólida.",
"multirun.fusion.provider.placeholder": "Proveedor",
"multirun.fusion.model.placeholder": "Modelo",
"multirun.fusion.sources.label": "Fuentes ({count})",
"multirun.fusion.actions.cancel": "Cancelar",
"multirun.fusion.actions.start": "Iniciar fusion",
"multirun.fusion.actions.starting": "Iniciando...",
"multirun.fusion.toast.noOutputs": "No se encontraron respuestas del asistente para fusionar.",
"multirun.fusion.toast.failed": "No se pudo iniciar fusion.",
"sessions.sidebar.header.actions.searchSessions": "Buscar sesiones",
"sessions.sidebar.header.actions.exitSelection": "Salir de selección",
"sessions.sidebar.header.actions.selectSessions": "Seleccionar sesiones",
@@ -243,6 +253,7 @@ export const dict: Record<I18nKey, string> = {
"sessions.sidebar.session.menu.copyLink": "Copiar enlace",
"sessions.sidebar.session.menu.unshare": "Dejar de compartir",
"sessions.sidebar.session.menu.exportMarkdown": "Exportar Markdown",
"sessions.sidebar.session.menu.runFusion": "Ejecutar fusion",
"sessions.sidebar.session.menu.openInSidePanel": "Abrir en panel lateral",
"sessions.sidebar.session.menu.betaBadge": "beta",
"sessions.sidebar.session.menu.label": "Menú de sesión",
@@ -164,6 +164,7 @@ export const settingsDict = {
'settings.magicPrompts.sidebar.item.planImplement': '계획 구현',
'settings.magicPrompts.sidebar.item.sessionSummary': '세션 요약',
'settings.magicPrompts.sidebar.item.sessionWorkspaceReview': '워크스페이스 리뷰',
'settings.magicPrompts.sidebar.item.sessionFusion': 'Fusion',
'settings.remoteInstances.sidebar.title': '원격 인스턴스',
'settings.remoteInstances.sidebar.total': '총 {count}개',
'settings.remoteInstances.sidebar.newSshInstanceName': '새 SSH 인스턴스',
@@ -1541,6 +1542,8 @@ export const settingsDict = {
'settings.magicPrompts.page.group.sessionSummary.description': '/summary slash command에서 사용하는 프롬프트입니다: 표시 사용자 메시지 + 숨겨진 지침. 비파괴적이며 세션 기록을 압축하지 않습니다.',
'settings.magicPrompts.page.group.sessionWorkspaceReview.title': '워크스페이스 리뷰',
'settings.magicPrompts.page.group.sessionWorkspaceReview.description': '/workspace-review slash command에서 사용하는 프롬프트입니다: 표시 사용자 메시지 + 숨겨진 지침. 현재 workspace 변경 사항에서 중요한 문제만 리뷰합니다.',
'settings.magicPrompts.page.group.sessionFusion.title': 'Fusion',
'settings.magicPrompts.page.group.sessionFusion.description': 'multi-run 출력을 하나의 최종 답변으로 결합할 때 사용하는 프롬프트입니다: 보이는 사용자 메시지 + 소스 결과 앞의 숨겨진 지침.',
'settings.magicPrompts.page.actions.resetting': '초기화 중...',
'settings.magicPrompts.page.actions.resetAllOverrides': '모든 오버라이드 초기화',
'settings.magicPrompts.page.actions.resetToDefault': '기본값으로 초기화',
+11
View File
@@ -181,6 +181,16 @@ export const dict: Record<I18nKey, string> = {
'multirun.modelMultiSelect.validation.minOnly': '모델을 최소 {min}개 선택하세요.',
'multirun.modelMultiSelect.validation.minToMax': '모델을 {min}~{max}개 선택하세요.',
'multirun.agentSelector.placeholder': '에이전트 선택',
'multirun.fusion.title': 'fusion 실행',
'multirun.fusion.description': '선택한 multi-run 출력을 하나의 더 강한 답변으로 결합합니다.',
'multirun.fusion.provider.placeholder': 'Provider',
'multirun.fusion.model.placeholder': 'Model',
'multirun.fusion.sources.label': '소스({count})',
'multirun.fusion.actions.cancel': '취소',
'multirun.fusion.actions.start': 'fusion 시작',
'multirun.fusion.actions.starting': '시작 중...',
'multirun.fusion.toast.noOutputs': 'fusion할 어시스턴트 응답을 찾지 못했습니다.',
'multirun.fusion.toast.failed': 'fusion을 시작하지 못했습니다.',
'sessions.sidebar.header.actions.searchSessions': '세션 검색',
'sessions.sidebar.header.actions.exitSelection': '선택 종료',
'sessions.sidebar.header.actions.selectSessions': '세션 선택',
@@ -243,6 +253,7 @@ export const dict: Record<I18nKey, string> = {
'sessions.sidebar.session.menu.copyLink': '링크 복사',
'sessions.sidebar.session.menu.unshare': '공유 해제',
'sessions.sidebar.session.menu.exportMarkdown': 'Markdown 내보내기',
'sessions.sidebar.session.menu.runFusion': 'fusion 실행',
'sessions.sidebar.session.menu.openInSidePanel': '사이드 패널에서 열기',
'sessions.sidebar.session.menu.betaBadge': 'beta',
'sessions.sidebar.session.menu.label': '세션 메뉴',
@@ -280,6 +280,8 @@ export const settingsDict = {
'settings.magicPrompts.page.group.sessionSummary.title': 'Podsumowanie Sesji',
'settings.magicPrompts.page.group.sessionWorkspaceReview.description': 'Prompty używane przez polecenie /workspace-review: widoczna wiadomość użytkownika + ukryte instrukcje. Przegląda zmiany w bieżącym obszarze roboczym tylko pod kątem istotnych problemów.',
'settings.magicPrompts.page.group.sessionWorkspaceReview.title': 'Przegląd obszaru roboczego',
'settings.magicPrompts.page.group.sessionFusion.title': 'Fusion',
'settings.magicPrompts.page.group.sessionFusion.description': 'Prompty używane do łączenia wyników multi-run w jedną końcową odpowiedź: widoczna wiadomość użytkownika + ukryte instrukcje przed wynikami źródłowymi.',
'settings.magicPrompts.page.loading.aria': 'Ładowanie',
'settings.magicPrompts.page.loading.text': 'Ładowanie Magicznych Promptów...',
'settings.magicPrompts.page.placeholdersLabel': 'Placeholdery:',
@@ -314,6 +316,7 @@ export const settingsDict = {
'settings.magicPrompts.sidebar.item.planTodo': 'Planowanie Todo',
'settings.magicPrompts.sidebar.item.sessionSummary': 'Podsumowanie Sesji',
'settings.magicPrompts.sidebar.item.sessionWorkspaceReview': 'Przegląd obszaru roboczego',
'settings.magicPrompts.sidebar.item.sessionFusion': 'Fusion',
'settings.magicPrompts.sidebar.title': 'Magiczne Prompty',
'settings.mcp.page.actions.authorize': 'Autoryzuj',
'settings.mcp.page.actions.clearAuth': 'Wyczyść autoryzację',
+11
View File
@@ -84,6 +84,7 @@ export const dict: Record<I18nKey, string> = {
'sessions.sidebar.session.menu.copyLink': 'Kopiuj link',
'sessions.sidebar.session.menu.unshare': 'Cofnij udostępnienie',
'sessions.sidebar.session.menu.exportMarkdown': 'Eksportuj Markdown',
'sessions.sidebar.session.menu.runFusion': 'Uruchom fusion',
'sessions.sidebar.session.menu.openInSidePanel': 'Otwórz w panelu bocznym',
'sessions.sidebar.session.menu.betaBadge': 'beta',
'sessions.sidebar.session.menu.label': 'Menu sesji',
@@ -256,6 +257,16 @@ export const dict: Record<I18nKey, string> = {
'multirun.modelMultiSelect.validation.minOnly': 'Wybierz przynajmniej {min} modele.',
'multirun.modelMultiSelect.validation.minToMax': 'Wybierz od {min} do {max} modeli.',
'multirun.agentSelector.placeholder': 'Wybierz agenta',
'multirun.fusion.title': 'Uruchom fusion',
'multirun.fusion.description': 'Połącz wybrane wyniki multi-run w jedną mocniejszą odpowiedź.',
'multirun.fusion.provider.placeholder': 'Provider',
'multirun.fusion.model.placeholder': 'Model',
'multirun.fusion.sources.label': 'Źródła ({count})',
'multirun.fusion.actions.cancel': 'Anuluj',
'multirun.fusion.actions.start': 'Uruchom fusion',
'multirun.fusion.actions.starting': 'Uruchamianie...',
'multirun.fusion.toast.noOutputs': 'Nie znaleziono odpowiedzi asystenta do fusion.',
'multirun.fusion.toast.failed': 'Nie udało się uruchomić fusion.',
'sessions.sidebar.session.export.dialog.includeSubtasks': 'Dołącz pod-agentów',
'sessions.sidebar.session.export.dialog.confirm': 'Eksportuj',
'sessions.sidebar.session.status.active': 'Sesja aktywna',
@@ -164,6 +164,7 @@ export const settingsDict = {
"settings.magicPrompts.sidebar.item.planImplement": "Implementar plano",
"settings.magicPrompts.sidebar.item.sessionSummary": "Resumo de sessão",
"settings.magicPrompts.sidebar.item.sessionWorkspaceReview": "Revisão do workspace",
"settings.magicPrompts.sidebar.item.sessionFusion": "Fusion",
"settings.remoteInstances.sidebar.title": "Instâncias remotas",
"settings.remoteInstances.sidebar.total": "Total {count}",
"settings.remoteInstances.sidebar.newSshInstanceName": "Nova instância SSH",
@@ -1541,6 +1542,8 @@ export const settingsDict = {
"settings.magicPrompts.page.group.sessionSummary.description": "Prompts usados pelo comando /summary: mensagem visível do usuário + instruções ocultas. Não destrutivo: não compacta o histórico da sessão.",
"settings.magicPrompts.page.group.sessionWorkspaceReview.title": "Revisão do workspace",
"settings.magicPrompts.page.group.sessionWorkspaceReview.description": "Prompts usados pelo comando /workspace-review: mensagem visível do usuário + instruções ocultas. Revise apenas problemas importantes nas alterações atuais do workspace.",
"settings.magicPrompts.page.group.sessionFusion.title": "Fusion",
"settings.magicPrompts.page.group.sessionFusion.description": "Prompts usados para combinar saídas de multi-run em uma resposta final: mensagem visível do usuário + instruções ocultas antes dos resultados de origem.",
"settings.magicPrompts.page.actions.resetting": "Redefinindo...",
"settings.magicPrompts.page.actions.resetAllOverrides": "Reiniciar todas as sobrescrituras",
"settings.magicPrompts.page.actions.resetToDefault": "Reiniciar valores padrões",
@@ -181,6 +181,16 @@ export const dict: Record<I18nKey, string> = {
"multirun.modelMultiSelect.validation.minOnly": "Selecione ao menos {min} modelos.",
"multirun.modelMultiSelect.validation.minToMax": "Selecione entre {min} e {max} modelos.",
"multirun.agentSelector.placeholder": "Selecione um agente",
"multirun.fusion.title": "Executar fusion",
"multirun.fusion.description": "Combine os resultados selecionados do multi-run em uma resposta mais forte.",
"multirun.fusion.provider.placeholder": "Provedor",
"multirun.fusion.model.placeholder": "Modelo",
"multirun.fusion.sources.label": "Fontes ({count})",
"multirun.fusion.actions.cancel": "Cancelar",
"multirun.fusion.actions.start": "Iniciar fusion",
"multirun.fusion.actions.starting": "Iniciando...",
"multirun.fusion.toast.noOutputs": "Nenhuma resposta do assistente encontrada para fusion.",
"multirun.fusion.toast.failed": "Falha ao iniciar fusion.",
"sessions.sidebar.header.actions.searchSessions": "Pesquisar sessões",
"sessions.sidebar.header.actions.exitSelection": "Sair da seleção",
"sessions.sidebar.header.actions.selectSessions": "Selecionar sessões",
@@ -243,6 +253,7 @@ export const dict: Record<I18nKey, string> = {
"sessions.sidebar.session.menu.copyLink": "Copiar link",
"sessions.sidebar.session.menu.unshare": "Parar de compartilhar",
"sessions.sidebar.session.menu.exportMarkdown": "Exportar Markdown",
"sessions.sidebar.session.menu.runFusion": "Executar fusion",
"sessions.sidebar.session.menu.openInSidePanel": "Abrir no painel lateral",
"sessions.sidebar.session.menu.betaBadge": "beta",
"sessions.sidebar.session.menu.label": "Menu da sessão",
@@ -164,6 +164,7 @@ export const settingsDict = {
"settings.magicPrompts.sidebar.item.planImplement": "Реалізувати план",
"settings.magicPrompts.sidebar.item.sessionSummary": "Підсумок сесії",
"settings.magicPrompts.sidebar.item.sessionWorkspaceReview": "Огляд робочого простору",
"settings.magicPrompts.sidebar.item.sessionFusion": "Fusion",
"settings.remoteInstances.sidebar.title": "Віддалені інстанси",
"settings.remoteInstances.sidebar.total": "Усього {count}",
"settings.remoteInstances.sidebar.newSshInstanceName": "Новий інстанс SSH",
@@ -1541,6 +1542,8 @@ export const settingsDict = {
"settings.magicPrompts.page.group.sessionSummary.description": "Промпти, які використовуються командою /summary: видиме повідомлення користувача + приховані інструкції. Неруйнівний – не стискає історію сесії.",
"settings.magicPrompts.page.group.sessionWorkspaceReview.title": "Огляд робочого простору",
"settings.magicPrompts.page.group.sessionWorkspaceReview.description": "Промпти, які використовуються командою /workspace-review: видиме повідомлення користувача + приховані інструкції. Переглядає поточні зміни робочого простору лише для проблем із сильним сигналом.",
"settings.magicPrompts.page.group.sessionFusion.title": "Fusion",
"settings.magicPrompts.page.group.sessionFusion.description": "Промпти для обʼєднання результатів multi-run в одну фінальну відповідь: видиме повідомлення користувача + приховані інструкції перед результатами джерел.",
"settings.magicPrompts.page.actions.resetting": "Скидання...",
"settings.magicPrompts.page.actions.resetAllOverrides": "Скинути всі перевизначення",
"settings.magicPrompts.page.actions.resetToDefault": "Скинути до замовчування",
+11
View File
@@ -181,6 +181,16 @@ export const dict: Record<I18nKey, string> = {
"multirun.modelMultiSelect.validation.minOnly": "Виберіть щонайменше моделей: {min}.",
"multirun.modelMultiSelect.validation.minToMax": "Виберіть від {min} до {max} моделей.",
"multirun.agentSelector.placeholder": "Вибрати агента",
"multirun.fusion.title": "Запустити fusion",
"multirun.fusion.description": "Обʼєднайте вибрані результати multi-run в одну сильнішу відповідь.",
"multirun.fusion.provider.placeholder": "Провайдер",
"multirun.fusion.model.placeholder": "Модель",
"multirun.fusion.sources.label": "Джерела ({count})",
"multirun.fusion.actions.cancel": "Скасувати",
"multirun.fusion.actions.start": "Запустити fusion",
"multirun.fusion.actions.starting": "Запуск...",
"multirun.fusion.toast.noOutputs": "Не знайдено відповідей асистента для fusion.",
"multirun.fusion.toast.failed": "Не вдалося запустити fusion.",
"sessions.sidebar.header.actions.searchSessions": "Пошук сесій",
"sessions.sidebar.header.actions.exitSelection": "Вийти з вибору",
"sessions.sidebar.header.actions.selectSessions": "Вибрати сесії",
@@ -243,6 +253,7 @@ export const dict: Record<I18nKey, string> = {
"sessions.sidebar.session.menu.copyLink": "Копіювати посилання",
"sessions.sidebar.session.menu.unshare": "Скасувати спільний доступ",
"sessions.sidebar.session.menu.exportMarkdown": "Експорт Markdown",
"sessions.sidebar.session.menu.runFusion": "Запустити fusion",
"sessions.sidebar.session.menu.openInSidePanel": "Відкрити на бічній панелі",
"sessions.sidebar.session.menu.betaBadge": "бета-версія",
"sessions.sidebar.session.menu.label": "Меню сесії",
@@ -164,6 +164,7 @@ export const settingsDict = {
'settings.magicPrompts.sidebar.item.planImplement': '执行计划',
'settings.magicPrompts.sidebar.item.sessionSummary': '会话总结',
'settings.magicPrompts.sidebar.item.sessionWorkspaceReview': '工作区审查',
'settings.magicPrompts.sidebar.item.sessionFusion': 'Fusion',
'settings.remoteInstances.sidebar.title': '远程实例',
'settings.remoteInstances.sidebar.total': '总计 {count}',
'settings.remoteInstances.sidebar.newSshInstanceName': '新建 SSH 实例',
@@ -1541,6 +1542,8 @@ export const settingsDict = {
'settings.magicPrompts.page.group.sessionSummary.description': '由 /summary 斜杠命令使用的提示词:可见用户消息 + 隐藏说明。非破坏性,不会压缩会话历史。',
'settings.magicPrompts.page.group.sessionWorkspaceReview.title': '工作区审查',
'settings.magicPrompts.page.group.sessionWorkspaceReview.description': '由 /workspace-review 斜杠命令使用的提示词:可见用户消息 + 隐藏说明。仅审查当前工作区的高信号问题。',
'settings.magicPrompts.page.group.sessionFusion.title': 'Fusion',
'settings.magicPrompts.page.group.sessionFusion.description': '用于将 multi-run 输出合并为一个最终答案的提示词:可见用户消息 + 源结果前的隐藏说明。',
'settings.magicPrompts.page.actions.resetting': '重置中...',
'settings.magicPrompts.page.actions.resetAllOverrides': '重置全部覆盖',
'settings.magicPrompts.page.actions.resetToDefault': '重置为默认',
@@ -181,6 +181,16 @@ export const dict: Record<I18nKey, string> = {
'multirun.modelMultiSelect.validation.minOnly': '请至少选择 {min} 个模型。',
'multirun.modelMultiSelect.validation.minToMax': '请选择 {min} 到 {max} 个模型。',
'multirun.agentSelector.placeholder': '选择智能体',
'multirun.fusion.title': '运行 fusion',
'multirun.fusion.description': '将选中的 multi-run 输出合并为一个更强的答案。',
'multirun.fusion.provider.placeholder': 'Provider',
'multirun.fusion.model.placeholder': 'Model',
'multirun.fusion.sources.label': '来源({count}',
'multirun.fusion.actions.cancel': '取消',
'multirun.fusion.actions.start': '开始 fusion',
'multirun.fusion.actions.starting': '正在开始...',
'multirun.fusion.toast.noOutputs': '未找到可用于 fusion 的助手输出。',
'multirun.fusion.toast.failed': '启动 fusion 失败。',
'sessions.sidebar.header.actions.searchSessions': '搜索会话',
'sessions.sidebar.header.actions.exitSelection': '退出选择',
'sessions.sidebar.header.actions.selectSessions': '选择会话',
@@ -243,6 +253,7 @@ export const dict: Record<I18nKey, string> = {
'sessions.sidebar.session.menu.copyLink': '复制链接',
'sessions.sidebar.session.menu.unshare': '取消分享',
'sessions.sidebar.session.menu.exportMarkdown': '导出 Markdown',
'sessions.sidebar.session.menu.runFusion': '运行 fusion',
'sessions.sidebar.session.menu.openInSidePanel': '在侧边面板中打开',
'sessions.sidebar.session.menu.betaBadge': 'beta',
'sessions.sidebar.session.menu.label': '会话菜单',
+23 -1
View File
@@ -26,7 +26,9 @@ export type MagicPromptId =
| 'session.summary.visible'
| 'session.summary.instructions'
| 'session.review.visible'
| 'session.review.instructions';
| 'session.review.instructions'
| 'session.fusion.visible'
| 'session.fusion.instructions';
export interface MagicPromptDefinition {
id: MagicPromptId;
@@ -570,6 +572,26 @@ Output:
Keep the review concise and practical.`,
},
{
id: 'session.fusion.visible',
title: 'Fusion Visible Prompt',
group: 'Session',
description: 'Visible user message for multi-run fusion sessions.',
template: 'Create the best combined answer from the multi-run results.',
},
{
id: 'session.fusion.instructions',
title: 'Fusion Instructions',
group: 'Session',
description: 'Hidden instructions used before multi-run source outputs in fusion sessions.',
template: `You are performing fusion over multiple model outputs from the same original task.
Goal: produce the strongest possible final answer by combining complementary information, resolving conflicts, removing duplicates, and preserving useful nuance.
Use the results below as source material. Do not mention that the inputs were hidden parts. If sources disagree, prefer the most specific, well-supported, and internally consistent answer.
--- FUSION INPUTS START ---`,
},
] as const;
const MAGIC_PROMPT_DEFINITION_BY_ID = new Map<MagicPromptId, MagicPromptDefinition>(
+42
View File
@@ -0,0 +1,42 @@
export type ParsedMultiRunTitle = {
groupSlug: string;
providerID: string;
modelID: string;
index?: number;
fusion: boolean;
};
const GROUP_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,48}[a-z0-9])?$/;
export const parseMultiRunSessionTitle = (title?: string | null): ParsedMultiRunTitle | null => {
if (!title) return null;
const segments = title.split('/');
if (segments.length !== 3 && segments.length !== 4) return null;
const [groupSlug, providerID, modelID, suffix] = segments;
if (!GROUP_SLUG_PATTERN.test(groupSlug)) return null;
if (!providerID?.trim() || !modelID?.trim()) return null;
if (providerID !== providerID.trim() || modelID !== modelID.trim()) return null;
if (segments.length === 3) {
return { groupSlug, providerID, modelID, fusion: false };
}
if (suffix === 'fusion') {
return { groupSlug, providerID, modelID, fusion: true };
}
if (!/^\d+$/.test(suffix)) return null;
const index = Number.parseInt(suffix, 10);
if (!Number.isSafeInteger(index) || index <= 0) return null;
return { groupSlug, providerID, modelID, index, fusion: false };
};
export const isMultiRunSessionTitle = (title?: string | null): boolean => {
return parseMultiRunSessionTitle(title) !== null;
};
export const getFusionSessionTitle = (groupSlug: string, providerID: string, modelID: string): string => {
return `${groupSlug}/${providerID}/${modelID}/fusion`;
};