mirror of
https://github.com/block/goose.git
synced 2026-10-19 09:34:12 +00:00
remove false optionality in desktop props (#12776)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
6ead9cb601
commit
91ecd8de7b
56 changed files with 336 additions and 568 deletions
|
|
@ -224,8 +224,7 @@ const SessionsRoute = () => {
|
|||
};
|
||||
|
||||
const SchedulesRoute = () => {
|
||||
const navigate = useNavigate();
|
||||
return <SchedulesView onClose={() => navigate('/')} />;
|
||||
return <SchedulesView />;
|
||||
};
|
||||
|
||||
const RecipesRoute = () => {
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ function updateRequestToCreate(
|
|||
models: request.models,
|
||||
supportsStreaming: request.supports_streaming ?? null,
|
||||
headers: request.headers ?? undefined,
|
||||
requiresAuth: request.requires_auth ?? true,
|
||||
requiresAuth: request.requires_auth,
|
||||
catalogProviderId: request.catalog_provider_id ?? null,
|
||||
basePath: request.base_path ?? null,
|
||||
toolshim: request.toolshim,
|
||||
|
|
@ -324,9 +324,9 @@ export async function acpReadDefaults(): Promise<{
|
|||
};
|
||||
}
|
||||
|
||||
export async function acpSaveDefaults(providerId: string, modelId?: string | null): Promise<void> {
|
||||
export async function acpSaveDefaults(providerId: string, modelId: string | null): Promise<void> {
|
||||
const client = await getAcpClient();
|
||||
await client.goose.defaultsSave_unstable({ providerId, modelId: modelId ?? null });
|
||||
await client.goose.defaultsSave_unstable({ providerId, modelId });
|
||||
}
|
||||
|
||||
export async function acpClearDefaults(): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ export async function deleteRecipe(id: string): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
export async function scheduleRecipe(id: string, cronSchedule?: string | null): Promise<void> {
|
||||
export async function scheduleRecipe(id: string, cronSchedule: string | null): Promise<void> {
|
||||
try {
|
||||
const client = await getAcpClient();
|
||||
await client.goose.recipesSchedule_unstable({ id, cron_schedule: cronSchedule });
|
||||
|
|
@ -137,7 +137,7 @@ export async function scheduleRecipe(id: string, cronSchedule?: string | null):
|
|||
|
||||
export async function setRecipeSlashCommand(
|
||||
id: string,
|
||||
slashCommand?: string | null
|
||||
slashCommand: string | null
|
||||
): Promise<void> {
|
||||
try {
|
||||
const client = await getAcpClient();
|
||||
|
|
|
|||
|
|
@ -62,12 +62,6 @@ const isUserMessage = (message: Message) => message.role === 'user';
|
|||
|
||||
interface BaseChatProps {
|
||||
setChat: (chat: ChatType) => void;
|
||||
onMessageSubmit?: (message: string) => void;
|
||||
renderHeader?: () => React.ReactNode;
|
||||
customChatInputProps?: Record<string, unknown>;
|
||||
customMainLayoutProps?: Record<string, unknown>;
|
||||
contentClassName?: string;
|
||||
disableSearch?: boolean;
|
||||
suppressEmptyState: boolean;
|
||||
sessionId: string;
|
||||
isActiveSession: boolean;
|
||||
|
|
@ -78,9 +72,6 @@ interface BaseChatProps {
|
|||
|
||||
export default function BaseChat({
|
||||
setChat,
|
||||
renderHeader,
|
||||
customChatInputProps = {},
|
||||
customMainLayoutProps = {},
|
||||
sessionId,
|
||||
initialMessage,
|
||||
noAutoSubmit,
|
||||
|
|
@ -135,10 +126,7 @@ export default function BaseChat({
|
|||
setQuoteButtonPos(null);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!conversationRef.current ||
|
||||
!conversationRef.current.contains(sel.anchorNode)
|
||||
) {
|
||||
if (!conversationRef.current || !conversationRef.current.contains(sel.anchorNode)) {
|
||||
setQuoteButtonPos(null);
|
||||
return;
|
||||
}
|
||||
|
|
@ -439,12 +427,7 @@ export default function BaseChat({
|
|||
if (sessionLoadError) {
|
||||
return (
|
||||
<div className="h-full flex flex-col min-h-0">
|
||||
<MainPanelLayout
|
||||
backgroundColor={'bg-background-primary'}
|
||||
removeTopPadding={true}
|
||||
{...customMainLayoutProps}
|
||||
>
|
||||
{renderHeader && renderHeader()}
|
||||
<MainPanelLayout backgroundColor={'bg-background-primary'} removeTopPadding={true}>
|
||||
<div className="flex flex-col flex-1 min-h-0 relative">
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<div className="flex flex-col items-center justify-center p-8">
|
||||
|
|
@ -472,14 +455,7 @@ export default function BaseChat({
|
|||
|
||||
return (
|
||||
<div className="h-full flex flex-col min-h-0">
|
||||
<MainPanelLayout
|
||||
backgroundColor={'bg-background-primary'}
|
||||
removeTopPadding={true}
|
||||
{...customMainLayoutProps}
|
||||
>
|
||||
{/* Custom header */}
|
||||
{renderHeader && renderHeader()}
|
||||
|
||||
<MainPanelLayout backgroundColor={'bg-background-primary'} removeTopPadding={true}>
|
||||
{/* Chat container with sticky recipe header */}
|
||||
<div className="flex flex-col flex-1 min-h-0 relative">
|
||||
{/* Goose watermark - top right */}
|
||||
|
|
@ -521,7 +497,6 @@ export default function BaseChat({
|
|||
<RecipeActivities
|
||||
append={appendToChat}
|
||||
activities={Array.isArray(recipe.activities) ? recipe.activities : null}
|
||||
title={recipe.title}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -530,17 +505,17 @@ export default function BaseChat({
|
|||
<>
|
||||
<SearchView>
|
||||
<div ref={conversationRef}>
|
||||
<ProgressiveMessageList
|
||||
messages={messages}
|
||||
sessionId={sessionId}
|
||||
toolCallNotifications={toolCallNotifications}
|
||||
append={appendToChat}
|
||||
isUserMessage={isUserMessage}
|
||||
isStreamingMessage={chatState !== ChatState.Idle}
|
||||
onRenderingComplete={handleRenderingComplete}
|
||||
onMessageUpdate={onMessageUpdate}
|
||||
submitElicitationResponse={submitElicitationResponse}
|
||||
/>
|
||||
<ProgressiveMessageList
|
||||
messages={messages}
|
||||
sessionId={sessionId}
|
||||
toolCallNotifications={toolCallNotifications}
|
||||
append={appendToChat}
|
||||
isUserMessage={isUserMessage}
|
||||
isStreamingMessage={chatState !== ChatState.Idle}
|
||||
onRenderingComplete={handleRenderingComplete}
|
||||
onMessageUpdate={onMessageUpdate}
|
||||
submitElicitationResponse={submitElicitationResponse}
|
||||
/>
|
||||
</div>
|
||||
</SearchView>
|
||||
|
||||
|
|
@ -636,7 +611,6 @@ export default function BaseChat({
|
|||
}}
|
||||
appendQuote={pendingQuote}
|
||||
onAppendQuoteConsumed={() => setPendingQuote(null)}
|
||||
{...customChatInputProps}
|
||||
/>
|
||||
</ChatInputCard>
|
||||
</MainPanelLayout>
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ interface ChatInputProps {
|
|||
recipeId?: string | null;
|
||||
initialPrompt?: string;
|
||||
append?: (message: Message) => void;
|
||||
onWorkingDirChange?: (newDir: string) => Promise<void> | void;
|
||||
onWorkingDirChange: (newDir: string) => Promise<void> | void;
|
||||
inputRef?: React.RefObject<HTMLTextAreaElement | null>;
|
||||
sessionModel?: string | null;
|
||||
sessionProvider?: string | null;
|
||||
|
|
@ -1746,7 +1746,7 @@ export default function ChatInput({
|
|||
sessionId={sessionId ?? undefined}
|
||||
workingDir={currentWorkingDir}
|
||||
onWorkingDirChange={async (newDir) => {
|
||||
await onWorkingDirChange?.(newDir);
|
||||
await onWorkingDirChange(newDir);
|
||||
setWorkingDirOverride(newDir);
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@ const i18n = defineMessages({
|
|||
},
|
||||
blockedMessage: {
|
||||
id: 'extensionInstallModal.blockedMessage',
|
||||
defaultMessage: 'This extension command is not in the allowed list and its installation is blocked.\n\nExtension: {name}\nCommand: {command}\n\nContact your administrator to request approval for this extension.',
|
||||
defaultMessage:
|
||||
'This extension command is not in the allowed list and its installation is blocked.\n\nExtension: {name}\nCommand: {command}\n\nContact your administrator to request approval for this extension.',
|
||||
},
|
||||
ok: {
|
||||
id: 'extensionInstallModal.ok',
|
||||
|
|
@ -41,15 +42,18 @@ const i18n = defineMessages({
|
|||
},
|
||||
untrustedSecurityMessage: {
|
||||
id: 'extensionInstallModal.untrustedSecurityMessage',
|
||||
defaultMessage: 'This extension command is not in the allowed list and will be able to access your conversations and provide additional functionality.\n\nInstalling extensions from untrusted sources may pose security risks.',
|
||||
defaultMessage:
|
||||
'This extension command is not in the allowed list and will be able to access your conversations and provide additional functionality.\n\nInstalling extensions from untrusted sources may pose security risks.',
|
||||
},
|
||||
untrustedMessageWithUrl: {
|
||||
id: 'extensionInstallModal.untrustedMessageWithUrl',
|
||||
defaultMessage: '{securityMessage}\n\nExtension: {name}\nURL: {url}\n\nContact your administrator if you are unsure about this.',
|
||||
defaultMessage:
|
||||
'{securityMessage}\n\nExtension: {name}\nURL: {url}\n\nContact your administrator if you are unsure about this.',
|
||||
},
|
||||
untrustedMessageWithCommand: {
|
||||
id: 'extensionInstallModal.untrustedMessageWithCommand',
|
||||
defaultMessage: '{securityMessage}\n\nExtension: {name}\nCommand: {command}\n\nContact your administrator if you are unsure about this.',
|
||||
defaultMessage:
|
||||
'{securityMessage}\n\nExtension: {name}\nCommand: {command}\n\nContact your administrator if you are unsure about this.',
|
||||
},
|
||||
installAnyway: {
|
||||
id: 'extensionInstallModal.installAnyway',
|
||||
|
|
@ -81,7 +85,8 @@ const i18n = defineMessages({
|
|||
},
|
||||
alreadyInstalledMessage: {
|
||||
id: 'extensionInstallModal.alreadyInstalledMessage',
|
||||
defaultMessage: "''{name}'' extension has already been installed successfully. Start a new chat session to use it.",
|
||||
defaultMessage:
|
||||
"''{name}'' extension has already been installed successfully. Start a new chat session to use it.",
|
||||
},
|
||||
installing: {
|
||||
id: 'extensionInstallModal.installing',
|
||||
|
|
@ -93,7 +98,7 @@ type ModalType = 'blocked' | 'untrusted' | 'trusted';
|
|||
|
||||
interface ExtensionInfo {
|
||||
name: string;
|
||||
command?: string;
|
||||
command: string;
|
||||
remoteUrl?: string;
|
||||
link: string;
|
||||
}
|
||||
|
|
@ -116,7 +121,7 @@ interface ExtensionModalConfig {
|
|||
}
|
||||
|
||||
interface ExtensionInstallModalProps {
|
||||
addExtension?: (name: string, config: ExtensionConfig, enabled: boolean) => Promise<void>;
|
||||
addExtension: (name: string, config: ExtensionConfig, enabled: boolean) => Promise<void>;
|
||||
setView: (view: View, options?: ViewOptions) => void;
|
||||
}
|
||||
|
||||
|
|
@ -200,7 +205,8 @@ export function ExtensionInstallModal({ addExtension, setView }: ExtensionInstal
|
|||
case 'blocked':
|
||||
return {
|
||||
title: intl.formatMessage(i18n.blockedTitle),
|
||||
message: '\n\n' + intl.formatMessage(i18n.blockedMessage, { name, command: displayCommand }),
|
||||
message:
|
||||
'\n\n' + intl.formatMessage(i18n.blockedMessage, { name, command: displayCommand }),
|
||||
confirmLabel: intl.formatMessage(i18n.ok),
|
||||
cancelLabel: '',
|
||||
showSingleButton: true,
|
||||
|
|
@ -210,8 +216,16 @@ export function ExtensionInstallModal({ addExtension, setView }: ExtensionInstal
|
|||
case 'untrusted': {
|
||||
const securityMessage = '\n\n' + intl.formatMessage(i18n.untrustedSecurityMessage);
|
||||
const message = remoteUrl
|
||||
? intl.formatMessage(i18n.untrustedMessageWithUrl, { securityMessage, name, url: remoteUrl })
|
||||
: intl.formatMessage(i18n.untrustedMessageWithCommand, { securityMessage, name, command: displayCommand });
|
||||
? intl.formatMessage(i18n.untrustedMessageWithUrl, {
|
||||
securityMessage,
|
||||
name,
|
||||
url: remoteUrl,
|
||||
})
|
||||
: intl.formatMessage(i18n.untrustedMessageWithCommand, {
|
||||
securityMessage,
|
||||
name,
|
||||
command: displayCommand,
|
||||
});
|
||||
|
||||
return {
|
||||
title: intl.formatMessage(i18n.untrustedTitle),
|
||||
|
|
@ -236,58 +250,59 @@ export function ExtensionInstallModal({ addExtension, setView }: ExtensionInstal
|
|||
}
|
||||
};
|
||||
|
||||
const handleExtensionRequest = useCallback(async (link: string): Promise<void> => {
|
||||
if (processingLinkRef.current === link) {
|
||||
return;
|
||||
}
|
||||
processingLinkRef.current = link;
|
||||
|
||||
try {
|
||||
|
||||
const command = extractCommand(link);
|
||||
const remoteUrl = extractRemoteUrl(link);
|
||||
const extName = extractExtensionName(link);
|
||||
const extensionsList = await getExtensionsRef.current(true);
|
||||
|
||||
if (extensionsList?.find((ext) => ext.name === extName)) {
|
||||
|
||||
toastService.success({
|
||||
title: intl.formatMessage(i18n.alreadyInstalledTitle, { name: extName }),
|
||||
msg: intl.formatMessage(i18n.alreadyInstalledMessage, { name: extName }),
|
||||
});
|
||||
const handleExtensionRequest = useCallback(
|
||||
async (link: string): Promise<void> => {
|
||||
if (processingLinkRef.current === link) {
|
||||
return;
|
||||
}
|
||||
processingLinkRef.current = link;
|
||||
|
||||
const extensionInfo: ExtensionInfo = {
|
||||
name: extName,
|
||||
command: command,
|
||||
remoteUrl: remoteUrl || undefined,
|
||||
link: link,
|
||||
};
|
||||
try {
|
||||
const command = extractCommand(link);
|
||||
const remoteUrl = extractRemoteUrl(link);
|
||||
const extName = extractExtensionName(link);
|
||||
const extensionsList = await getExtensionsRef.current(true);
|
||||
|
||||
const modalType = await determineModalType(command, remoteUrl);
|
||||
if (extensionsList?.find((ext) => ext.name === extName)) {
|
||||
toastService.success({
|
||||
title: intl.formatMessage(i18n.alreadyInstalledTitle, { name: extName }),
|
||||
msg: intl.formatMessage(i18n.alreadyInstalledMessage, { name: extName }),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setModalState({
|
||||
isOpen: true,
|
||||
modalType,
|
||||
extensionInfo,
|
||||
isPending: false,
|
||||
error: null,
|
||||
});
|
||||
const extensionInfo: ExtensionInfo = {
|
||||
name: extName,
|
||||
command: command,
|
||||
remoteUrl: remoteUrl || undefined,
|
||||
link: link,
|
||||
};
|
||||
|
||||
setPendingLink(modalType === 'blocked' ? null : link);
|
||||
const modalType = await determineModalType(command, remoteUrl);
|
||||
|
||||
window.electron.logInfo(`Extension modal opened: ${modalType} for ${extName}`);
|
||||
} catch (error) {
|
||||
console.error('Error processing extension request:', error);
|
||||
setModalState((prev) => ({
|
||||
...prev,
|
||||
error: errorMessage(error, 'Unknown error'),
|
||||
}));
|
||||
} finally {
|
||||
processingLinkRef.current = null;
|
||||
}
|
||||
}, [intl]);
|
||||
setModalState({
|
||||
isOpen: true,
|
||||
modalType,
|
||||
extensionInfo,
|
||||
isPending: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
setPendingLink(modalType === 'blocked' ? null : link);
|
||||
|
||||
window.electron.logInfo(`Extension modal opened: ${modalType} for ${extName}`);
|
||||
} catch (error) {
|
||||
console.error('Error processing extension request:', error);
|
||||
setModalState((prev) => ({
|
||||
...prev,
|
||||
error: errorMessage(error, 'Unknown error'),
|
||||
}));
|
||||
} finally {
|
||||
processingLinkRef.current = null;
|
||||
}
|
||||
},
|
||||
[intl]
|
||||
);
|
||||
|
||||
const dismissModal = useCallback(() => {
|
||||
setModalState({
|
||||
|
|
@ -308,18 +323,13 @@ export function ExtensionInstallModal({ addExtension, setView }: ExtensionInstal
|
|||
setModalState((prev) => ({ ...prev, isPending: true }));
|
||||
|
||||
try {
|
||||
|
||||
if (addExtension) {
|
||||
await addExtensionFromDeepLink(
|
||||
pendingLink,
|
||||
addExtension,
|
||||
(view: string, options?: ViewOptions) => {
|
||||
setView(view as View, options);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
throw new Error('addExtension function not provided to component');
|
||||
}
|
||||
await addExtensionFromDeepLink(
|
||||
pendingLink,
|
||||
addExtension,
|
||||
(view: string, options?: ViewOptions) => {
|
||||
setView(view as View, options);
|
||||
}
|
||||
);
|
||||
dismissModal();
|
||||
} catch (error) {
|
||||
setModalState((prev) => ({
|
||||
|
|
@ -331,7 +341,6 @@ export function ExtensionInstallModal({ addExtension, setView }: ExtensionInstal
|
|||
}, [pendingLink, dismissModal, addExtension, setView]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const handleAddExtension = async (_event: IpcRendererEvent, ...args: unknown[]) => {
|
||||
const link = args[0] as string;
|
||||
await handleExtensionRequest(link);
|
||||
|
|
|
|||
|
|
@ -28,22 +28,20 @@ const i18n = defineMessages({
|
|||
});
|
||||
|
||||
interface ThemeSelectorProps {
|
||||
className?: string;
|
||||
hideTitle?: boolean;
|
||||
horizontal?: boolean;
|
||||
className: string;
|
||||
hideTitle: boolean;
|
||||
horizontal: boolean;
|
||||
}
|
||||
|
||||
const ThemeSelector: React.FC<ThemeSelectorProps> = ({
|
||||
className = '',
|
||||
hideTitle = false,
|
||||
horizontal = false,
|
||||
}) => {
|
||||
const ThemeSelector: React.FC<ThemeSelectorProps> = ({ className, hideTitle, horizontal }) => {
|
||||
const intl = useIntl();
|
||||
const { userThemePreference, setUserThemePreference } = useTheme();
|
||||
|
||||
return (
|
||||
<div className={`${!horizontal ? 'px-1 py-2 space-y-2' : ''} ${className}`}>
|
||||
{!hideTitle && <div className="text-xs text-text-primary px-3">{intl.formatMessage(i18n.theme)}</div>}
|
||||
{!hideTitle && (
|
||||
<div className="text-xs text-text-primary px-3">{intl.formatMessage(i18n.theme)}</div>
|
||||
)}
|
||||
<div
|
||||
className={`${horizontal ? 'flex' : 'grid grid-cols-4'} gap-1 ${!horizontal ? 'px-3' : ''}`}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -109,9 +109,6 @@ const NavRow: React.FC<NavRowProps> = ({ item, active, onClick }) => {
|
|||
<button onClick={onClick} className={navItemClass(active)}>
|
||||
<Icon className="w-5 h-5 flex-shrink-0 text-text-secondary" />
|
||||
<span className="text-left flex-1 truncate">{getNavItemLabel(item, intl)}</span>
|
||||
{item.getTag && (
|
||||
<span className="text-xs font-mono text-text-secondary">{item.getTag()}</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
|
@ -242,15 +239,14 @@ const SessionRow: React.FC<SessionRowProps> = ({
|
|||
};
|
||||
|
||||
export const Navigation: React.FC<{
|
||||
className?: string;
|
||||
activeLiveVoiceSessionId: string | null;
|
||||
}> = ({ className, activeLiveVoiceSessionId }) => {
|
||||
}> = ({ activeLiveVoiceSessionId }) => {
|
||||
const intl = useIntl();
|
||||
const { isNavExpanded } = useNavigationContext();
|
||||
const location = useLocation();
|
||||
const { extensionsList } = useConfig();
|
||||
|
||||
const appsExtensionEnabled = !!extensionsList?.find((ext) => ext.name === 'apps')?.enabled;
|
||||
const appsExtensionEnabled = !!extensionsList.find((ext) => ext.name === 'apps')?.enabled;
|
||||
|
||||
const visibleItems = useMemo<NavItem[]>(() => {
|
||||
return NAV_ITEMS.filter((item) => {
|
||||
|
|
@ -338,7 +334,7 @@ export const Navigation: React.FC<{
|
|||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.15 }}
|
||||
className={cn('bg-background-primary outline-none flex flex-col h-full', className)}
|
||||
className="bg-background-primary outline-none flex flex-col h-full"
|
||||
>
|
||||
<div className="h-[48px] no-drag" />
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@
|
|||
import {
|
||||
AppBridge,
|
||||
PostMessageTransport,
|
||||
type AppInfo,
|
||||
type RequestHandlerExtra,
|
||||
type SandboxConfig,
|
||||
} from '@mcp-ui/client';
|
||||
|
|
@ -177,7 +176,7 @@ interface GooseAppFrameProps {
|
|||
toolInput?: Record<string, unknown>;
|
||||
toolInputPartial?: Record<string, unknown>;
|
||||
toolResult?: CallToolResult;
|
||||
toolCancelled?: boolean;
|
||||
toolCancelled: boolean;
|
||||
onMessage: (params: {
|
||||
content: Array<{ type: string; text?: string }>;
|
||||
}) => Promise<Record<string, unknown>>;
|
||||
|
|
@ -196,9 +195,8 @@ interface GooseAppFrameProps {
|
|||
request: JSONRPCRequest,
|
||||
extra: RequestHandlerExtra
|
||||
) => Promise<Record<string, unknown>>;
|
||||
onSizeChanged?: (params: McpUiSizeChangedNotification['params']) => void;
|
||||
onInitialized?: (appInfo: AppInfo) => void;
|
||||
onError?: (error: Error) => void;
|
||||
onSizeChanged: (params: McpUiSizeChangedNotification['params']) => void;
|
||||
onError: (error: Error) => void;
|
||||
}
|
||||
|
||||
const SANDBOX_PROXY_READY_METHOD = 'ui/notifications/sandbox-proxy-ready';
|
||||
|
|
@ -228,7 +226,6 @@ function GooseAppFrame({
|
|||
onLoggingMessage,
|
||||
onFallbackRequest,
|
||||
onSizeChanged,
|
||||
onInitialized,
|
||||
onError,
|
||||
}: GooseAppFrameProps) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
|
@ -245,7 +242,6 @@ function GooseAppFrame({
|
|||
const onLoggingMessageRef = useRef(onLoggingMessage);
|
||||
const onFallbackRequestRef = useRef(onFallbackRequest);
|
||||
const onSizeChangedRef = useRef(onSizeChanged);
|
||||
const onInitializedRef = useRef(onInitialized);
|
||||
const onErrorRef = useRef(onError);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -257,7 +253,6 @@ function GooseAppFrame({
|
|||
onLoggingMessageRef.current = onLoggingMessage;
|
||||
onFallbackRequestRef.current = onFallbackRequest;
|
||||
onSizeChangedRef.current = onSizeChanged;
|
||||
onInitializedRef.current = onInitialized;
|
||||
onErrorRef.current = onError;
|
||||
});
|
||||
|
||||
|
|
@ -305,7 +300,7 @@ function GooseAppFrame({
|
|||
settled = true;
|
||||
cleanupReadyListener();
|
||||
if (active) {
|
||||
onErrorRef.current?.(error);
|
||||
onErrorRef.current(error);
|
||||
}
|
||||
};
|
||||
const ready = () => {
|
||||
|
|
@ -330,7 +325,7 @@ function GooseAppFrame({
|
|||
if (!active || !iframe.contentWindow) return;
|
||||
try {
|
||||
bridge.onsizechange = (params) => {
|
||||
onSizeChangedRef.current?.(params);
|
||||
onSizeChangedRef.current(params);
|
||||
if (params.width !== undefined) {
|
||||
iframe.style.width = `${params.width}px`;
|
||||
}
|
||||
|
|
@ -342,10 +337,6 @@ function GooseAppFrame({
|
|||
bridge.oninitialized = () => {
|
||||
if (!active) return;
|
||||
setInitialized(true);
|
||||
onInitializedRef.current?.({
|
||||
appVersion: bridge.getAppVersion(),
|
||||
appCapabilities: bridge.getAppCapabilities(),
|
||||
});
|
||||
};
|
||||
await bridge.connect(new PostMessageTransport(iframe.contentWindow, iframe.contentWindow));
|
||||
if (!active) return;
|
||||
|
|
@ -353,7 +344,7 @@ function GooseAppFrame({
|
|||
setConnected(true);
|
||||
} catch (error) {
|
||||
if (!active) return;
|
||||
onErrorRef.current?.(error instanceof Error ? error : new Error(String(error)));
|
||||
onErrorRef.current(error instanceof Error ? error : new Error(String(error)));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -382,7 +373,7 @@ function GooseAppFrame({
|
|||
if (!connected || !bridge) return;
|
||||
void Promise.resolve(bridge.sendSandboxResourceReady({ html, csp: sandbox.csp })).catch(
|
||||
(error: unknown) => {
|
||||
onErrorRef.current?.(error instanceof Error ? error : new Error(String(error)));
|
||||
onErrorRef.current(error instanceof Error ? error : new Error(String(error)));
|
||||
}
|
||||
);
|
||||
}, [connected, html, sandbox.csp]);
|
||||
|
|
|
|||
|
|
@ -315,18 +315,16 @@ export default function ToolCallWithResponse({
|
|||
|
||||
interface ToolCallExpandableProps {
|
||||
label: string | React.ReactNode;
|
||||
isStartExpanded?: boolean;
|
||||
isStartExpanded: boolean;
|
||||
isForceExpand?: boolean;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function ToolCallExpandable({
|
||||
label,
|
||||
isStartExpanded = false,
|
||||
isStartExpanded,
|
||||
isForceExpand,
|
||||
children,
|
||||
className = '',
|
||||
}: ToolCallExpandableProps) {
|
||||
const [isExpandedState, setIsExpanded] = React.useState<boolean | null>(null);
|
||||
const isExpanded = isExpandedState === null ? isStartExpanded : isExpandedState;
|
||||
|
|
@ -336,7 +334,7 @@ function ToolCallExpandable({
|
|||
}, [isForceExpand]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div>
|
||||
<Button
|
||||
onClick={toggleExpand}
|
||||
className="group w-full flex justify-between items-center pr-2 transition-colors rounded-none"
|
||||
|
|
@ -1082,7 +1080,7 @@ function ToolLogsView({
|
|||
}: {
|
||||
logs: string[];
|
||||
working: boolean;
|
||||
isStartExpanded?: boolean;
|
||||
isStartExpanded: boolean;
|
||||
}) {
|
||||
const intl = useIntl();
|
||||
const boxRef = useRef<HTMLDivElement>(null);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ const alertIcons: Record<AlertType, React.ReactNode> = {
|
|||
interface AlertBoxProps {
|
||||
alert: Alert;
|
||||
className?: string;
|
||||
compactButtonEnabled?: boolean;
|
||||
}
|
||||
|
||||
const i18n = defineMessages({
|
||||
|
|
|
|||
|
|
@ -81,9 +81,7 @@ interface DirSwitcherProps {
|
|||
className: string;
|
||||
sessionId: string | undefined;
|
||||
workingDir: string;
|
||||
onWorkingDirChange?: (newDir: string) => Promise<void> | void;
|
||||
onRestartStart?: () => void;
|
||||
onRestartEnd?: () => void;
|
||||
onWorkingDirChange: (newDir: string) => Promise<void> | void;
|
||||
}
|
||||
|
||||
export const DirSwitcher: React.FC<DirSwitcherProps> = ({
|
||||
|
|
@ -91,8 +89,6 @@ export const DirSwitcher: React.FC<DirSwitcherProps> = ({
|
|||
sessionId,
|
||||
workingDir,
|
||||
onWorkingDirChange,
|
||||
onRestartStart,
|
||||
onRestartEnd,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const [isTooltipOpen, setIsTooltipOpen] = useState(false);
|
||||
|
|
@ -129,19 +125,15 @@ export const DirSwitcher: React.FC<DirSwitcherProps> = ({
|
|||
|
||||
const applyDirectoryChange = async (newDir: string) => {
|
||||
if (sessionId) {
|
||||
onRestartStart?.();
|
||||
|
||||
try {
|
||||
await onWorkingDirChange?.(newDir);
|
||||
await onWorkingDirChange(newDir);
|
||||
} catch (error) {
|
||||
console.error('[DirSwitcher] Failed to update working directory:', error);
|
||||
toast.error(intl.formatMessage(i18n.failedToUpdateWorkingDir));
|
||||
return;
|
||||
} finally {
|
||||
onRestartEnd?.();
|
||||
}
|
||||
} else {
|
||||
await onWorkingDirChange?.(newDir);
|
||||
await onWorkingDirChange(newDir);
|
||||
}
|
||||
|
||||
// Only record the directory after the backend confirmed the change, so a
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@ interface InlineEditTextProps {
|
|||
disabled?: boolean;
|
||||
className?: string;
|
||||
editClassName?: string;
|
||||
onEditStart?: () => void;
|
||||
onEditEnd?: () => void;
|
||||
onEditStart: () => void;
|
||||
onEditEnd: () => void;
|
||||
allowEmpty?: boolean;
|
||||
singleClickEdit?: boolean;
|
||||
}
|
||||
|
|
@ -75,13 +75,13 @@ export const InlineEditText: React.FC<InlineEditTextProps> = ({
|
|||
if (disabled || isSaving) return;
|
||||
setIsEditing(true);
|
||||
setEditValue(value);
|
||||
onEditStart?.();
|
||||
onEditStart();
|
||||
}, [disabled, isSaving, value, onEditStart]);
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
setIsEditing(false);
|
||||
setEditValue(originalValue.current);
|
||||
onEditEnd?.();
|
||||
onEditEnd();
|
||||
}, [onEditEnd]);
|
||||
|
||||
const handleSave = useCallback(async () => {
|
||||
|
|
@ -106,7 +106,7 @@ export const InlineEditText: React.FC<InlineEditTextProps> = ({
|
|||
await onSave(trimmedValue);
|
||||
originalValue.current = trimmedValue;
|
||||
setIsEditing(false);
|
||||
onEditEnd?.();
|
||||
onEditEnd();
|
||||
} catch (error) {
|
||||
const errMsg = errorMessage(error, intl.formatMessage(i18n.failedToSave));
|
||||
console.error('InlineEditText save error:', errMsg);
|
||||
|
|
@ -197,7 +197,13 @@ export const InlineEditText: React.FC<InlineEditTextProps> = ({
|
|||
`}
|
||||
onClick={handleClick}
|
||||
onDoubleClick={handleDoubleClick}
|
||||
title={disabled ? '' : singleClickEdit ? intl.formatMessage(i18n.clickToEdit) : intl.formatMessage(i18n.doubleClickToEdit)}
|
||||
title={
|
||||
disabled
|
||||
? ''
|
||||
: singleClickEdit
|
||||
? intl.formatMessage(i18n.clickToEdit)
|
||||
: intl.formatMessage(i18n.doubleClickToEdit)
|
||||
}
|
||||
>
|
||||
{value || <span className="text-text-subtle italic">{resolvedPlaceholder}</span>}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -36,8 +36,8 @@ interface SearchBarProps {
|
|||
onSearch: (term: string, caseSensitive: boolean) => void;
|
||||
/** Callback fired when the search bar is closed */
|
||||
onClose: () => void;
|
||||
/** Optional callback for navigating between search results */
|
||||
onNavigate?: (direction: 'next' | 'prev') => void;
|
||||
/** Callback for navigating between search results */
|
||||
onNavigate: (direction: 'next' | 'prev') => void;
|
||||
/** Current search results state */
|
||||
searchResults?: {
|
||||
count: number;
|
||||
|
|
@ -153,7 +153,7 @@ export const SearchBar: React.FC<SearchBarProps> = ({
|
|||
e?.preventDefault();
|
||||
if (searchResults && searchResults.count > 0) {
|
||||
inputRef.current?.focus();
|
||||
onNavigate?.(direction);
|
||||
onNavigate(direction);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -86,18 +86,18 @@ const i18n = defineMessages({
|
|||
interface ParameterInputProps {
|
||||
parameter: Parameter;
|
||||
onChange: (name: string, updatedParameter: Partial<Parameter>) => void;
|
||||
onDelete?: (parameterKey: string) => void;
|
||||
isUnused?: boolean;
|
||||
isExpanded?: boolean;
|
||||
onToggleExpanded?: (parameterKey: string) => void;
|
||||
onDelete: (parameterKey: string) => void;
|
||||
isUnused: boolean;
|
||||
isExpanded: boolean;
|
||||
onToggleExpanded: (parameterKey: string) => void;
|
||||
}
|
||||
|
||||
const ParameterInput: React.FC<ParameterInputProps> = ({
|
||||
parameter,
|
||||
onChange,
|
||||
onDelete,
|
||||
isUnused = false,
|
||||
isExpanded = true,
|
||||
isUnused,
|
||||
isExpanded,
|
||||
onToggleExpanded,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
|
@ -106,7 +106,7 @@ const ParameterInput: React.FC<ParameterInputProps> = ({
|
|||
|
||||
const handleToggleExpanded = (e: React.MouseEvent) => {
|
||||
// Only toggle if we're not clicking on the delete button
|
||||
if (onToggleExpanded && !(e.target as HTMLElement).closest('button')) {
|
||||
if (!(e.target as HTMLElement).closest('button')) {
|
||||
onToggleExpanded(key);
|
||||
}
|
||||
};
|
||||
|
|
@ -115,26 +115,24 @@ const ParameterInput: React.FC<ParameterInputProps> = ({
|
|||
<div className="parameter-input my-4 border rounded-lg bg-background-secondary shadow-sm relative">
|
||||
{/* Collapsed header - always visible */}
|
||||
<div
|
||||
className={`flex items-center justify-between p-4 ${onToggleExpanded ? 'cursor-pointer hover:bg-background-primary/50' : ''} transition-colors`}
|
||||
className="flex items-center justify-between p-4 cursor-pointer hover:bg-background-primary/50 transition-colors"
|
||||
onClick={handleToggleExpanded}
|
||||
>
|
||||
<div className="flex items-center gap-2 flex-1">
|
||||
{onToggleExpanded && (
|
||||
<button
|
||||
type="button"
|
||||
className="p-1 hover:bg-background-primary rounded transition-colors"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onToggleExpanded(key);
|
||||
}}
|
||||
>
|
||||
{isExpanded ? (
|
||||
<ChevronDown className="w-4 h-4 text-text-secondary" />
|
||||
) : (
|
||||
<ChevronRight className="w-4 h-4 text-text-secondary" />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="p-1 hover:bg-background-primary rounded transition-colors"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onToggleExpanded(key);
|
||||
}}
|
||||
>
|
||||
{isExpanded ? (
|
||||
<ChevronDown className="w-4 h-4 text-text-secondary" />
|
||||
) : (
|
||||
<ChevronRight className="w-4 h-4 text-text-secondary" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-md font-bold text-text-primary">
|
||||
|
|
@ -146,25 +144,25 @@ const ParameterInput: React.FC<ParameterInputProps> = ({
|
|||
title={intl.formatMessage(i18n.unusedWarningTitle)}
|
||||
>
|
||||
<AlertTriangle className="w-4 h-4 text-orange-500" />
|
||||
<span className="text-xs text-orange-500 font-normal">{intl.formatMessage(i18n.unused)}</span>
|
||||
<span className="text-xs text-orange-500 font-normal">
|
||||
{intl.formatMessage(i18n.unused)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{onDelete && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete(key);
|
||||
}}
|
||||
className="p-1 text-red-500 hover:text-red-700 hover:bg-red-50 rounded transition-colors"
|
||||
title={intl.formatMessage(i18n.deleteParameter, { key })}
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete(key);
|
||||
}}
|
||||
className="p-1 text-red-500 hover:text-red-700 hover:bg-red-50 rounded transition-colors"
|
||||
title={intl.formatMessage(i18n.deleteParameter, { key })}
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Expandable content - only shown when expanded */}
|
||||
|
|
|
|||
|
|
@ -113,7 +113,6 @@ interface CreateEditRecipeModalProps {
|
|||
recipe?: Recipe;
|
||||
isCreateMode?: boolean;
|
||||
recipeId?: string | null;
|
||||
onRecipeSaved?: (savedRecipeId: string) => void;
|
||||
}
|
||||
|
||||
export default function CreateEditRecipeModal({
|
||||
|
|
@ -122,7 +121,6 @@ export default function CreateEditRecipeModal({
|
|||
recipe,
|
||||
isCreateMode = false,
|
||||
recipeId,
|
||||
onRecipeSaved,
|
||||
}: CreateEditRecipeModalProps) {
|
||||
const intl = useIntl();
|
||||
const getInitialValues = React.useCallback((): RecipeFormData => {
|
||||
|
|
@ -423,11 +421,7 @@ export default function CreateEditRecipeModal({
|
|||
try {
|
||||
const recipe = getCurrentRecipe();
|
||||
|
||||
const { id: savedRecipeId } = await saveRecipe(recipe, recipeId);
|
||||
|
||||
if (onRecipeSaved) {
|
||||
onRecipeSaved(savedRecipeId);
|
||||
}
|
||||
await saveRecipe(recipe, recipeId);
|
||||
|
||||
onClose(true);
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import MarkdownContent from '../MarkdownContent';
|
|||
interface RecipeActivitiesProps {
|
||||
append: (text: string) => void;
|
||||
activities: string[] | null;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export default function RecipeActivities({ append, activities }: RecipeActivitiesProps) {
|
||||
|
|
|
|||
|
|
@ -129,14 +129,14 @@ interface CreateSubRecipeInlineProps {
|
|||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSubRecipeSaved: (subRecipe: SubRecipeFormData) => void;
|
||||
existingSubRecipes?: SubRecipeFormData[];
|
||||
existingSubRecipes: SubRecipeFormData[];
|
||||
}
|
||||
|
||||
export default function CreateSubRecipeInline({
|
||||
isOpen,
|
||||
onClose,
|
||||
onSubRecipeSaved,
|
||||
existingSubRecipes = [],
|
||||
existingSubRecipes,
|
||||
}: CreateSubRecipeInlineProps) {
|
||||
const intl = useIntl();
|
||||
useEscapeKey(isOpen, onClose);
|
||||
|
|
@ -221,12 +221,24 @@ export default function CreateSubRecipeInline({
|
|||
|
||||
toastError({
|
||||
title: intl.formatMessage(i18n.saveFailed),
|
||||
msg: intl.formatMessage(i18n.saveFailedMsg, { error: error instanceof Error ? error.message : 'Unknown error' }),
|
||||
msg: intl.formatMessage(i18n.saveFailedMsg, {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
}),
|
||||
});
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
}, [form, name, toolDescription, sequentialWhenRepeated, values, existingSubRecipes, onSubRecipeSaved, onClose, intl]);
|
||||
}, [
|
||||
form,
|
||||
name,
|
||||
toolDescription,
|
||||
sequentialWhenRepeated,
|
||||
values,
|
||||
existingSubRecipes,
|
||||
onSubRecipeSaved,
|
||||
onClose,
|
||||
intl,
|
||||
]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
|
|
@ -236,10 +248,10 @@ export default function CreateSubRecipeInline({
|
|||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-6 border-b border-borderSubtle">
|
||||
<div>
|
||||
<h2 className="text-xl font-medium text-textProminent">{intl.formatMessage(i18n.title)}</h2>
|
||||
<p className="text-textSubtle text-sm">
|
||||
{intl.formatMessage(i18n.subtitle)}
|
||||
</p>
|
||||
<h2 className="text-xl font-medium text-textProminent">
|
||||
{intl.formatMessage(i18n.title)}
|
||||
</h2>
|
||||
<p className="text-textSubtle text-sm">{intl.formatMessage(i18n.subtitle)}</p>
|
||||
</div>
|
||||
<Button
|
||||
onClick={onClose}
|
||||
|
|
@ -270,9 +282,7 @@ export default function CreateSubRecipeInline({
|
|||
className="w-full p-3 border border-border-subtle rounded-lg bg-background-primary text-text-standard focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
placeholder={intl.formatMessage(i18n.namePlaceholder)}
|
||||
/>
|
||||
<p className="text-xs text-text-muted mt-1">
|
||||
{intl.formatMessage(i18n.nameHint)}
|
||||
</p>
|
||||
<p className="text-xs text-text-muted mt-1">{intl.formatMessage(i18n.nameHint)}</p>
|
||||
</div>
|
||||
|
||||
{/* Title Field */}
|
||||
|
|
@ -283,7 +293,8 @@ export default function CreateSubRecipeInline({
|
|||
htmlFor="subrecipe-title"
|
||||
className="block text-sm font-medium text-text-standard mb-2"
|
||||
>
|
||||
{intl.formatMessage(i18n.recipeTitleLabel)} <span className="text-text-danger">*</span>
|
||||
{intl.formatMessage(i18n.recipeTitleLabel)}{' '}
|
||||
<span className="text-text-danger">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="subrecipe-title"
|
||||
|
|
@ -306,7 +317,8 @@ export default function CreateSubRecipeInline({
|
|||
htmlFor="recipe-description"
|
||||
className="block text-sm font-medium text-text-standard mb-2"
|
||||
>
|
||||
{intl.formatMessage(i18n.recipeDescriptionLabel)} <span className="text-text-danger">*</span>
|
||||
{intl.formatMessage(i18n.recipeDescriptionLabel)}{' '}
|
||||
<span className="text-text-danger">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="recipe-description"
|
||||
|
|
@ -329,7 +341,8 @@ export default function CreateSubRecipeInline({
|
|||
htmlFor="subrecipe-instructions"
|
||||
className="block text-sm font-medium text-text-standard mb-2"
|
||||
>
|
||||
{intl.formatMessage(i18n.instructionsLabel)} <span className="text-text-danger">*</span>
|
||||
{intl.formatMessage(i18n.instructionsLabel)}{' '}
|
||||
<span className="text-text-danger">*</span>
|
||||
</label>
|
||||
<textarea
|
||||
id="subrecipe-instructions"
|
||||
|
|
|
|||
|
|
@ -104,15 +104,7 @@ import { RecipeExtensionSelector } from './RecipeExtensionSelector';
|
|||
type FormFieldApi<_T = any> = any;
|
||||
|
||||
interface RecipeFormFieldsProps {
|
||||
// Form instance from parent
|
||||
form: RecipeFormApi;
|
||||
|
||||
// Event handlers
|
||||
onTitleChange?: (value: string) => void;
|
||||
onDescriptionChange?: (value: string) => void;
|
||||
onInstructionsChange?: (value: string) => void;
|
||||
onPromptChange?: (value: string) => void;
|
||||
onJsonSchemaChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
const BUILT_IN_RECIPE_DIR_PARAM = 'recipe_dir';
|
||||
|
|
@ -138,14 +130,7 @@ export const extractTemplateVariables = (content: string): string[] => {
|
|||
return variables;
|
||||
};
|
||||
|
||||
export function RecipeFormFields({
|
||||
form,
|
||||
onTitleChange,
|
||||
onDescriptionChange,
|
||||
onInstructionsChange,
|
||||
onPromptChange,
|
||||
onJsonSchemaChange,
|
||||
}: RecipeFormFieldsProps) {
|
||||
export function RecipeFormFields({ form }: RecipeFormFieldsProps) {
|
||||
const intl = useIntl();
|
||||
const [showJsonSchemaEditor, setShowJsonSchemaEditor] = useState(false);
|
||||
const [showInstructionsEditor, setShowInstructionsEditor] = useState(false);
|
||||
|
|
@ -269,10 +254,7 @@ export function RecipeFormFields({
|
|||
id="recipe-title"
|
||||
type="text"
|
||||
value={field.state.value}
|
||||
onChange={(e) => {
|
||||
field.handleChange(e.target.value);
|
||||
onTitleChange?.(e.target.value);
|
||||
}}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
onBlur={field.handleBlur}
|
||||
className={`w-full p-3 border rounded-lg bg-background-primary text-text-primary focus:outline-none focus:ring-2 focus:ring-blue-500 ${
|
||||
field.state.meta.errors.length > 0 ? 'border-red-500' : 'border-border-primary'
|
||||
|
|
@ -301,10 +283,7 @@ export function RecipeFormFields({
|
|||
id="recipe-description"
|
||||
type="text"
|
||||
value={field.state.value}
|
||||
onChange={(e) => {
|
||||
field.handleChange(e.target.value);
|
||||
onDescriptionChange?.(e.target.value);
|
||||
}}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
onBlur={field.handleBlur}
|
||||
className={`w-full p-3 border rounded-lg bg-background-primary text-text-primary focus:outline-none focus:ring-2 focus:ring-blue-500 ${
|
||||
field.state.meta.errors.length > 0 ? 'border-red-500' : 'border-border-primary'
|
||||
|
|
@ -343,10 +322,7 @@ export function RecipeFormFields({
|
|||
<textarea
|
||||
id="recipe-instructions"
|
||||
value={field.state.value}
|
||||
onChange={(e) => {
|
||||
field.handleChange(e.target.value);
|
||||
onInstructionsChange?.(e.target.value);
|
||||
}}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
onBlur={() => {
|
||||
field.handleBlur();
|
||||
updateParametersFromFields();
|
||||
|
|
@ -372,7 +348,6 @@ export function RecipeFormFields({
|
|||
value={field.state.value}
|
||||
onChange={(value) => {
|
||||
field.handleChange(value);
|
||||
onInstructionsChange?.(value);
|
||||
updateParametersFromFields();
|
||||
}}
|
||||
error={field.state.meta.errors.length > 0 ? field.state.meta.errors[0] : undefined}
|
||||
|
|
@ -397,10 +372,7 @@ export function RecipeFormFields({
|
|||
<textarea
|
||||
id="recipe-prompt"
|
||||
value={field.state.value || ''}
|
||||
onChange={(e) => {
|
||||
field.handleChange(e.target.value);
|
||||
onPromptChange?.(e.target.value);
|
||||
}}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
onBlur={() => {
|
||||
field.handleBlur();
|
||||
updateParametersFromFields();
|
||||
|
|
@ -636,10 +608,7 @@ export function RecipeFormFields({
|
|||
isOpen={showJsonSchemaEditor}
|
||||
onClose={() => setShowJsonSchemaEditor(false)}
|
||||
value={field.state.value || ''}
|
||||
onChange={(value) => {
|
||||
field.handleChange(value);
|
||||
onJsonSchemaChange?.(value);
|
||||
}}
|
||||
onChange={(value) => field.handleChange(value)}
|
||||
error={
|
||||
field.state.meta.errors.length > 0 ? field.state.meta.errors[0] : undefined
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,10 +79,6 @@ const i18n = defineMessages({
|
|||
inspectError: { id: 'schedulesView.inspectError', defaultMessage: 'Inspect Job Error' },
|
||||
});
|
||||
|
||||
interface SchedulesViewProps {
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
const ScheduleCard: React.FC<{
|
||||
job: ScheduledJobDto;
|
||||
onNavigateToDetail: (id: string) => void;
|
||||
|
|
@ -238,7 +234,7 @@ const ScheduleCard: React.FC<{
|
|||
);
|
||||
};
|
||||
|
||||
const SchedulesView: React.FC<SchedulesViewProps> = ({ onClose: _onClose }) => {
|
||||
const SchedulesView = () => {
|
||||
const intl = useIntl();
|
||||
const location = useLocation();
|
||||
const [schedules, setSchedules] = useState<ScheduledJobDto[]>([]);
|
||||
|
|
|
|||
|
|
@ -146,11 +146,10 @@ interface EditSessionModalProps {
|
|||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (sessionId: string, newDescription: string) => Promise<void>;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const EditSessionModal = React.memo<EditSessionModalProps>(
|
||||
({ session, isOpen, onClose, onSave, disabled = false }) => {
|
||||
({ session, isOpen, onClose, onSave }) => {
|
||||
const intl = useIntl();
|
||||
const [description, setDescription] = useState('');
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
|
|
@ -165,7 +164,7 @@ const EditSessionModal = React.memo<EditSessionModalProps>(
|
|||
}, [session, isOpen]);
|
||||
|
||||
const handleSave = useCallback(async () => {
|
||||
if (!session || disabled) return;
|
||||
if (!session) return;
|
||||
|
||||
const trimmedDescription = description.trim();
|
||||
if (trimmedDescription === session.name) {
|
||||
|
|
@ -189,7 +188,7 @@ const EditSessionModal = React.memo<EditSessionModalProps>(
|
|||
} finally {
|
||||
setIsUpdating(false);
|
||||
}
|
||||
}, [session, description, onSave, onClose, disabled, intl]);
|
||||
}, [session, description, onSave, onClose, intl]);
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
if (!isUpdating) {
|
||||
|
|
@ -233,18 +232,18 @@ const EditSessionModal = React.memo<EditSessionModalProps>(
|
|||
autoFocus
|
||||
maxLength={200}
|
||||
onKeyDown={handleKeyDown}
|
||||
disabled={isUpdating || disabled}
|
||||
disabled={isUpdating}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end space-x-3 mt-6">
|
||||
<Button onClick={handleCancel} variant="ghost" disabled={isUpdating || disabled}>
|
||||
<Button onClick={handleCancel} variant="ghost" disabled={isUpdating}>
|
||||
{intl.formatMessage(i18n.cancel)}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={!description.trim() || isUpdating || disabled}
|
||||
disabled={!description.trim() || isUpdating}
|
||||
variant="default"
|
||||
>
|
||||
{isUpdating ? intl.formatMessage(i18n.saving) : intl.formatMessage(i18n.save)}
|
||||
|
|
@ -810,7 +809,7 @@ const SessionListView: React.FC<SessionListViewProps> = React.memo(({ onSelectSe
|
|||
);
|
||||
});
|
||||
|
||||
const SessionSkeleton = React.memo(({ variant = 0 }: { variant?: number }) => {
|
||||
const SessionSkeleton = React.memo(({ variant }: { variant: number }) => {
|
||||
const titleWidths = ['w-3/4', 'w-2/3', 'w-4/5', 'w-1/2'];
|
||||
const pathWidths = ['w-32', 'w-28', 'w-36', 'w-24'];
|
||||
const tokenWidths = ['w-12', 'w-10', 'w-14', 'w-8'];
|
||||
|
|
@ -909,52 +908,54 @@ const SessionListView: React.FC<SessionListViewProps> = React.memo(({ onSelectSe
|
|||
visibleGroupsCount >= activeDateGroups.length &&
|
||||
memoizedScheduledDateGroups.length > 0 && (
|
||||
<div className="space-y-4">
|
||||
<button
|
||||
onClick={() => setIsScheduledExpanded((v) => !v)}
|
||||
aria-expanded={isScheduledExpanded}
|
||||
aria-controls="scheduled-job-sessions"
|
||||
className="sticky top-0 z-10 w-full flex items-center justify-between bg-background-primary/95 backdrop-blur-sm py-2 px-1 rounded-lg hover:bg-background-secondary transition-colors cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Clock className="w-4 h-4 text-text-secondary" />
|
||||
<h2 className="text-text-secondary font-medium">
|
||||
{intl.formatMessage(i18n.scheduledJobs)}
|
||||
</h2>
|
||||
<span className="text-xs text-text-tertiary bg-background-secondary px-2 py-0.5 rounded-full">
|
||||
{intl.formatMessage(i18n.scheduledJobsCount, { count: scheduledSessions.length })}
|
||||
</span>
|
||||
</div>
|
||||
{isScheduledExpanded ? (
|
||||
<ChevronDown className="w-4 h-4 text-text-secondary" />
|
||||
) : (
|
||||
<ChevronRight className="w-4 h-4 text-text-secondary" />
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsScheduledExpanded((v) => !v)}
|
||||
aria-expanded={isScheduledExpanded}
|
||||
aria-controls="scheduled-job-sessions"
|
||||
className="sticky top-0 z-10 w-full flex items-center justify-between bg-background-primary/95 backdrop-blur-sm py-2 px-1 rounded-lg hover:bg-background-secondary transition-colors cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Clock className="w-4 h-4 text-text-secondary" />
|
||||
<h2 className="text-text-secondary font-medium">
|
||||
{intl.formatMessage(i18n.scheduledJobs)}
|
||||
</h2>
|
||||
<span className="text-xs text-text-tertiary bg-background-secondary px-2 py-0.5 rounded-full">
|
||||
{intl.formatMessage(i18n.scheduledJobsCount, {
|
||||
count: scheduledSessions.length,
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
{isScheduledExpanded ? (
|
||||
<ChevronDown className="w-4 h-4 text-text-secondary" />
|
||||
) : (
|
||||
<ChevronRight className="w-4 h-4 text-text-secondary" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
{isScheduledExpanded && (
|
||||
<div id="scheduled-job-sessions" className="space-y-8">
|
||||
{memoizedScheduledDateGroups.map((group) => (
|
||||
<div key={group.label} className="space-y-4">
|
||||
<div className="sticky top-0 z-10 bg-background-primary/95 backdrop-blur-sm">
|
||||
<h2 className="text-text-secondary">{group.label}</h2>
|
||||
{isScheduledExpanded && (
|
||||
<div id="scheduled-job-sessions" className="space-y-8">
|
||||
{memoizedScheduledDateGroups.map((group) => (
|
||||
<div key={group.label} className="space-y-4">
|
||||
<div className="sticky top-0 z-10 bg-background-primary/95 backdrop-blur-sm">
|
||||
<h2 className="text-text-secondary">{group.label}</h2>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4">
|
||||
{group.sessions.map((session) => (
|
||||
<SessionItem
|
||||
key={session.id}
|
||||
session={session}
|
||||
onEditClick={handleEditSession}
|
||||
onDuplicateClick={handleDuplicateSession}
|
||||
onDeleteClick={handleDeleteSession}
|
||||
onExportClick={handleExportSession}
|
||||
onOpenInNewWindow={handleOpenInNewWindow}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4">
|
||||
{group.sessions.map((session) => (
|
||||
<SessionItem
|
||||
key={session.id}
|
||||
session={session}
|
||||
onEditClick={handleEditSession}
|
||||
onDuplicateClick={handleDuplicateSession}
|
||||
onDeleteClick={handleDeleteSession}
|
||||
onExportClick={handleExportSession}
|
||||
onOpenInNewWindow={handleOpenInNewWindow}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,12 +43,11 @@ const i18n = defineMessages({
|
|||
interface ExtensionSectionProps {
|
||||
deepLinkConfig?: ExtensionConfig;
|
||||
showEnvVars?: boolean;
|
||||
hideButtons?: boolean;
|
||||
hideButtons: boolean;
|
||||
disableConfiguration?: boolean;
|
||||
customToggle?: (extension: FixedExtensionEntry) => Promise<boolean | void>;
|
||||
selectedExtensions?: string[]; // Add controlled state
|
||||
onModalClose?: (extensionName: string) => void;
|
||||
searchTerm?: string;
|
||||
onModalClose: (extensionName: string) => void;
|
||||
searchTerm: string;
|
||||
}
|
||||
|
||||
export default function ExtensionsSection({
|
||||
|
|
@ -56,10 +55,9 @@ export default function ExtensionsSection({
|
|||
showEnvVars,
|
||||
hideButtons,
|
||||
disableConfiguration,
|
||||
customToggle,
|
||||
selectedExtensions = [],
|
||||
onModalClose,
|
||||
searchTerm = '',
|
||||
searchTerm,
|
||||
}: ExtensionSectionProps) {
|
||||
const intl = useIntl();
|
||||
const { getExtensions, addExtension, removeExtension, setExtensionEnabled, extensionsList } =
|
||||
|
|
@ -109,11 +107,6 @@ export default function ExtensionsSection({
|
|||
}, [getExtensions]);
|
||||
|
||||
const handleExtensionToggle = async (extensionConfig: FixedExtensionEntry) => {
|
||||
if (customToggle) {
|
||||
await customToggle(extensionConfig);
|
||||
return true;
|
||||
}
|
||||
|
||||
const toggleDirection = extensionConfig.enabled ? 'toggleOff' : 'toggleOn';
|
||||
const configKey = extensionConfig.configKey ?? nameToKey(extensionConfig.name);
|
||||
|
||||
|
|
@ -146,11 +139,9 @@ export default function ExtensionsSection({
|
|||
console.error('Failed to add extension:', error);
|
||||
} finally {
|
||||
await fetchExtensions();
|
||||
if (onModalClose) {
|
||||
setTimeout(() => {
|
||||
onModalClose(formData.name);
|
||||
}, 200);
|
||||
}
|
||||
setTimeout(() => {
|
||||
onModalClose(formData.name);
|
||||
}, 200);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ interface ExtensionConfigFieldsProps {
|
|||
full_cmd: string;
|
||||
endpoint: string;
|
||||
onChange: (key: string, value: string) => void;
|
||||
submitAttempted?: boolean;
|
||||
isValid?: boolean;
|
||||
submitAttempted: boolean;
|
||||
isValid: boolean;
|
||||
}
|
||||
|
||||
export default function ExtensionConfigFields({
|
||||
|
|
@ -42,7 +42,7 @@ export default function ExtensionConfigFields({
|
|||
full_cmd,
|
||||
endpoint,
|
||||
onChange,
|
||||
submitAttempted = false,
|
||||
submitAttempted,
|
||||
isValid,
|
||||
}: ExtensionConfigFieldsProps) {
|
||||
const intl = useIntl();
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ describe('ExtensionItem', () => {
|
|||
it('reflects the toggle as OFF immediately when disabling, before the async toggle resolves', async () => {
|
||||
// onToggle stays pending so we observe the in-flight (optimistic) state
|
||||
const onToggle = vi.fn(() => new Promise<void>(() => {}));
|
||||
renderWithIntl(<ExtensionItem extension={makeExtension(true)} onToggle={onToggle} />);
|
||||
renderWithIntl(
|
||||
<ExtensionItem extension={makeExtension(true)} onToggle={onToggle} onConfigure={vi.fn()} />
|
||||
);
|
||||
|
||||
const toggle = screen.getByRole('switch');
|
||||
expect(toggle).toHaveAttribute('aria-checked', 'true');
|
||||
|
|
@ -33,7 +35,9 @@ describe('ExtensionItem', () => {
|
|||
|
||||
it('reflects the toggle as ON immediately when enabling, before the async toggle resolves', async () => {
|
||||
const onToggle = vi.fn(() => new Promise<void>(() => {}));
|
||||
renderWithIntl(<ExtensionItem extension={makeExtension(false)} onToggle={onToggle} />);
|
||||
renderWithIntl(
|
||||
<ExtensionItem extension={makeExtension(false)} onToggle={onToggle} onConfigure={vi.fn()} />
|
||||
);
|
||||
|
||||
const toggle = screen.getByRole('switch');
|
||||
expect(toggle).toHaveAttribute('aria-checked', 'false');
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ const i18n = defineMessages({
|
|||
interface ExtensionItemProps {
|
||||
extension: FixedExtensionEntry;
|
||||
onToggle: (extension: FixedExtensionEntry) => Promise<boolean | void> | void;
|
||||
onConfigure?: (extension: FixedExtensionEntry) => void;
|
||||
onConfigure: (extension: FixedExtensionEntry) => void;
|
||||
isStatic?: boolean; // to not allow users to edit configuration
|
||||
}
|
||||
|
||||
|
|
@ -100,7 +100,7 @@ export default function ExtensionItem({
|
|||
aria-label={intl.formatMessage(i18n.configureExtension, {
|
||||
name: getFriendlyTitle(extension),
|
||||
})}
|
||||
onClick={() => onConfigure?.(extension)}
|
||||
onClick={() => onConfigure(extension)}
|
||||
>
|
||||
<Gear className="w-4 h-4" />
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -27,10 +27,10 @@ const i18n = defineMessages({
|
|||
interface ExtensionListProps {
|
||||
extensions: FixedExtensionEntry[];
|
||||
onToggle: (extension: FixedExtensionEntry) => Promise<boolean | void> | void;
|
||||
onConfigure?: (extension: FixedExtensionEntry) => void;
|
||||
onConfigure: (extension: FixedExtensionEntry) => void;
|
||||
isStatic?: boolean;
|
||||
disableConfiguration?: boolean;
|
||||
searchTerm?: string;
|
||||
searchTerm: string;
|
||||
}
|
||||
|
||||
export default function ExtensionList({
|
||||
|
|
@ -39,7 +39,7 @@ export default function ExtensionList({
|
|||
onConfigure,
|
||||
isStatic,
|
||||
disableConfiguration: _disableConfiguration,
|
||||
searchTerm = '',
|
||||
searchTerm,
|
||||
}: ExtensionListProps) {
|
||||
const matchesSearch = (extension: FixedExtensionEntry): boolean => {
|
||||
if (!searchTerm) return true;
|
||||
|
|
|
|||
|
|
@ -97,13 +97,11 @@ function ProviderCards({
|
|||
isOnboarding,
|
||||
refreshProviders,
|
||||
setView,
|
||||
onModelSelected,
|
||||
}: {
|
||||
providers: ProviderDetails[];
|
||||
isOnboarding: boolean;
|
||||
refreshProviders?: () => void;
|
||||
setView?: (view: View) => void;
|
||||
onModelSelected?: (model?: string) => void;
|
||||
refreshProviders: () => void;
|
||||
setView: (view: View) => void;
|
||||
}) {
|
||||
const intl = useIntl();
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
|
@ -168,9 +166,7 @@ function ProviderCards({
|
|||
const providerId = editingProvider.id;
|
||||
setShowCustomProviderModal(false);
|
||||
setEditingProvider(null);
|
||||
if (refreshProviders) {
|
||||
refreshProviders();
|
||||
}
|
||||
refreshProviders();
|
||||
setSwitchModelProvider(providerId);
|
||||
setShowSwitchModelModal(true);
|
||||
},
|
||||
|
|
@ -184,9 +180,7 @@ function ProviderCards({
|
|||
setShowCustomProviderModal(false);
|
||||
setEditingProvider(null);
|
||||
setIsActiveProvider(false);
|
||||
if (refreshProviders) {
|
||||
refreshProviders();
|
||||
}
|
||||
refreshProviders();
|
||||
}, [editingProvider, refreshProviders]);
|
||||
|
||||
const handleCloseModal = useCallback(() => {
|
||||
|
|
@ -197,17 +191,13 @@ function ProviderCards({
|
|||
|
||||
const onCloseProviderConfig = useCallback(() => {
|
||||
setConfiguringProvider(null);
|
||||
if (refreshProviders) {
|
||||
refreshProviders();
|
||||
}
|
||||
refreshProviders();
|
||||
}, [refreshProviders]);
|
||||
|
||||
const onProviderConfigured = useCallback(
|
||||
async (provider: ProviderDetails) => {
|
||||
setConfiguringProvider(null);
|
||||
if (refreshProviders) {
|
||||
await refreshProviders();
|
||||
}
|
||||
await refreshProviders();
|
||||
setSwitchModelProvider(provider.name);
|
||||
setShowSwitchModelModal(true);
|
||||
},
|
||||
|
|
@ -221,9 +211,7 @@ function ProviderCards({
|
|||
const handleSetView = useCallback(
|
||||
(view: View) => {
|
||||
setShowSwitchModelModal(false);
|
||||
if (setView) {
|
||||
setView(view);
|
||||
}
|
||||
setView(view);
|
||||
},
|
||||
[setView]
|
||||
);
|
||||
|
|
@ -233,9 +221,7 @@ function ProviderCards({
|
|||
const result = await acpCreateCustomProviderFromRequest(data);
|
||||
const providerId = result.provider_name;
|
||||
setShowCustomProviderModal(false);
|
||||
if (refreshProviders) {
|
||||
await refreshProviders();
|
||||
}
|
||||
await refreshProviders();
|
||||
setSwitchModelProvider(providerId);
|
||||
setShowSwitchModelModal(true);
|
||||
},
|
||||
|
|
@ -295,7 +281,7 @@ function ProviderCards({
|
|||
api_key: '',
|
||||
models: editingProvider.config.models ?? [],
|
||||
supports_streaming: editingProvider.config.supportsStreaming ?? true,
|
||||
requires_auth: editingProvider.config.requiresAuth ?? true,
|
||||
requires_auth: editingProvider.config.requiresAuth,
|
||||
headers: editingProvider.config.headers ?? undefined,
|
||||
catalog_provider_id: editingProvider.config.catalogProviderId ?? undefined,
|
||||
toolshim: editingProvider.config.toolshim,
|
||||
|
|
@ -357,7 +343,6 @@ function ProviderCards({
|
|||
sessionId={null}
|
||||
onClose={onCloseSwitchModelModal}
|
||||
setView={handleSetView}
|
||||
onModelSelected={onModelSelected}
|
||||
initialProvider={switchModelProvider}
|
||||
titleOverride={intl.formatMessage(i18n.chooseModel)}
|
||||
/>
|
||||
|
|
@ -371,13 +356,11 @@ export default function ProviderGrid({
|
|||
isOnboarding,
|
||||
refreshProviders,
|
||||
setView,
|
||||
onModelSelected,
|
||||
}: {
|
||||
providers: ProviderDetails[];
|
||||
isOnboarding: boolean;
|
||||
refreshProviders?: () => void;
|
||||
setView?: (view: View) => void;
|
||||
onModelSelected?: (model?: string) => void;
|
||||
refreshProviders: () => void;
|
||||
setView: (view: View) => void;
|
||||
}) {
|
||||
return (
|
||||
<ProviderCards
|
||||
|
|
@ -385,7 +368,6 @@ export default function ProviderGrid({
|
|||
isOnboarding={isOnboarding}
|
||||
refreshProviders={refreshProviders}
|
||||
setView={setView}
|
||||
onModelSelected={onModelSelected}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,14 +34,9 @@ const i18n = defineMessages({
|
|||
interface ProviderSettingsProps {
|
||||
onClose: () => void;
|
||||
isOnboarding: boolean;
|
||||
onProviderLaunched?: (model?: string) => void;
|
||||
}
|
||||
|
||||
export default function ProviderSettings({
|
||||
onClose,
|
||||
isOnboarding,
|
||||
onProviderLaunched,
|
||||
}: ProviderSettingsProps) {
|
||||
export default function ProviderSettings({ onClose, isOnboarding }: ProviderSettingsProps) {
|
||||
const intl = useIntl();
|
||||
const navigate = useNavigate();
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
|
@ -118,7 +113,6 @@ export default function ProviderSettings({
|
|||
isOnboarding={isOnboarding}
|
||||
refreshProviders={refreshProviders}
|
||||
setView={setView}
|
||||
onModelSelected={onProviderLaunched}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { Button } from '../../../../ui/button';
|
||||
import { Search, ExternalLink, Check } from 'lucide-react';
|
||||
import { Input } from '../../../../ui/input';
|
||||
import { Select } from '../../../../ui/Select';
|
||||
|
|
@ -59,23 +58,13 @@ const i18n = defineMessages({
|
|||
id: 'providerCatalogPicker.requiresEnvVar',
|
||||
defaultMessage: ' • Requires {envVar}',
|
||||
},
|
||||
cancel: {
|
||||
id: 'providerCatalogPicker.cancel',
|
||||
defaultMessage: 'Cancel',
|
||||
},
|
||||
});
|
||||
|
||||
interface ProviderCatalogPickerProps {
|
||||
onSelect: (template: ProviderTemplateDto) => void;
|
||||
onCancel: () => void;
|
||||
embedded?: boolean;
|
||||
}
|
||||
|
||||
export default function ProviderCatalogPicker({
|
||||
onSelect,
|
||||
onCancel,
|
||||
embedded,
|
||||
}: ProviderCatalogPickerProps) {
|
||||
export default function ProviderCatalogPicker({ onSelect }: ProviderCatalogPickerProps) {
|
||||
const intl = useIntl();
|
||||
const [selectedFormat, setSelectedFormat] = useState<string>('openai');
|
||||
const [providers, setProviders] = useState<ProviderTemplateCatalogEntryDto[]>([]);
|
||||
|
|
@ -237,15 +226,6 @@ export default function ProviderCatalogPicker({
|
|||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Actions */}
|
||||
{!embedded && (
|
||||
<div className="flex justify-end space-x-2 pt-4">
|
||||
<Button type="button" variant="outline" onClick={onCancel}>
|
||||
{intl.formatMessage(i18n.cancel)}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,14 +44,14 @@ const i18n = defineMessages({
|
|||
interface ProviderSetupActionsProps {
|
||||
onCancel: () => void;
|
||||
onSubmit: (e: SyntheticEvent) => void;
|
||||
onDelete?: () => void;
|
||||
showDeleteConfirmation?: boolean;
|
||||
onConfirmDelete?: () => void;
|
||||
onCancelDelete?: () => void;
|
||||
canDelete?: boolean;
|
||||
providerName?: string;
|
||||
primaryParameters?: ConfigKey[];
|
||||
isActiveProvider?: boolean; // Made optional with default false
|
||||
onDelete: () => void;
|
||||
showDeleteConfirmation: boolean;
|
||||
onConfirmDelete: () => void;
|
||||
onCancelDelete: () => void;
|
||||
canDelete: boolean;
|
||||
providerName: string;
|
||||
primaryParameters: ConfigKey[];
|
||||
isActiveProvider: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -68,7 +68,7 @@ export default function ProviderSetupActions({
|
|||
canDelete,
|
||||
providerName,
|
||||
primaryParameters,
|
||||
isActiveProvider = false, // Default value provided
|
||||
isActiveProvider,
|
||||
}: ProviderSetupActionsProps) {
|
||||
const intl = useIntl();
|
||||
|
||||
|
|
|
|||
|
|
@ -251,7 +251,7 @@ interface CustomProviderFormProps {
|
|||
onDelete?: () => Promise<void>;
|
||||
isActiveProvider?: boolean;
|
||||
initialData: UpdateCustomProviderRequest | null;
|
||||
isEditable?: boolean;
|
||||
isEditable: boolean;
|
||||
}
|
||||
|
||||
export default function CustomProviderForm({
|
||||
|
|
@ -315,7 +315,7 @@ export default function CustomProviderForm({
|
|||
setModels(initialData.models.join(', '));
|
||||
setSupportsStreaming(initialData.supports_streaming ?? true);
|
||||
setToolshim(initialData.toolshim);
|
||||
setRequiresAuth(initialData.requires_auth ?? true);
|
||||
setRequiresAuth(initialData.requires_auth);
|
||||
|
||||
if (initialData.headers) {
|
||||
const headerList = Object.entries(initialData.headers).map(([key, value]) => ({
|
||||
|
|
@ -453,7 +453,7 @@ export default function CustomProviderForm({
|
|||
const errors: Record<string, string> = {};
|
||||
if (!displayName) errors.displayName = intl.formatMessage(i18n.displayNameRequired);
|
||||
if (!apiUrl) errors.apiUrl = intl.formatMessage(i18n.apiUrlRequired);
|
||||
const existingHadAuth = initialData && (initialData.requires_auth ?? true);
|
||||
const existingHadAuth = initialData && initialData.requires_auth;
|
||||
if (requiresAuth && !apiKey && !existingHadAuth)
|
||||
errors.apiKey = intl.formatMessage(i18n.apiKeyRequired);
|
||||
if (!models) errors.models = intl.formatMessage(i18n.modelsRequired);
|
||||
|
|
@ -577,7 +577,7 @@ export default function CustomProviderForm({
|
|||
if (step === 'catalog') {
|
||||
return (
|
||||
<div className="mt-4">
|
||||
<ProviderCatalogPicker onSelect={handleTemplateSelect} onCancel={handleCancel} embedded />
|
||||
<ProviderCatalogPicker onSelect={handleTemplateSelect} />
|
||||
<div className="flex justify-between pt-4">
|
||||
<Button type="button" variant="ghost" onClick={handleBackToChoice}>
|
||||
{intl.formatMessage(i18n.back)}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ interface CardContainerProps {
|
|||
grayedOut: boolean;
|
||||
testId?: string;
|
||||
borderStyle?: 'solid' | 'dashed';
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function GlowingRing() {
|
||||
|
|
@ -33,10 +32,9 @@ export default function CardContainer({
|
|||
header,
|
||||
body,
|
||||
onClick,
|
||||
grayedOut = false,
|
||||
grayedOut,
|
||||
testId,
|
||||
borderStyle = 'solid',
|
||||
className = '',
|
||||
}: CardContainerProps) {
|
||||
return (
|
||||
<div
|
||||
|
|
@ -61,8 +59,7 @@ export default function CardContainer({
|
|||
grayedOut
|
||||
? 'border-border-primary'
|
||||
: 'border-border-primary hover:border-border-primary'
|
||||
}
|
||||
${className}`}
|
||||
}`}
|
||||
>
|
||||
{header && (
|
||||
<div style={{ opacity: grayedOut ? '0.5' : '1' }}>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,11 @@
|
|||
import { useMemo } from 'react';
|
||||
import CardContainer from './CardContainer';
|
||||
import CardHeader from './CardHeader';
|
||||
import CardBody from './CardBody';
|
||||
import DefaultCardButtons from './buttons/DefaultCardButtons';
|
||||
import type { ProviderDetails, ProviderMetadata } from '../../../../types/providers';
|
||||
import type { ProviderDetails } from '../../../../types/providers';
|
||||
import { defineMessages, useIntl } from '../../../../i18n';
|
||||
|
||||
const i18n = defineMessages({
|
||||
noMetadata: {
|
||||
id: 'providerCard.noMetadata',
|
||||
defaultMessage: 'ProviderCard error: No metadata provided',
|
||||
},
|
||||
unknownProvider: {
|
||||
id: 'providerCard.unknownProvider',
|
||||
defaultMessage: 'Unknown Provider',
|
||||
|
|
@ -35,15 +30,7 @@ export const ProviderCard = function ProviderCard({
|
|||
isOnboarding,
|
||||
}: ProviderCardProps) {
|
||||
const intl = useIntl();
|
||||
// Safely access metadata with null checks
|
||||
const providerMetadata: ProviderMetadata | null = provider?.metadata || null;
|
||||
|
||||
// Instead of useEffect for logging, use useMemo to memoize the metadata
|
||||
const metadata = useMemo(() => providerMetadata, [providerMetadata]);
|
||||
|
||||
if (!metadata) {
|
||||
return <div>{intl.formatMessage(i18n.noMetadata)}</div>;
|
||||
}
|
||||
const metadata = provider.metadata;
|
||||
|
||||
const handleCardClick = () => {
|
||||
if (!isOnboarding) {
|
||||
|
|
@ -63,9 +50,9 @@ export const ProviderCard = function ProviderCard({
|
|||
onClick={handleCardClick}
|
||||
header={
|
||||
<CardHeader
|
||||
name={metadata.display_name || provider?.name || intl.formatMessage(i18n.unknownProvider)}
|
||||
name={metadata.display_name || provider.name || intl.formatMessage(i18n.unknownProvider)}
|
||||
description={description}
|
||||
isConfigured={provider?.is_configured || false}
|
||||
isConfigured={provider.is_configured}
|
||||
/>
|
||||
}
|
||||
body={
|
||||
|
|
|
|||
|
|
@ -80,7 +80,8 @@ const i18n = defineMessages({
|
|||
},
|
||||
mlEndpointDescription: {
|
||||
id: 'securityToggle.mlEndpointDescription',
|
||||
defaultMessage: 'Enter the full URL for your ML classification service (including model identifier)',
|
||||
defaultMessage:
|
||||
'Enter the full URL for your ML classification service (including model identifier)',
|
||||
},
|
||||
mlTokenDescription: {
|
||||
id: 'securityToggle.mlTokenDescription',
|
||||
|
|
@ -110,10 +111,10 @@ interface ClassifierEndpointInputsProps {
|
|||
disabled: boolean;
|
||||
endpointPlaceholder: string;
|
||||
tokenPlaceholder: string;
|
||||
endpointLabel?: string;
|
||||
endpointDescription?: string;
|
||||
tokenLabel?: string;
|
||||
tokenDescription?: string;
|
||||
endpointLabel: string;
|
||||
endpointDescription: string;
|
||||
tokenLabel: string;
|
||||
tokenDescription: string;
|
||||
}
|
||||
|
||||
const ClassifierEndpointInputs = ({
|
||||
|
|
@ -185,13 +186,11 @@ export const SecurityToggle = () => {
|
|||
const { config, upsert } = useConfig();
|
||||
|
||||
const promptEnabledOverride = window.appConfig?.get('SECURITY_PROMPT_ENABLED_OVERRIDE') as
|
||||
| string
|
||||
| undefined;
|
||||
string | undefined;
|
||||
const commandClassifierOverride = window.appConfig?.get(
|
||||
'SECURITY_COMMAND_CLASSIFIER_ENABLED_OVERRIDE'
|
||||
) as string | undefined;
|
||||
const isPromptOverridden =
|
||||
promptEnabledOverride === 'true' || promptEnabledOverride === 'false';
|
||||
const isPromptOverridden = promptEnabledOverride === 'true' || promptEnabledOverride === 'false';
|
||||
const isCommandClassifierOverridden =
|
||||
commandClassifierOverride === 'true' || commandClassifierOverride === 'false';
|
||||
const promptOverrideValue = promptEnabledOverride === 'true';
|
||||
|
|
|
|||
|
|
@ -22,14 +22,14 @@ interface ChatProviderProps {
|
|||
children: ReactNode;
|
||||
chat: ChatType;
|
||||
setChat: (chat: ChatType) => void;
|
||||
contextKey?: string; // Optional context key, defaults to 'hub'
|
||||
contextKey: string;
|
||||
}
|
||||
|
||||
export const ChatProvider: React.FC<ChatProviderProps> = ({
|
||||
children,
|
||||
chat,
|
||||
setChat,
|
||||
contextKey = 'hub',
|
||||
contextKey,
|
||||
}) => {
|
||||
const resetChat = () => {
|
||||
setChat({
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ interface UseAutoSubmitProps {
|
|||
messages: Message[];
|
||||
chatState: ChatState;
|
||||
initialMessage: UserInput | undefined;
|
||||
canAutoSubmit?: boolean;
|
||||
canAutoSubmit: boolean;
|
||||
handleSubmit: (input: UserInput) => void;
|
||||
}
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ export function useAutoSubmit({
|
|||
messages,
|
||||
chatState,
|
||||
initialMessage,
|
||||
canAutoSubmit = true,
|
||||
canAutoSubmit,
|
||||
handleSubmit,
|
||||
}: UseAutoSubmitProps): UseAutoSubmitReturn {
|
||||
const [searchParams] = useSearchParams();
|
||||
|
|
|
|||
|
|
@ -16,8 +16,6 @@ export interface NavItem {
|
|||
path: string;
|
||||
label: string;
|
||||
icon: LucideIcon;
|
||||
getTag?: () => string;
|
||||
tagAlign?: 'left' | 'right';
|
||||
}
|
||||
|
||||
/** Top-level nav items (excluding Settings which is pinned to the bottom). */
|
||||
|
|
|
|||
|
|
@ -2720,9 +2720,6 @@
|
|||
"promptsSettings.unsavedChanges": {
|
||||
"defaultMessage": "Sie haben nicht gespeicherte Änderungen"
|
||||
},
|
||||
"providerCard.noMetadata": {
|
||||
"defaultMessage": "ProviderCard-Fehler: Keine Metadaten bereitgestellt"
|
||||
},
|
||||
"providerCard.unknownProvider": {
|
||||
"defaultMessage": "Unbekannter Anbieter"
|
||||
},
|
||||
|
|
@ -2732,9 +2729,6 @@
|
|||
"providerCatalogPicker.apiFormat": {
|
||||
"defaultMessage": "API-Format"
|
||||
},
|
||||
"providerCatalogPicker.cancel": {
|
||||
"defaultMessage": "Abbrechen"
|
||||
},
|
||||
"providerCatalogPicker.chooseProvider": {
|
||||
"defaultMessage": "Anbieter auswählen"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2747,9 +2747,6 @@
|
|||
"providerCard.deprecatedReplacement": {
|
||||
"defaultMessage": "Deprecated — use {replacement} instead."
|
||||
},
|
||||
"providerCard.noMetadata": {
|
||||
"defaultMessage": "ProviderCard error: No metadata provided"
|
||||
},
|
||||
"providerCard.unknownProvider": {
|
||||
"defaultMessage": "Unknown Provider"
|
||||
},
|
||||
|
|
@ -2759,9 +2756,6 @@
|
|||
"providerCatalogPicker.apiFormat": {
|
||||
"defaultMessage": "API Format"
|
||||
},
|
||||
"providerCatalogPicker.cancel": {
|
||||
"defaultMessage": "Cancel"
|
||||
},
|
||||
"providerCatalogPicker.chooseProvider": {
|
||||
"defaultMessage": "Choose Provider"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2720,9 +2720,6 @@
|
|||
"promptsSettings.unsavedChanges": {
|
||||
"defaultMessage": "Tienes cambios sin guardar"
|
||||
},
|
||||
"providerCard.noMetadata": {
|
||||
"defaultMessage": "Error de ProviderCard: no se proporcionaron metadatos"
|
||||
},
|
||||
"providerCard.unknownProvider": {
|
||||
"defaultMessage": "Proveedor desconocido"
|
||||
},
|
||||
|
|
@ -2732,9 +2729,6 @@
|
|||
"providerCatalogPicker.apiFormat": {
|
||||
"defaultMessage": "Formato de API"
|
||||
},
|
||||
"providerCatalogPicker.cancel": {
|
||||
"defaultMessage": "Cancelar"
|
||||
},
|
||||
"providerCatalogPicker.chooseProvider": {
|
||||
"defaultMessage": "Elegir proveedor"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2720,9 +2720,6 @@
|
|||
"promptsSettings.unsavedChanges": {
|
||||
"defaultMessage": "Vous avez des modifications non enregistrées"
|
||||
},
|
||||
"providerCard.noMetadata": {
|
||||
"defaultMessage": "Erreur ProviderCard : aucune métadonnée fournie"
|
||||
},
|
||||
"providerCard.unknownProvider": {
|
||||
"defaultMessage": "Fournisseur inconnu"
|
||||
},
|
||||
|
|
@ -2732,9 +2729,6 @@
|
|||
"providerCatalogPicker.apiFormat": {
|
||||
"defaultMessage": "Format d'API"
|
||||
},
|
||||
"providerCatalogPicker.cancel": {
|
||||
"defaultMessage": "Annuler"
|
||||
},
|
||||
"providerCatalogPicker.chooseProvider": {
|
||||
"defaultMessage": "Choisir un fournisseur"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2720,9 +2720,6 @@
|
|||
"promptsSettings.unsavedChanges": {
|
||||
"defaultMessage": "आपके पास सहेजे नहीं गए परिवर्तन हैं"
|
||||
},
|
||||
"providerCard.noMetadata": {
|
||||
"defaultMessage": "प्रदाता कार्ड त्रुटि: कोई मेटाडेटा प्रदान नहीं किया गया"
|
||||
},
|
||||
"providerCard.unknownProvider": {
|
||||
"defaultMessage": "अज्ञात प्रदाता"
|
||||
},
|
||||
|
|
@ -2732,9 +2729,6 @@
|
|||
"providerCatalogPicker.apiFormat": {
|
||||
"defaultMessage": "API प्रारूप"
|
||||
},
|
||||
"providerCatalogPicker.cancel": {
|
||||
"defaultMessage": "रद्द करें"
|
||||
},
|
||||
"providerCatalogPicker.chooseProvider": {
|
||||
"defaultMessage": "प्रदाता चुनें"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2720,9 +2720,6 @@
|
|||
"promptsSettings.unsavedChanges": {
|
||||
"defaultMessage": "Anda memiliki perubahan yang belum disimpan"
|
||||
},
|
||||
"providerCard.noMetadata": {
|
||||
"defaultMessage": "Kesalahan ProviderCard: Tidak ada metadata yang disediakan"
|
||||
},
|
||||
"providerCard.unknownProvider": {
|
||||
"defaultMessage": "Penyedia Tidak Dikenal"
|
||||
},
|
||||
|
|
@ -2732,9 +2729,6 @@
|
|||
"providerCatalogPicker.apiFormat": {
|
||||
"defaultMessage": "Format API"
|
||||
},
|
||||
"providerCatalogPicker.cancel": {
|
||||
"defaultMessage": "Batal"
|
||||
},
|
||||
"providerCatalogPicker.chooseProvider": {
|
||||
"defaultMessage": "Pilih Penyedia"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2720,9 +2720,6 @@
|
|||
"promptsSettings.unsavedChanges": {
|
||||
"defaultMessage": "Hai modifiche non salvate"
|
||||
},
|
||||
"providerCard.noMetadata": {
|
||||
"defaultMessage": "Errore ProviderCard: nessun metadato fornito"
|
||||
},
|
||||
"providerCard.unknownProvider": {
|
||||
"defaultMessage": "Provider sconosciuto"
|
||||
},
|
||||
|
|
@ -2732,9 +2729,6 @@
|
|||
"providerCatalogPicker.apiFormat": {
|
||||
"defaultMessage": "Formato API"
|
||||
},
|
||||
"providerCatalogPicker.cancel": {
|
||||
"defaultMessage": "Annulla"
|
||||
},
|
||||
"providerCatalogPicker.chooseProvider": {
|
||||
"defaultMessage": "Scegli provider"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2720,9 +2720,6 @@
|
|||
"promptsSettings.unsavedChanges": {
|
||||
"defaultMessage": "未保存の変更があります"
|
||||
},
|
||||
"providerCard.noMetadata": {
|
||||
"defaultMessage": "ProviderCardエラー: メタデータが提供されていません"
|
||||
},
|
||||
"providerCard.unknownProvider": {
|
||||
"defaultMessage": "不明なプロバイダー"
|
||||
},
|
||||
|
|
@ -2732,9 +2729,6 @@
|
|||
"providerCatalogPicker.apiFormat": {
|
||||
"defaultMessage": "API形式"
|
||||
},
|
||||
"providerCatalogPicker.cancel": {
|
||||
"defaultMessage": "キャンセル"
|
||||
},
|
||||
"providerCatalogPicker.chooseProvider": {
|
||||
"defaultMessage": "プロバイダーを選択"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2720,9 +2720,6 @@
|
|||
"promptsSettings.unsavedChanges": {
|
||||
"defaultMessage": "저장되지 않은 변경 사항이 있습니다."
|
||||
},
|
||||
"providerCard.noMetadata": {
|
||||
"defaultMessage": "ProviderCard 오류: 메타데이터가 제공되지 않았습니다."
|
||||
},
|
||||
"providerCard.unknownProvider": {
|
||||
"defaultMessage": "알 수 없는 제공업체"
|
||||
},
|
||||
|
|
@ -2732,9 +2729,6 @@
|
|||
"providerCatalogPicker.apiFormat": {
|
||||
"defaultMessage": "API 형식"
|
||||
},
|
||||
"providerCatalogPicker.cancel": {
|
||||
"defaultMessage": "취소"
|
||||
},
|
||||
"providerCatalogPicker.chooseProvider": {
|
||||
"defaultMessage": "제공업체 선택"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2720,9 +2720,6 @@
|
|||
"promptsSettings.unsavedChanges": {
|
||||
"defaultMessage": "Anda mempunyai perubahan yang belum disimpan"
|
||||
},
|
||||
"providerCard.noMetadata": {
|
||||
"defaultMessage": "Ralat ProviderCard: Tiada metadata disediakan"
|
||||
},
|
||||
"providerCard.unknownProvider": {
|
||||
"defaultMessage": "Penyedia Tidak Diketahui"
|
||||
},
|
||||
|
|
@ -2732,9 +2729,6 @@
|
|||
"providerCatalogPicker.apiFormat": {
|
||||
"defaultMessage": "Format API"
|
||||
},
|
||||
"providerCatalogPicker.cancel": {
|
||||
"defaultMessage": "Batal"
|
||||
},
|
||||
"providerCatalogPicker.chooseProvider": {
|
||||
"defaultMessage": "Pilih Penyedia"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2720,9 +2720,6 @@
|
|||
"promptsSettings.unsavedChanges": {
|
||||
"defaultMessage": "Tem alterações por guardar"
|
||||
},
|
||||
"providerCard.noMetadata": {
|
||||
"defaultMessage": "Erro de ProviderCard: nenhum metadado fornecido"
|
||||
},
|
||||
"providerCard.unknownProvider": {
|
||||
"defaultMessage": "Fornecedor desconhecido"
|
||||
},
|
||||
|
|
@ -2732,9 +2729,6 @@
|
|||
"providerCatalogPicker.apiFormat": {
|
||||
"defaultMessage": "Formato da API"
|
||||
},
|
||||
"providerCatalogPicker.cancel": {
|
||||
"defaultMessage": "Cancelar"
|
||||
},
|
||||
"providerCatalogPicker.chooseProvider": {
|
||||
"defaultMessage": "Escolher fornecedor"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2720,9 +2720,6 @@
|
|||
"promptsSettings.unsavedChanges": {
|
||||
"defaultMessage": "У вас есть несохраненные изменения"
|
||||
},
|
||||
"providerCard.noMetadata": {
|
||||
"defaultMessage": "Ошибка ProviderCard: метаданные не предоставлены"
|
||||
},
|
||||
"providerCard.unknownProvider": {
|
||||
"defaultMessage": "Неизвестный провайдер"
|
||||
},
|
||||
|
|
@ -2732,9 +2729,6 @@
|
|||
"providerCatalogPicker.apiFormat": {
|
||||
"defaultMessage": "Формат API"
|
||||
},
|
||||
"providerCatalogPicker.cancel": {
|
||||
"defaultMessage": "Отмена"
|
||||
},
|
||||
"providerCatalogPicker.chooseProvider": {
|
||||
"defaultMessage": "Выберите провайдера"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2720,9 +2720,6 @@
|
|||
"promptsSettings.unsavedChanges": {
|
||||
"defaultMessage": "Kaydedilmemiş değişiklikleriniz var"
|
||||
},
|
||||
"providerCard.noMetadata": {
|
||||
"defaultMessage": "ProviderCard hatası: Meta veri sağlanmadı"
|
||||
},
|
||||
"providerCard.unknownProvider": {
|
||||
"defaultMessage": "Bilinmeyen Sağlayıcı"
|
||||
},
|
||||
|
|
@ -2732,9 +2729,6 @@
|
|||
"providerCatalogPicker.apiFormat": {
|
||||
"defaultMessage": "API Biçimi"
|
||||
},
|
||||
"providerCatalogPicker.cancel": {
|
||||
"defaultMessage": "İptal"
|
||||
},
|
||||
"providerCatalogPicker.chooseProvider": {
|
||||
"defaultMessage": "Sağlayıcıyı Seçin"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2720,9 +2720,6 @@
|
|||
"promptsSettings.unsavedChanges": {
|
||||
"defaultMessage": "Bạn có các thay đổi chưa lưu"
|
||||
},
|
||||
"providerCard.noMetadata": {
|
||||
"defaultMessage": "Lỗi ProviderCard: Không có metadata được cung cấp"
|
||||
},
|
||||
"providerCard.unknownProvider": {
|
||||
"defaultMessage": "Nhà cung cấp không xác định"
|
||||
},
|
||||
|
|
@ -2732,9 +2729,6 @@
|
|||
"providerCatalogPicker.apiFormat": {
|
||||
"defaultMessage": "Định dạng API"
|
||||
},
|
||||
"providerCatalogPicker.cancel": {
|
||||
"defaultMessage": "Hủy"
|
||||
},
|
||||
"providerCatalogPicker.chooseProvider": {
|
||||
"defaultMessage": "Chọn nhà cung cấp"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2720,9 +2720,6 @@
|
|||
"promptsSettings.unsavedChanges": {
|
||||
"defaultMessage": "你有未保存的更改"
|
||||
},
|
||||
"providerCard.noMetadata": {
|
||||
"defaultMessage": "ProviderCard 错误:未提供元数据"
|
||||
},
|
||||
"providerCard.unknownProvider": {
|
||||
"defaultMessage": "未知提供商"
|
||||
},
|
||||
|
|
@ -2732,9 +2729,6 @@
|
|||
"providerCatalogPicker.apiFormat": {
|
||||
"defaultMessage": "API 格式"
|
||||
},
|
||||
"providerCatalogPicker.cancel": {
|
||||
"defaultMessage": "取消"
|
||||
},
|
||||
"providerCatalogPicker.chooseProvider": {
|
||||
"defaultMessage": "选择提供商"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2720,9 +2720,6 @@
|
|||
"promptsSettings.unsavedChanges": {
|
||||
"defaultMessage": "您有未儲存的變更"
|
||||
},
|
||||
"providerCard.noMetadata": {
|
||||
"defaultMessage": "ProviderCard 錯誤:未提供中繼資料"
|
||||
},
|
||||
"providerCard.unknownProvider": {
|
||||
"defaultMessage": "未知的提供者"
|
||||
},
|
||||
|
|
@ -2732,9 +2729,6 @@
|
|||
"providerCatalogPicker.apiFormat": {
|
||||
"defaultMessage": "API 格式"
|
||||
},
|
||||
"providerCatalogPicker.cancel": {
|
||||
"defaultMessage": "取消"
|
||||
},
|
||||
"providerCatalogPicker.chooseProvider": {
|
||||
"defaultMessage": "選擇提供者"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -37,13 +37,13 @@ export const deleteRecipe = async (id: string): Promise<void> => {
|
|||
await acpDeleteRecipe(id);
|
||||
};
|
||||
|
||||
export const scheduleRecipe = async (id: string, cronSchedule?: string | null): Promise<void> => {
|
||||
export const scheduleRecipe = async (id: string, cronSchedule: string | null): Promise<void> => {
|
||||
await acpScheduleRecipe(id, cronSchedule);
|
||||
};
|
||||
|
||||
export const setRecipeSlashCommand = async (
|
||||
id: string,
|
||||
slashCommand?: string | null
|
||||
slashCommand: string | null
|
||||
): Promise<void> => {
|
||||
await acpSetRecipeSlashCommand(id, slashCommand);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ export type UpdateCustomProviderRequest = {
|
|||
headers?: Record<string, string> | null;
|
||||
models: string[];
|
||||
preserves_thinking?: boolean | null;
|
||||
requires_auth?: boolean;
|
||||
requires_auth: boolean;
|
||||
supports_streaming?: boolean | null;
|
||||
toolshim: boolean;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -326,11 +326,7 @@ export function registerUpdateIpcHandlers() {
|
|||
}
|
||||
|
||||
// Configure auto-updater
|
||||
export function setupAutoUpdater(tray?: Tray) {
|
||||
if (tray) {
|
||||
trayRef = tray;
|
||||
}
|
||||
|
||||
export function setupAutoUpdater() {
|
||||
log.info('Setting up auto-updater...');
|
||||
log.info(`Current app version: ${app.getVersion()}`);
|
||||
log.info(`Platform: ${process.platform}, Arch: ${process.arch}`);
|
||||
|
|
@ -486,7 +482,11 @@ export function setupAutoUpdater(tray?: Tray) {
|
|||
|
||||
if (!autoDownloadDisabled) {
|
||||
log.info('Auto-downloading update via GitHub fallback on startup...');
|
||||
await githubAutoDownload(result.downloadUrl!, result.latestVersion!, 'on startup');
|
||||
await githubAutoDownload(
|
||||
result.downloadUrl!,
|
||||
result.latestVersion!,
|
||||
'on startup'
|
||||
);
|
||||
} else {
|
||||
log.info('Auto-download disabled — skipping GitHub fallback download on startup');
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue