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:
@@ -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;
|
disabled?: boolean;
|
||||||
/** ID for accessibility */
|
/** ID for accessibility */
|
||||||
id?: string;
|
id?: string;
|
||||||
|
/** Portal menu to body instead of nearest dialog. */
|
||||||
|
portalToBody?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,6 +36,7 @@ export const AgentSelector: React.FC<AgentSelectorProps> = ({
|
|||||||
className,
|
className,
|
||||||
disabled,
|
disabled,
|
||||||
id,
|
id,
|
||||||
|
portalToBody,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const getVisibleAgents = useConfigStore((state) => state.getVisibleAgents);
|
const getVisibleAgents = useConfigStore((state) => state.getVisibleAgents);
|
||||||
@@ -95,7 +98,7 @@ export const AgentSelector: React.FC<AgentSelectorProps> = ({
|
|||||||
>
|
>
|
||||||
<SelectValue placeholder={t('multirun.agentSelector.placeholder')} />
|
<SelectValue placeholder={t('multirun.agentSelector.placeholder')} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent fitContent>
|
<SelectContent fitContent portalToBody={portalToBody}>
|
||||||
{selectableAgents.length > 0 && (
|
{selectableAgents.length > 0 && (
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
{selectableAgents.map((agent) => (
|
{selectableAgents.map((agent) => (
|
||||||
|
|||||||
@@ -100,6 +100,14 @@ export interface ModelMultiSelectProps {
|
|||||||
maxModels?: number;
|
maxModels?: number;
|
||||||
/** Optional className for add model trigger button */
|
/** Optional className for add model trigger button */
|
||||||
addButtonClassName?: string;
|
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,
|
showChips = true,
|
||||||
maxModels,
|
maxModels,
|
||||||
addButtonClassName,
|
addButtonClassName,
|
||||||
|
dropdownSide = 'top',
|
||||||
|
dropdownClassName,
|
||||||
|
containerClassName,
|
||||||
|
triggerIcon,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const providers = useConfigStore((state) => state.providers);
|
const providers = useConfigStore((state) => state.providers);
|
||||||
@@ -128,7 +140,8 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
|
|||||||
const dropdownRef = React.useRef<HTMLDivElement>(null);
|
const dropdownRef = React.useRef<HTMLDivElement>(null);
|
||||||
const triggerRef = React.useRef<HTMLButtonElement>(null);
|
const triggerRef = React.useRef<HTMLButtonElement>(null);
|
||||||
const itemRefs = 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
|
// Count occurrences of each model for display purposes
|
||||||
const modelCounts = React.useMemo(() => {
|
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;
|
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(() => {
|
React.useEffect(() => {
|
||||||
if (!isOpen || !triggerRef.current) return;
|
if (!isOpen || !triggerRef.current) return;
|
||||||
|
|
||||||
const triggerRect = triggerRef.current.getBoundingClientRect();
|
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
|
// Find the nearest dialog or overflow ancestor to constrain within
|
||||||
let container: HTMLElement | null = triggerRef.current.parentElement;
|
let container: HTMLElement | null = triggerRef.current.parentElement;
|
||||||
while (container) {
|
while (container) {
|
||||||
@@ -231,7 +253,7 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
|
|||||||
const spaceAbove = triggerRect.top - topBound - 16;
|
const spaceAbove = triggerRect.top - topBound - 16;
|
||||||
// Cap: min 150, max 300
|
// Cap: min 150, max 300
|
||||||
setAvailableHeight(Math.max(150, Math.min(300, spaceAbove)));
|
setAvailableHeight(Math.max(150, Math.min(300, spaceAbove)));
|
||||||
}, [isOpen]);
|
}, [dropdownSide, isOpen]);
|
||||||
|
|
||||||
// Focus search input when opened
|
// Focus search input when opened
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -292,12 +314,27 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
|
|||||||
type="button"
|
type="button"
|
||||||
disabled={!canAddModel}
|
disabled={!canAddModel}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onAdd({
|
const nextModel = {
|
||||||
providerID,
|
providerID,
|
||||||
modelID,
|
modelID,
|
||||||
displayName: (model.name as string) || modelID,
|
displayName: (model.name as string) || modelID,
|
||||||
instanceId: generateInstanceId(),
|
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
|
// Don't close dropdown - allow selecting multiple
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => setSelectedIndex(flatIndex)}
|
onMouseEnter={() => setSelectedIndex(flatIndex)}
|
||||||
@@ -333,7 +370,7 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex flex-wrap gap-1.5 items-center">
|
<div className="flex flex-wrap gap-1.5 items-center">
|
||||||
{/* Add model button (dropdown trigger) */}
|
{/* Add model button (dropdown trigger) */}
|
||||||
<div className="relative" ref={dropdownRef}>
|
<div className={cn('relative', containerClassName)} ref={dropdownRef}>
|
||||||
<Button
|
<Button
|
||||||
ref={triggerRef}
|
ref={triggerRef}
|
||||||
type="button"
|
type="button"
|
||||||
@@ -349,7 +386,7 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
|
|||||||
setIsOpen(!isOpen);
|
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')}
|
{addButtonLabel ?? t('multirun.modelMultiSelect.actions.addModel')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
@@ -398,12 +435,22 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const selectedItem = flatModelList[selectedIndex];
|
const selectedItem = flatModelList[selectedIndex];
|
||||||
if (selectedItem && canAddModel) {
|
if (selectedItem && canAddModel) {
|
||||||
onAdd({
|
const nextModel = {
|
||||||
providerID: selectedItem.providerID,
|
providerID: selectedItem.providerID,
|
||||||
modelID: selectedItem.modelID,
|
modelID: selectedItem.modelID,
|
||||||
displayName: (selectedItem.model.name as string) || selectedItem.modelID,
|
displayName: (selectedItem.model.name as string) || selectedItem.modelID,
|
||||||
instanceId: generateInstanceId(),
|
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') {
|
} else if (e.key === 'Escape') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -418,7 +465,11 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<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={{
|
style={{
|
||||||
background: 'linear-gradient(var(--surface-elevated),var(--surface-elevated)),linear-gradient(var(--surface-background),var(--surface-background))',
|
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' },
|
{ 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);
|
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: [
|
items: [
|
||||||
{ id: 'session.summary', titleKey: 'settings.magicPrompts.sidebar.item.sessionSummary' },
|
{ id: 'session.summary', titleKey: 'settings.magicPrompts.sidebar.item.sessionSummary' },
|
||||||
{ id: 'session.review', titleKey: 'settings.magicPrompts.sidebar.item.sessionWorkspaceReview' },
|
{ id: 'session.review', titleKey: 'settings.magicPrompts.sidebar.item.sessionWorkspaceReview' },
|
||||||
|
{ id: 'session.fusion', titleKey: 'settings.magicPrompts.sidebar.item.sessionFusion' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ import { useSessionDisplayStore } from '@/stores/useSessionDisplayStore';
|
|||||||
import { useSessionUnseenCount } from '@/sync/notification-store';
|
import { useSessionUnseenCount } from '@/sync/notification-store';
|
||||||
import { useSessionMultiSelectStore } from '@/stores/useSessionMultiSelectStore';
|
import { useSessionMultiSelectStore } from '@/stores/useSessionMultiSelectStore';
|
||||||
import { useI18n } from '@/lib/i18n';
|
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[] };
|
type Folder = { id: string; name: string; sessionIds: string[] };
|
||||||
|
|
||||||
@@ -319,6 +322,8 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
|
|||||||
const sessionUpdatedLabel = formatSessionDateLabel(sessionTimestamp);
|
const sessionUpdatedLabel = formatSessionDateLabel(sessionTimestamp);
|
||||||
const sessionCompactUpdatedLabel = formatSessionCompactDateLabel(sessionTimestamp);
|
const sessionCompactUpdatedLabel = formatSessionCompactDateLabel(sessionTimestamp);
|
||||||
const isMenuOpen = openSidebarMenuKey === menuInstanceKey;
|
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]);
|
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" />
|
<Icon name="download" className="mr-1 h-4 w-4" />
|
||||||
{t('sessions.sidebar.session.menu.exportMarkdown')}
|
{t('sessions.sidebar.session.menu.exportMarkdown')}
|
||||||
</DropdownMenuItem>
|
</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 ? (() => {
|
{sessionDirectory && !archivedBucket ? (() => {
|
||||||
const scopeFolders = getFoldersForScope(sessionDirectory);
|
const scopeFolders = getFoldersForScope(sessionDirectory);
|
||||||
@@ -980,6 +991,13 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
|
|||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
{isMultiRunLikeSession ? (
|
||||||
|
<MultiRunFusionDialog
|
||||||
|
session={resolvedSession}
|
||||||
|
open={fusionDialogOpen}
|
||||||
|
onOpenChange={setFusionDialogOpen}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ function SelectTrigger({
|
|||||||
type SelectContentExtra = {
|
type SelectContentExtra = {
|
||||||
position?: "popper" | "item-aligned";
|
position?: "popper" | "item-aligned";
|
||||||
fitContent?: boolean;
|
fitContent?: boolean;
|
||||||
|
portalToBody?: boolean;
|
||||||
sideOffset?: number;
|
sideOffset?: number;
|
||||||
side?: "top" | "right" | "bottom" | "left";
|
side?: "top" | "right" | "bottom" | "left";
|
||||||
align?: "start" | "center" | "end";
|
align?: "start" | "center" | "end";
|
||||||
@@ -165,6 +166,7 @@ function SelectContent({
|
|||||||
children,
|
children,
|
||||||
position = "popper",
|
position = "popper",
|
||||||
fitContent = false,
|
fitContent = false,
|
||||||
|
portalToBody = false,
|
||||||
sideOffset,
|
sideOffset,
|
||||||
side,
|
side,
|
||||||
align,
|
align,
|
||||||
@@ -175,13 +177,13 @@ function SelectContent({
|
|||||||
const portalContainer = portalContext?.portalContainer ?? null;
|
const portalContainer = portalContext?.portalContainer ?? null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseSelect.Portal container={portalContainer || undefined}>
|
<BaseSelect.Portal container={portalToBody ? undefined : portalContainer || undefined}>
|
||||||
<BaseSelect.Positioner
|
<BaseSelect.Positioner
|
||||||
alignItemWithTrigger={alignItemWithTrigger}
|
alignItemWithTrigger={alignItemWithTrigger}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
side={side}
|
side={side}
|
||||||
align={align}
|
align={align}
|
||||||
className="z-[120] pointer-events-auto"
|
className="absolute z-[120] pointer-events-auto"
|
||||||
>
|
>
|
||||||
<BaseSelect.Popup
|
<BaseSelect.Popup
|
||||||
data-slot="select-content"
|
data-slot="select-content"
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ export const settingsDict = {
|
|||||||
'settings.magicPrompts.sidebar.item.planImplement': 'Implement Plan',
|
'settings.magicPrompts.sidebar.item.planImplement': 'Implement Plan',
|
||||||
'settings.magicPrompts.sidebar.item.sessionSummary': 'Session Summary',
|
'settings.magicPrompts.sidebar.item.sessionSummary': 'Session Summary',
|
||||||
'settings.magicPrompts.sidebar.item.sessionWorkspaceReview': 'Workspace Review',
|
'settings.magicPrompts.sidebar.item.sessionWorkspaceReview': 'Workspace Review',
|
||||||
|
'settings.magicPrompts.sidebar.item.sessionFusion': 'Fusion',
|
||||||
'settings.remoteInstances.sidebar.title': 'Remote Instances',
|
'settings.remoteInstances.sidebar.title': 'Remote Instances',
|
||||||
'settings.remoteInstances.sidebar.total': 'Total {count}',
|
'settings.remoteInstances.sidebar.total': 'Total {count}',
|
||||||
'settings.remoteInstances.sidebar.newSshInstanceName': 'New SSH Instance',
|
'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.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.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.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.resetting': 'Resetting...',
|
||||||
'settings.magicPrompts.page.actions.resetAllOverrides': 'Reset All Overrides',
|
'settings.magicPrompts.page.actions.resetAllOverrides': 'Reset All Overrides',
|
||||||
'settings.magicPrompts.page.actions.resetToDefault': 'Reset to Default',
|
'settings.magicPrompts.page.actions.resetToDefault': 'Reset to Default',
|
||||||
|
|||||||
@@ -180,6 +180,16 @@ export const dict = {
|
|||||||
'multirun.modelMultiSelect.validation.minOnly': 'Select at least {min} models.',
|
'multirun.modelMultiSelect.validation.minOnly': 'Select at least {min} models.',
|
||||||
'multirun.modelMultiSelect.validation.minToMax': 'Select from {min} to {max} models.',
|
'multirun.modelMultiSelect.validation.minToMax': 'Select from {min} to {max} models.',
|
||||||
'multirun.agentSelector.placeholder': 'Select an agent',
|
'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.searchSessions': 'Search sessions',
|
||||||
'sessions.sidebar.header.actions.exitSelection': 'Exit selection',
|
'sessions.sidebar.header.actions.exitSelection': 'Exit selection',
|
||||||
'sessions.sidebar.header.actions.selectSessions': 'Select sessions',
|
'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.copyLink': 'Copy link',
|
||||||
'sessions.sidebar.session.menu.unshare': 'Unshare',
|
'sessions.sidebar.session.menu.unshare': 'Unshare',
|
||||||
'sessions.sidebar.session.menu.exportMarkdown': 'Export Markdown',
|
'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.openInSidePanel': 'Open in Side Panel',
|
||||||
'sessions.sidebar.session.menu.betaBadge': 'beta',
|
'sessions.sidebar.session.menu.betaBadge': 'beta',
|
||||||
'sessions.sidebar.session.menu.label': 'Session menu',
|
'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.planImplement": "Implementar plan",
|
||||||
"settings.magicPrompts.sidebar.item.sessionSummary": "Resumen de sesión",
|
"settings.magicPrompts.sidebar.item.sessionSummary": "Resumen de sesión",
|
||||||
"settings.magicPrompts.sidebar.item.sessionWorkspaceReview": "Revisión del espacio de trabajo",
|
"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.title": "Instancias remotas",
|
||||||
"settings.remoteInstances.sidebar.total": "Total {count}",
|
"settings.remoteInstances.sidebar.total": "Total {count}",
|
||||||
"settings.remoteInstances.sidebar.newSshInstanceName": "Nueva instancia SSH",
|
"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.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.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.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.resetting": "Restableciendo...",
|
||||||
"settings.magicPrompts.page.actions.resetAllOverrides": "Restablecer todas las sobrescrituras",
|
"settings.magicPrompts.page.actions.resetAllOverrides": "Restablecer todas las sobrescrituras",
|
||||||
"settings.magicPrompts.page.actions.resetToDefault": "Restablecer valores predeterminados",
|
"settings.magicPrompts.page.actions.resetToDefault": "Restablecer valores predeterminados",
|
||||||
|
|||||||
@@ -181,6 +181,16 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
"multirun.modelMultiSelect.validation.minOnly": "Selecciona al menos {min} modelos.",
|
"multirun.modelMultiSelect.validation.minOnly": "Selecciona al menos {min} modelos.",
|
||||||
"multirun.modelMultiSelect.validation.minToMax": "Selecciona entre {min} y {max} modelos.",
|
"multirun.modelMultiSelect.validation.minToMax": "Selecciona entre {min} y {max} modelos.",
|
||||||
"multirun.agentSelector.placeholder": "Selecciona un agente",
|
"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.searchSessions": "Buscar sesiones",
|
||||||
"sessions.sidebar.header.actions.exitSelection": "Salir de selección",
|
"sessions.sidebar.header.actions.exitSelection": "Salir de selección",
|
||||||
"sessions.sidebar.header.actions.selectSessions": "Seleccionar sesiones",
|
"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.copyLink": "Copiar enlace",
|
||||||
"sessions.sidebar.session.menu.unshare": "Dejar de compartir",
|
"sessions.sidebar.session.menu.unshare": "Dejar de compartir",
|
||||||
"sessions.sidebar.session.menu.exportMarkdown": "Exportar Markdown",
|
"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.openInSidePanel": "Abrir en panel lateral",
|
||||||
"sessions.sidebar.session.menu.betaBadge": "beta",
|
"sessions.sidebar.session.menu.betaBadge": "beta",
|
||||||
"sessions.sidebar.session.menu.label": "Menú de sesión",
|
"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.planImplement': '계획 구현',
|
||||||
'settings.magicPrompts.sidebar.item.sessionSummary': '세션 요약',
|
'settings.magicPrompts.sidebar.item.sessionSummary': '세션 요약',
|
||||||
'settings.magicPrompts.sidebar.item.sessionWorkspaceReview': '워크스페이스 리뷰',
|
'settings.magicPrompts.sidebar.item.sessionWorkspaceReview': '워크스페이스 리뷰',
|
||||||
|
'settings.magicPrompts.sidebar.item.sessionFusion': 'Fusion',
|
||||||
'settings.remoteInstances.sidebar.title': '원격 인스턴스',
|
'settings.remoteInstances.sidebar.title': '원격 인스턴스',
|
||||||
'settings.remoteInstances.sidebar.total': '총 {count}개',
|
'settings.remoteInstances.sidebar.total': '총 {count}개',
|
||||||
'settings.remoteInstances.sidebar.newSshInstanceName': '새 SSH 인스턴스',
|
'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.sessionSummary.description': '/summary slash command에서 사용하는 프롬프트입니다: 표시 사용자 메시지 + 숨겨진 지침. 비파괴적이며 세션 기록을 압축하지 않습니다.',
|
||||||
'settings.magicPrompts.page.group.sessionWorkspaceReview.title': '워크스페이스 리뷰',
|
'settings.magicPrompts.page.group.sessionWorkspaceReview.title': '워크스페이스 리뷰',
|
||||||
'settings.magicPrompts.page.group.sessionWorkspaceReview.description': '/workspace-review slash command에서 사용하는 프롬프트입니다: 표시 사용자 메시지 + 숨겨진 지침. 현재 workspace 변경 사항에서 중요한 문제만 리뷰합니다.',
|
'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.resetting': '초기화 중...',
|
||||||
'settings.magicPrompts.page.actions.resetAllOverrides': '모든 오버라이드 초기화',
|
'settings.magicPrompts.page.actions.resetAllOverrides': '모든 오버라이드 초기화',
|
||||||
'settings.magicPrompts.page.actions.resetToDefault': '기본값으로 초기화',
|
'settings.magicPrompts.page.actions.resetToDefault': '기본값으로 초기화',
|
||||||
|
|||||||
@@ -181,6 +181,16 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'multirun.modelMultiSelect.validation.minOnly': '모델을 최소 {min}개 선택하세요.',
|
'multirun.modelMultiSelect.validation.minOnly': '모델을 최소 {min}개 선택하세요.',
|
||||||
'multirun.modelMultiSelect.validation.minToMax': '모델을 {min}~{max}개 선택하세요.',
|
'multirun.modelMultiSelect.validation.minToMax': '모델을 {min}~{max}개 선택하세요.',
|
||||||
'multirun.agentSelector.placeholder': '에이전트 선택',
|
'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.searchSessions': '세션 검색',
|
||||||
'sessions.sidebar.header.actions.exitSelection': '선택 종료',
|
'sessions.sidebar.header.actions.exitSelection': '선택 종료',
|
||||||
'sessions.sidebar.header.actions.selectSessions': '세션 선택',
|
'sessions.sidebar.header.actions.selectSessions': '세션 선택',
|
||||||
@@ -243,6 +253,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'sessions.sidebar.session.menu.copyLink': '링크 복사',
|
'sessions.sidebar.session.menu.copyLink': '링크 복사',
|
||||||
'sessions.sidebar.session.menu.unshare': '공유 해제',
|
'sessions.sidebar.session.menu.unshare': '공유 해제',
|
||||||
'sessions.sidebar.session.menu.exportMarkdown': 'Markdown 내보내기',
|
'sessions.sidebar.session.menu.exportMarkdown': 'Markdown 내보내기',
|
||||||
|
'sessions.sidebar.session.menu.runFusion': 'fusion 실행',
|
||||||
'sessions.sidebar.session.menu.openInSidePanel': '사이드 패널에서 열기',
|
'sessions.sidebar.session.menu.openInSidePanel': '사이드 패널에서 열기',
|
||||||
'sessions.sidebar.session.menu.betaBadge': 'beta',
|
'sessions.sidebar.session.menu.betaBadge': 'beta',
|
||||||
'sessions.sidebar.session.menu.label': '세션 메뉴',
|
'sessions.sidebar.session.menu.label': '세션 메뉴',
|
||||||
|
|||||||
@@ -280,6 +280,8 @@ export const settingsDict = {
|
|||||||
'settings.magicPrompts.page.group.sessionSummary.title': 'Podsumowanie Sesji',
|
'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.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.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.aria': 'Ładowanie',
|
||||||
'settings.magicPrompts.page.loading.text': 'Ładowanie Magicznych Promptów...',
|
'settings.magicPrompts.page.loading.text': 'Ładowanie Magicznych Promptów...',
|
||||||
'settings.magicPrompts.page.placeholdersLabel': 'Placeholdery:',
|
'settings.magicPrompts.page.placeholdersLabel': 'Placeholdery:',
|
||||||
@@ -314,6 +316,7 @@ export const settingsDict = {
|
|||||||
'settings.magicPrompts.sidebar.item.planTodo': 'Planowanie Todo',
|
'settings.magicPrompts.sidebar.item.planTodo': 'Planowanie Todo',
|
||||||
'settings.magicPrompts.sidebar.item.sessionSummary': 'Podsumowanie Sesji',
|
'settings.magicPrompts.sidebar.item.sessionSummary': 'Podsumowanie Sesji',
|
||||||
'settings.magicPrompts.sidebar.item.sessionWorkspaceReview': 'Przegląd obszaru roboczego',
|
'settings.magicPrompts.sidebar.item.sessionWorkspaceReview': 'Przegląd obszaru roboczego',
|
||||||
|
'settings.magicPrompts.sidebar.item.sessionFusion': 'Fusion',
|
||||||
'settings.magicPrompts.sidebar.title': 'Magiczne Prompty',
|
'settings.magicPrompts.sidebar.title': 'Magiczne Prompty',
|
||||||
'settings.mcp.page.actions.authorize': 'Autoryzuj',
|
'settings.mcp.page.actions.authorize': 'Autoryzuj',
|
||||||
'settings.mcp.page.actions.clearAuth': 'Wyczyść autoryzację',
|
'settings.mcp.page.actions.clearAuth': 'Wyczyść autoryzację',
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'sessions.sidebar.session.menu.copyLink': 'Kopiuj link',
|
'sessions.sidebar.session.menu.copyLink': 'Kopiuj link',
|
||||||
'sessions.sidebar.session.menu.unshare': 'Cofnij udostępnienie',
|
'sessions.sidebar.session.menu.unshare': 'Cofnij udostępnienie',
|
||||||
'sessions.sidebar.session.menu.exportMarkdown': 'Eksportuj Markdown',
|
'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.openInSidePanel': 'Otwórz w panelu bocznym',
|
||||||
'sessions.sidebar.session.menu.betaBadge': 'beta',
|
'sessions.sidebar.session.menu.betaBadge': 'beta',
|
||||||
'sessions.sidebar.session.menu.label': 'Menu sesji',
|
'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.minOnly': 'Wybierz przynajmniej {min} modele.',
|
||||||
'multirun.modelMultiSelect.validation.minToMax': 'Wybierz od {min} do {max} modeli.',
|
'multirun.modelMultiSelect.validation.minToMax': 'Wybierz od {min} do {max} modeli.',
|
||||||
'multirun.agentSelector.placeholder': 'Wybierz agenta',
|
'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.includeSubtasks': 'Dołącz pod-agentów',
|
||||||
'sessions.sidebar.session.export.dialog.confirm': 'Eksportuj',
|
'sessions.sidebar.session.export.dialog.confirm': 'Eksportuj',
|
||||||
'sessions.sidebar.session.status.active': 'Sesja aktywna',
|
'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.planImplement": "Implementar plano",
|
||||||
"settings.magicPrompts.sidebar.item.sessionSummary": "Resumo de sessão",
|
"settings.magicPrompts.sidebar.item.sessionSummary": "Resumo de sessão",
|
||||||
"settings.magicPrompts.sidebar.item.sessionWorkspaceReview": "Revisão do workspace",
|
"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.title": "Instâncias remotas",
|
||||||
"settings.remoteInstances.sidebar.total": "Total {count}",
|
"settings.remoteInstances.sidebar.total": "Total {count}",
|
||||||
"settings.remoteInstances.sidebar.newSshInstanceName": "Nova instância SSH",
|
"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.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.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.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.resetting": "Redefinindo...",
|
||||||
"settings.magicPrompts.page.actions.resetAllOverrides": "Reiniciar todas as sobrescrituras",
|
"settings.magicPrompts.page.actions.resetAllOverrides": "Reiniciar todas as sobrescrituras",
|
||||||
"settings.magicPrompts.page.actions.resetToDefault": "Reiniciar valores padrões",
|
"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.minOnly": "Selecione ao menos {min} modelos.",
|
||||||
"multirun.modelMultiSelect.validation.minToMax": "Selecione entre {min} e {max} modelos.",
|
"multirun.modelMultiSelect.validation.minToMax": "Selecione entre {min} e {max} modelos.",
|
||||||
"multirun.agentSelector.placeholder": "Selecione um agente",
|
"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.searchSessions": "Pesquisar sessões",
|
||||||
"sessions.sidebar.header.actions.exitSelection": "Sair da seleção",
|
"sessions.sidebar.header.actions.exitSelection": "Sair da seleção",
|
||||||
"sessions.sidebar.header.actions.selectSessions": "Selecionar sessões",
|
"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.copyLink": "Copiar link",
|
||||||
"sessions.sidebar.session.menu.unshare": "Parar de compartilhar",
|
"sessions.sidebar.session.menu.unshare": "Parar de compartilhar",
|
||||||
"sessions.sidebar.session.menu.exportMarkdown": "Exportar Markdown",
|
"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.openInSidePanel": "Abrir no painel lateral",
|
||||||
"sessions.sidebar.session.menu.betaBadge": "beta",
|
"sessions.sidebar.session.menu.betaBadge": "beta",
|
||||||
"sessions.sidebar.session.menu.label": "Menu da sessão",
|
"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.planImplement": "Реалізувати план",
|
||||||
"settings.magicPrompts.sidebar.item.sessionSummary": "Підсумок сесії",
|
"settings.magicPrompts.sidebar.item.sessionSummary": "Підсумок сесії",
|
||||||
"settings.magicPrompts.sidebar.item.sessionWorkspaceReview": "Огляд робочого простору",
|
"settings.magicPrompts.sidebar.item.sessionWorkspaceReview": "Огляд робочого простору",
|
||||||
|
"settings.magicPrompts.sidebar.item.sessionFusion": "Fusion",
|
||||||
"settings.remoteInstances.sidebar.title": "Віддалені інстанси",
|
"settings.remoteInstances.sidebar.title": "Віддалені інстанси",
|
||||||
"settings.remoteInstances.sidebar.total": "Усього {count}",
|
"settings.remoteInstances.sidebar.total": "Усього {count}",
|
||||||
"settings.remoteInstances.sidebar.newSshInstanceName": "Новий інстанс SSH",
|
"settings.remoteInstances.sidebar.newSshInstanceName": "Новий інстанс SSH",
|
||||||
@@ -1541,6 +1542,8 @@ export const settingsDict = {
|
|||||||
"settings.magicPrompts.page.group.sessionSummary.description": "Промпти, які використовуються командою /summary: видиме повідомлення користувача + приховані інструкції. Неруйнівний – не стискає історію сесії.",
|
"settings.magicPrompts.page.group.sessionSummary.description": "Промпти, які використовуються командою /summary: видиме повідомлення користувача + приховані інструкції. Неруйнівний – не стискає історію сесії.",
|
||||||
"settings.magicPrompts.page.group.sessionWorkspaceReview.title": "Огляд робочого простору",
|
"settings.magicPrompts.page.group.sessionWorkspaceReview.title": "Огляд робочого простору",
|
||||||
"settings.magicPrompts.page.group.sessionWorkspaceReview.description": "Промпти, які використовуються командою /workspace-review: видиме повідомлення користувача + приховані інструкції. Переглядає поточні зміни робочого простору лише для проблем із сильним сигналом.",
|
"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.resetting": "Скидання...",
|
||||||
"settings.magicPrompts.page.actions.resetAllOverrides": "Скинути всі перевизначення",
|
"settings.magicPrompts.page.actions.resetAllOverrides": "Скинути всі перевизначення",
|
||||||
"settings.magicPrompts.page.actions.resetToDefault": "Скинути до замовчування",
|
"settings.magicPrompts.page.actions.resetToDefault": "Скинути до замовчування",
|
||||||
|
|||||||
@@ -181,6 +181,16 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
"multirun.modelMultiSelect.validation.minOnly": "Виберіть щонайменше моделей: {min}.",
|
"multirun.modelMultiSelect.validation.minOnly": "Виберіть щонайменше моделей: {min}.",
|
||||||
"multirun.modelMultiSelect.validation.minToMax": "Виберіть від {min} до {max} моделей.",
|
"multirun.modelMultiSelect.validation.minToMax": "Виберіть від {min} до {max} моделей.",
|
||||||
"multirun.agentSelector.placeholder": "Вибрати агента",
|
"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.searchSessions": "Пошук сесій",
|
||||||
"sessions.sidebar.header.actions.exitSelection": "Вийти з вибору",
|
"sessions.sidebar.header.actions.exitSelection": "Вийти з вибору",
|
||||||
"sessions.sidebar.header.actions.selectSessions": "Вибрати сесії",
|
"sessions.sidebar.header.actions.selectSessions": "Вибрати сесії",
|
||||||
@@ -243,6 +253,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
"sessions.sidebar.session.menu.copyLink": "Копіювати посилання",
|
"sessions.sidebar.session.menu.copyLink": "Копіювати посилання",
|
||||||
"sessions.sidebar.session.menu.unshare": "Скасувати спільний доступ",
|
"sessions.sidebar.session.menu.unshare": "Скасувати спільний доступ",
|
||||||
"sessions.sidebar.session.menu.exportMarkdown": "Експорт Markdown",
|
"sessions.sidebar.session.menu.exportMarkdown": "Експорт Markdown",
|
||||||
|
"sessions.sidebar.session.menu.runFusion": "Запустити fusion",
|
||||||
"sessions.sidebar.session.menu.openInSidePanel": "Відкрити на бічній панелі",
|
"sessions.sidebar.session.menu.openInSidePanel": "Відкрити на бічній панелі",
|
||||||
"sessions.sidebar.session.menu.betaBadge": "бета-версія",
|
"sessions.sidebar.session.menu.betaBadge": "бета-версія",
|
||||||
"sessions.sidebar.session.menu.label": "Меню сесії",
|
"sessions.sidebar.session.menu.label": "Меню сесії",
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ export const settingsDict = {
|
|||||||
'settings.magicPrompts.sidebar.item.planImplement': '执行计划',
|
'settings.magicPrompts.sidebar.item.planImplement': '执行计划',
|
||||||
'settings.magicPrompts.sidebar.item.sessionSummary': '会话总结',
|
'settings.magicPrompts.sidebar.item.sessionSummary': '会话总结',
|
||||||
'settings.magicPrompts.sidebar.item.sessionWorkspaceReview': '工作区审查',
|
'settings.magicPrompts.sidebar.item.sessionWorkspaceReview': '工作区审查',
|
||||||
|
'settings.magicPrompts.sidebar.item.sessionFusion': 'Fusion',
|
||||||
'settings.remoteInstances.sidebar.title': '远程实例',
|
'settings.remoteInstances.sidebar.title': '远程实例',
|
||||||
'settings.remoteInstances.sidebar.total': '总计 {count}',
|
'settings.remoteInstances.sidebar.total': '总计 {count}',
|
||||||
'settings.remoteInstances.sidebar.newSshInstanceName': '新建 SSH 实例',
|
'settings.remoteInstances.sidebar.newSshInstanceName': '新建 SSH 实例',
|
||||||
@@ -1541,6 +1542,8 @@ export const settingsDict = {
|
|||||||
'settings.magicPrompts.page.group.sessionSummary.description': '由 /summary 斜杠命令使用的提示词:可见用户消息 + 隐藏说明。非破坏性,不会压缩会话历史。',
|
'settings.magicPrompts.page.group.sessionSummary.description': '由 /summary 斜杠命令使用的提示词:可见用户消息 + 隐藏说明。非破坏性,不会压缩会话历史。',
|
||||||
'settings.magicPrompts.page.group.sessionWorkspaceReview.title': '工作区审查',
|
'settings.magicPrompts.page.group.sessionWorkspaceReview.title': '工作区审查',
|
||||||
'settings.magicPrompts.page.group.sessionWorkspaceReview.description': '由 /workspace-review 斜杠命令使用的提示词:可见用户消息 + 隐藏说明。仅审查当前工作区的高信号问题。',
|
'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.resetting': '重置中...',
|
||||||
'settings.magicPrompts.page.actions.resetAllOverrides': '重置全部覆盖',
|
'settings.magicPrompts.page.actions.resetAllOverrides': '重置全部覆盖',
|
||||||
'settings.magicPrompts.page.actions.resetToDefault': '重置为默认',
|
'settings.magicPrompts.page.actions.resetToDefault': '重置为默认',
|
||||||
|
|||||||
@@ -181,6 +181,16 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'multirun.modelMultiSelect.validation.minOnly': '请至少选择 {min} 个模型。',
|
'multirun.modelMultiSelect.validation.minOnly': '请至少选择 {min} 个模型。',
|
||||||
'multirun.modelMultiSelect.validation.minToMax': '请选择 {min} 到 {max} 个模型。',
|
'multirun.modelMultiSelect.validation.minToMax': '请选择 {min} 到 {max} 个模型。',
|
||||||
'multirun.agentSelector.placeholder': '选择智能体',
|
'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.searchSessions': '搜索会话',
|
||||||
'sessions.sidebar.header.actions.exitSelection': '退出选择',
|
'sessions.sidebar.header.actions.exitSelection': '退出选择',
|
||||||
'sessions.sidebar.header.actions.selectSessions': '选择会话',
|
'sessions.sidebar.header.actions.selectSessions': '选择会话',
|
||||||
@@ -243,6 +253,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'sessions.sidebar.session.menu.copyLink': '复制链接',
|
'sessions.sidebar.session.menu.copyLink': '复制链接',
|
||||||
'sessions.sidebar.session.menu.unshare': '取消分享',
|
'sessions.sidebar.session.menu.unshare': '取消分享',
|
||||||
'sessions.sidebar.session.menu.exportMarkdown': '导出 Markdown',
|
'sessions.sidebar.session.menu.exportMarkdown': '导出 Markdown',
|
||||||
|
'sessions.sidebar.session.menu.runFusion': '运行 fusion',
|
||||||
'sessions.sidebar.session.menu.openInSidePanel': '在侧边面板中打开',
|
'sessions.sidebar.session.menu.openInSidePanel': '在侧边面板中打开',
|
||||||
'sessions.sidebar.session.menu.betaBadge': 'beta',
|
'sessions.sidebar.session.menu.betaBadge': 'beta',
|
||||||
'sessions.sidebar.session.menu.label': '会话菜单',
|
'sessions.sidebar.session.menu.label': '会话菜单',
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ export type MagicPromptId =
|
|||||||
| 'session.summary.visible'
|
| 'session.summary.visible'
|
||||||
| 'session.summary.instructions'
|
| 'session.summary.instructions'
|
||||||
| 'session.review.visible'
|
| 'session.review.visible'
|
||||||
| 'session.review.instructions';
|
| 'session.review.instructions'
|
||||||
|
| 'session.fusion.visible'
|
||||||
|
| 'session.fusion.instructions';
|
||||||
|
|
||||||
export interface MagicPromptDefinition {
|
export interface MagicPromptDefinition {
|
||||||
id: MagicPromptId;
|
id: MagicPromptId;
|
||||||
@@ -570,6 +572,26 @@ Output:
|
|||||||
|
|
||||||
Keep the review concise and practical.`,
|
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;
|
] as const;
|
||||||
|
|
||||||
const MAGIC_PROMPT_DEFINITION_BY_ID = new Map<MagicPromptId, MagicPromptDefinition>(
|
const MAGIC_PROMPT_DEFINITION_BY_ID = new Map<MagicPromptId, MagicPromptDefinition>(
|
||||||
|
|||||||
@@ -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`;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user