mirror of
https://github.com/block/goose.git
synced 2026-07-09 16:09:22 +00:00
chore(desktop): remove dead code from ui/desktop
Dead files (no importers): SessionViewComponents, RecipeNameField +
recipeNameUtils, ui/Pill, ui/sheet, ui/separator, ui/Dot, types/electron,
the providers/interfaces chain, and 26 unused icon components.
Dead feature paths: pauseQueueOnStop (hardcoded false end to end),
write-only queue sessionStorage persistence in ChatInput, unused
ChatInput props (recipe, recipeId, append), unused BaseChat props
(suppressEmptyState, onMessageSubmit, disableSearch, contentClassName,
renderHeader, customChatInputProps, customMainLayoutProps), and the
unused setActiveSessions prop on PairRouteWrapper.
Unused exports: deleted fully dead functions (clearToolsCache,
sanitizeName, createModelStruct, resumeSession, getAutoDownloadDisabled,
six analytics track* helpers, two date formatters, two AppEvents enum
members) and un-exported symbols only used within their own module.
Dependencies: removed 15 unused deps and 6 unused devDeps; added
@radix-ui/react-{collapsible,dropdown-menu,switch,tooltip}, which were
used but only available transitively through the removed
@radix-ui/themes.
Locale files: dropped translations for deleted components.
Verified with tsc, eslint, i18n:check, vitest (549 passed), and knip
(zero unused exports remain).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
1bced6616b
commit
b9b9b819fe
192 changed files with 2208 additions and 5035 deletions
|
|
@ -1,5 +1,5 @@
|
|||
// Map of announcement file names to their content
|
||||
export const announcementContents: Record<string, string> = {};
|
||||
const announcementContents: Record<string, string> = {};
|
||||
|
||||
// Helper function to get announcement content by filename
|
||||
export function getAnnouncementContent(filename: string): string | null {
|
||||
|
|
|
|||
|
|
@ -53,31 +53,24 @@
|
|||
"@agentclientprotocol/sdk": "^0.19.0",
|
||||
"@mcp-ui/client": "6.1.0",
|
||||
"@modelcontextprotocol/ext-apps": "^1.1.1",
|
||||
"@radix-ui/react-accordion": "^1.2.12",
|
||||
"@radix-ui/react-avatar": "^1.1.11",
|
||||
"@radix-ui/react-collapsible": "^1.1.14",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-icons": "^1.3.2",
|
||||
"@radix-ui/react-popover": "^1.1.15",
|
||||
"@radix-ui/react-radio-group": "^1.3.8",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.18",
|
||||
"@radix-ui/react-scroll-area": "^1.2.10",
|
||||
"@radix-ui/react-select": "^2.2.6",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-switch": "^1.3.1",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@radix-ui/themes": "^3.3.0",
|
||||
"@radix-ui/react-tooltip": "^1.2.10",
|
||||
"@tanstack/react-form": "1.28.3",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"compare-versions": "^6.1.1",
|
||||
"cors": "^2.8.6",
|
||||
"cronstrue": "^3.12.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"dotenv": "^17.3.1",
|
||||
"electron-log": "^5.4.3",
|
||||
"electron-squirrel-startup": "^1.0.1",
|
||||
"electron-updater": "^6.8.3",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"express": "^5.2.1",
|
||||
"framer-motion": "^12.34.3",
|
||||
"katex": "^0.16.33",
|
||||
"lodash": "^4.17.23",
|
||||
|
|
@ -96,13 +89,9 @@
|
|||
"remark-gfm": "^4.0.1",
|
||||
"remark-math": "^6.0.0",
|
||||
"shell-quote": "^1.8.3",
|
||||
"split-type": "^0.3.4",
|
||||
"swr": "^2.4.0",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"turndown": "^7.2.4",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"unist-util-visit": "^5.1.0",
|
||||
"uuid": "^13.0.0",
|
||||
"zod": "^3.25.76",
|
||||
"zod-to-json-schema": "3.25.1"
|
||||
|
|
@ -123,15 +112,12 @@
|
|||
"@formatjs/icu-messageformat-parser": "3.5.3",
|
||||
"@modelcontextprotocol/sdk": "^1.27.0",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@tailwindcss/line-clamp": "^0.4.4",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tailwindcss/vite": "^4.2.1",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/electron-squirrel-startup": "^1.0.2",
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/lodash": "^4.17.24",
|
||||
"@types/node": "^25.5.0",
|
||||
"@types/react": "^19.2.14",
|
||||
|
|
@ -139,13 +125,11 @@
|
|||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@types/shell-quote": "^1.7.5",
|
||||
"@types/turndown": "^5.0.6",
|
||||
"@types/yauzl": "^2.10.3",
|
||||
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
||||
"@typescript-eslint/parser": "^8.56.1",
|
||||
"@vitejs/plugin-react": "^5.1.4",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"@vitest/ui": "^4.0.18",
|
||||
"autoprefixer": "^10.4.24",
|
||||
"electron": "41.0.0",
|
||||
"electron-devtools-installer": "^4.0.0",
|
||||
"eslint": "^9.39.2",
|
||||
|
|
@ -153,7 +137,6 @@
|
|||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"jsdom": "^28.1.0",
|
||||
"knip": "^5.85.0",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.8.1",
|
||||
"tailwindcss": "^4.2.1",
|
||||
"typescript": "~5.9.3",
|
||||
|
|
|
|||
|
|
@ -88,9 +88,6 @@ const PairRouteWrapper = ({
|
|||
initialMessage?: UserInput;
|
||||
noAutoSubmit?: boolean;
|
||||
}>;
|
||||
setActiveSessions: (
|
||||
sessions: Array<{ sessionId: string; initialMessage?: UserInput; noAutoSubmit?: boolean }>
|
||||
) => void;
|
||||
}) => {
|
||||
const { extensionsList } = useConfig();
|
||||
const location = useLocation();
|
||||
|
|
@ -637,15 +634,7 @@ export function AppInner() {
|
|||
}
|
||||
>
|
||||
<Route index element={<HubRouteWrapper />} />
|
||||
<Route
|
||||
path="pair"
|
||||
element={
|
||||
<PairRouteWrapper
|
||||
activeSessions={activeSessions}
|
||||
setActiveSessions={setActiveSessions}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route path="pair" element={<PairRouteWrapper activeSessions={activeSessions} />} />
|
||||
<Route path="settings" element={<SettingsRoute />} />
|
||||
<Route
|
||||
path="extensions"
|
||||
|
|
|
|||
|
|
@ -353,7 +353,10 @@ describe('acpChatSessionController.updateMessage', () => {
|
|||
resolvePromptCancellation!();
|
||||
await updatePromise;
|
||||
|
||||
expect(acpTruncateSessionConversation).toHaveBeenCalledWith(SESSION_ID, existingMessage.created);
|
||||
expect(acpTruncateSessionConversation).toHaveBeenCalledWith(
|
||||
SESSION_ID,
|
||||
existingMessage.created
|
||||
);
|
||||
expect(acpPromptSession).toHaveBeenCalled();
|
||||
expect(acpChatSessionActions.clearPromptCancellation).not.toHaveBeenCalledWith(
|
||||
SESSION_ID,
|
||||
|
|
|
|||
|
|
@ -583,9 +583,7 @@ describe('createAcpSessionNotificationAdapter', () => {
|
|||
status: { type: 'progress', message: 'Still working' },
|
||||
})
|
||||
);
|
||||
expect(progressStateChanges).toEqual([
|
||||
{ type: 'progressMessage', message: 'Still working' },
|
||||
]);
|
||||
expect(progressStateChanges).toEqual([{ type: 'progressMessage', message: 'Still working' }]);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -105,18 +105,10 @@ export async function getAcpClient(): Promise<GooseClient> {
|
|||
return (await getInitializedAcpClient()).client;
|
||||
}
|
||||
|
||||
export function getAcpClientSync(): GooseClient | null {
|
||||
return resolvedClient?.client ?? null;
|
||||
}
|
||||
|
||||
export async function getAcpInitializeResponse(): Promise<InitializeResponse> {
|
||||
return (await getInitializedAcpClient()).initializeResponse;
|
||||
}
|
||||
|
||||
export function isAcpClientReady(): boolean {
|
||||
return resolvedClient !== null;
|
||||
}
|
||||
|
||||
async function getInitializedAcpClient(): Promise<InitializedAcpClient> {
|
||||
if (resolvedClient) {
|
||||
return resolvedClient;
|
||||
|
|
|
|||
|
|
@ -11,10 +11,7 @@ function isSlashCommandItemType(value: unknown): value is SlashCommandItemType {
|
|||
return typeof value === 'string' && SLASH_COMMAND_ITEM_TYPES.has(value);
|
||||
}
|
||||
|
||||
function stringMetaValue(
|
||||
meta: AvailableCommand['_meta'],
|
||||
key: string
|
||||
): string | undefined {
|
||||
function stringMetaValue(meta: AvailableCommand['_meta'], key: string): string | undefined {
|
||||
const value = meta?.[key];
|
||||
return typeof value === 'string' && value.trim() ? value : undefined;
|
||||
}
|
||||
|
|
@ -33,7 +30,8 @@ export function availableCommandToDisplayItem(
|
|||
}
|
||||
|
||||
const sourcePath = stringMetaValue(command._meta, 'sourcePath');
|
||||
const extra = commandType === 'Recipe' ? sourcePath ?? command.description : command.description;
|
||||
const extra =
|
||||
commandType === 'Recipe' ? (sourcePath ?? command.description) : command.description;
|
||||
|
||||
return {
|
||||
name: command.name,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { InitializeResponse } from '@agentclientprotocol/sdk';
|
||||
import { getAcpInitializeResponse } from './acpConnection';
|
||||
|
||||
export interface AcpFeatureCapabilities {
|
||||
interface AcpFeatureCapabilities {
|
||||
localInference: boolean;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,8 @@ import { toolNotificationEvent } from './adapter/toolNotifications';
|
|||
import { acpChatSessionActions, acpChatSessionStore } from './chatSessionStore';
|
||||
|
||||
export function handleAcpSessionNotification(notification: SessionNotification): Promise<void> {
|
||||
const sessionNameBeforeNotification = acpChatSessionStore.getSnapshot(
|
||||
notification.sessionId
|
||||
)?.session?.name;
|
||||
const sessionNameBeforeNotification = acpChatSessionStore.getSnapshot(notification.sessionId)
|
||||
?.session?.name;
|
||||
const updatedName =
|
||||
notification.update.sessionUpdate === 'session_info_update'
|
||||
? notification.update.title
|
||||
|
|
|
|||
|
|
@ -5,11 +5,7 @@ import { ChatState } from '../types/chatState';
|
|||
import type { Session } from '../types/session';
|
||||
import { errorMessage } from '../utils/conversionUtils';
|
||||
import { showExtensionLoadResults } from '../utils/extensionErrorUtils';
|
||||
import {
|
||||
createUserMessage,
|
||||
getPendingToolConfirmationIds,
|
||||
type Message,
|
||||
} from '../types/message';
|
||||
import { createUserMessage, getPendingToolConfirmationIds, type Message } from '../types/message';
|
||||
import {
|
||||
acpChatSessionActions,
|
||||
acpChatSessionStore,
|
||||
|
|
@ -29,19 +25,19 @@ import {
|
|||
type AcpRecipeOptions,
|
||||
} from './sessions';
|
||||
|
||||
export interface AcpLoadSessionOptions {
|
||||
interface AcpLoadSessionOptions {
|
||||
onSessionLoaded?: () => void;
|
||||
}
|
||||
|
||||
export interface AcpSnapshotOptions {
|
||||
interface AcpSnapshotOptions {
|
||||
getCurrentSnapshot(): AcpChatSessionSnapshot | undefined;
|
||||
}
|
||||
|
||||
export interface AcpSubmitMessageOptions extends AcpSnapshotOptions {
|
||||
interface AcpSubmitMessageOptions extends AcpSnapshotOptions {
|
||||
onFinish(error?: string): void | Promise<void>;
|
||||
}
|
||||
|
||||
export interface AcpChatSessionController {
|
||||
interface AcpChatSessionController {
|
||||
createSession(
|
||||
cwd: string,
|
||||
gooseExtensions: GooseExtension[],
|
||||
|
|
|
|||
|
|
@ -49,11 +49,11 @@ const initialTokenState: TokenState = {
|
|||
accumulatedTotalTokens: 0,
|
||||
};
|
||||
|
||||
export interface AcpChatSessionStore {
|
||||
interface AcpChatSessionStore {
|
||||
getSnapshot(sessionId: string): AcpChatSessionSnapshot | undefined;
|
||||
}
|
||||
|
||||
export interface AcpChatSessionActions {
|
||||
interface AcpChatSessionActions {
|
||||
deleteSnapshot(sessionId: string): void;
|
||||
|
||||
applyAcpSessionNotification(notification: SessionNotification): AcpChatSessionSnapshot;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { getAcpClient } from './acpConnection';
|
||||
|
||||
export type ConfigReadValue = unknown;
|
||||
type ConfigReadValue = unknown;
|
||||
|
||||
export async function acpReadConfig(
|
||||
key: string,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { Stream } from '@aaif/goose-sdk';
|
||||
|
||||
export type ClosableAcpStream = Stream & {
|
||||
type ClosableAcpStream = Stream & {
|
||||
close: () => void;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { getAcpClient } from './acpConnection';
|
|||
|
||||
export type { DictationProviderStatusEntry };
|
||||
|
||||
export type DictationProviders = Record<string, DictationProviderStatusEntry>;
|
||||
type DictationProviders = Record<string, DictationProviderStatusEntry>;
|
||||
export type LocalDictationModel = DictationLocalModelStatus;
|
||||
export type LocalDictationDownloadProgress = DictationDownloadProgress;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import type { ExtensionConfig, ExtensionEntry } from '../types/extensions';
|
|||
import type { GooseExtension, GooseExtensionEntry } from '@aaif/goose-sdk';
|
||||
import { getAcpClient } from './acpConnection';
|
||||
|
||||
export type ConfiguredExtensionEntry = ExtensionEntry & { configKey?: string };
|
||||
type ConfiguredExtensionEntry = ExtensionEntry & { configKey?: string };
|
||||
|
||||
export interface ConfiguredExtensionsResponse {
|
||||
interface ConfiguredExtensionsResponse {
|
||||
extensions: ConfiguredExtensionEntry[];
|
||||
warnings: string[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { getAcpClient } from './acpConnection';
|
|||
|
||||
type JsonRecord = Record<string, unknown>;
|
||||
export type McpAppTool = ToolListItem;
|
||||
export type McpAppResourceResponse = {
|
||||
type McpAppResourceResponse = {
|
||||
uri: string;
|
||||
mimeType: string | null;
|
||||
text: string;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import type { ToolListItem, ToolPermissionEntry, ToolPermissionLevel } from '@aaif/goose-sdk';
|
||||
import { getAcpClient } from './acpConnection';
|
||||
|
||||
export type { ToolListItem, ToolPermissionEntry, ToolPermissionLevel };
|
||||
export type { ToolListItem, ToolPermissionLevel };
|
||||
|
||||
export async function listTools(sessionId: string, extensionName?: string): Promise<ToolListItem[]> {
|
||||
export async function listTools(
|
||||
sessionId: string,
|
||||
extensionName?: string
|
||||
): Promise<ToolListItem[]> {
|
||||
const client = await getAcpClient();
|
||||
const response = await client.goose.toolsList_unstable({
|
||||
sessionId,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
import type {
|
||||
GetPromptResponse_unstable,
|
||||
PromptTemplateEntry,
|
||||
} from '@aaif/goose-sdk';
|
||||
import type { GetPromptResponse_unstable, PromptTemplateEntry } from '@aaif/goose-sdk';
|
||||
import { getAcpClient } from './acpConnection';
|
||||
|
||||
export type PromptTemplate = PromptTemplateEntry;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,11 @@ import type {
|
|||
ProviderTemplateCatalogEntryDto,
|
||||
ProviderTemplateDto,
|
||||
} from '@aaif/goose-sdk';
|
||||
import type { ProviderDetails, ThinkingEffort, UpdateCustomProviderRequest } from '../types/providers';
|
||||
import type {
|
||||
ProviderDetails,
|
||||
ThinkingEffort,
|
||||
UpdateCustomProviderRequest,
|
||||
} from '../types/providers';
|
||||
import { getAcpClient } from './acpConnection';
|
||||
|
||||
export type { CanonicalModelInfoDto, ProviderSecretDto };
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import type {
|
|||
} from '@aaif/goose-sdk';
|
||||
import { v7 as uuidv7 } from 'uuid';
|
||||
|
||||
export interface AcpRecipeParamRequest {
|
||||
interface AcpRecipeParamRequest {
|
||||
id: string;
|
||||
sessionId: string;
|
||||
parameters: RecipeParameterDto[];
|
||||
|
|
|
|||
|
|
@ -42,19 +42,19 @@ export interface SessionListItem {
|
|||
hasRecipe?: boolean;
|
||||
}
|
||||
|
||||
export interface SessionListPage {
|
||||
interface SessionListPage {
|
||||
sessions: SessionListItem[];
|
||||
nextCursor: string | null;
|
||||
}
|
||||
|
||||
export interface LoadSessionMeta {
|
||||
interface LoadSessionMeta {
|
||||
recipe?: Recipe | null;
|
||||
userRecipeValues?: Record<string, string> | null;
|
||||
extensionResults?: ExtensionLoadResult[] | null;
|
||||
workingDir?: string;
|
||||
}
|
||||
|
||||
export interface AcpLoadSessionResult {
|
||||
interface AcpLoadSessionResult {
|
||||
sessionInfo: SessionInfo;
|
||||
response: LoadSessionResponse;
|
||||
meta: LoadSessionMeta;
|
||||
|
|
@ -72,7 +72,7 @@ function parseSessionResponseMeta(rawMeta: unknown): LoadSessionMeta {
|
|||
};
|
||||
}
|
||||
|
||||
export function parseLoadMeta(response: LoadSessionResponse): LoadSessionMeta {
|
||||
function parseLoadMeta(response: LoadSessionResponse): LoadSessionMeta {
|
||||
return parseSessionResponseMeta(response._meta);
|
||||
}
|
||||
|
||||
|
|
@ -131,7 +131,7 @@ function sessionInfoToListItem(s: SessionInfo): SessionListItem {
|
|||
};
|
||||
}
|
||||
|
||||
export interface SessionListFilter {
|
||||
interface SessionListFilter {
|
||||
keyword?: string;
|
||||
}
|
||||
|
||||
|
|
@ -215,7 +215,7 @@ async function loadAcpSession(sessionId: string): Promise<AcpLoadSessionResult>
|
|||
};
|
||||
}
|
||||
|
||||
export interface AcpNewSessionResult {
|
||||
interface AcpNewSessionResult {
|
||||
sessionId: string;
|
||||
sessionInfo: SessionInfo;
|
||||
meta: LoadSessionMeta;
|
||||
|
|
@ -302,10 +302,7 @@ export async function acpExportSession(sessionId: string): Promise<string> {
|
|||
return response.data;
|
||||
}
|
||||
|
||||
export async function acpImportSession(
|
||||
input: string,
|
||||
source: SessionImportSource
|
||||
): Promise<void> {
|
||||
export async function acpImportSession(input: string, source: SessionImportSource): Promise<void> {
|
||||
const client = await getAcpClient();
|
||||
await client.goose.sessionImport_unstable({ input, source });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ const PROBE_TIMEOUT_MS = 1000;
|
|||
type FetchInput = Parameters<typeof globalThis.fetch>[0];
|
||||
type FetchInit = Parameters<typeof globalThis.fetch>[1];
|
||||
|
||||
export interface CheckServerStatusOptions {
|
||||
interface CheckServerStatusOptions {
|
||||
onEvent?: (name: string, details?: Record<string, unknown>) => void;
|
||||
}
|
||||
|
||||
export interface CheckBackendStatusParams {
|
||||
interface CheckBackendStatusParams {
|
||||
baseUrl: string;
|
||||
serverSecret: string;
|
||||
fetch: typeof globalThis.fetch;
|
||||
|
|
@ -19,7 +19,7 @@ export interface CheckBackendStatusParams {
|
|||
options?: CheckServerStatusOptions;
|
||||
}
|
||||
|
||||
export const isFatalError = (line: string): boolean => {
|
||||
const isFatalError = (line: string): boolean => {
|
||||
const fatalPatterns = [/panicked at/, /RUST_BACKTRACE/, /fatal error/i];
|
||||
return fatalPatterns.some((pattern) => pattern.test(line));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -43,13 +43,6 @@ const i18n = defineMessages({
|
|||
|
||||
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;
|
||||
initialMessage?: UserInput;
|
||||
|
|
@ -58,9 +51,6 @@ interface BaseChatProps {
|
|||
|
||||
export default function BaseChat({
|
||||
setChat,
|
||||
renderHeader,
|
||||
customChatInputProps = {},
|
||||
customMainLayoutProps = {},
|
||||
sessionId,
|
||||
initialMessage,
|
||||
noAutoSubmit,
|
||||
|
|
@ -96,7 +86,6 @@ export default function BaseChat({
|
|||
sessionLoadError,
|
||||
tokenState,
|
||||
notifications: toolCallNotifications,
|
||||
pauseQueueOnStop,
|
||||
queueProcessingBlocked,
|
||||
onMessageUpdate,
|
||||
} = useChatSession({
|
||||
|
|
@ -354,12 +343,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">
|
||||
|
|
@ -387,14 +371,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 */}
|
||||
|
|
@ -483,7 +460,6 @@ export default function BaseChat({
|
|||
chatState={chatState}
|
||||
onStop={stopStreaming}
|
||||
onSteerQueuedMessage={onSteerQueuedMessage}
|
||||
pauseQueueOnStop={pauseQueueOnStop}
|
||||
queueProcessingBlocked={queueProcessingBlocked}
|
||||
commandHistory={commandHistory}
|
||||
initialValue={initialPrompt}
|
||||
|
|
@ -504,7 +480,6 @@ export default function BaseChat({
|
|||
onFilesProcessed={() => setDroppedFiles([])} // Clear dropped files after processing
|
||||
messages={messages}
|
||||
disableAnimation={disableAnimation}
|
||||
recipe={recipe}
|
||||
recipeAccepted={!hasNotAcceptedRecipe}
|
||||
initialPrompt={initialPrompt}
|
||||
sessionModel={sessionModel}
|
||||
|
|
@ -513,7 +488,6 @@ export default function BaseChat({
|
|||
workingDir={session?.working_dir}
|
||||
onWorkingDirChange={handleWorkingDirChange}
|
||||
latestInference={latestInference}
|
||||
{...customChatInputProps}
|
||||
/>
|
||||
</ChatInputCard>
|
||||
</MainPanelLayout>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import { COST_TRACKING_ENABLED } from '../updates';
|
|||
import { CostTracker } from './bottom_menu/CostTracker';
|
||||
import { ContextWindowIndicator } from './bottom_menu/ContextWindowIndicator';
|
||||
import { DroppedFile, useFileDrop } from '../hooks/useFileDrop';
|
||||
import { Recipe } from '../recipe';
|
||||
import { MessageQueue, QueuedMessage } from './MessageQueue';
|
||||
import { detectInterruption } from '../utils/interruptionDetector';
|
||||
import { DiagnosticsModal } from './ui/Diagnostics';
|
||||
|
|
@ -162,7 +161,6 @@ interface ChatInputProps {
|
|||
chatState: ChatState;
|
||||
onStop?: () => void;
|
||||
onSteerQueuedMessage?: (input: UserInput) => Promise<boolean>;
|
||||
pauseQueueOnStop?: boolean;
|
||||
queueProcessingBlocked?: boolean;
|
||||
commandHistory?: string[];
|
||||
initialValue?: string;
|
||||
|
|
@ -175,11 +173,8 @@ interface ChatInputProps {
|
|||
accumulatedCost?: number | null;
|
||||
messages?: Message[];
|
||||
disableAnimation?: boolean;
|
||||
recipe?: Recipe | null;
|
||||
recipeId?: string | null;
|
||||
recipeAccepted?: boolean;
|
||||
initialPrompt?: string;
|
||||
append?: (message: Message) => void;
|
||||
onWorkingDirChange?: (newDir: string) => Promise<void> | void;
|
||||
inputRef?: React.RefObject<HTMLTextAreaElement | null>;
|
||||
sessionModel?: string | null;
|
||||
|
|
@ -197,7 +192,6 @@ export default function ChatInput({
|
|||
chatState = ChatState.Idle,
|
||||
onStop,
|
||||
onSteerQueuedMessage,
|
||||
pauseQueueOnStop = false,
|
||||
queueProcessingBlocked = false,
|
||||
commandHistory = [],
|
||||
initialValue = '',
|
||||
|
|
@ -210,11 +204,8 @@ export default function ChatInput({
|
|||
accumulatedCost,
|
||||
messages = [],
|
||||
disableAnimation = false,
|
||||
recipe: _recipe,
|
||||
recipeId: _recipeId,
|
||||
recipeAccepted,
|
||||
initialPrompt,
|
||||
append: _append,
|
||||
onWorkingDirChange,
|
||||
inputRef,
|
||||
sessionModel,
|
||||
|
|
@ -336,36 +327,6 @@ export default function ChatInput({
|
|||
setWorkingDirOverride(null);
|
||||
}, [sessionId, workingDir]);
|
||||
|
||||
// Save queue state (paused/interrupted) to storage
|
||||
useEffect(() => {
|
||||
try {
|
||||
window.sessionStorage.setItem('goose-queue-paused', JSON.stringify(queuePausedRef.current));
|
||||
} catch (error) {
|
||||
console.error('Error saving queue pause state:', error);
|
||||
}
|
||||
}, [queuedMessages]); // Save when queue changes
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
window.sessionStorage.setItem('goose-queue-interruption', JSON.stringify(lastInterruption));
|
||||
} catch (error) {
|
||||
console.error('Error saving queue interruption state:', error);
|
||||
}
|
||||
}, [lastInterruption]);
|
||||
|
||||
// Cleanup effect - save final state on component unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
// Save final queue state when component unmounts
|
||||
try {
|
||||
window.sessionStorage.setItem('goose-queue-paused', JSON.stringify(queuePausedRef.current));
|
||||
window.sessionStorage.setItem('goose-queue-interruption', JSON.stringify(lastInterruption));
|
||||
} catch (error) {
|
||||
console.error('Error saving queue state on unmount:', error);
|
||||
}
|
||||
};
|
||||
}, [lastInterruption]); // Include lastInterruption in dependency array
|
||||
|
||||
// Queue processing
|
||||
useEffect(() => {
|
||||
const becameIdle = wasLoadingRef.current && !isLoading;
|
||||
|
|
@ -1434,13 +1395,6 @@ export default function ChatInput({
|
|||
if (onStop) onStop();
|
||||
};
|
||||
|
||||
const handleStop = () => {
|
||||
if (pauseQueueOnStop && queuedMessages.length > 0) {
|
||||
pauseRemainingQueue();
|
||||
}
|
||||
if (onStop) onStop();
|
||||
};
|
||||
|
||||
const handleResumeQueue = () => {
|
||||
queuePausedRef.current = false;
|
||||
setLastInterruption(null);
|
||||
|
|
@ -1807,7 +1761,7 @@ export default function ChatInput({
|
|||
{isLoading && !hasSubmittableContent ? (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleStop}
|
||||
onClick={onStop}
|
||||
size="sm"
|
||||
shape="round"
|
||||
variant="ghost"
|
||||
|
|
|
|||
|
|
@ -53,7 +53,6 @@ export default function ChatSessionsContainer({
|
|||
sessionId={session.sessionId}
|
||||
initialMessage={session.initialMessage}
|
||||
noAutoSubmit={session.noAutoSubmit}
|
||||
suppressEmptyState={false}
|
||||
isActiveSession={isVisible}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -72,7 +72,9 @@ export function ErrorUI({ error }: { error: string }) {
|
|||
<AlertTriangle className="w-8 h-8 text-destructive" />
|
||||
</div>
|
||||
|
||||
<h1 className="text-2xl font-semibold text-foreground dark:text-white">{intl.formatMessage(i18n.heading)}</h1>
|
||||
<h1 className="text-2xl font-semibold text-foreground dark:text-white">
|
||||
{intl.formatMessage(i18n.heading)}
|
||||
</h1>
|
||||
|
||||
<p className="text-base text-text-secondary dark:text-muted-foreground mb-2">
|
||||
{version !== undefined
|
||||
|
|
|
|||
|
|
@ -55,7 +55,9 @@ describe('ExtensionInstallModal', () => {
|
|||
it('should handle trusted extension (default behaviour, no allowlist)', async () => {
|
||||
mockElectron.getAllowedExtensions.mockResolvedValue([]);
|
||||
|
||||
renderWithIntl(<ExtensionInstallModal addExtension={mockAddExtension} setView={mockSetView} />);
|
||||
renderWithIntl(
|
||||
<ExtensionInstallModal addExtension={mockAddExtension} setView={mockSetView} />
|
||||
);
|
||||
|
||||
const eventHandler = getAddExtensionEventHandler();
|
||||
|
||||
|
|
@ -72,7 +74,9 @@ describe('ExtensionInstallModal', () => {
|
|||
it('should handle trusted extension (from allowlist)', async () => {
|
||||
mockElectron.getAllowedExtensions.mockResolvedValue(['npx test-extension']);
|
||||
|
||||
renderWithIntl(<ExtensionInstallModal addExtension={mockAddExtension} setView={mockSetView} />);
|
||||
renderWithIntl(
|
||||
<ExtensionInstallModal addExtension={mockAddExtension} setView={mockSetView} />
|
||||
);
|
||||
|
||||
const eventHandler = getAddExtensionEventHandler();
|
||||
|
||||
|
|
@ -90,7 +94,9 @@ describe('ExtensionInstallModal', () => {
|
|||
});
|
||||
mockElectron.getAllowedExtensions.mockResolvedValue(['uvx allowed-package']);
|
||||
|
||||
renderWithIntl(<ExtensionInstallModal addExtension={mockAddExtension} setView={mockSetView} />);
|
||||
renderWithIntl(
|
||||
<ExtensionInstallModal addExtension={mockAddExtension} setView={mockSetView} />
|
||||
);
|
||||
|
||||
const eventHandler = getAddExtensionEventHandler();
|
||||
|
||||
|
|
@ -109,7 +115,9 @@ describe('ExtensionInstallModal', () => {
|
|||
it('should handle i-ching-mcp-server as allowed command', async () => {
|
||||
mockElectron.getAllowedExtensions.mockResolvedValue([]);
|
||||
|
||||
renderWithIntl(<ExtensionInstallModal addExtension={mockAddExtension} setView={mockSetView} />);
|
||||
renderWithIntl(
|
||||
<ExtensionInstallModal addExtension={mockAddExtension} setView={mockSetView} />
|
||||
);
|
||||
|
||||
const eventHandler = getAddExtensionEventHandler();
|
||||
|
||||
|
|
@ -128,7 +136,9 @@ describe('ExtensionInstallModal', () => {
|
|||
it('should handle blocked extension', async () => {
|
||||
mockElectron.getAllowedExtensions.mockResolvedValue(['uvx allowed-package']);
|
||||
|
||||
renderWithIntl(<ExtensionInstallModal addExtension={mockAddExtension} setView={mockSetView} />);
|
||||
renderWithIntl(
|
||||
<ExtensionInstallModal addExtension={mockAddExtension} setView={mockSetView} />
|
||||
);
|
||||
|
||||
const eventHandler = getAddExtensionEventHandler();
|
||||
|
||||
|
|
@ -147,7 +157,9 @@ describe('ExtensionInstallModal', () => {
|
|||
it('should dismiss modal correctly', async () => {
|
||||
mockElectron.getAllowedExtensions.mockResolvedValue([]);
|
||||
|
||||
renderWithIntl(<ExtensionInstallModal addExtension={mockAddExtension} setView={mockSetView} />);
|
||||
renderWithIntl(
|
||||
<ExtensionInstallModal addExtension={mockAddExtension} setView={mockSetView} />
|
||||
);
|
||||
|
||||
const eventHandler = getAddExtensionEventHandler();
|
||||
|
||||
|
|
@ -168,7 +180,9 @@ describe('ExtensionInstallModal', () => {
|
|||
vi.mocked(addExtensionFromDeepLink).mockResolvedValue(undefined);
|
||||
mockElectron.getAllowedExtensions.mockResolvedValue([]);
|
||||
|
||||
renderWithIntl(<ExtensionInstallModal addExtension={mockAddExtension} setView={mockSetView} />);
|
||||
renderWithIntl(
|
||||
<ExtensionInstallModal addExtension={mockAddExtension} setView={mockSetView} />
|
||||
);
|
||||
|
||||
const eventHandler = getAddExtensionEventHandler();
|
||||
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
@ -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,7 +323,6 @@ export function ExtensionInstallModal({ addExtension, setView }: ExtensionInstal
|
|||
setModalState((prev) => ({ ...prev, isPending: true }));
|
||||
|
||||
try {
|
||||
|
||||
if (addExtension) {
|
||||
await addExtensionFromDeepLink(
|
||||
pendingLink,
|
||||
|
|
@ -331,7 +345,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);
|
||||
|
|
|
|||
|
|
@ -39,7 +39,9 @@ const ThemeSelector: React.FC<ThemeSelectorProps> = ({
|
|||
|
||||
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-3'} gap-1 ${!horizontal ? 'px-3' : ''}`}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ const i18n = defineMessages({
|
|||
},
|
||||
partiallyLoaded: {
|
||||
id: 'groupedExtensionLoadingToast.partiallyLoaded',
|
||||
defaultMessage: 'Loaded {successCount}/{totalCount, plural, one {# extension} other {# extensions}}',
|
||||
defaultMessage:
|
||||
'Loaded {successCount}/{totalCount, plural, one {# extension} other {# extensions}}',
|
||||
},
|
||||
failedToLoad: {
|
||||
id: 'groupedExtensionLoadingToast.failedToLoad',
|
||||
|
|
@ -158,7 +159,10 @@ export function GroupedExtensionLoadingToast({
|
|||
{ext.status === 'error' && ext.error && (
|
||||
<div className="ml-7 flex flex-col gap-2">
|
||||
<div className="text-xs opacity-75 break-words">
|
||||
{formatExtensionErrorMessage(ext.error, intl.formatMessage(i18n.failedToAddExtension))}
|
||||
{formatExtensionErrorMessage(
|
||||
ext.error,
|
||||
intl.formatMessage(i18n.failedToAddExtension)
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
{ext.recoverHints && setView && (
|
||||
|
|
@ -185,7 +189,9 @@ export function GroupedExtensionLoadingToast({
|
|||
setTimeout(() => setCopiedExtension(null), 2000);
|
||||
}}
|
||||
>
|
||||
{copiedExtension === ext.name ? intl.formatMessage(i18n.copied) : intl.formatMessage(i18n.copyError)}
|
||||
{copiedExtension === ext.name
|
||||
? intl.formatMessage(i18n.copied)
|
||||
: intl.formatMessage(i18n.copyError)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -202,7 +208,11 @@ export function GroupedExtensionLoadingToast({
|
|||
<CollapsibleTrigger asChild>
|
||||
<button
|
||||
className="flex items-center justify-center gap-1 text-xs opacity-60 hover:opacity-100 transition-opacity mt-2 py-1.5 w-full"
|
||||
aria-label={isOpen ? intl.formatMessage(i18n.collapseDetails) : intl.formatMessage(i18n.expandDetails)}
|
||||
aria-label={
|
||||
isOpen
|
||||
? intl.formatMessage(i18n.collapseDetails)
|
||||
: intl.formatMessage(i18n.expandDetails)
|
||||
}
|
||||
>
|
||||
{isOpen ? (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ interface IconInfo {
|
|||
color: string;
|
||||
}
|
||||
|
||||
export const getItemIcon = (item: DisplayItem): IconInfo => {
|
||||
const getItemIcon = (item: DisplayItem): IconInfo => {
|
||||
switch (item.itemType) {
|
||||
case 'Builtin':
|
||||
return { Icon: Zap, color: '#3b82f6' }; // Blue
|
||||
|
|
|
|||
|
|
@ -135,8 +135,14 @@ export const Navigation: React.FC<{ className?: string }> = ({ className }) => {
|
|||
|
||||
const isActive = useCallback((path: string) => location.pathname === path, [location.pathname]);
|
||||
|
||||
const { recentSessions, recentSessionsByProject, activeSessionId, fetchSessions, handleNavClick, handleSessionClick } =
|
||||
useNavigationSessions();
|
||||
const {
|
||||
recentSessions,
|
||||
recentSessionsByProject,
|
||||
activeSessionId,
|
||||
fetchSessions,
|
||||
handleNavClick,
|
||||
handleSessionClick,
|
||||
} = useNavigationSessions();
|
||||
|
||||
const [sessionStatuses, setSessionStatuses] = useState<Map<string, SessionStatus>>(new Map());
|
||||
|
||||
|
|
|
|||
|
|
@ -301,7 +301,9 @@ const MarkdownContent = memo(function MarkdownContent({
|
|||
<ConfirmationModal
|
||||
isOpen={pendingLink !== null}
|
||||
title={intl.formatMessage(i18n.openExternalLink)}
|
||||
message={intl.formatMessage(i18n.openProtocolLink, { protocol: pendingLink?.protocol ?? '' })}
|
||||
message={intl.formatMessage(i18n.openProtocolLink, {
|
||||
protocol: pendingLink?.protocol ?? '',
|
||||
})}
|
||||
detail={intl.formatMessage(i18n.thisWillOpen, { href: pendingLink?.href ?? '' })}
|
||||
onConfirm={handleConfirmOpen}
|
||||
onCancel={handleCancelOpen}
|
||||
|
|
|
|||
|
|
@ -36,15 +36,3 @@ export function getCachedTools(
|
|||
cache.set(key, promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
export function clearToolsCache(sessionId?: string): void {
|
||||
if (!sessionId) {
|
||||
cache.clear();
|
||||
return;
|
||||
}
|
||||
for (const key of cache.keys()) {
|
||||
if (key.startsWith(`${sessionId}:`)) {
|
||||
cache.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ interface UseDisplayModeOptions {
|
|||
containerRef: React.RefObject<HTMLDivElement | null>;
|
||||
}
|
||||
|
||||
export interface DisplayModeState {
|
||||
interface DisplayModeState {
|
||||
activeDisplayMode: GooseDisplayMode;
|
||||
effectiveDisplayModes: McpUiDisplayMode[];
|
||||
isStandalone: boolean;
|
||||
|
|
|
|||
|
|
@ -548,5 +548,3 @@ export const MessageQueue: React.FC<MessageQueueProps> = ({
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MessageQueue;
|
||||
|
|
|
|||
|
|
@ -270,15 +270,17 @@ export default function ProgressiveMessageList({
|
|||
const previousResolvedModel = currentResolvedModel ? getPreviousResolvedModel(index) : null;
|
||||
const showModelChangeDisclosure = Boolean(
|
||||
currentResolvedModel &&
|
||||
previousResolvedModel &&
|
||||
currentResolvedModel !== previousResolvedModel
|
||||
previousResolvedModel &&
|
||||
currentResolvedModel !== previousResolvedModel
|
||||
);
|
||||
|
||||
const messageKey = message.id ?? `msg-${index}-${message.created}`;
|
||||
|
||||
return (
|
||||
<Fragment key={messageKey}>
|
||||
{showModelChangeDisclosure && currentResolvedModel && previousResolvedModel &&
|
||||
{showModelChangeDisclosure &&
|
||||
currentResolvedModel &&
|
||||
previousResolvedModel &&
|
||||
renderModelChangeDisclosure(previousResolvedModel, currentResolvedModel)}
|
||||
<div
|
||||
className={`relative ${index === 0 ? 'mt-0' : 'mt-4'} ${isUser ? 'user' : 'assistant'} ${messageIsInChain ? 'in-chain' : ''}`}
|
||||
|
|
|
|||
|
|
@ -505,7 +505,10 @@ export default function SessionActionsHeader({
|
|||
<Button variant="outline" onClick={() => setIsRenameOpen(false)} disabled={isRenaming}>
|
||||
{intl.formatMessage(i18n.cancel)}
|
||||
</Button>
|
||||
<Button onClick={() => void handleRename()} disabled={isRenaming || !renameValue.trim()}>
|
||||
<Button
|
||||
onClick={() => void handleRename()}
|
||||
disabled={isRenaming || !renameValue.trim()}
|
||||
>
|
||||
{isRenaming ? intl.formatMessage(i18n.saving) : intl.formatMessage(i18n.save)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
|
|
|||
|
|
@ -44,7 +44,10 @@ export const SessionIndicators = React.memo<SessionIndicatorsProps>(
|
|||
if (isStreaming) {
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
<Loader2 className="w-3 h-3 text-blue-500 animate-spin" aria-label={intl.formatMessage(i18n.streaming)} />
|
||||
<Loader2
|
||||
className="w-3 h-3 text-blue-500 animate-spin"
|
||||
aria-label={intl.formatMessage(i18n.streaming)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -52,7 +55,10 @@ export const SessionIndicators = React.memo<SessionIndicatorsProps>(
|
|||
if (hasUnread) {
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full" aria-label={intl.formatMessage(i18n.newActivity)} />
|
||||
<div
|
||||
className="w-2 h-2 bg-green-500 rounded-full"
|
||||
aria-label={intl.formatMessage(i18n.newActivity)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ const globalApprovalState = new Map<
|
|||
}
|
||||
>();
|
||||
|
||||
export interface ToolApprovalData {
|
||||
interface ToolApprovalData {
|
||||
id: string;
|
||||
toolName: string;
|
||||
prompt?: string;
|
||||
|
|
@ -133,7 +133,11 @@ export default function ToolApprovalButtons({ data }: { data: ToolApprovalData }
|
|||
{intl.formatMessage(i18n.alwaysAllow)}
|
||||
</Button>
|
||||
)}
|
||||
<Button className="rounded-full" variant="outline" onClick={() => handleAction('deny_once')}>
|
||||
<Button
|
||||
className="rounded-full"
|
||||
variant="outline"
|
||||
onClick={() => handleAction('deny_once')}
|
||||
>
|
||||
{intl.formatMessage(i18n.deny)}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -33,14 +33,18 @@ export function ToolCallArguments({ args }: ToolCallArgumentsProps) {
|
|||
|
||||
return (
|
||||
<div className="font-sans text-sm mb-2">
|
||||
<div className={`flex flex-row items-stretch ${!isExpanded && needsExpansion ? 'truncate min-w-0' : ''}`}>
|
||||
<div
|
||||
className={`flex flex-row items-stretch ${!isExpanded && needsExpansion ? 'truncate min-w-0' : ''}`}
|
||||
>
|
||||
<button
|
||||
onClick={() => needsExpansion && toggleKey(key)}
|
||||
className={`flex text-left text-text-secondary min-w-[140px] ${needsExpansion ? 'cursor-pointer' : 'cursor-default'}`}
|
||||
>
|
||||
<span>{key}</span>
|
||||
</button>
|
||||
<div className={`w-full flex items-stretch ${!isExpanded && needsExpansion ? 'truncate min-w-0' : ''}`}>
|
||||
<div
|
||||
className={`w-full flex items-stretch ${!isExpanded && needsExpansion ? 'truncate min-w-0' : ''}`}
|
||||
>
|
||||
{isExpanded ? (
|
||||
<pre className="font-mono text-xs text-text-secondary whitespace-pre-wrap max-w-full overflow-x-auto">
|
||||
{text}
|
||||
|
|
|
|||
|
|
@ -16,10 +16,7 @@ interface ToolCallStatusIndicatorProps {
|
|||
className?: string;
|
||||
}
|
||||
|
||||
export const ToolCallStatusIndicator: React.FC<ToolCallStatusIndicatorProps> = ({
|
||||
status,
|
||||
className,
|
||||
}) => {
|
||||
const ToolCallStatusIndicator: React.FC<ToolCallStatusIndicatorProps> = ({ status, className }) => {
|
||||
const intl = useIntl();
|
||||
const getStatusStyles = () => {
|
||||
switch (status) {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import {
|
|||
ToolConfirmationData,
|
||||
} from '../types/message';
|
||||
import { cn, snakeToTitleCase } from '../utils';
|
||||
import { LoadingStatus } from './ui/Dot';
|
||||
import { ChevronRight, ExternalLink } from 'lucide-react';
|
||||
import { TooltipWrapper } from './settings/providers/subcomponents/buttons/TooltipWrapper';
|
||||
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
||||
|
|
@ -22,6 +21,8 @@ import McpAppRenderer from './McpApps/McpAppRenderer';
|
|||
import ToolApprovalButtons from './ToolApprovalButtons';
|
||||
import { defineMessages, useIntl } from '../i18n';
|
||||
|
||||
type LoadingStatus = 'loading' | 'success' | 'error';
|
||||
|
||||
const i18n = defineMessages({
|
||||
viewSubagentSession: {
|
||||
id: 'toolCallWithResponse.viewSubagentSession',
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ const i18n = defineMessages({
|
|||
},
|
||||
editInPlaceDescription: {
|
||||
id: 'userMessage.editInPlaceDescription',
|
||||
defaultMessage: '<b>Edit in Place</b> updates this session • <b>Fork Session</b> creates a new session',
|
||||
defaultMessage:
|
||||
'<b>Edit in Place</b> updates this session • <b>Fork Session</b> creates a new session',
|
||||
},
|
||||
cancel: {
|
||||
id: 'userMessage.cancel',
|
||||
|
|
@ -233,7 +234,11 @@ export default function UserMessage({ message, onMessageUpdate }: UserMessagePro
|
|||
})}
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<Button onClick={handleCancel} variant="ghost" aria-label={intl.formatMessage(i18n.cancelAriaLabel)}>
|
||||
<Button
|
||||
onClick={handleCancel}
|
||||
variant="ghost"
|
||||
aria-label={intl.formatMessage(i18n.cancelAriaLabel)}
|
||||
>
|
||||
{intl.formatMessage(i18n.cancel)}
|
||||
</Button>
|
||||
<Button
|
||||
|
|
@ -292,7 +297,9 @@ export default function UserMessage({ message, onMessageUpdate }: UserMessagePro
|
|||
}
|
||||
}}
|
||||
className="flex items-center gap-1 text-xs text-text-secondary hover:cursor-pointer hover:text-text-primary transition-all duration-200 opacity-0 group-hover:opacity-100 -translate-y-4 group-hover:translate-y-0 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-50 rounded"
|
||||
aria-label={intl.formatMessage(i18n.editMessageAriaLabel, { preview: `${textContent.substring(0, 50)}${textContent.length > 50 ? '...' : ''}` })}
|
||||
aria-label={intl.formatMessage(i18n.editMessageAriaLabel, {
|
||||
preview: `${textContent.substring(0, 50)}${textContent.length > 50 ? '...' : ''}`,
|
||||
})}
|
||||
aria-expanded={isEditing}
|
||||
title={intl.formatMessage(i18n.editMessageTitle)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -89,7 +89,11 @@ export const AlertBox = ({ alert, className }: AlertBoxProps) => {
|
|||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving threshold:', error);
|
||||
window.alert(intl.formatMessage(i18n.failedToSaveThreshold, { error: errorMessage(error, 'Unknown error') }));
|
||||
window.alert(
|
||||
intl.formatMessage(i18n.failedToSaveThreshold, {
|
||||
error: errorMessage(error, 'Unknown error'),
|
||||
})
|
||||
);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
|
|
@ -111,7 +115,9 @@ export const AlertBox = ({ alert, className }: AlertBoxProps) => {
|
|||
<div className="flex items-center justify-center gap-1 min-h-[20px]">
|
||||
{isEditingThreshold ? (
|
||||
<>
|
||||
<span className="text-[10px] opacity-70">{intl.formatMessage(i18n.autoCompactAt)}</span>
|
||||
<span className="text-[10px] opacity-70">
|
||||
{intl.formatMessage(i18n.autoCompactAt)}
|
||||
</span>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
|
|
|
|||
|
|
@ -261,7 +261,7 @@ function SessionExtensionsMenu({ sessionId }: { sessionId: string }) {
|
|||
|
||||
const loadSessionExtensions = useCallback(
|
||||
async (targetSessionId: string, signal?: GetSessionExtensionsSignal) => {
|
||||
const extensions = await getAcpSessionExtensions(targetSessionId)
|
||||
const extensions = await getAcpSessionExtensions(targetSessionId);
|
||||
|
||||
if (signal?.aborted || latestSessionIdRef.current !== targetSessionId) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ const i18n = defineMessages({
|
|||
},
|
||||
costUnavailable: {
|
||||
id: 'costTracker.costUnavailable',
|
||||
defaultMessage: 'Cost data not available for {model} ({inputTokens} input, {outputTokens} output tokens)',
|
||||
defaultMessage:
|
||||
'Cost data not available for {model} ({inputTokens} input, {outputTokens} output tokens)',
|
||||
},
|
||||
totalSessionCost: {
|
||||
id: 'costTracker.totalSessionCost',
|
||||
|
|
@ -19,7 +20,8 @@ const i18n = defineMessages({
|
|||
},
|
||||
inputOutputTooltip: {
|
||||
id: 'costTracker.inputOutputTooltip',
|
||||
defaultMessage: 'Input: {inputTokens} tokens ({inputCost}) | Output: {outputTokens} tokens ({outputCost})',
|
||||
defaultMessage:
|
||||
'Input: {inputTokens} tokens ({inputCost}) | Output: {outputTokens} tokens ({outputCost})',
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -116,8 +118,7 @@ export function CostTracker({
|
|||
|
||||
if (
|
||||
accumulatedCost == null &&
|
||||
(!costInfo ||
|
||||
(costInfo.inputTokenCost === undefined && costInfo.outputTokenCost === undefined))
|
||||
(!costInfo || (costInfo.inputTokenCost === undefined && costInfo.outputTokenCost === undefined))
|
||||
) {
|
||||
const freeProviders = ['ollama', 'local', 'localhost'];
|
||||
if (freeProviders.includes(currentProvider.toLowerCase())) {
|
||||
|
|
@ -160,19 +161,24 @@ export function CostTracker({
|
|||
// Build tooltip content
|
||||
const getTooltipContent = (): string => {
|
||||
if (pricingFailed) {
|
||||
return intl.formatMessage(i18n.pricingUnavailable, { model: `${currentProvider}/${currentModel}` });
|
||||
return intl.formatMessage(i18n.pricingUnavailable, {
|
||||
model: `${currentProvider}/${currentModel}`,
|
||||
});
|
||||
}
|
||||
|
||||
const currency = costInfo?.currency || '$';
|
||||
|
||||
if (accumulatedCost != null) {
|
||||
return intl.formatMessage(i18n.totalSessionCost, { cost: `${currency}${totalCost.toFixed(4)}` })
|
||||
+ `\n` + intl.formatMessage(i18n.inputOutputTooltip, {
|
||||
return (
|
||||
intl.formatMessage(i18n.totalSessionCost, { cost: `${currency}${totalCost.toFixed(4)}` }) +
|
||||
`\n` +
|
||||
intl.formatMessage(i18n.inputOutputTooltip, {
|
||||
inputTokens: inputTokens.toLocaleString(),
|
||||
inputCost: `${currency}${((inputTokens * (costInfo?.inputTokenCost || 0)) / 1_000_000).toFixed(6)}`,
|
||||
outputTokens: outputTokens.toLocaleString(),
|
||||
outputCost: `${currency}${((outputTokens * (costInfo?.outputTokenCost || 0)) / 1_000_000).toFixed(6)}`,
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const inputCostStr = `${currency}${((inputTokens * (costInfo?.inputTokenCost || 0)) / 1_000_000).toFixed(6)}`;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ interface SearchBarProps {
|
|||
/**
|
||||
* SearchBar provides a search input with case-sensitive toggle and result navigation.
|
||||
*/
|
||||
export const SearchBar: React.FC<SearchBarProps> = ({
|
||||
const SearchBar: React.FC<SearchBarProps> = ({
|
||||
onSearch,
|
||||
onClose,
|
||||
onNavigate,
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
export default function Anthropic({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 1200 1200"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M 233.959793 800.214905 L 468.644287 668.536987 L 472.590637 657.100647 L 468.644287 650.738403 L 457.208069 650.738403 L 417.986633 648.322144 L 283.892639 644.69812 L 167.597321 639.865845 L 54.926208 633.825623 L 26.577238 627.785339 L 3.3e-05 592.751709 L 2.73832 575.27533 L 26.577238 559.248352 L 60.724873 562.228149 L 136.187973 567.382629 L 249.422867 575.194763 L 331.570496 580.026978 L 453.261841 592.671082 L 472.590637 592.671082 L 475.328857 584.859009 L 468.724915 580.026978 L 463.570557 575.194763 L 346.389313 495.785217 L 219.543671 411.865906 L 153.100723 363.543762 L 117.181267 339.060425 L 99.060455 316.107361 L 91.248367 266.01355 L 123.865784 230.093994 L 167.677887 233.073853 L 178.872513 236.053772 L 223.248367 270.201477 L 318.040283 343.570496 L 441.825592 434.738342 L 459.946411 449.798706 L 467.194672 444.64447 L 468.080597 441.020203 L 459.946411 427.409485 L 392.617493 305.718323 L 320.778564 181.932983 L 288.80542 130.630859 L 280.348999 99.865845 C 277.369171 87.221436 275.194641 76.590698 275.194641 63.624268 L 312.322174 13.20813 L 332.8591 6.604126 L 382.389313 13.20813 L 403.248352 31.328979 L 434.013519 101.71814 L 483.865753 212.537048 L 561.181274 363.221497 L 583.812134 407.919434 L 595.892639 449.315491 L 600.40271 461.959839 L 608.214783 461.959839 L 608.214783 454.711609 L 614.577271 369.825623 L 626.335632 265.61084 L 637.771851 131.516846 L 641.718201 93.745117 L 660.402832 48.483276 L 697.530334 24.000122 L 726.52356 37.852417 L 750.362549 72 L 747.060486 94.067139 L 732.886047 186.201416 L 705.100708 330.52356 L 686.979919 427.167847 L 697.530334 427.167847 L 709.61084 415.087341 L 758.496704 350.174561 L 840.644348 247.490051 L 876.885925 206.738342 L 919.167847 161.71814 L 946.308838 140.29541 L 997.61084 140.29541 L 1035.38269 196.429626 L 1018.469849 254.416199 L 965.637634 321.422852 L 921.825562 378.201538 L 859.006714 462.765259 L 819.785278 530.41626 L 823.409424 535.812073 L 832.75177 534.92627 L 974.657776 504.724915 L 1051.328979 490.872559 L 1142.818848 475.167786 L 1184.214844 494.496582 L 1188.724854 514.147644 L 1172.456421 554.335693 L 1074.604126 578.496765 L 959.838989 601.449829 L 788.939636 641.879272 L 786.845764 643.409485 L 789.261841 646.389343 L 866.255127 653.637634 L 899.194702 655.409424 L 979.812134 655.409424 L 1129.932861 666.604187 L 1169.154419 692.537109 L 1192.671265 724.268677 L 1188.724854 748.429688 L 1128.322144 779.194641 L 1046.818848 759.865845 L 856.590759 714.604126 L 791.355774 698.335754 L 782.335693 698.335754 L 782.335693 703.731567 L 836.69812 756.885986 L 936.322205 846.845581 L 1061.073975 962.81897 L 1067.436279 991.490112 L 1051.409424 1014.120911 L 1034.496704 1011.704712 L 924.885986 929.234924 L 882.604126 892.107544 L 786.845764 811.48999 L 780.483276 811.48999 L 780.483276 819.946289 L 802.550415 852.241699 L 919.087341 1027.409424 L 925.127625 1081.127686 L 916.671204 1098.604126 L 886.469849 1109.154419 L 853.288696 1103.114136 L 785.073914 1007.355835 L 714.684631 899.516785 L 657.906067 802.872498 L 650.979858 806.81897 L 617.476624 1167.704834 L 601.771851 1186.147705 L 565.530212 1200 L 535.328857 1177.046997 L 519.302124 1139.919556 L 535.328857 1066.550537 L 554.657776 970.792053 L 570.362488 894.68457 L 584.536926 800.134277 L 592.993347 768.724976 L 592.429626 766.630859 L 585.503479 767.516968 L 514.22821 865.369263 L 405.825531 1011.865906 L 320.053711 1103.677979 L 299.516815 1111.812256 L 263.919525 1093.369263 L 267.221497 1060.429688 L 287.114136 1031.114136 L 405.825531 880.107361 L 477.422913 786.52356 L 523.651062 732.483276 L 523.328918 724.671265 L 520.590698 724.671265 L 205.288605 929.395935 L 149.154434 936.644409 L 124.993355 914.01355 L 127.973183 876.885986 L 139.409409 864.80542 L 234.201385 799.570435 L 233.879227 799.8927 Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
interface ArrowRightProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function ArrowRight({ className = '' }: ArrowRightProps) {
|
||||
return (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M3.33678 8.00138L12.6701 8.00555M12.6701 8.00555L8.00553 3.3368M12.6701 8.00555L8.00136 12.6701"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
export default function Back({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
width="1.5rem"
|
||||
height="1.5rem"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M.5 12c0-.413.17-.807.47-1.09l9-8.5a1.5 1.5 0 1 1 2.06 2.18L5.773 10.5H21.5a1.5 1.5 0 1 1 0 3H5.773l6.257 5.91a1.5 1.5 0 1 1-2.06 2.18l-9-8.5A1.5 1.5 0 0 1 .5 12Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
export default function ChatGPT({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
export default function ChatSmart({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M18.2625 2.72219C18.3379 2.49299 18.6621 2.49299 18.7375 2.72219L19.5742 5.26646C19.5991 5.34188 19.6582 5.40103 19.7336 5.42584L22.2779 6.2626C22.5071 6.33798 22.5071 6.66219 22.2779 6.73757L19.7336 7.57433C19.6582 7.59914 19.5991 7.65829 19.5742 7.73372L18.7375 10.278C18.6621 10.5072 18.3379 10.5072 18.2625 10.278L17.4258 7.73372C17.4009 7.65829 17.3418 7.59914 17.2664 7.57433L14.7221 6.73757C14.4929 6.66219 14.4929 6.33798 14.7221 6.2626L17.2664 5.42584C17.3418 5.40103 17.4009 5.34188 17.4258 5.26646L18.2625 2.72219ZM1.5 6.00016C1.5 4.89559 2.39543 4.00016 3.5 4.00016H12C12.5523 4.00016 13 4.44788 13 5.00016C13 5.55245 12.5523 6.00016 12 6.00016L3.5 6.00016V16.5002L9.68478 16.5002C10.0951 16.5002 10.4955 16.6264 10.8317 16.8617L14 19.0795V17.5002C14 17.2349 14.1054 16.9806 14.2929 16.7931C14.4804 16.6055 14.7348 16.5002 15 16.5002L20.5 16.5002V11.0002C20.5 10.4479 20.9477 10.0002 21.5 10.0002C22.0523 10.0002 22.5 10.4479 22.5 11.0002V16.5002C22.5 17.6047 21.6046 18.5002 20.5 18.5002L16 18.5002V21.0002C16 21.373 15.7925 21.7149 15.4618 21.8871C15.1311 22.0593 14.732 22.0332 14.4265 21.8194L9.68478 18.5002L3.5 18.5002C2.39543 18.5002 1.5 17.6047 1.5 16.5002V6.00016ZM14.2375 8.72234C14.1621 8.49315 13.8379 8.49315 13.7625 8.72234L13.297 10.1378C13.2722 10.2133 13.213 10.2724 13.1376 10.2972L11.7221 10.7628C11.4929 10.8381 11.4929 11.1623 11.7221 11.2377L13.1376 11.7033C13.213 11.7281 13.2722 11.7872 13.297 11.8626L13.7625 13.2781C13.8379 13.5073 14.1621 13.5073 14.2375 13.2781L14.703 11.8626C14.7278 11.7872 14.787 11.7281 14.8624 11.7033L16.2779 11.2377C16.5071 11.1623 16.5071 10.8381 16.2779 10.7628L14.8624 10.2972C14.787 10.2724 14.7278 10.2133 14.703 10.1378L14.2375 8.72234Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
export default function ChevronDown({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
width="1.5rem"
|
||||
height="1.5rem"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M12.293 10.293a1 1 0 0 1 1.414 0L18 14.586l4.293-4.293a1 1 0 1 1 1.414 1.414l-5 5a1 1 0 0 1-1.414 0l-5-5a1 1 0 0 1 0-1.414Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
interface Props extends React.ComponentPropsWithoutRef<'svg'> {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function ChevronRight({ className = '', ...props }: Props) {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M9 18L15 12L9 6"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
export default function ChevronUp({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
width="1.5rem"
|
||||
height="1.5rem"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M17.293 9.293a1 1 0 0 1 1.414 0l5 5a1 1 0 0 1-1.414 1.414L18 11.414l-4.293 4.293a1 1 0 0 1-1.414-1.414l5-5Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
export default function Discord({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
width="1.5rem"
|
||||
height="1.5rem"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 640 512"
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M524.5 69.8a1.5 1.5 0 0 0 -.8-.7A485.1 485.1 0 0 0 404.1 32a1.8 1.8 0 0 0 -1.9 .9 337.5 337.5 0 0 0 -14.9 30.6 447.8 447.8 0 0 0 -134.4 0 309.5 309.5 0 0 0 -15.1-30.6 1.9 1.9 0 0 0 -1.9-.9A483.7 483.7 0 0 0 116.1 69.1a1.7 1.7 0 0 0 -.8 .7C39.1 183.7 18.2 294.7 28.4 404.4a2 2 0 0 0 .8 1.4A487.7 487.7 0 0 0 176 479.9a1.9 1.9 0 0 0 2.1-.7A348.2 348.2 0 0 0 208.1 430.4a1.9 1.9 0 0 0 -1-2.6 321.2 321.2 0 0 1 -45.9-21.9 1.9 1.9 0 0 1 -.2-3.1c3.1-2.3 6.2-4.7 9.1-7.1a1.8 1.8 0 0 1 1.9-.3c96.2 43.9 200.4 43.9 295.5 0a1.8 1.8 0 0 1 1.9 .2c2.9 2.4 6 4.9 9.1 7.2a1.9 1.9 0 0 1 -.2 3.1 301.4 301.4 0 0 1 -45.9 21.8 1.9 1.9 0 0 0 -1 2.6 391.1 391.1 0 0 0 30 48.8 1.9 1.9 0 0 0 2.1 .7A486 486 0 0 0 610.7 405.7a1.9 1.9 0 0 0 .8-1.4C623.7 277.6 590.9 167.5 524.5 69.8zM222.5 337.6c-29 0-52.8-26.6-52.8-59.2S193.1 219.1 222.5 219.1c29.7 0 53.3 26.8 52.8 59.2C275.3 311 251.9 337.6 222.5 337.6zm195.4 0c-29 0-52.8-26.6-52.8-59.2S388.4 219.1 417.9 219.1c29.7 0 53.3 26.8 52.8 59.2C470.7 311 447.5 337.6 417.9 337.6z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
export default function Document({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
width="1.5rem"
|
||||
height="1.5rem"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M14.546 4.503a6.26 6.26 0 0 0-.264-.003H6.6c-.297 0-.459 0-.575.01l-.014.001v.014C6 4.64 6 4.804 6 5.1v13.8c0 .297 0 .459.01.575l.001.014h.014c.116.01.278.011.575.011h10.8c.297 0 .459 0 .575-.01l.014-.001v-.014c.01-.116.011-.278.011-.575V8.608c0-.12 0-.18-.002-.224v-.003l-.002-.002a6.215 6.215 0 0 0-.147-.17l-3.12-3.508a6.24 6.24 0 0 0-.177-.195l-.002-.003h-.004Zm-.21-2.003c.206 0 .464-.001.716.064a2 2 0 0 1 .613.275c.216.145.387.339.523.492l.037.042 3.118 3.508.032.035c.116.13.263.295.372.49.094.17.164.352.206.542.048.218.047.438.047.613v10.371c0 .252 0 .498-.017.706a2.022 2.022 0 0 1-.201.77 2 2 0 0 1-.874.874 2.022 2.022 0 0 1-.77.201 9.144 9.144 0 0 1-.706.017H6.568c-.252 0-.498 0-.706-.017a2.022 2.022 0 0 1-.77-.2 2 2 0 0 1-.874-.875 2.021 2.021 0 0 1-.201-.77C4 19.43 4 19.184 4 18.932V5.068c0-.252 0-.498.017-.706.019-.229.063-.499.201-.77a2 2 0 0 1 .874-.874c.271-.138.541-.182.77-.201.208-.017.454-.017.706-.017h7.769ZM8 14a1 1 0 0 1 1-1h6a1 1 0 1 1 0 2H9a1 1 0 0 1-1-1Zm1-6a1 1 0 0 0 0 2h4a1 1 0 1 0 0-2H9Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
export default function Idea({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
width="1.5rem"
|
||||
height="1.5rem"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M12 3C7.67283 3 4.98694 7.96755 7.41702 11.7635L8.84879 14L15.1514 14L16.5831 11.7633C19.013 7.96731 16.3271 3 12 3ZM5.73261 12.8418C2.49843 7.78981 6.00376 1 12 1C17.9961 1 21.5015 7.78948 18.2676 12.8415L16.7382 15.2309C16.4351 15.7043 15.9116 16 15.339 16L8.66122 16C8.0886 16 7.56511 15.7043 7.26208 15.2309L5.73261 12.8418ZM8.00002 18C8.00002 17.4477 8.44774 17 9.00002 17H12H15C15.5523 17 16 17.4477 16 18C16 18.5523 15.5523 19 15 19H12H9.00002C8.44774 19 8.00002 18.5523 8.00002 18ZM10 20C9.44774 20 9.00003 20.4477 9.00003 21C9.00003 21.5523 9.44774 22 10 22H12H14C14.5523 22 15 21.5523 15 21C15 20.4477 14.5523 20 14 20H12H10Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
interface KeyProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Key({ className = '' }: KeyProps) {
|
||||
return (
|
||||
<svg
|
||||
width="16"
|
||||
height="17"
|
||||
viewBox="0 0 16 17"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<g clipPath="url(#clip0_986_19974)">
|
||||
<path
|
||||
d="M10.3382 5.68265L11.8709 7.21667C11.9954 7.33888 12.1629 7.40737 12.3374 7.40745C12.5119 7.40753 12.6795 7.33918 12.8042 7.21709L14.2048 5.81771C14.327 5.69315 14.3955 5.52563 14.3956 5.35113C14.3957 5.17663 14.3273 5.00905 14.2052 4.88438L12.6726 3.35036M14.0064 2.01762L7.60358 8.41476M8.66915 11.0152C8.66825 13.0403 7.02589 14.6812 5.00085 14.6803C2.9758 14.6794 1.33492 13.037 1.33582 11.012C1.33673 8.98692 2.97908 7.34603 5.00413 7.34693C7.02917 7.34784 8.67006 8.9902 8.66915 11.0152Z"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_986_19974">
|
||||
<rect
|
||||
width="16"
|
||||
height="16"
|
||||
fill="white"
|
||||
transform="translate(0.00708008 0.677979) rotate(0.0256089)"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
export default function LinkedIn({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
width="1.5rem"
|
||||
height="1.5rem"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 448 512"
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M416 32H31.9C14.3 32 0 46.5 0 64.3v383.4C0 465.5 14.3 480 31.9 480H416c17.6 0 32-14.5 32-32.3V64.3c0-17.8-14.4-32.3-32-32.3zM135.4 416H69V202.2h66.5V416zm-33.2-243c-21.3 0-38.5-17.3-38.5-38.5S80.9 96 102.2 96c21.2 0 38.5 17.3 38.5 38.5 0 21.3-17.2 38.5-38.5 38.5zm282.1 243h-66.4V312c0-24.8-.5-56.7-34.5-56.7-34.6 0-39.9 27-39.9 54.9V416h-66.4V202.2h63.7v29.2h.9c8.9-16.8 30.6-34.5 62.9-34.5 67.2 0 79.7 44.3 79.7 101.9V416z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
export default function More({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M5 14a2 2 0 1 1 .001-4.001A2 2 0 0 1 5 14Zm7 0a2 2 0 1 1 .001-4.001A2 2 0 0 1 12 14Zm5-2a2 2 0 1 0 4.001-.001A2 2 0 0 0 17 12Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
export default function Ollama({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
width="69"
|
||||
height="88"
|
||||
viewBox="0 0 69 88"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M65 85L62.0637 70.8077C62.0222 70.6074 62.0432 70.3992 62.1238 70.2112L64.3359 65.0497C65.7194 61.8214 65.7378 58.1708 64.387 54.9287L62.4897 50.3753C61.8577 48.8586 61.8754 47.149 62.5386 45.6457L64.1296 42.0397C66.8761 35.8141 64.3334 28.5656 58.0958 25.8464C55.8537 24.869 53.319 23.8076 50.5427 22.7131C50.1848 22.572 49.843 22.3777 49.5389 22.1421C46.6697 19.9189 44.1695 18.0613 42.1059 16.5691C38.1157 13.6838 32.8534 13.645 28.9759 16.68C27.1886 18.0789 25.0635 19.8198 22.638 21.9398C22.2177 22.3071 21.7135 22.5905 21.1813 22.7586C18.6462 23.5593 16.4068 24.2677 14.4298 24.8938C6.83415 27.2996 3.28234 35.8919 6.92194 42.9796L8.28723 45.6383C9.05963 47.1424 9.16151 48.9027 8.5678 50.4859L6.65444 55.5882C5.5855 58.4387 5.55086 61.5739 6.55656 64.4473L8.42485 69.7853C8.47432 69.9266 8.49142 70.0773 8.47488 70.2261L7 83.5"
|
||||
stroke="currentColor"
|
||||
strokeWidth="5"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<circle cx="18.5" cy="40.5" r="3" fill="currentColor" />
|
||||
<circle cx="52.5" cy="40.5" r="3" fill="currentColor" />
|
||||
<path
|
||||
d="M35.5 38C42.1664 38 47 42.3128 47 47C47 51.6872 42.1664 56 35.5 56C28.8336 56 24 51.6872 24 47C24 42.3128 28.8336 38 35.5 38Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth="3"
|
||||
/>
|
||||
<path
|
||||
d="M33.5 45L35.5 47M35.5 47L37.5 45M35.5 47V50"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M25 20L23.8142 14.0708C23.2738 11.369 22.3756 8.75127 21.1434 6.28682L19.6522 3.30442C18.9533 1.90667 16.9727 1.86785 16.2196 3.23714V3.23714C13.7795 7.67371 12.5 12.6549 12.5 17.7183V26"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M46.5 20.5L47.7083 14.6911C48.2867 11.9102 49.2445 9.22205 50.5549 6.70202L52.1438 3.64663C52.8504 2.28778 54.7805 2.24912 55.5409 3.57858V3.57858C58.1353 8.11412 59.5 13.2486 59.5 18.4737V26.5"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
export default function OpenAI({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
export default function OpenRouter({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
width="202"
|
||||
height="171"
|
||||
viewBox="0 0 202 171"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M2.20001 85.5004C8.10001 85.5004 30.8 80.4004 42.5 73.7004C54.3 67.1004 54.3 67.1004 78.6 49.8004C109.4 28.0004 131.1 35.3004 166.8 35.3004"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M2.20001 85.5004C8.10001 85.5004 30.8 80.4004 42.5 73.7004C54.3 67.1004 54.3 67.1004 78.6 49.8004C109.4 28.0004 131.1 35.3004 166.8 35.3004"
|
||||
stroke="currentColor"
|
||||
strokeWidth="35.3"
|
||||
/>
|
||||
<path
|
||||
d="M201.3 35.5002L141 70.3002V0.700195L201.3 35.5002Z"
|
||||
fill="currentColor"
|
||||
stroke="currentColor"
|
||||
strokeWidth="0.4"
|
||||
/>
|
||||
<path
|
||||
d="M1 85.5C6.9 85.5 29.6 90.5 41.4 97.2C53.1 103.9 53.1 103.9 77.4 121.2C108.2 143 129.9 135.7 165.6 135.7"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M1 85.5C6.9 85.5 29.6 90.5 41.4 97.2C53.1 103.9 53.1 103.9 77.4 121.2C108.2 143 129.9 135.7 165.6 135.7"
|
||||
stroke="currentColor"
|
||||
strokeWidth="35.3"
|
||||
/>
|
||||
<path
|
||||
d="M200.1 135.4L139.8 100.7V170.2L200.1 135.4Z"
|
||||
fill="currentColor"
|
||||
stroke="currentColor"
|
||||
strokeWidth="0.4"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
export default function Refresh({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M14.1508 1.74076C13.7315 1.38134 13.1002 1.4299 12.7407 1.84923C12.3813 2.26855 12.4299 2.89985 12.8492 3.25927L14.2968 4.50004H11.5001C6.8057 4.50004 3.00012 8.30564 3.00012 13.0001C3.00012 17.6945 6.80568 21.5 11.5001 21.5C16.1945 21.5 20.0001 17.6944 20.0001 13C20.0001 12.4477 19.5523 12 19.0001 12C18.4478 12 18.0001 12.4477 18.0001 13C18.0001 16.5899 15.0899 19.5 11.5001 19.5C7.91026 19.5 5.00012 16.5899 5.00012 13.0001C5.00012 9.4102 7.91027 6.50004 11.5001 6.50004H14.2967L12.8492 7.74076C12.4299 8.10018 12.3813 8.73148 12.7407 9.15081C13.1002 9.57014 13.7315 9.6187 14.1508 9.25927L17.6417 6.26706C17.8608 6.08362 18.0001 5.8081 18.0001 5.50004C18.0001 5.19294 17.8616 4.91817 17.6438 4.73474L14.1508 1.74076Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
export default function Send({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M20.707 3.293a1 1 0 0 1 .25.994l-5.4 18a1 1 0 0 1-1.886.084l-3.44-8.602-8.602-3.44a1 1 0 0 1 .084-1.887l18-5.4a1 1 0 0 1 .994.25Zm-8.534 9.948 2.292 5.729L18.509 5.49 5.03 9.535l5.73 2.292 1.333-1.334a1 1 0 0 1 1.414 1.414l-1.334 1.334Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
export default function SensitiveHidden({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
width="1.5rem"
|
||||
height="1.5rem"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M19.731 5.182a1 1 0 1 0-1.462-1.364l-2.71 2.903C14.471 6.287 13.271 6 12 6 9.608 6 7.469 7.018 5.849 8.114a18.644 18.644 0 0 0-3.333 2.916 1.45 1.45 0 0 0 0 1.94 18.644 18.644 0 0 0 3.333 2.916c.231.157.473.312.725.462l-2.305 2.47a1 1 0 0 0 1.462 1.364l2.71-2.903C9.529 17.713 10.729 18 12 18c2.392 0 4.531-1.018 6.151-2.114a18.644 18.644 0 0 0 3.333-2.916 1.45 1.45 0 0 0 0-1.94 18.644 18.644 0 0 0-3.333-2.916 15.453 15.453 0 0 0-.725-.462l2.305-2.47ZM14.07 8.317A7.21 7.21 0 0 0 12 8c-1.833 0-3.58.789-5.03 1.77A16.633 16.633 0 0 0 4.343 12c.55.575 1.467 1.445 2.627 2.23.32.217.657.425 1.005.617l6.094-6.53Zm-4.138 7.366 6.094-6.53c.348.192.684.4 1.006.617A16.631 16.631 0 0 1 19.657 12a16.633 16.633 0 0 1-2.627 2.23C15.58 15.21 13.834 16 12 16c-.707 0-1.4-.117-2.07-.317Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
export default function SensitiveVisible({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
width="1.5rem"
|
||||
height="1.5rem"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M6.97 14.23A16.635 16.635 0 0 1 4.343 12 16.633 16.633 0 0 1 6.97 9.77C8.42 8.79 10.167 8 12 8c1.833 0 3.58.789 5.03 1.77A16.631 16.631 0 0 1 19.657 12a16.633 16.633 0 0 1-2.627 2.23C15.58 15.21 13.834 16 12 16c-1.833 0-3.58-.789-5.03-1.77ZM12 6C9.608 6 7.469 7.018 5.849 8.114a18.644 18.644 0 0 0-3.333 2.916 1.45 1.45 0 0 0 0 1.94 18.644 18.644 0 0 0 3.333 2.916C7.469 16.982 9.609 18 12 18c2.392 0 4.531-1.018 6.151-2.114a18.644 18.644 0 0 0 3.333-2.916 1.45 1.45 0 0 0 0-1.94 18.644 18.644 0 0 0-3.333-2.916C16.531 7.018 14.392 6 12 6Zm0 8a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
export default function Settings({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10.6326 3.5C10.3981 3.5 10.1862 3.69609 10.1862 3.96499C10.1862 4.98498 9.56936 5.81161 8.80634 6.2491C8.77137 6.26915 8.73659 6.28953 8.702 6.31022C7.94634 6.76246 6.91891 6.89623 6.03237 6.3778C5.82611 6.25719 5.55643 6.3235 5.4295 6.54618L4.06296 8.94353C3.93162 9.17396 4.0146 9.46279 4.22888 9.5881C5.11053 10.1037 5.50474 11.0605 5.49681 11.938L5.49653 12L5.49681 12.062C5.50475 12.9395 5.11053 13.8963 4.22888 14.4119C4.0146 14.5372 3.93162 14.8261 4.06296 15.0565L5.42949 17.4538C5.55643 17.6765 5.82612 17.7428 6.03237 17.6222L6.53717 18.4854L6.03237 17.6222C6.91892 17.1038 7.94635 17.2375 8.70202 17.6898L8.70203 17.6898C8.73658 17.7105 8.77135 17.7308 8.80635 17.7509C9.56936 18.1884 10.1862 19.015 10.1862 20.035C10.1862 20.3039 10.3981 20.5 10.6326 20.5H13.3674C13.6019 20.5 13.8138 20.3039 13.8138 20.035C13.8138 19.015 14.4306 18.1884 15.1937 17.7509L15.6911 18.6184L15.1936 17.7509C15.1937 17.7509 15.1937 17.7509 15.1937 17.7509C15.2287 17.7308 15.2634 17.7105 15.298 17.6898L15.8115 18.5479L15.298 17.6898C16.0536 17.2375 17.0811 17.1038 17.9676 17.6222C18.1739 17.7428 18.4436 17.6765 18.5705 17.4538L19.937 15.0565C20.0684 14.8261 19.9854 14.5372 19.7711 14.4119C18.8895 13.8963 18.4953 12.9395 18.5032 12.062V12.062L18.5034 12.0265L19.497 12.0302L18.5034 12.0264L18.5035 12L18.5032 11.938V11.938C18.4953 11.0605 18.8895 10.1037 19.7711 9.5881C19.9854 9.46279 20.0684 9.17396 19.937 8.94354L18.5705 6.54618C18.4436 6.3235 18.1739 6.25719 17.9676 6.3778C17.0811 6.89623 16.0537 6.76246 15.298 6.31022L15.8115 5.45215L15.298 6.31022C15.2634 6.28953 15.2286 6.26915 15.1937 6.2491C14.4306 5.81161 13.8138 4.98498 13.8138 3.96499C13.8138 3.69609 13.6019 3.5 13.3674 3.5H10.6326ZM8.1862 3.96499C8.1862 2.6157 9.26947 1.5 10.6326 1.5H13.3674C14.7305 1.5 15.8138 2.6157 15.8138 3.96499C15.8138 4.13868 15.9259 4.36351 16.1885 4.51406C16.2343 4.54032 16.2798 4.56699 16.325 4.59407C16.5852 4.74977 16.8228 4.73044 16.958 4.65133C18.1358 3.96259 19.6359 4.37659 20.308 5.55575L21.6746 7.95311C22.3428 9.12533 21.9513 10.6301 20.7807 11.3146C20.6417 11.3959 20.5002 11.6037 20.5031 11.9199C20.5033 11.9466 20.5035 11.9733 20.5035 12L20.5034 12.0339V12.034L20.5031 12.0801C20.5002 12.3963 20.6417 12.6042 20.7807 12.6854C21.9513 13.37 22.3428 14.8747 21.6746 16.0469L20.308 18.4443C19.6359 19.6234 18.1358 20.0374 16.958 19.3487C16.8227 19.2696 16.5852 19.2502 16.325 19.4059C16.2798 19.433 16.2342 19.4597 16.1885 19.4859L15.7151 18.6603L16.1885 19.4859C15.9259 19.6365 15.8138 19.8613 15.8138 20.035C15.8138 21.3843 14.7305 22.5 13.3674 22.5H10.6326C9.26947 22.5 8.1862 21.3843 8.1862 20.035C8.1862 19.8613 8.0741 19.6365 7.81152 19.4859C7.76575 19.4597 7.72023 19.433 7.67497 19.4059C7.41481 19.2502 7.17725 19.2696 7.04197 19.3487C5.86418 20.0374 4.36409 19.6234 3.69195 18.4443L2.32542 16.0469C1.65723 14.8747 2.04873 13.37 3.21928 12.6854C3.35829 12.6042 3.49975 12.3963 3.49689 12.0801L4.49685 12.0711L3.49689 12.0801C3.49665 12.0534 3.49653 12.0267 3.49653 12C3.49653 11.9733 3.49665 11.9466 3.49689 11.9199C3.49975 11.6037 3.35829 11.3959 3.21928 11.3146C2.04873 10.6301 1.65724 9.12533 2.32542 7.95311L3.69195 5.55575C4.3641 4.37659 5.86418 3.96259 7.04197 4.65133C7.17724 4.73044 7.4148 4.74977 7.67495 4.59407L8.18848 5.45215L7.67495 4.59407C7.7202 4.56699 7.76573 4.54032 7.81152 4.51406C8.0741 4.36351 8.1862 4.13868 8.1862 3.96499ZM12 9.2C10.4923 9.2 9.24826 10.4415 9.24826 12C9.24826 13.5585 10.4923 14.8 12 14.8C13.5077 14.8 14.7517 13.5585 14.7517 12C14.7517 10.4415 13.5077 9.2 12 9.2ZM7.24826 12C7.24826 9.36112 9.36367 7.2 12 7.2C14.6363 7.2 16.7517 9.36112 16.7517 12C16.7517 14.6389 14.6363 16.8 12 16.8C9.36367 16.8 7.24826 14.6389 7.24826 12Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
export default function Tetrate({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 694 701"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M343.5 226C390.72 226 429 264.28 429 311.5C429 321.676 427.222 331.436 423.961 340.487C409.127 381.654 375 419.742 375 463.499C375 507.257 409.127 545.346 423.961 586.512C427.222 595.564 429 605.324 429 615.5C429 662.72 390.72 701 343.5 701C296.28 701 258 662.72 258 615.5C258 606.44 259.409 597.709 262.021 589.514C275.211 548.119 308 509.828 308 466.382V460.616C308 417.171 275.211 378.88 262.021 337.485C259.409 329.291 258 320.56 258 311.5C258 264.28 296.28 226 343.5 226Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M272.622 39.9541C297.034 1.458 347.7 -11.255 387.546 11.75C395.392 16.2801 402.249 21.8668 408.04 28.2256C437.293 60.3466 454.06 107.888 491.685 129.61L496.678 132.493C534.303 154.216 583.859 144.966 626.303 154.239C634.705 156.075 642.971 159.22 650.817 163.75C691.711 187.36 705.722 239.652 682.112 280.546C658.502 321.44 606.211 335.451 565.317 311.841C556.505 306.753 548.941 300.333 542.733 292.982C514.499 259.553 498.577 210.955 460.682 189.076C436.139 174.906 406.497 174.321 376.956 173.566C358.811 173.102 340.613 173.735 322.491 174.754C290.52 176.551 257.865 176.779 231.635 192.853C194.326 215.717 179.682 264.716 152.333 298.874C146.319 306.385 138.926 313 130.25 318.317C89.9879 342.99 37.3483 330.353 12.6754 290.091C-11.9974 249.829 0.639271 197.189 40.901 172.517C48.6259 167.783 56.8065 164.422 65.1578 162.367C107.345 151.985 157.126 159.934 194.17 137.233L199.086 134.221C232.774 113.576 248.658 72.037 272.622 39.9541Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
export default function Time({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M6.34315 6.34314C9.46734 3.21895 14.5327 3.21895 17.6569 6.34315C20.781 9.46734 20.781 14.5327 17.6569 17.6569C14.5327 20.781 9.46734 20.781 6.34315 17.6569C3.21895 14.5327 3.21895 9.46734 6.34315 6.34314ZM19.0711 4.92893C15.1658 1.02369 8.83417 1.02369 4.92893 4.92893C1.02369 8.83417 1.02369 15.1658 4.92893 19.0711C8.83418 22.9763 15.1658 22.9763 19.0711 19.0711C22.9763 15.1658 22.9763 8.83418 19.0711 4.92893ZM13 8.5C13 7.94772 12.5523 7.5 12 7.5C11.4477 7.5 11 7.94772 11 8.5V13C11 13.5523 11.4477 14 12 14H15.5C16.0523 14 16.5 13.5523 16.5 13C16.5 12.4477 16.0523 12 15.5 12H13V8.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
export default function Youtube({ className = '' }) {
|
||||
return (
|
||||
<svg
|
||||
width="1.5rem"
|
||||
height="1.5rem"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 576 512"
|
||||
aria-hidden="true"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M549.7 124.1c-6.3-23.7-24.8-42.3-48.3-48.6C458.8 64 288 64 288 64S117.2 64 74.6 75.5c-23.5 6.3-42 24.9-48.3 48.6-11.4 42.9-11.4 132.3-11.4 132.3s0 89.4 11.4 132.3c6.3 23.7 24.8 41.5 48.3 47.8C117.2 448 288 448 288 448s170.8 0 213.4-11.5c23.5-6.3 42-24.2 48.3-47.8 11.4-42.9 11.4-132.3 11.4-132.3s0-89.4-11.4-132.3zm-317.5 213.5V175.2l142.7 81.2-142.7 81.2z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,51 +1,25 @@
|
|||
import ArrowDown from './ArrowDown';
|
||||
import ArrowUp from './ArrowUp';
|
||||
import Attach from './Attach';
|
||||
import Back from './Back';
|
||||
import ChatGPT from './ChatGPT';
|
||||
import { Bird1 } from './Bird1';
|
||||
import { Bird2 } from './Bird2';
|
||||
import { Bird3 } from './Bird3';
|
||||
import { Bird4 } from './Bird4';
|
||||
import { Bird5 } from './Bird5';
|
||||
import { Bird6 } from './Bird6';
|
||||
import ChatSmart from './ChatSmart';
|
||||
import Check from './Check';
|
||||
import ChevronDown from './ChevronDown';
|
||||
import ChevronUp from './ChevronUp';
|
||||
import { ChevronRight } from './ChevronRight';
|
||||
import Close from './Close';
|
||||
import { CodeXml } from './CodeXml';
|
||||
import { Cog } from './Cog';
|
||||
import CoinIcon from './CoinIcon';
|
||||
import Copy from './Copy';
|
||||
import Discord from './Discord';
|
||||
import Document from './Document';
|
||||
import Edit from './Edit';
|
||||
import { Fuel } from './Fuel';
|
||||
import { GalleryHorizontalEnd } from './GalleryHorizontalEnd';
|
||||
import { Gavel } from './Gavel';
|
||||
import { GlassWater } from './GlassWater';
|
||||
import { Grape } from './Grape';
|
||||
import Idea from './Idea';
|
||||
import LinkedIn from './LinkedIn';
|
||||
import More from './More';
|
||||
import OpenRouter from './OpenRouter';
|
||||
import Ollama from './Ollama';
|
||||
import Refresh from './Refresh';
|
||||
import SensitiveHidden from './SensitiveHidden';
|
||||
import SensitiveVisible from './SensitiveVisible';
|
||||
import Send from './Send';
|
||||
import Settings from './Settings';
|
||||
import Time from './Time';
|
||||
import { Gear } from './Gear';
|
||||
import Youtube from './Youtube';
|
||||
import { Goose } from './Goose';
|
||||
import Anthropic from './Anthropic';
|
||||
import { ArrowRight } from './ArrowRight';
|
||||
import { Key } from './Key';
|
||||
import OpenAI from './OpenAI';
|
||||
import Tetrate from './Tetrate';
|
||||
import { Microphone } from './Microphone';
|
||||
import { Watch0 } from './Watch0';
|
||||
import { Watch1 } from './Watch1';
|
||||
|
|
@ -59,45 +33,25 @@ export {
|
|||
ArrowDown,
|
||||
ArrowUp,
|
||||
Attach,
|
||||
Back,
|
||||
ChatGPT,
|
||||
Bird1,
|
||||
Bird2,
|
||||
Bird3,
|
||||
Bird4,
|
||||
Bird5,
|
||||
Bird6,
|
||||
ChatSmart,
|
||||
Check,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
ChevronUp,
|
||||
Close,
|
||||
CodeXml,
|
||||
Cog,
|
||||
CoinIcon,
|
||||
Copy,
|
||||
Discord,
|
||||
Document,
|
||||
Edit,
|
||||
Fuel,
|
||||
GalleryHorizontalEnd,
|
||||
Gavel,
|
||||
Gear,
|
||||
GlassWater,
|
||||
Grape,
|
||||
Idea,
|
||||
LinkedIn,
|
||||
Microphone,
|
||||
More,
|
||||
OpenRouter,
|
||||
Ollama,
|
||||
Refresh,
|
||||
SensitiveHidden,
|
||||
SensitiveVisible,
|
||||
Send,
|
||||
Settings,
|
||||
Time,
|
||||
Watch0,
|
||||
Watch1,
|
||||
Watch2,
|
||||
|
|
@ -105,11 +59,5 @@ export {
|
|||
Watch4,
|
||||
Watch5,
|
||||
Watch6,
|
||||
Youtube,
|
||||
Goose,
|
||||
Anthropic,
|
||||
ArrowRight,
|
||||
Key,
|
||||
OpenAI,
|
||||
Tetrate,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
export const Harddrive = ({ className }: { className?: string }) => (
|
||||
<svg
|
||||
width="11"
|
||||
height="11"
|
||||
viewBox="0 0 11 11"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<rect width="11" height="11" rx="2" fill="#1E1E20" />
|
||||
<path
|
||||
d="M5 3.5C5 4.32843 4.32843 5 3.5 5C2.67157 5 2 4.32843 2 3.5C2 2.67157 2.67157 2 3.5 2C4.32843 2 5 2.67157 5 3.5Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
|
@ -8,7 +8,6 @@ export { FileEdit } from './FileEdit';
|
|||
export { FilePlus } from './FilePlus';
|
||||
export { FileText } from './FileText';
|
||||
export { Globe } from './Globe';
|
||||
export { Harddrive } from './Harddrive';
|
||||
export { Monitor } from './Monitor';
|
||||
export { Numbers } from './Numbers';
|
||||
export { Save } from './Save';
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@ const i18n = defineMessages({
|
|||
},
|
||||
localModelsNote: {
|
||||
id: 'localModelPicker.localModelsNote',
|
||||
defaultMessage: 'Local models keep everything on your machine for full privacy. Performance and context window size may vary compared to cloud providers depending on your hardware and model size.',
|
||||
defaultMessage:
|
||||
'Local models keep everything on your machine for full privacy. Performance and context window size may vary compared to cloud providers depending on your hardware and model size.',
|
||||
},
|
||||
failedToLoad: {
|
||||
id: 'localModelPicker.failedToLoad',
|
||||
|
|
@ -310,7 +311,9 @@ export default function LocalModelPicker({ onConfigured }: LocalModelPickerProps
|
|||
onClick={() => setShowAllModels(!showAllModels)}
|
||||
className="text-sm text-blue-500 hover:text-blue-400 transition-colors flex items-center gap-1"
|
||||
>
|
||||
{showAllModels ? intl.formatMessage(i18n.hideOtherSizes) : intl.formatMessage(i18n.showOtherSizes, { count: otherModels.length })}
|
||||
{showAllModels
|
||||
? intl.formatMessage(i18n.hideOtherSizes)
|
||||
: intl.formatMessage(i18n.showOtherSizes, { count: otherModels.length })}
|
||||
<svg
|
||||
className={`w-3.5 h-3.5 transition-transform ${showAllModels ? 'rotate-180' : ''}`}
|
||||
fill="none"
|
||||
|
|
@ -376,10 +379,12 @@ export default function LocalModelPicker({ onConfigured }: LocalModelPickerProps
|
|||
{selectedModel?.status.state === 'Downloaded'
|
||||
? intl.formatMessage(i18n.useModel, { modelId: selectedModel.id })
|
||||
: selectedModel
|
||||
? intl.formatMessage(i18n.downloadModel, { modelId: selectedModel.id, size: formatSize(selectedModel.sizeBytes) })
|
||||
? intl.formatMessage(i18n.downloadModel, {
|
||||
modelId: selectedModel.id,
|
||||
size: formatSize(selectedModel.sizeBytes),
|
||||
})
|
||||
: intl.formatMessage(i18n.selectModel)}
|
||||
</button>
|
||||
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -427,7 +432,9 @@ export default function LocalModelPicker({ onConfigured }: LocalModelPickerProps
|
|||
) : (
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-t-2 border-b-2 border-text-muted"></div>
|
||||
<span className="text-sm text-text-muted">{intl.formatMessage(i18n.startingDownload)}</span>
|
||||
<span className="text-sm text-text-muted">
|
||||
{intl.formatMessage(i18n.startingDownload)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -106,7 +106,12 @@ export default function OnboardingGuard({ children }: OnboardingGuardProps) {
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isCheckingProvider && !hasProvider && !checkProviderError && !hasTrackedOnboardingStart.current) {
|
||||
if (
|
||||
!isCheckingProvider &&
|
||||
!hasProvider &&
|
||||
!checkProviderError &&
|
||||
!hasTrackedOnboardingStart.current
|
||||
) {
|
||||
trackOnboardingStarted();
|
||||
hasTrackedOnboardingStart.current = true;
|
||||
}
|
||||
|
|
@ -152,11 +157,13 @@ export default function OnboardingGuard({ children }: OnboardingGuardProps) {
|
|||
<div className="mb-4">
|
||||
<Goose className="size-8 mx-auto" />
|
||||
</div>
|
||||
<h1 className="text-xl font-light mb-3">{intl.formatMessage(i18n.checkProviderErrorTitle)}</h1>
|
||||
<p className="text-text-muted mb-6">{intl.formatMessage(i18n.checkProviderErrorDescription)}</p>
|
||||
<Button onClick={() => checkProvider()}>
|
||||
{intl.formatMessage(i18n.retry)}
|
||||
</Button>
|
||||
<h1 className="text-xl font-light mb-3">
|
||||
{intl.formatMessage(i18n.checkProviderErrorTitle)}
|
||||
</h1>
|
||||
<p className="text-text-muted mb-6">
|
||||
{intl.formatMessage(i18n.checkProviderErrorDescription)}
|
||||
</p>
|
||||
<Button onClick={() => checkProvider()}>{intl.formatMessage(i18n.retry)}</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -185,7 +192,9 @@ export default function OnboardingGuard({ children }: OnboardingGuardProps) {
|
|||
<div className="mb-4">
|
||||
<Goose className="size-8" />
|
||||
</div>
|
||||
<h1 className="text-2xl sm:text-4xl font-light mb-3">{intl.formatMessage(i18n.welcomeTitle)}</h1>
|
||||
<h1 className="text-2xl sm:text-4xl font-light mb-3">
|
||||
{intl.formatMessage(i18n.welcomeTitle)}
|
||||
</h1>
|
||||
<p className="text-text-muted text-base sm:text-lg">
|
||||
{intl.formatMessage(i18n.welcomeDescription)}
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ const i18n = defineMessages({
|
|||
},
|
||||
privacyDescription: {
|
||||
id: 'onboardingSuccess.privacyDescription',
|
||||
defaultMessage: 'Anonymous usage data helps improve goose. We never collect your conversations, code, or personal data.',
|
||||
defaultMessage:
|
||||
'Anonymous usage data helps improve goose. We never collect your conversations, code, or personal data.',
|
||||
},
|
||||
learnMore: {
|
||||
id: 'onboardingSuccess.learnMore',
|
||||
|
|
@ -80,7 +81,9 @@ export default function OnboardingSuccess({ providerName, onFinish }: Onboarding
|
|||
</div>
|
||||
|
||||
<div className="w-full p-4 bg-transparent border rounded-xl text-left mb-6">
|
||||
<h3 className="font-medium text-text-default text-sm mb-1">{intl.formatMessage(i18n.privacyTitle)}</h3>
|
||||
<h3 className="font-medium text-text-default text-sm mb-1">
|
||||
{intl.formatMessage(i18n.privacyTitle)}
|
||||
</h3>
|
||||
<p className="text-text-muted text-sm">
|
||||
{intl.formatMessage(i18n.privacyDescription)}{' '}
|
||||
<button
|
||||
|
|
@ -97,7 +100,9 @@ export default function OnboardingSuccess({ providerName, onFinish }: Onboarding
|
|||
onChange={(e) => setTelemetryOptIn(e.target.checked)}
|
||||
className="rounded"
|
||||
/>
|
||||
<span className="text-text-muted text-sm">{intl.formatMessage(i18n.shareUsageData)}</span>
|
||||
<span className="text-text-muted text-sm">
|
||||
{intl.formatMessage(i18n.shareUsageData)}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ const i18n = defineMessages({
|
|||
},
|
||||
description: {
|
||||
id: 'privacyInfoModal.description',
|
||||
defaultMessage: 'Anonymous usage data helps us understand how goose is used and identify areas for improvement.',
|
||||
defaultMessage:
|
||||
'Anonymous usage data helps us understand how goose is used and identify areas for improvement.',
|
||||
},
|
||||
whatWeCollect: {
|
||||
id: 'privacyInfoModal.whatWeCollect',
|
||||
|
|
@ -40,7 +41,8 @@ const i18n = defineMessages({
|
|||
},
|
||||
neverCollect: {
|
||||
id: 'privacyInfoModal.neverCollect',
|
||||
defaultMessage: 'We never collect your conversations, code, tool arguments, error messages, or any personal data. You can change this setting anytime in Settings.',
|
||||
defaultMessage:
|
||||
'We never collect your conversations, code, tool arguments, error messages, or any personal data. You can change this setting anytime in Settings.',
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -60,10 +62,10 @@ export default function PrivacyInfoModal({ isOpen, onClose }: PrivacyInfoModalPr
|
|||
</DialogHeader>
|
||||
|
||||
<div>
|
||||
<p className="text-text-muted text-sm mb-3">
|
||||
{intl.formatMessage(i18n.description)}
|
||||
<p className="text-text-muted text-sm mb-3">{intl.formatMessage(i18n.description)}</p>
|
||||
<p className="font-medium text-text-default text-sm mb-1.5">
|
||||
{intl.formatMessage(i18n.whatWeCollect)}
|
||||
</p>
|
||||
<p className="font-medium text-text-default text-sm mb-1.5">{intl.formatMessage(i18n.whatWeCollect)}</p>
|
||||
<ul className="text-text-muted text-sm list-disc list-outside space-y-0.5 ml-5 mb-3">
|
||||
<li>{intl.formatMessage(i18n.collectOs)}</li>
|
||||
<li>{intl.formatMessage(i18n.collectVersion)}</li>
|
||||
|
|
@ -72,9 +74,7 @@ export default function PrivacyInfoModal({ isOpen, onClose }: PrivacyInfoModalPr
|
|||
<li>{intl.formatMessage(i18n.collectSession)}</li>
|
||||
<li>{intl.formatMessage(i18n.collectErrors)}</li>
|
||||
</ul>
|
||||
<p className="text-text-muted text-sm">
|
||||
{intl.formatMessage(i18n.neverCollect)}
|
||||
</p>
|
||||
<p className="text-text-muted text-sm">{intl.formatMessage(i18n.neverCollect)}</p>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
|
|
|||
|
|
@ -146,7 +146,9 @@ 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>
|
||||
|
|
|
|||
|
|
@ -47,7 +47,8 @@ const i18n = defineMessages({
|
|||
},
|
||||
reviewWarning: {
|
||||
id: 'importRecipeForm.reviewWarning',
|
||||
defaultMessage: 'Ensure you review contents of recipe files before adding them to your goose interface.',
|
||||
defaultMessage:
|
||||
'Ensure you review contents of recipe files before adding them to your goose interface.',
|
||||
},
|
||||
cancel: {
|
||||
id: 'importRecipeForm.cancel',
|
||||
|
|
@ -67,7 +68,8 @@ const i18n = defineMessages({
|
|||
},
|
||||
schemaDescription: {
|
||||
id: 'importRecipeForm.schemaDescription',
|
||||
defaultMessage: 'Your YAML or JSON file should follow this structure. Required fields are: title, description, and either instructions or prompt.',
|
||||
defaultMessage:
|
||||
'Your YAML or JSON file should follow this structure. Required fields are: title, description, and either instructions or prompt.',
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -208,7 +210,9 @@ export default function ImportRecipeForm({ isOpen, onClose, onSuccess }: ImportR
|
|||
<>
|
||||
<div className="fixed inset-0 z-[300] flex items-center justify-center bg-black/50">
|
||||
<div className="bg-background-primary border border-border-primary rounded-lg p-6 w-[500px] max-w-[90vw]">
|
||||
<h3 className="text-lg font-medium text-text-primary mb-4">{intl.formatMessage(i18n.importRecipeTitle)}</h3>
|
||||
<h3 className="text-lg font-medium text-text-primary mb-4">
|
||||
{intl.formatMessage(i18n.importRecipeTitle)}
|
||||
</h3>
|
||||
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
|
|
@ -354,7 +358,9 @@ export default function ImportRecipeForm({ isOpen, onClose, onSuccess }: ImportR
|
|||
disabled={!canSubmit || importing || isSubmitting}
|
||||
variant="default"
|
||||
>
|
||||
{importing || isSubmitting ? intl.formatMessage(i18n.importing) : intl.formatMessage(i18n.importRecipeButton)}
|
||||
{importing || isSubmitting
|
||||
? intl.formatMessage(i18n.importing)
|
||||
: intl.formatMessage(i18n.importRecipeButton)}
|
||||
</Button>
|
||||
)}
|
||||
</importRecipeForm.Subscribe>
|
||||
|
|
@ -368,7 +374,9 @@ export default function ImportRecipeForm({ isOpen, onClose, onSuccess }: ImportR
|
|||
<div className="fixed inset-0 z-[400] flex items-center justify-center bg-black/50">
|
||||
<div className="bg-background-primary border border-border-primary rounded-lg p-6 w-[800px] max-w-[90vw] max-h-[80vh] flex flex-col">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-medium text-text-primary">{intl.formatMessage(i18n.expectedRecipeStructure)}</h3>
|
||||
<h3 className="text-lg font-medium text-text-primary">
|
||||
{intl.formatMessage(i18n.expectedRecipeStructure)}
|
||||
</h3>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowSchemaModal(false)}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ const i18n = defineMessages({
|
|||
},
|
||||
activitiesDescription: {
|
||||
id: 'recipeActivityEditor.activitiesDescription',
|
||||
defaultMessage: 'The top-line prompts and activity buttons that will display in the recipe chat window.',
|
||||
defaultMessage:
|
||||
'The top-line prompts and activity buttons that will display in the recipe chat window.',
|
||||
},
|
||||
messageLabel: {
|
||||
id: 'recipeActivityEditor.messageLabel',
|
||||
|
|
@ -17,11 +18,13 @@ const i18n = defineMessages({
|
|||
},
|
||||
messageDescription: {
|
||||
id: 'recipeActivityEditor.messageDescription',
|
||||
defaultMessage: 'A formatted message that will appear at the top of the recipe. Supports markdown formatting.',
|
||||
defaultMessage:
|
||||
'A formatted message that will appear at the top of the recipe. Supports markdown formatting.',
|
||||
},
|
||||
messagePlaceholder: {
|
||||
id: 'recipeActivityEditor.messagePlaceholder',
|
||||
defaultMessage: 'Enter a user facing introduction message for your recipe (supports **bold**, *italic*, `code`, etc.)',
|
||||
defaultMessage:
|
||||
'Enter a user facing introduction message for your recipe (supports **bold**, *italic*, `code`, etc.)',
|
||||
},
|
||||
activityButtonsLabel: {
|
||||
id: 'recipeActivityEditor.activityButtonsLabel',
|
||||
|
|
@ -29,7 +32,8 @@ const i18n = defineMessages({
|
|||
},
|
||||
activityButtonsDescription: {
|
||||
id: 'recipeActivityEditor.activityButtonsDescription',
|
||||
defaultMessage: 'Clickable buttons that will appear below the message to help users interact with your recipe.',
|
||||
defaultMessage:
|
||||
'Clickable buttons that will appear below the message to help users interact with your recipe.',
|
||||
},
|
||||
addNewActivityPlaceholder: {
|
||||
id: 'recipeActivityEditor.addNewActivityPlaceholder',
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -107,7 +107,9 @@ Use {{parameter_name}} syntax for any user-provided values.`;
|
|||
>
|
||||
<div className="bg-background-primary border border-border-primary rounded-lg p-6 w-[900px] max-w-[90vw] max-h-[90vh] overflow-hidden flex flex-col">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-medium text-text-primary">{intl.formatMessage(i18n.title)}</h3>
|
||||
<h3 className="text-lg font-medium text-text-primary">
|
||||
{intl.formatMessage(i18n.title)}
|
||||
</h3>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCancel}
|
||||
|
|
@ -120,7 +122,9 @@ Use {{parameter_name}} syntax for any user-provided values.`;
|
|||
<div className="flex-1 flex flex-col min-h-0">
|
||||
<div className="mb-4">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label className="block text-sm font-medium text-text-primary">{intl.formatMessage(i18n.label)}</label>
|
||||
<label className="block text-sm font-medium text-text-primary">
|
||||
{intl.formatMessage(i18n.label)}
|
||||
</label>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={insertExample}
|
||||
|
|
|
|||
|
|
@ -127,7 +127,9 @@ export default function JsonSchemaEditor({
|
|||
>
|
||||
<div className="bg-background-primary border border-border-primary rounded-lg p-6 w-[800px] max-w-[90vw] max-h-[90vh] overflow-hidden flex flex-col">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-medium text-text-primary">{intl.formatMessage(i18n.title)}</h3>
|
||||
<h3 className="text-lg font-medium text-text-primary">
|
||||
{intl.formatMessage(i18n.title)}
|
||||
</h3>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCancel}
|
||||
|
|
|
|||
|
|
@ -185,9 +185,7 @@ export const RecipeModelSelector = ({
|
|||
<label className="block text-sm font-medium text-textStandard mb-2">
|
||||
{intl.formatMessage(i18n.providerLabel)}
|
||||
</label>
|
||||
<p className="text-xs text-textSubtle mb-2">
|
||||
{intl.formatMessage(i18n.providerHint)}
|
||||
</p>
|
||||
<p className="text-xs text-textSubtle mb-2">{intl.formatMessage(i18n.providerHint)}</p>
|
||||
<Select
|
||||
options={providerOptions}
|
||||
value={
|
||||
|
|
@ -203,7 +201,9 @@ export const RecipeModelSelector = ({
|
|||
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<label className="block text-sm font-medium text-textStandard">{intl.formatMessage(i18n.modelLabel)}</label>
|
||||
<label className="block text-sm font-medium text-textStandard">
|
||||
{intl.formatMessage(i18n.modelLabel)}
|
||||
</label>
|
||||
{isCustomModel && (
|
||||
<button
|
||||
onClick={() => {
|
||||
|
|
@ -217,9 +217,7 @@ export const RecipeModelSelector = ({
|
|||
</button>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-textSubtle mb-2">
|
||||
{intl.formatMessage(i18n.modelHint)}
|
||||
</p>
|
||||
<p className="text-xs text-textSubtle mb-2">{intl.formatMessage(i18n.modelHint)}</p>
|
||||
{isCustomModel ? (
|
||||
<Input
|
||||
type="text"
|
||||
|
|
|
|||
|
|
@ -1,81 +0,0 @@
|
|||
import { recipeNameSchema, RECIPE_NAME_PLACEHOLDER } from './recipeNameUtils';
|
||||
import { defineMessages, useIntl } from '../../../i18n';
|
||||
|
||||
const i18n = defineMessages({
|
||||
defaultLabel: {
|
||||
id: 'recipeNameField.defaultLabel',
|
||||
defaultMessage: 'Recipe Name',
|
||||
},
|
||||
formatHint: {
|
||||
id: 'recipeNameField.formatHint',
|
||||
defaultMessage: 'Will be automatically formatted (lowercase, dashes for spaces)',
|
||||
},
|
||||
});
|
||||
|
||||
interface RecipeNameFieldProps {
|
||||
id: string;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
onBlur: () => void;
|
||||
errors: string[];
|
||||
label?: string;
|
||||
required?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function RecipeNameField({
|
||||
id,
|
||||
value,
|
||||
onChange,
|
||||
onBlur,
|
||||
errors,
|
||||
label,
|
||||
required = true,
|
||||
disabled = false,
|
||||
}: RecipeNameFieldProps) {
|
||||
const intl = useIntl();
|
||||
const displayLabel = label || intl.formatMessage(i18n.defaultLabel);
|
||||
return (
|
||||
<div>
|
||||
<label htmlFor={id} className="block text-sm font-medium text-text-primary mb-2">
|
||||
{displayLabel} {required && <span className="text-red-500">*</span>}
|
||||
</label>
|
||||
<input
|
||||
id={id}
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
// Allow typing normally, only filter out invalid characters but keep spaces
|
||||
const rawValue = e.target.value;
|
||||
const filtered = rawValue.replace(/[^a-zA-Z0-9\s-]/g, '');
|
||||
onChange(filtered);
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
// Transform on blur: convert to lowercase and replace spaces with dashes
|
||||
const rawValue = e.target.value;
|
||||
const transformed = rawValue
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/[^a-z0-9-]/g, '')
|
||||
.replace(/-+/g, '-')
|
||||
.replace(/^-+|-+$/g, '');
|
||||
|
||||
onChange(transformed);
|
||||
onBlur();
|
||||
}}
|
||||
disabled={disabled}
|
||||
className={`w-full p-3 border rounded-lg bg-background-primary text-text-primary focus:outline-none focus:ring-2 focus:ring-blue-500 ${
|
||||
errors.length > 0 ? 'border-red-500' : 'border-border-primary'
|
||||
} ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||
placeholder={RECIPE_NAME_PLACEHOLDER}
|
||||
data-testid="recipe-name-input"
|
||||
/>
|
||||
<p className="text-xs text-text-secondary mt-1">
|
||||
{intl.formatMessage(i18n.formatHint)}
|
||||
</p>
|
||||
{errors.length > 0 && <p className="text-red-500 text-sm mt-1">{errors[0]}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { recipeNameSchema };
|
||||
|
|
@ -22,7 +22,8 @@ const i18n = defineMessages({
|
|||
},
|
||||
description: {
|
||||
id: 'subRecipeEditor.description',
|
||||
defaultMessage: 'Subrecipes are recipes that can be called as tools during execution. They enable multi-step workflows and reusable components.',
|
||||
defaultMessage:
|
||||
'Subrecipes are recipes that can be called as tools during execution. They enable multi-step workflows and reusable components.',
|
||||
},
|
||||
sequential: {
|
||||
id: 'subRecipeEditor.sequential',
|
||||
|
|
@ -118,7 +119,9 @@ export default function SubRecipeEditor({ subRecipes, onChange }: SubRecipeEdito
|
|||
return (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label className="block text-md text-textProminent font-bold">{intl.formatMessage(i18n.label)}</label>
|
||||
<label className="block text-md text-textProminent font-bold">
|
||||
{intl.formatMessage(i18n.label)}
|
||||
</label>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
|
|
@ -143,9 +146,7 @@ export default function SubRecipeEditor({ subRecipes, onChange }: SubRecipeEdito
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-textSubtle text-sm mb-4">
|
||||
{intl.formatMessage(i18n.description)}
|
||||
</p>
|
||||
<p className="text-textSubtle text-sm mb-4">{intl.formatMessage(i18n.description)}</p>
|
||||
|
||||
{subRecipes.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
|
|
@ -170,7 +171,9 @@ export default function SubRecipeEditor({ subRecipes, onChange }: SubRecipeEdito
|
|||
)}
|
||||
{subRecipe.values && Object.keys(subRecipe.values).length > 0 && (
|
||||
<div className="mt-2">
|
||||
<p className="text-xs text-text-muted mb-1">{intl.formatMessage(i18n.preconfiguredValues)}</p>
|
||||
<p className="text-xs text-text-muted mb-1">
|
||||
{intl.formatMessage(i18n.preconfiguredValues)}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{Object.entries(subRecipe.values).map(([key, value]) => (
|
||||
<span
|
||||
|
|
|
|||
|
|
@ -179,11 +179,11 @@ export default function SubRecipeModal({
|
|||
<div className="flex items-center justify-between p-6 border-b border-borderSubtle">
|
||||
<div>
|
||||
<h2 className="text-xl font-medium text-textProminent">
|
||||
{subRecipe ? intl.formatMessage(i18n.configureTitle) : intl.formatMessage(i18n.addTitle)}
|
||||
{subRecipe
|
||||
? intl.formatMessage(i18n.configureTitle)
|
||||
: intl.formatMessage(i18n.addTitle)}
|
||||
</h2>
|
||||
<p className="text-textSubtle text-sm">
|
||||
{intl.formatMessage(i18n.subtitle)}
|
||||
</p>
|
||||
<p className="text-textSubtle text-sm">{intl.formatMessage(i18n.subtitle)}</p>
|
||||
</div>
|
||||
<Button
|
||||
onClick={onClose}
|
||||
|
|
@ -214,9 +214,7 @@ export default function SubRecipeModal({
|
|||
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>
|
||||
|
||||
{/* Path Field */}
|
||||
|
|
@ -246,9 +244,7 @@ export default function SubRecipeModal({
|
|||
{intl.formatMessage(i18n.browse)}
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-text-muted mt-1">
|
||||
{intl.formatMessage(i18n.pathHint)}
|
||||
</p>
|
||||
<p className="text-xs text-text-muted mt-1">{intl.formatMessage(i18n.pathHint)}</p>
|
||||
</div>
|
||||
|
||||
{/* Description Field */}
|
||||
|
|
|
|||
|
|
@ -11,9 +11,6 @@ const parameterSchema = z.object({
|
|||
options: z.array(z.string()).nullable().optional(),
|
||||
});
|
||||
|
||||
// Export the parameter type for use in components
|
||||
export type RecipeParameter = z.infer<typeof parameterSchema>;
|
||||
|
||||
// Zod schema for SubRecipe - matching ACP SubRecipeDto type
|
||||
const subRecipeSchema = z.object({
|
||||
name: z.string().min(1, 'Subrecipe name is required'),
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Validation schema for recipe names
|
||||
*/
|
||||
export const recipeNameSchema = z.string().min(3, 'Recipe name must be at least 3 characters');
|
||||
|
||||
/**
|
||||
* Transform a string to a valid recipe name format:
|
||||
* - Convert to lowercase
|
||||
* - Replace spaces with dashes
|
||||
* - Remove invalid characters
|
||||
* - Trim whitespace and dashes
|
||||
*/
|
||||
export function transformToRecipeName(input: string): string {
|
||||
return input
|
||||
.toLowerCase()
|
||||
.replace(/[^a-zA-Z0-9\s-]/g, '') // Remove invalid characters
|
||||
.replace(/\s+/g, '-') // Replace spaces with dashes
|
||||
.replace(/--+/g, '-') // Replace multiple dashes with single dash
|
||||
.replace(/^-+|-+$/g, '') // Remove leading/trailing dashes
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a recipe name from a title
|
||||
*/
|
||||
export function generateRecipeNameFromTitle(title: string): string {
|
||||
if (!title.trim()) {
|
||||
return '';
|
||||
}
|
||||
return transformToRecipeName(title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Common placeholder text for recipe name inputs
|
||||
*/
|
||||
export const RECIPE_NAME_PLACEHOLDER = 'my-awesome-recipe';
|
||||
|
|
@ -25,14 +25,32 @@ import { defineMessages, useIntl } from '../../i18n';
|
|||
import { useNavigation } from '../../hooks/useNavigation';
|
||||
|
||||
const i18n = defineMessages({
|
||||
scheduleNotFound: { id: 'scheduleDetailView.scheduleNotFound', defaultMessage: 'Schedule Not Found' },
|
||||
noScheduleId: { id: 'scheduleDetailView.noScheduleId', defaultMessage: 'No schedule ID provided. Return to schedules list.' },
|
||||
scheduleNotFound: {
|
||||
id: 'scheduleDetailView.scheduleNotFound',
|
||||
defaultMessage: 'Schedule Not Found',
|
||||
},
|
||||
noScheduleId: {
|
||||
id: 'scheduleDetailView.noScheduleId',
|
||||
defaultMessage: 'No schedule ID provided. Return to schedules list.',
|
||||
},
|
||||
scheduleDetails: { id: 'scheduleDetailView.scheduleDetails', defaultMessage: 'Schedule Details' },
|
||||
viewingScheduleId: { id: 'scheduleDetailView.viewingScheduleId', defaultMessage: 'Viewing Schedule ID: {id}' },
|
||||
scheduleInformation: { id: 'scheduleDetailView.scheduleInformation', defaultMessage: 'Schedule Information' },
|
||||
loadingSchedule: { id: 'scheduleDetailView.loadingSchedule', defaultMessage: 'Loading schedule...' },
|
||||
viewingScheduleId: {
|
||||
id: 'scheduleDetailView.viewingScheduleId',
|
||||
defaultMessage: 'Viewing Schedule ID: {id}',
|
||||
},
|
||||
scheduleInformation: {
|
||||
id: 'scheduleDetailView.scheduleInformation',
|
||||
defaultMessage: 'Schedule Information',
|
||||
},
|
||||
loadingSchedule: {
|
||||
id: 'scheduleDetailView.loadingSchedule',
|
||||
defaultMessage: 'Loading schedule...',
|
||||
},
|
||||
errorPrefix: { id: 'scheduleDetailView.errorPrefix', defaultMessage: 'Error: {error}' },
|
||||
currentlyRunning: { id: 'scheduleDetailView.currentlyRunning', defaultMessage: 'Currently Running' },
|
||||
currentlyRunning: {
|
||||
id: 'scheduleDetailView.currentlyRunning',
|
||||
defaultMessage: 'Currently Running',
|
||||
},
|
||||
paused: { id: 'scheduleDetailView.paused', defaultMessage: 'Paused' },
|
||||
scheduleLabel: { id: 'scheduleDetailView.scheduleLabel', defaultMessage: 'Schedule:' },
|
||||
cronExpression: { id: 'scheduleDetailView.cronExpression', defaultMessage: 'Cron Expression:' },
|
||||
|
|
@ -45,37 +63,83 @@ const i18n = defineMessages({
|
|||
editSchedule: { id: 'scheduleDetailView.editSchedule', defaultMessage: 'Edit Schedule' },
|
||||
unpauseSchedule: { id: 'scheduleDetailView.unpauseSchedule', defaultMessage: 'Unpause Schedule' },
|
||||
pauseSchedule: { id: 'scheduleDetailView.pauseSchedule', defaultMessage: 'Pause Schedule' },
|
||||
inspectRunningJob: { id: 'scheduleDetailView.inspectRunningJob', defaultMessage: 'Inspect Running Job' },
|
||||
inspectRunningJob: {
|
||||
id: 'scheduleDetailView.inspectRunningJob',
|
||||
defaultMessage: 'Inspect Running Job',
|
||||
},
|
||||
killRunningJob: { id: 'scheduleDetailView.killRunningJob', defaultMessage: 'Kill Running Job' },
|
||||
cannotModifyRunning: { id: 'scheduleDetailView.cannotModifyRunning', defaultMessage: 'Cannot trigger or modify a schedule while it\'s already running.' },
|
||||
pausedWarning: { id: 'scheduleDetailView.pausedWarning', defaultMessage: 'This schedule is paused and will not run automatically. Use "Run Schedule Now" to trigger it manually or unpause to resume automatic execution.' },
|
||||
cannotModifyRunning: {
|
||||
id: 'scheduleDetailView.cannotModifyRunning',
|
||||
defaultMessage: "Cannot trigger or modify a schedule while it's already running.",
|
||||
},
|
||||
pausedWarning: {
|
||||
id: 'scheduleDetailView.pausedWarning',
|
||||
defaultMessage:
|
||||
'This schedule is paused and will not run automatically. Use "Run Schedule Now" to trigger it manually or unpause to resume automatic execution.',
|
||||
},
|
||||
recentSessions: { id: 'scheduleDetailView.recentSessions', defaultMessage: 'Recent Sessions' },
|
||||
loadingSessions: { id: 'scheduleDetailView.loadingSessions', defaultMessage: 'Loading sessions...' },
|
||||
noSessions: { id: 'scheduleDetailView.noSessions', defaultMessage: 'No sessions found for this schedule.' },
|
||||
loadingSessions: {
|
||||
id: 'scheduleDetailView.loadingSessions',
|
||||
defaultMessage: 'Loading sessions...',
|
||||
},
|
||||
noSessions: {
|
||||
id: 'scheduleDetailView.noSessions',
|
||||
defaultMessage: 'No sessions found for this schedule.',
|
||||
},
|
||||
sessionId: { id: 'scheduleDetailView.sessionId', defaultMessage: 'Session ID: {id}' },
|
||||
created: { id: 'scheduleDetailView.created', defaultMessage: 'Created: {date}' },
|
||||
messages: { id: 'scheduleDetailView.messages', defaultMessage: 'Messages: {count}' },
|
||||
dir: { id: 'scheduleDetailView.dir', defaultMessage: 'Dir: {path}' },
|
||||
idLabel: { id: 'scheduleDetailView.idLabel', defaultMessage: 'ID:' },
|
||||
jobCancelled: { id: 'scheduleDetailView.jobCancelled', defaultMessage: 'Job Cancelled' },
|
||||
jobCancelledMsg: { id: 'scheduleDetailView.jobCancelledMsg', defaultMessage: 'The job was cancelled while starting up.' },
|
||||
scheduleCompleted: { id: 'scheduleDetailView.scheduleCompleted', defaultMessage: 'Run completed' },
|
||||
completedSession: { id: 'scheduleDetailView.completedSession', defaultMessage: 'Session: {sessionId}' },
|
||||
runScheduleError: { id: 'scheduleDetailView.runScheduleError', defaultMessage: 'Run Schedule Error' },
|
||||
scheduleUnpaused: { id: 'scheduleDetailView.scheduleUnpaused', defaultMessage: 'Schedule Unpaused' },
|
||||
jobCancelledMsg: {
|
||||
id: 'scheduleDetailView.jobCancelledMsg',
|
||||
defaultMessage: 'The job was cancelled while starting up.',
|
||||
},
|
||||
scheduleCompleted: {
|
||||
id: 'scheduleDetailView.scheduleCompleted',
|
||||
defaultMessage: 'Run completed',
|
||||
},
|
||||
completedSession: {
|
||||
id: 'scheduleDetailView.completedSession',
|
||||
defaultMessage: 'Session: {sessionId}',
|
||||
},
|
||||
runScheduleError: {
|
||||
id: 'scheduleDetailView.runScheduleError',
|
||||
defaultMessage: 'Run Schedule Error',
|
||||
},
|
||||
scheduleUnpaused: {
|
||||
id: 'scheduleDetailView.scheduleUnpaused',
|
||||
defaultMessage: 'Schedule Unpaused',
|
||||
},
|
||||
unpausedMsg: { id: 'scheduleDetailView.unpausedMsg', defaultMessage: 'Unpaused "{id}"' },
|
||||
schedulePaused: { id: 'scheduleDetailView.schedulePaused', defaultMessage: 'Schedule Paused' },
|
||||
pausedMsg: { id: 'scheduleDetailView.pausedMsg', defaultMessage: 'Paused "{id}"' },
|
||||
pauseUnpauseError: { id: 'scheduleDetailView.pauseUnpauseError', defaultMessage: 'Pause/Unpause Error' },
|
||||
pauseUnpauseError: {
|
||||
id: 'scheduleDetailView.pauseUnpauseError',
|
||||
defaultMessage: 'Pause/Unpause Error',
|
||||
},
|
||||
jobKilled: { id: 'scheduleDetailView.jobKilled', defaultMessage: 'Job Killed' },
|
||||
killJobError: { id: 'scheduleDetailView.killJobError', defaultMessage: 'Kill Job Error' },
|
||||
jobInspection: { id: 'scheduleDetailView.jobInspection', defaultMessage: 'Job Inspection' },
|
||||
inspectNoInfo: { id: 'scheduleDetailView.inspectNoInfo', defaultMessage: 'No detailed information available' },
|
||||
inspectJobError: { id: 'scheduleDetailView.inspectJobError', defaultMessage: 'Inspect Job Error' },
|
||||
inspectNoInfo: {
|
||||
id: 'scheduleDetailView.inspectNoInfo',
|
||||
defaultMessage: 'No detailed information available',
|
||||
},
|
||||
inspectJobError: {
|
||||
id: 'scheduleDetailView.inspectJobError',
|
||||
defaultMessage: 'Inspect Job Error',
|
||||
},
|
||||
scheduleUpdated: { id: 'scheduleDetailView.scheduleUpdated', defaultMessage: 'Schedule Updated' },
|
||||
updatedMsg: { id: 'scheduleDetailView.updatedMsg', defaultMessage: 'Updated "{id}"' },
|
||||
updateScheduleError: { id: 'scheduleDetailView.updateScheduleError', defaultMessage: 'Update Schedule Error' },
|
||||
scheduleNotFoundError: { id: 'scheduleDetailView.scheduleNotFoundError', defaultMessage: 'Schedule not found' },
|
||||
updateScheduleError: {
|
||||
id: 'scheduleDetailView.updateScheduleError',
|
||||
defaultMessage: 'Update Schedule Error',
|
||||
},
|
||||
scheduleNotFoundError: {
|
||||
id: 'scheduleDetailView.scheduleNotFoundError',
|
||||
defaultMessage: 'Schedule not found',
|
||||
},
|
||||
});
|
||||
|
||||
interface ScheduleDetailViewProps {
|
||||
|
|
@ -125,23 +189,26 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
|
|||
}
|
||||
};
|
||||
|
||||
const fetchSchedule = useCallback(async (sId: string) => {
|
||||
setIsLoadingSchedule(true);
|
||||
setScheduleError(null);
|
||||
try {
|
||||
const allSchedules = await acpListSchedules();
|
||||
const schedule = allSchedules.find((s) => s.id === sId);
|
||||
if (schedule) {
|
||||
setScheduleDetails(schedule);
|
||||
} else {
|
||||
setScheduleError(intl.formatMessage(i18n.scheduleNotFoundError));
|
||||
const fetchSchedule = useCallback(
|
||||
async (sId: string) => {
|
||||
setIsLoadingSchedule(true);
|
||||
setScheduleError(null);
|
||||
try {
|
||||
const allSchedules = await acpListSchedules();
|
||||
const schedule = allSchedules.find((s) => s.id === sId);
|
||||
if (schedule) {
|
||||
setScheduleDetails(schedule);
|
||||
} else {
|
||||
setScheduleError(intl.formatMessage(i18n.scheduleNotFoundError));
|
||||
}
|
||||
} catch (err) {
|
||||
setScheduleError(errorMessage(err, 'Failed to fetch schedule'));
|
||||
} finally {
|
||||
setIsLoadingSchedule(false);
|
||||
}
|
||||
} catch (err) {
|
||||
setScheduleError(errorMessage(err, 'Failed to fetch schedule'));
|
||||
} finally {
|
||||
setIsLoadingSchedule(false);
|
||||
}
|
||||
}, [intl]);
|
||||
},
|
||||
[intl]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (scheduleId) {
|
||||
|
|
@ -150,12 +217,15 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
|
|||
}
|
||||
}, [scheduleId, fetchSchedule]);
|
||||
|
||||
const openSession = useCallback((sessionId: string) => {
|
||||
setView('pair', {
|
||||
disableAnimation: true,
|
||||
resumeSessionId: sessionId,
|
||||
});
|
||||
}, [setView]);
|
||||
const openSession = useCallback(
|
||||
(sessionId: string) => {
|
||||
setView('pair', {
|
||||
disableAnimation: true,
|
||||
resumeSessionId: sessionId,
|
||||
});
|
||||
},
|
||||
[setView]
|
||||
);
|
||||
|
||||
const handleRunNow = async () => {
|
||||
if (!scheduleId) return;
|
||||
|
|
@ -164,7 +234,10 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
|
|||
const result = await acpRunScheduleNow(scheduleId);
|
||||
trackScheduleRunNow(true);
|
||||
if (result.status === 'completed' && result.sessionId) {
|
||||
toastSuccess({ title: intl.formatMessage(i18n.scheduleCompleted), msg: intl.formatMessage(i18n.completedSession, { sessionId: result.sessionId }) });
|
||||
toastSuccess({
|
||||
title: intl.formatMessage(i18n.scheduleCompleted),
|
||||
msg: intl.formatMessage(i18n.completedSession, { sessionId: result.sessionId }),
|
||||
});
|
||||
}
|
||||
await fetchSessions(scheduleId);
|
||||
await fetchSchedule(scheduleId);
|
||||
|
|
@ -186,10 +259,16 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
|
|||
try {
|
||||
if (scheduleDetails.paused) {
|
||||
await acpUnpauseSchedule(scheduleId);
|
||||
toastSuccess({ title: intl.formatMessage(i18n.scheduleUnpaused), msg: intl.formatMessage(i18n.unpausedMsg, { id: scheduleId }) });
|
||||
toastSuccess({
|
||||
title: intl.formatMessage(i18n.scheduleUnpaused),
|
||||
msg: intl.formatMessage(i18n.unpausedMsg, { id: scheduleId }),
|
||||
});
|
||||
} else {
|
||||
await acpPauseSchedule(scheduleId);
|
||||
toastSuccess({ title: intl.formatMessage(i18n.schedulePaused), msg: intl.formatMessage(i18n.pausedMsg, { id: scheduleId }) });
|
||||
toastSuccess({
|
||||
title: intl.formatMessage(i18n.schedulePaused),
|
||||
msg: intl.formatMessage(i18n.pausedMsg, { id: scheduleId }),
|
||||
});
|
||||
}
|
||||
await fetchSchedule(scheduleId);
|
||||
} catch (err) {
|
||||
|
|
@ -235,7 +314,10 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
|
|||
msg: `Session: ${result.sessionId}\nRunning for: ${duration}`,
|
||||
});
|
||||
} else {
|
||||
toastSuccess({ title: intl.formatMessage(i18n.jobInspection), msg: intl.formatMessage(i18n.inspectNoInfo) });
|
||||
toastSuccess({
|
||||
title: intl.formatMessage(i18n.jobInspection),
|
||||
msg: intl.formatMessage(i18n.inspectNoInfo),
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
const errorMsg = errorMessage(err, 'Failed to inspect job');
|
||||
|
|
@ -253,7 +335,10 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
|
|||
setIsActionLoading(true);
|
||||
try {
|
||||
await acpUpdateSchedule(scheduleId, payload as string);
|
||||
toastSuccess({ title: intl.formatMessage(i18n.scheduleUpdated), msg: intl.formatMessage(i18n.updatedMsg, { id: scheduleId }) });
|
||||
toastSuccess({
|
||||
title: intl.formatMessage(i18n.scheduleUpdated),
|
||||
msg: intl.formatMessage(i18n.updatedMsg, { id: scheduleId }),
|
||||
});
|
||||
await fetchSchedule(scheduleId);
|
||||
setIsModalOpen(false);
|
||||
} catch (err) {
|
||||
|
|
@ -271,10 +356,10 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
|
|||
return (
|
||||
<div className="h-screen w-full flex flex-col items-center justify-center bg-white dark:bg-gray-900 text-text-primary p-8">
|
||||
<BackButton onClick={onNavigateBack} />
|
||||
<h1 className="text-2xl font-medium text-text-primary mt-4">{intl.formatMessage(i18n.scheduleNotFound)}</h1>
|
||||
<p className="text-text-secondary mt-2">
|
||||
{intl.formatMessage(i18n.noScheduleId)}
|
||||
</p>
|
||||
<h1 className="text-2xl font-medium text-text-primary mt-4">
|
||||
{intl.formatMessage(i18n.scheduleNotFound)}
|
||||
</h1>
|
||||
<p className="text-text-secondary mt-2">{intl.formatMessage(i18n.noScheduleId)}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -293,17 +378,24 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
|
|||
<div className="h-screen w-full flex flex-col bg-background-primary text-text-primary">
|
||||
<div className="px-8 pt-6 pb-4 border-b border-border-primary flex-shrink-0">
|
||||
<BackButton onClick={onNavigateBack} />
|
||||
<h1 className="text-4xl font-light mt-1 mb-1 pt-8">{intl.formatMessage(i18n.scheduleDetails)}</h1>
|
||||
<p className="text-sm text-text-secondary mb-1">{intl.formatMessage(i18n.viewingScheduleId, { id: scheduleId })}</p>
|
||||
<h1 className="text-4xl font-light mt-1 mb-1 pt-8">
|
||||
{intl.formatMessage(i18n.scheduleDetails)}
|
||||
</h1>
|
||||
<p className="text-sm text-text-secondary mb-1">
|
||||
{intl.formatMessage(i18n.viewingScheduleId, { id: scheduleId })}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ScrollArea className="flex-grow">
|
||||
<div className="p-8 space-y-6">
|
||||
<section>
|
||||
<h2 className="text-xl font-semibold text-text-primary mb-3">{intl.formatMessage(i18n.scheduleInformation)}</h2>
|
||||
<h2 className="text-xl font-semibold text-text-primary mb-3">
|
||||
{intl.formatMessage(i18n.scheduleInformation)}
|
||||
</h2>
|
||||
{isLoadingSchedule && (
|
||||
<div className="flex items-center text-text-secondary">
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> {intl.formatMessage(i18n.loadingSchedule)}
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />{' '}
|
||||
{intl.formatMessage(i18n.loadingSchedule)}
|
||||
</div>
|
||||
)}
|
||||
{scheduleError && (
|
||||
|
|
@ -334,13 +426,16 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
|
|||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-text-primary">
|
||||
<span className="font-semibold">{intl.formatMessage(i18n.scheduleLabel)}</span> {readableCron}
|
||||
<span className="font-semibold">{intl.formatMessage(i18n.scheduleLabel)}</span>{' '}
|
||||
{readableCron}
|
||||
</p>
|
||||
<p className="text-sm text-text-primary">
|
||||
<span className="font-semibold">{intl.formatMessage(i18n.cronExpression)}</span> {scheduleDetails.cron}
|
||||
<span className="font-semibold">{intl.formatMessage(i18n.cronExpression)}</span>{' '}
|
||||
{scheduleDetails.cron}
|
||||
</p>
|
||||
<p className="text-sm text-text-primary">
|
||||
<span className="font-semibold">{intl.formatMessage(i18n.recipeSource)}</span> {scheduleDetails.source}
|
||||
<span className="font-semibold">{intl.formatMessage(i18n.recipeSource)}</span>{' '}
|
||||
{scheduleDetails.source}
|
||||
</p>
|
||||
<p className="text-sm text-text-primary">
|
||||
<span className="font-semibold">{intl.formatMessage(i18n.lastRun)}</span>{' '}
|
||||
|
|
@ -348,13 +443,17 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
|
|||
</p>
|
||||
{scheduleDetails.currentlyRunning && scheduleDetails.currentSessionId && (
|
||||
<p className="text-sm text-text-primary">
|
||||
<span className="font-semibold">{intl.formatMessage(i18n.currentSession)}</span>{' '}
|
||||
<span className="font-semibold">
|
||||
{intl.formatMessage(i18n.currentSession)}
|
||||
</span>{' '}
|
||||
{scheduleDetails.currentSessionId}
|
||||
</p>
|
||||
)}
|
||||
{scheduleDetails.currentlyRunning && scheduleDetails.jobStartTime && (
|
||||
<p className="text-sm text-text-primary">
|
||||
<span className="font-semibold">{intl.formatMessage(i18n.processStarted)}</span>{' '}
|
||||
<span className="font-semibold">
|
||||
{intl.formatMessage(i18n.processStarted)}
|
||||
</span>{' '}
|
||||
{formatToLocalDateWithTimezone(scheduleDetails.jobStartTime)}
|
||||
</p>
|
||||
)}
|
||||
|
|
@ -364,7 +463,9 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
|
|||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-semibold text-text-primary mb-3">{intl.formatMessage(i18n.actions)}</h2>
|
||||
<h2 className="text-xl font-semibold text-text-primary mb-3">
|
||||
{intl.formatMessage(i18n.actions)}
|
||||
</h2>
|
||||
<div className="flex flex-col md:flex-row gap-2">
|
||||
<Button
|
||||
onClick={handleRunNow}
|
||||
|
|
@ -448,8 +549,12 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
|
|||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-semibold text-text-primary mb-4">{intl.formatMessage(i18n.recentSessions)}</h2>
|
||||
{isLoadingSessions && <p className="text-text-secondary">{intl.formatMessage(i18n.loadingSessions)}</p>}
|
||||
<h2 className="text-xl font-semibold text-text-primary mb-4">
|
||||
{intl.formatMessage(i18n.recentSessions)}
|
||||
</h2>
|
||||
{isLoadingSessions && (
|
||||
<p className="text-text-secondary">{intl.formatMessage(i18n.loadingSessions)}</p>
|
||||
)}
|
||||
{sessionsError && (
|
||||
<p className="text-text-danger text-sm p-3 bg-background-danger border border-border-danger rounded-md">
|
||||
{intl.formatMessage(i18n.errorPrefix, { error: sessionsError })}
|
||||
|
|
@ -482,7 +587,9 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
|
|||
{sessionName || intl.formatMessage(i18n.sessionId, { id: sessionId })}
|
||||
</h3>
|
||||
<p className="text-xs text-text-secondary mt-1">
|
||||
{intl.formatMessage(i18n.created, { date: createdAt ? formatToLocalDateWithTimezone(createdAt) : 'N/A' })}
|
||||
{intl.formatMessage(i18n.created, {
|
||||
date: createdAt ? formatToLocalDateWithTimezone(createdAt) : 'N/A',
|
||||
})}
|
||||
</p>
|
||||
{messageCount !== undefined && (
|
||||
<p className="text-xs text-text-secondary mt-1">
|
||||
|
|
@ -498,7 +605,8 @@ const ScheduleDetailView: React.FC<ScheduleDetailViewProps> = ({ scheduleId, onN
|
|||
</p>
|
||||
)}
|
||||
<p className="text-xs text-text-secondary mt-1">
|
||||
{intl.formatMessage(i18n.idLabel)} <span className="font-mono">{sessionId}</span>
|
||||
{intl.formatMessage(i18n.idLabel)}{' '}
|
||||
<span className="font-mono">{sessionId}</span>
|
||||
</p>
|
||||
</Card>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -11,30 +11,60 @@ import { defineMessages, useIntl } from '../../i18n';
|
|||
|
||||
const i18n = defineMessages({
|
||||
editSchedule: { id: 'scheduleModal.editSchedule', defaultMessage: 'Edit Schedule' },
|
||||
createNewSchedule: { id: 'scheduleModal.createNewSchedule', defaultMessage: 'Create New Schedule' },
|
||||
createNewSchedule: {
|
||||
id: 'scheduleModal.createNewSchedule',
|
||||
defaultMessage: 'Create New Schedule',
|
||||
},
|
||||
nameLabel: { id: 'scheduleModal.nameLabel', defaultMessage: 'Name:' },
|
||||
namePlaceholder: { id: 'scheduleModal.namePlaceholder', defaultMessage: 'e.g., daily-summary-job' },
|
||||
namePlaceholder: {
|
||||
id: 'scheduleModal.namePlaceholder',
|
||||
defaultMessage: 'e.g., daily-summary-job',
|
||||
},
|
||||
sourceLabel: { id: 'scheduleModal.sourceLabel', defaultMessage: 'Source:' },
|
||||
yaml: { id: 'scheduleModal.yaml', defaultMessage: 'YAML' },
|
||||
deepLink: { id: 'scheduleModal.deepLink', defaultMessage: 'Deep link' },
|
||||
browseYaml: { id: 'scheduleModal.browseYaml', defaultMessage: 'Browse for YAML file...' },
|
||||
selected: { id: 'scheduleModal.selected', defaultMessage: 'Selected: {path}' },
|
||||
deepLinkPlaceholder: { id: 'scheduleModal.deepLinkPlaceholder', defaultMessage: 'Paste goose://recipe link here...' },
|
||||
deepLinkPlaceholder: {
|
||||
id: 'scheduleModal.deepLinkPlaceholder',
|
||||
defaultMessage: 'Paste goose://recipe link here...',
|
||||
},
|
||||
recipeParsed: { id: 'scheduleModal.recipeParsed', defaultMessage: 'Recipe parsed successfully' },
|
||||
recipeTitle: { id: 'scheduleModal.recipeTitle', defaultMessage: 'Title: {title}' },
|
||||
recipeDescription: { id: 'scheduleModal.recipeDescription', defaultMessage: 'Description: {description}' },
|
||||
recipeDescription: {
|
||||
id: 'scheduleModal.recipeDescription',
|
||||
defaultMessage: 'Description: {description}',
|
||||
},
|
||||
scheduleLabel: { id: 'scheduleModal.scheduleLabel', defaultMessage: 'Schedule:' },
|
||||
cancel: { id: 'scheduleModal.cancel', defaultMessage: 'Cancel' },
|
||||
updating: { id: 'scheduleModal.updating', defaultMessage: 'Updating...' },
|
||||
creating: { id: 'scheduleModal.creating', defaultMessage: 'Creating...' },
|
||||
updateSchedule: { id: 'scheduleModal.updateSchedule', defaultMessage: 'Update Schedule' },
|
||||
createSchedule: { id: 'scheduleModal.createSchedule', defaultMessage: 'Create Schedule' },
|
||||
invalidDeepLink: { id: 'scheduleModal.invalidDeepLink', defaultMessage: 'Invalid deep link. Please use a goose://recipe link.' },
|
||||
failedReadFile: { id: 'scheduleModal.failedReadFile', defaultMessage: 'Failed to read the selected file.' },
|
||||
failedParseRecipe: { id: 'scheduleModal.failedParseRecipe', defaultMessage: 'Failed to parse recipe from file.' },
|
||||
invalidFileType: { id: 'scheduleModal.invalidFileType', defaultMessage: 'Invalid file type: Please select a YAML file (.yaml or .yml)' },
|
||||
scheduleIdRequired: { id: 'scheduleModal.scheduleIdRequired', defaultMessage: 'Schedule ID is required.' },
|
||||
provideValidRecipe: { id: 'scheduleModal.provideValidRecipe', defaultMessage: 'Please provide a valid recipe source.' },
|
||||
invalidDeepLink: {
|
||||
id: 'scheduleModal.invalidDeepLink',
|
||||
defaultMessage: 'Invalid deep link. Please use a goose://recipe link.',
|
||||
},
|
||||
failedReadFile: {
|
||||
id: 'scheduleModal.failedReadFile',
|
||||
defaultMessage: 'Failed to read the selected file.',
|
||||
},
|
||||
failedParseRecipe: {
|
||||
id: 'scheduleModal.failedParseRecipe',
|
||||
defaultMessage: 'Failed to parse recipe from file.',
|
||||
},
|
||||
invalidFileType: {
|
||||
id: 'scheduleModal.invalidFileType',
|
||||
defaultMessage: 'Invalid file type: Please select a YAML file (.yaml or .yml)',
|
||||
},
|
||||
scheduleIdRequired: {
|
||||
id: 'scheduleModal.scheduleIdRequired',
|
||||
defaultMessage: 'Schedule ID is required.',
|
||||
},
|
||||
provideValidRecipe: {
|
||||
id: 'scheduleModal.provideValidRecipe',
|
||||
defaultMessage: 'Please provide a valid recipe source.',
|
||||
},
|
||||
});
|
||||
|
||||
export interface NewSchedulePayload {
|
||||
|
|
@ -86,26 +116,29 @@ export const ScheduleModal: React.FC<ScheduleModalProps> = ({
|
|||
setScheduleId(cleanId);
|
||||
};
|
||||
|
||||
const handleDeepLinkChange = useCallback(async (value: string) => {
|
||||
setDeepLinkInput(value);
|
||||
setInternalValidationError(null);
|
||||
const handleDeepLinkChange = useCallback(
|
||||
async (value: string) => {
|
||||
setDeepLinkInput(value);
|
||||
setInternalValidationError(null);
|
||||
|
||||
if (value.trim()) {
|
||||
try {
|
||||
const recipe = await parseDeeplink(value.trim());
|
||||
if (!recipe) throw new Error();
|
||||
setParsedRecipe(recipe);
|
||||
if (recipe.title) {
|
||||
setScheduleIdFromTitle(recipe.title);
|
||||
if (value.trim()) {
|
||||
try {
|
||||
const recipe = await parseDeeplink(value.trim());
|
||||
if (!recipe) throw new Error();
|
||||
setParsedRecipe(recipe);
|
||||
if (recipe.title) {
|
||||
setScheduleIdFromTitle(recipe.title);
|
||||
}
|
||||
} catch {
|
||||
setParsedRecipe(null);
|
||||
setInternalValidationError(intl.formatMessage(i18n.invalidDeepLink));
|
||||
}
|
||||
} catch {
|
||||
} else {
|
||||
setParsedRecipe(null);
|
||||
setInternalValidationError(intl.formatMessage(i18n.invalidDeepLink));
|
||||
}
|
||||
} else {
|
||||
setParsedRecipe(null);
|
||||
}
|
||||
}, [intl]);
|
||||
},
|
||||
[intl]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
|
|
@ -199,7 +232,9 @@ export const ScheduleModal: React.FC<ScheduleModalProps> = ({
|
|||
<img src={ClockIcon} alt="Clock" className="w-8 h-8" />
|
||||
<div className="flex-1">
|
||||
<h2 className="text-base font-semibold text-text-primary">
|
||||
{isEditMode ? intl.formatMessage(i18n.editSchedule) : intl.formatMessage(i18n.createNewSchedule)}
|
||||
{isEditMode
|
||||
? intl.formatMessage(i18n.editSchedule)
|
||||
: intl.formatMessage(i18n.createNewSchedule)}
|
||||
</h2>
|
||||
{isEditMode && <p className="text-sm text-text-secondary">{schedule.id}</p>}
|
||||
</div>
|
||||
|
|
@ -240,7 +275,7 @@ export const ScheduleModal: React.FC<ScheduleModalProps> = ({
|
|||
|
||||
<div>
|
||||
<label className={modalLabelClassName}>
|
||||
{intl.formatMessage(i18n.sourceLabel)} <span className="text-red-500">*</span>
|
||||
{intl.formatMessage(i18n.sourceLabel)} <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="space-y-2">
|
||||
<div className="flex bg-gray-100 dark:bg-gray-700 rounded-full p-1">
|
||||
|
|
@ -304,7 +339,9 @@ export const ScheduleModal: React.FC<ScheduleModalProps> = ({
|
|||
{intl.formatMessage(i18n.recipeTitle, { title: parsedRecipe.title })}
|
||||
</p>
|
||||
<p className="text-xs text-green-600 dark:text-green-400">
|
||||
{intl.formatMessage(i18n.recipeDescription, { description: parsedRecipe.description })}
|
||||
{intl.formatMessage(i18n.recipeDescription, {
|
||||
description: parsedRecipe.description,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -40,25 +40,41 @@ const i18n = defineMessages({
|
|||
refreshing: { id: 'schedulesView.refreshing', defaultMessage: 'Refreshing...' },
|
||||
refresh: { id: 'schedulesView.refresh', defaultMessage: 'Refresh' },
|
||||
createSchedule: { id: 'schedulesView.createSchedule', defaultMessage: 'Create Schedule' },
|
||||
description: { id: 'schedulesView.description', defaultMessage: 'Create and manage scheduled tasks to run recipes automatically at specified times.' },
|
||||
description: {
|
||||
id: 'schedulesView.description',
|
||||
defaultMessage:
|
||||
'Create and manage scheduled tasks to run recipes automatically at specified times.',
|
||||
},
|
||||
errorPrefix: { id: 'schedulesView.errorPrefix', defaultMessage: 'Error: {error}' },
|
||||
noSchedules: { id: 'schedulesView.noSchedules', defaultMessage: 'No schedules yet' },
|
||||
scheduleUpdated: { id: 'schedulesView.scheduleUpdated', defaultMessage: 'Schedule Updated' },
|
||||
scheduleUpdatedMsg: { id: 'schedulesView.scheduleUpdatedMsg', defaultMessage: 'Successfully updated schedule "{id}"' },
|
||||
scheduleUpdatedMsg: {
|
||||
id: 'schedulesView.scheduleUpdatedMsg',
|
||||
defaultMessage: 'Successfully updated schedule "{id}"',
|
||||
},
|
||||
confirmDelete: {
|
||||
id: 'schedulesView.confirmDelete',
|
||||
defaultMessage: 'Remove schedule "{id}"? The recipe will be kept.',
|
||||
},
|
||||
schedulePaused: { id: 'schedulesView.schedulePaused', defaultMessage: 'Schedule Paused' },
|
||||
schedulePausedMsg: { id: 'schedulesView.schedulePausedMsg', defaultMessage: 'Successfully paused schedule "{id}"' },
|
||||
schedulePausedMsg: {
|
||||
id: 'schedulesView.schedulePausedMsg',
|
||||
defaultMessage: 'Successfully paused schedule "{id}"',
|
||||
},
|
||||
pauseError: { id: 'schedulesView.pauseError', defaultMessage: 'Pause Schedule Error' },
|
||||
scheduleUnpaused: { id: 'schedulesView.scheduleUnpaused', defaultMessage: 'Schedule Unpaused' },
|
||||
scheduleUnpausedMsg: { id: 'schedulesView.scheduleUnpausedMsg', defaultMessage: 'Successfully unpaused schedule "{id}"' },
|
||||
scheduleUnpausedMsg: {
|
||||
id: 'schedulesView.scheduleUnpausedMsg',
|
||||
defaultMessage: 'Successfully unpaused schedule "{id}"',
|
||||
},
|
||||
unpauseError: { id: 'schedulesView.unpauseError', defaultMessage: 'Unpause Schedule Error' },
|
||||
jobKilled: { id: 'schedulesView.jobKilled', defaultMessage: 'Job Killed' },
|
||||
killError: { id: 'schedulesView.killError', defaultMessage: 'Kill Job Error' },
|
||||
jobInspection: { id: 'schedulesView.jobInspection', defaultMessage: 'Job Inspection' },
|
||||
inspectNoInfo: { id: 'schedulesView.inspectNoInfo', defaultMessage: 'No detailed information available for this job' },
|
||||
inspectNoInfo: {
|
||||
id: 'schedulesView.inspectNoInfo',
|
||||
defaultMessage: 'No detailed information available for this job',
|
||||
},
|
||||
inspectError: { id: 'schedulesView.inspectError', defaultMessage: 'Inspect Job Error' },
|
||||
});
|
||||
|
||||
|
|
@ -493,7 +509,9 @@ const SchedulesView: React.FC<SchedulesViewProps> = ({ onClose: _onClose }) => {
|
|||
className="flex items-center gap-2"
|
||||
>
|
||||
<RefreshCw className={`h-4 w-4 ${isRefreshing ? 'animate-spin' : ''}`} />
|
||||
{isRefreshing ? intl.formatMessage(i18n.refreshing) : intl.formatMessage(i18n.refresh)}
|
||||
{isRefreshing
|
||||
? intl.formatMessage(i18n.refreshing)
|
||||
: intl.formatMessage(i18n.refresh)}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
|
|
@ -519,7 +537,9 @@ const SchedulesView: React.FC<SchedulesViewProps> = ({ onClose: _onClose }) => {
|
|||
<div className="h-full relative">
|
||||
{apiError && (
|
||||
<div className="mb-4 p-4 bg-background-danger border border-border-danger rounded-md">
|
||||
<p className="text-text-danger text-sm">{intl.formatMessage(i18n.errorPrefix, { error: apiError })}</p>
|
||||
<p className="text-text-danger text-sm">
|
||||
{intl.formatMessage(i18n.errorPrefix, { error: apiError })}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,212 +0,0 @@
|
|||
import React from 'react';
|
||||
import { MessageSquare, AlertCircle } from 'lucide-react';
|
||||
import { defineMessages, useIntl } from '../../i18n';
|
||||
import { Card } from '../ui/card';
|
||||
import { Button } from '../ui/button';
|
||||
import { ScrollArea } from '../ui/scroll-area';
|
||||
import MarkdownContent from '../MarkdownContent';
|
||||
import ToolCallWithResponse from '../ToolCallWithResponse';
|
||||
import ImagePreview from '../ImagePreview';
|
||||
import {
|
||||
getTextAndImageContent,
|
||||
getThinkingContent,
|
||||
ToolRequestMessageContent,
|
||||
ToolResponseMessageContent,
|
||||
type Message,
|
||||
} from '../../types/message';
|
||||
import { formatMessageTimestamp } from '../../utils/timeUtils';
|
||||
|
||||
const i18n = defineMessages({
|
||||
errorLoadingDetails: {
|
||||
id: 'sessionViewComponents.error.loading',
|
||||
defaultMessage: 'Error Loading Session Details',
|
||||
},
|
||||
tryAgain: {
|
||||
id: 'sessionViewComponents.error.tryAgain',
|
||||
defaultMessage: 'Try Again',
|
||||
},
|
||||
noMessages: {
|
||||
id: 'sessionViewComponents.empty.title',
|
||||
defaultMessage: 'No messages found',
|
||||
},
|
||||
noMessagesDesc: {
|
||||
id: 'sessionViewComponents.empty.description',
|
||||
defaultMessage: "This session doesn't contain any messages",
|
||||
},
|
||||
you: {
|
||||
id: 'sessionViewComponents.role.user',
|
||||
defaultMessage: 'You',
|
||||
},
|
||||
goose: {
|
||||
id: 'sessionViewComponents.role.assistant',
|
||||
defaultMessage: 'Goose',
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Get tool responses map from messages
|
||||
*/
|
||||
export const getToolResponsesMap = (
|
||||
messages: Message[],
|
||||
messageIndex: number,
|
||||
toolRequests: ToolRequestMessageContent[]
|
||||
) => {
|
||||
const responseMap = new Map();
|
||||
|
||||
if (messageIndex >= 0) {
|
||||
for (let i = messageIndex + 1; i < messages.length; i++) {
|
||||
const responses = messages[i].content
|
||||
.filter((c) => c.type === 'toolResponse')
|
||||
.map((c) => c as ToolResponseMessageContent);
|
||||
|
||||
for (const response of responses) {
|
||||
const matchingRequest = toolRequests.find((req) => req.id === response.id);
|
||||
if (matchingRequest) {
|
||||
responseMap.set(response.id, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return responseMap;
|
||||
};
|
||||
|
||||
interface SessionMessagesProps {
|
||||
messages: Message[];
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
onRetry: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Common component for displaying session messages
|
||||
*/
|
||||
export const SessionMessages: React.FC<SessionMessagesProps> = ({
|
||||
messages,
|
||||
isLoading,
|
||||
error,
|
||||
onRetry,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
return (
|
||||
<ScrollArea className="h-full w-full">
|
||||
<div className="p-4">
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div className="space-y-4 mb-6">
|
||||
{isLoading ? (
|
||||
<div className="flex justify-center items-center py-12">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2"></div>
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="flex flex-col items-center justify-center py-8 text-text-secondary">
|
||||
<div className="text-red-500 mb-4">
|
||||
<AlertCircle size={32} />
|
||||
</div>
|
||||
<p className="text-md mb-2">{intl.formatMessage(i18n.errorLoadingDetails)}</p>
|
||||
<p className="text-sm text-center mb-4">{error}</p>
|
||||
<Button onClick={onRetry} variant="default">
|
||||
{intl.formatMessage(i18n.tryAgain)}
|
||||
</Button>
|
||||
</div>
|
||||
) : messages?.length > 0 ? (
|
||||
messages
|
||||
.map((message, index) => {
|
||||
const { textContent, imagePaths } = getTextAndImageContent(message);
|
||||
const thinkingContent = getThinkingContent(message);
|
||||
|
||||
// Get tool requests from the message
|
||||
const toolRequests = message.content
|
||||
.filter((c) => c.type === 'toolRequest')
|
||||
.map((c) => c as ToolRequestMessageContent);
|
||||
|
||||
// Get tool responses map using the helper function
|
||||
const toolResponsesMap = getToolResponsesMap(messages, index, toolRequests);
|
||||
|
||||
// Skip pure tool response messages for cleaner display
|
||||
const isOnlyToolResponse =
|
||||
message.content.length > 0 &&
|
||||
message.content.every((c) => c.type === 'toolResponse');
|
||||
|
||||
if (message.role === 'user' && isOnlyToolResponse) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card
|
||||
key={index}
|
||||
className={`p-4 ${
|
||||
message.role === 'user'
|
||||
? 'bg-bgSecondary border border-border-primary'
|
||||
: 'bg-background-secondary'
|
||||
}`}
|
||||
>
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<span className="font-medium text-text-primary">
|
||||
{message.role === 'user' ? intl.formatMessage(i18n.you) : intl.formatMessage(i18n.goose)}
|
||||
</span>
|
||||
<span className="text-xs text-text-secondary">
|
||||
{formatMessageTimestamp(message.created)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col w-full">
|
||||
{/* Thinking content */}
|
||||
{thinkingContent && (
|
||||
<div className="mb-2 text-sm text-gray-400 italic">
|
||||
<MarkdownContent content={thinkingContent} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Text content */}
|
||||
{textContent && (
|
||||
<div
|
||||
className={`${toolRequests.length > 0 || imagePaths.length > 0 ? 'mb-4' : ''}`}
|
||||
>
|
||||
<MarkdownContent content={textContent} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{imagePaths.length > 0 && (
|
||||
<div className="flex flex-wrap gap-2 mt-2 mb-2">
|
||||
{imagePaths.map((imagePath, imageIndex) => (
|
||||
<ImagePreview key={imageIndex} src={imagePath} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tool requests and responses */}
|
||||
{toolRequests.length > 0 && (
|
||||
<div className="goose-message-tool bg-background-primary border border-border-primary dark:border-gray-700 rounded-b-2xl px-4 pt-4 pb-2 mt-1">
|
||||
{toolRequests.map((toolRequest) => (
|
||||
<ToolCallWithResponse
|
||||
// In the session history page, if no tool response found for given request, it means the tool call
|
||||
// is broken or cancelled.
|
||||
isCancelledMessage={
|
||||
toolResponsesMap.get(toolRequest.id) == undefined
|
||||
}
|
||||
isPendingApproval={false}
|
||||
key={toolRequest.id}
|
||||
toolRequest={toolRequest}
|
||||
toolResponse={toolResponsesMap.get(toolRequest.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
})
|
||||
.filter(Boolean) // Filter out null entries
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center py-8 text-text-secondary">
|
||||
<MessageSquare className="w-12 h-12 mb-4" />
|
||||
<p className="text-lg mb-2">{intl.formatMessage(i18n.noMessages)}</p>
|
||||
<p className="text-sm">{intl.formatMessage(i18n.noMessagesDesc)}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
);
|
||||
};
|
||||
|
|
@ -56,12 +56,12 @@ const i18n = defineMessages({
|
|||
},
|
||||
certFingerprintHelp: {
|
||||
id: 'externalBackendSection.certFingerprintHelp',
|
||||
defaultMessage: 'Pin a specific TLS certificate fingerprint. If omitted, the certificate is trusted on first use (TOFU).',
|
||||
defaultMessage:
|
||||
'Pin a specific TLS certificate fingerprint. If omitted, the certificate is trusted on first use (TOFU).',
|
||||
},
|
||||
restartNote: {
|
||||
id: 'externalBackendSection.restartNote',
|
||||
defaultMessage:
|
||||
'Changes apply to new chat windows. Restart Goose to update existing windows.',
|
||||
defaultMessage: 'Changes apply to new chat windows. Restart Goose to update existing windows.',
|
||||
},
|
||||
urlProtocolError: {
|
||||
id: 'externalBackendSection.urlProtocolError',
|
||||
|
|
@ -77,7 +77,8 @@ const i18n = defineMessages({
|
|||
},
|
||||
urlBaseError: {
|
||||
id: 'externalBackendSection.urlBaseError',
|
||||
defaultMessage: 'URL must be the backend base URL before /acp, without query parameters or fragments',
|
||||
defaultMessage:
|
||||
'URL must be the backend base URL before /acp, without query parameters or fragments',
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -95,10 +96,7 @@ export default function ExternalBackendSection() {
|
|||
loadSettings();
|
||||
}, []);
|
||||
|
||||
const validateUrl = (
|
||||
value: string,
|
||||
certFingerprint = config.certFingerprint
|
||||
): boolean => {
|
||||
const validateUrl = (value: string, certFingerprint = config.certFingerprint): boolean => {
|
||||
if (!value) {
|
||||
setUrlError(null);
|
||||
return true;
|
||||
|
|
@ -175,14 +173,14 @@ export default function ExternalBackendSection() {
|
|||
<Card className="pb-2">
|
||||
<CardHeader className="pb-0">
|
||||
<CardTitle>{intl.formatMessage(i18n.title)}</CardTitle>
|
||||
<CardDescription>
|
||||
{intl.formatMessage(i18n.description)}
|
||||
</CardDescription>
|
||||
<CardDescription>{intl.formatMessage(i18n.description)}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-4 space-y-4 px-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-text-primary text-xs">{intl.formatMessage(i18n.useExternalServer)}</h3>
|
||||
<h3 className="text-text-primary text-xs">
|
||||
{intl.formatMessage(i18n.useExternalServer)}
|
||||
</h3>
|
||||
<p className="text-xs text-text-secondary max-w-md mt-[2px]">
|
||||
{intl.formatMessage(i18n.useExternalServerDescription)}
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -65,7 +65,9 @@ describe('AuthSettingsSection', () => {
|
|||
renderWithIntl(<AuthSettingsSection />);
|
||||
|
||||
expect(screen.getByText('Loading credentials...')).toBeInTheDocument();
|
||||
expect(await screen.findByText('No locally stored provider credentials were found.')).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByText('No locally stored provider credentials were found.')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders provider credentials with storage and expiry status', async () => {
|
||||
|
|
@ -98,9 +100,7 @@ describe('AuthSettingsSection', () => {
|
|||
|
||||
it('deletes a credential after confirmation and refreshes the list', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockedListProviderSecrets
|
||||
.mockResolvedValueOnce([providerSecret])
|
||||
.mockResolvedValueOnce([]);
|
||||
mockedListProviderSecrets.mockResolvedValueOnce([providerSecret]).mockResolvedValueOnce([]);
|
||||
|
||||
renderWithIntl(<AuthSettingsSection />);
|
||||
|
||||
|
|
@ -108,7 +108,9 @@ describe('AuthSettingsSection', () => {
|
|||
|
||||
await user.click(screen.getByRole('button', { name: 'Delete credential' }));
|
||||
|
||||
expect(screen.getByText('Delete the OPENAI_API_KEY credential for OpenAI?')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('Delete the OPENAI_API_KEY credential for OpenAI?')
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
'This is the active provider. New requests may fail until you configure another credential.'
|
||||
|
|
@ -123,7 +125,9 @@ describe('AuthSettingsSection', () => {
|
|||
await waitFor(() => {
|
||||
expect(mockedToast.success).toHaveBeenCalledWith('Credential deleted');
|
||||
});
|
||||
expect(await screen.findByText('No locally stored provider credentials were found.')).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByText('No locally stored provider credentials were found.')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('configures the permanent Hugging Face credential row', async () => {
|
||||
|
|
@ -143,16 +147,14 @@ describe('AuthSettingsSection', () => {
|
|||
configureProvider: 'huggingface',
|
||||
};
|
||||
|
||||
mockedListProviderSecrets
|
||||
.mockResolvedValueOnce([huggingFaceSecret])
|
||||
.mockResolvedValueOnce([
|
||||
{
|
||||
...huggingFaceSecret,
|
||||
configured: true,
|
||||
hasSecret: true,
|
||||
canDelete: true,
|
||||
},
|
||||
]);
|
||||
mockedListProviderSecrets.mockResolvedValueOnce([huggingFaceSecret]).mockResolvedValueOnce([
|
||||
{
|
||||
...huggingFaceSecret,
|
||||
configured: true,
|
||||
hasSecret: true,
|
||||
canDelete: true,
|
||||
},
|
||||
]);
|
||||
|
||||
renderWithIntl(<AuthSettingsSection />);
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue