fix: lint

(cherry picked from commit 2aac8ff0563483d01b16bb5ee8c191fafa8aff14)
This commit is contained in:
Sergey Kozyrenko
2026-04-14 21:05:05 +07:00
parent 2ec8ef3d9b
commit 93443fc959
26 changed files with 190 additions and 172 deletions
+15 -12
View File
@@ -147,8 +147,8 @@ const Markdown = ({ children, className, searchValue }: MarkdownProps) => {
);
// Optimized helper function to process text nodes recursively
const processTextNode = useCallback(
(nodeChildren: any): any => {
const processTextNode = useMemo(() => {
const fn = (nodeChildren: any): any => {
if (!processedSearch) {
return nodeChildren;
}
@@ -163,15 +163,13 @@ const Markdown = ({ children, className, searchValue }: MarkdownProps) => {
return createHighlightedText(child);
}
// Avoid deep cloning React elements to prevent memory leaks
// Only process if it's a simple object with props
if (child && typeof child === 'object' && child.props && child.props.children !== undefined) {
return {
...child,
key: child.key || `processed-${index}`,
props: {
...child.props,
children: processTextNode(child.props.children),
children: fn(child.props.children),
},
};
}
@@ -180,7 +178,6 @@ const Markdown = ({ children, className, searchValue }: MarkdownProps) => {
});
}
// Handle React elements safely
if (
nodeChildren &&
typeof nodeChildren === 'object' &&
@@ -191,25 +188,31 @@ const Markdown = ({ children, className, searchValue }: MarkdownProps) => {
...nodeChildren,
props: {
...nodeChildren.props,
children: processTextNode(nodeChildren.props.children),
children: fn(nodeChildren.props.children),
},
};
}
return nodeChildren;
},
[processedSearch, createHighlightedText],
);
};
return fn;
}, [processedSearch, createHighlightedText]);
// Create a simple component renderer factory to avoid recreating functions
const createComponentRenderer = useCallback(
(ComponentName: string) => {
return ({ children: nodeChildren, ...props }: any) => {
const Component = ComponentName as React.ElementType;
const Renderer = ({ children: nodeChildren, ...props }: Record<string, unknown>) => {
const processedChildren = processTextNode(nodeChildren);
const Component = ComponentName as any;
return <Component {...props}>{processedChildren}</Component>;
};
Renderer.displayName = `Highlighted(${ComponentName})`;
return Renderer;
},
[processTextNode],
);
@@ -67,7 +67,11 @@ const injectedColorClasses = new Set<string>();
const rgbStringToHex = (rgb: string): string =>
rgb
.split(',')
.map((part) => Math.min(255, Math.max(0, parseInt(part.trim(), 10))).toString(16).padStart(2, '0'))
.map((part) =>
Math.min(255, Math.max(0, parseInt(part.trim(), 10)))
.toString(16)
.padStart(2, '0'),
)
.join('');
/**
@@ -102,10 +102,7 @@ export function useXterm({ theme }: { theme: 'dark' | 'light' | 'system' }): Use
const openLink = (event: MouseEvent, uri: string) => {
const uriLower = uri.toLowerCase();
if (
(mac ? event.metaKey : event.ctrlKey) &&
SAFE_PROTOCOLS.some((p) => uriLower.startsWith(p))
) {
if ((mac ? event.metaKey : event.ctrlKey) && SAFE_PROTOCOLS.some((p) => uriLower.startsWith(p))) {
window.open(uri, '_blank', 'noopener,noreferrer');
}
};
+1 -1
View File
@@ -121,7 +121,7 @@ function InputGroupText({ className, ...props }: React.ComponentProps<'span'>) {
);
}
function InputGroupTextarea({ className, ...props }: React.ComponentProps<'textarea'>) {
function InputGroupTextarea({ className, ...props }: React.ComponentProps<typeof Textarea>) {
return (
<Textarea
className={cn(
+1 -1
View File
@@ -2,7 +2,7 @@ import * as React from 'react';
import { cn } from '@/lib/utils';
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
const Input = React.forwardRef<HTMLInputElement, InputProps>(({ className, type, ...props }, ref) => {
return (
+23 -23
View File
@@ -1,28 +1,28 @@
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils';
function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
return (
<kbd
data-slot="kbd"
className={cn(
"bg-muted text-muted-foreground pointer-events-none inline-flex h-5 w-fit min-w-5 select-none items-center justify-center gap-1 rounded-sm px-1 font-sans text-xs font-medium",
"[&_svg:not([class*='size-'])]:size-3",
"[[data-slot=tooltip-content]_&]:bg-background/20 [[data-slot=tooltip-content]_&]:text-background dark:[[data-slot=tooltip-content]_&]:bg-background/10",
className
)}
{...props}
/>
)
function Kbd({ className, ...props }: React.ComponentProps<'kbd'>) {
return (
<kbd
className={cn(
'bg-muted text-muted-foreground pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm px-1 font-sans text-xs font-medium select-none',
"[&_svg:not([class*='size-'])]:size-3",
'[[data-slot=tooltip-content]_&]:bg-background/20 [[data-slot=tooltip-content]_&]:text-background dark:[[data-slot=tooltip-content]_&]:bg-background/10',
className,
)}
data-slot="kbd"
{...props}
/>
);
}
function KbdGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<kbd
data-slot="kbd-group"
className={cn("inline-flex items-center gap-1", className)}
{...props}
/>
)
function KbdGroup({ className, ...props }: React.ComponentProps<'div'>) {
return (
<kbd
className={cn('inline-flex items-center gap-1', className)}
data-slot="kbd-group"
{...props}
/>
);
}
export { Kbd, KbdGroup }
export { Kbd, KbdGroup };
+4 -4
View File
@@ -16,7 +16,7 @@ const useTextarea = ({
textareaRef,
triggerAutoSize,
}: UseTextareaProps) => {
const [init, setInit] = React.useState(true);
const initRef = React.useRef(true);
React.useEffect(() => {
const offsetBorder = 0;
@@ -26,20 +26,20 @@ const useTextarea = ({
return;
}
if (init) {
if (initRef.current) {
textareaElement.style.minHeight = `${minHeight + offsetBorder}px`;
if (maxHeight > minHeight) {
textareaElement.style.maxHeight = `${maxHeight}px`;
}
setInit(false);
initRef.current = false;
}
textareaElement.style.height = `${minHeight + offsetBorder}px`;
const scrollHeight = textareaElement.scrollHeight;
textareaElement.style.height = scrollHeight > maxHeight ? `${maxHeight}px` : `${scrollHeight + offsetBorder}px`;
}, [textareaRef.current, triggerAutoSize]);
}, [triggerAutoSize, maxHeight, minHeight, textareaRef]);
};
type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement> & {
@@ -1,5 +1,5 @@
import { Copy } from 'lucide-react';
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
import { memo, useCallback, useMemo, useState } from 'react';
import type { AgentLogFragmentFragment } from '@/graphql/types';
@@ -45,20 +45,23 @@ const FlowAgent = ({ log, searchValue = '' }: FlowAgentProps) => {
const [isDetailsVisible, setIsDetailsVisible] = useState(false);
// Auto-expand details if they contain search matches
useEffect(() => {
const [prevSearchValue, setPrevSearchValue] = useState(searchValue);
const [prevHasResultMatch, setPrevHasResultMatch] = useState(searchChecks.hasResultMatch);
if (searchValue !== prevSearchValue || searchChecks.hasResultMatch !== prevHasResultMatch) {
setPrevSearchValue(searchValue);
setPrevHasResultMatch(searchChecks.hasResultMatch);
const trimmedSearch = searchValue.trim();
if (trimmedSearch) {
// Expand result block only if it contains the search term
if (searchChecks.hasResultMatch) {
setIsDetailsVisible(true);
}
} else {
// Reset to default state when search is cleared
setIsDetailsVisible(false);
}
}, [searchValue, searchChecks.hasResultMatch]);
}
// Determine if we should show full task or preview
// Show full task if: search found in task OR details are manually visible OR task is short
@@ -1,5 +1,5 @@
import { Copy } from 'lucide-react';
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
import { memo, useCallback, useMemo, useState } from 'react';
import type { AssistantLogFragmentFragment, MessageLogFragmentFragment } from '@/graphql/types';
@@ -48,26 +48,34 @@ const FlowMessage = ({ log, searchValue = '' }: FlowMessageProps) => {
const [isDetailsVisible, setIsDetailsVisible] = useState(isReportMessage);
const [isThinkingVisible, setIsThinkingVisible] = useState(false);
// Auto-expand blocks if they contain search matches
useEffect(() => {
const [prevSearchValue, setPrevSearchValue] = useState(searchValue);
const [prevHasThinkingMatch, setPrevHasThinkingMatch] = useState(searchChecks.hasThinkingMatch);
const [prevHasResultMatch, setPrevHasResultMatch] = useState(searchChecks.hasResultMatch);
if (
searchValue !== prevSearchValue ||
searchChecks.hasThinkingMatch !== prevHasThinkingMatch ||
searchChecks.hasResultMatch !== prevHasResultMatch
) {
setPrevSearchValue(searchValue);
setPrevHasThinkingMatch(searchChecks.hasThinkingMatch);
setPrevHasResultMatch(searchChecks.hasResultMatch);
const trimmedSearch = searchValue.trim();
if (trimmedSearch) {
// Expand thinking block only if it contains the search term
if (searchChecks.hasThinkingMatch) {
setIsThinkingVisible(true);
}
// Expand result block only if it contains the search term
if (searchChecks.hasResultMatch) {
setIsDetailsVisible(true);
}
} else {
// Reset to default state when search is cleared
setIsDetailsVisible(isReportMessage);
setIsThinkingVisible(false);
}
}, [searchValue, searchChecks.hasThinkingMatch, searchChecks.hasResultMatch, isReportMessage]);
}
// Use useCallback to memoize the toggle functions
const toggleDetails = useCallback(() => {
@@ -1,5 +1,5 @@
import { ListCheck, ListTodo } from 'lucide-react';
import { memo, useEffect, useMemo, useState } from 'react';
import { memo, useMemo, useState } from 'react';
import type { SubtaskFragmentFragment } from '@/graphql/types';
@@ -41,20 +41,27 @@ const FlowSubtask = ({ searchValue = '', subtask }: FlowSubtaskProps) => {
};
}, [searchValue, description, result]);
// Auto-expand details if they contain search matches
useEffect(() => {
const [prevSearchValue, setPrevSearchValue] = useState(searchValue);
const [prevHasMatch, setPrevHasMatch] = useState(
searchChecks.hasDescriptionMatch || searchChecks.hasResultMatch,
);
const hasMatch = searchChecks.hasDescriptionMatch || searchChecks.hasResultMatch;
if (searchValue !== prevSearchValue || hasMatch !== prevHasMatch) {
setPrevSearchValue(searchValue);
setPrevHasMatch(hasMatch);
const trimmedSearch = searchValue.trim();
if (trimmedSearch) {
// Expand details if description or result contains the search term
if (searchChecks.hasDescriptionMatch || searchChecks.hasResultMatch) {
if (hasMatch) {
setIsDetailsVisible(true);
}
} else {
// Reset to default state when search is cleared
setIsDetailsVisible(false);
}
}, [searchValue, searchChecks.hasDescriptionMatch, searchChecks.hasResultMatch]);
}
return (
<div className="group relative flex gap-2.5 pb-4 pl-0.5">
@@ -1,4 +1,4 @@
import { memo, useEffect, useMemo, useState } from 'react';
import { memo, useMemo, useState } from 'react';
import type { TaskFragmentFragment } from '@/graphql/types';
@@ -41,20 +41,23 @@ const FlowTask = ({ searchValue = '', task }: FlowTaskProps) => {
};
}, [searchValue, result]);
// Auto-expand details if they contain search matches
useEffect(() => {
const [prevSearchValue, setPrevSearchValue] = useState(searchValue);
const [prevHasResultMatch, setPrevHasResultMatch] = useState(searchChecks.hasResultMatch);
if (searchValue !== prevSearchValue || searchChecks.hasResultMatch !== prevHasResultMatch) {
setPrevSearchValue(searchValue);
setPrevHasResultMatch(searchChecks.hasResultMatch);
const trimmedSearch = searchValue.trim();
if (trimmedSearch) {
// Expand result block only if it contains the search term
if (searchChecks.hasResultMatch) {
setIsDetailsVisible(true);
}
} else {
// Reset to default state when search is cleared
setIsDetailsVisible(false);
}
}, [searchValue, searchChecks.hasResultMatch]);
}
const sortedSubtasks = [...(subtasks || [])].sort((a, b) => +a.id - +b.id);
const hasSubtasks = subtasks && subtasks.length > 0;
@@ -1,5 +1,5 @@
import { Copy, Hammer } from 'lucide-react';
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
import { memo, useCallback, useMemo, useState } from 'react';
import type { SearchLogFragmentFragment } from '@/graphql/types';
@@ -41,21 +41,23 @@ const FlowTool = ({ log, searchValue = '' }: FlowToolProps) => {
}, [searchValue, query, result]);
const [isDetailsVisible, setIsDetailsVisible] = useState(false);
const [prevSearchValue, setPrevSearchValue] = useState(searchValue);
const [prevHasResultMatch, setPrevHasResultMatch] = useState(searchChecks.hasResultMatch);
if (searchValue !== prevSearchValue || searchChecks.hasResultMatch !== prevHasResultMatch) {
setPrevSearchValue(searchValue);
setPrevHasResultMatch(searchChecks.hasResultMatch);
// Auto-expand details if they contain search matches
useEffect(() => {
const trimmedSearch = searchValue.trim();
if (trimmedSearch) {
// Expand result block only if it contains the search term
if (searchChecks.hasResultMatch) {
setIsDetailsVisible(true);
}
} else {
// Reset to default state when search is cleared
setIsDetailsVisible(false);
}
}, [searchValue, searchChecks.hasResultMatch]);
}
const handleCopy = useCallback(async () => {
await copyMessageToClipboard({
@@ -1,5 +1,5 @@
import { Copy } from 'lucide-react';
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
import { memo, useCallback, useMemo, useState } from 'react';
import type { VectorStoreLogFragmentFragment } from '@/graphql/types';
@@ -85,21 +85,23 @@ const FlowVectorStore = ({ log, searchValue = '' }: FlowVectorStoreProps) => {
}, [searchValue, query, result]);
const [isDetailsVisible, setIsDetailsVisible] = useState(false);
const [prevSearchValue, setPrevSearchValue] = useState(searchValue);
const [prevHasResultMatch, setPrevHasResultMatch] = useState(searchChecks.hasResultMatch);
if (searchValue !== prevSearchValue || searchChecks.hasResultMatch !== prevHasResultMatch) {
setPrevSearchValue(searchValue);
setPrevHasResultMatch(searchChecks.hasResultMatch);
// Auto-expand details if they contain search matches
useEffect(() => {
const trimmedSearch = searchValue.trim();
if (trimmedSearch) {
// Expand result block only if it contains the search term
if (searchChecks.hasResultMatch) {
setIsDetailsVisible(true);
}
} else {
// Reset to default state when search is cleared
setIsDetailsVisible(false);
}
}, [searchValue, searchChecks.hasResultMatch]);
}
const description = getDescription(log);
@@ -82,9 +82,7 @@ export const useAdaptiveColumnVisibility = ({
const userPreference = userPreferences[column.id];
const isVisible =
userPreference !== undefined
? !shouldHideByWidth && userPreference
: !shouldHideByWidth;
userPreference !== undefined ? !shouldHideByWidth && userPreference : !shouldHideByWidth;
return [column.id, isVisible];
}),
+1 -3
View File
@@ -287,9 +287,7 @@ const parseMarkdownTokens = (markdown: string): ParsedContent[] => {
}
case 'list': {
const tokenItems = (
Array.isArray(token.items) ? token.items : []
) as Array<Record<string, unknown>>;
const tokenItems = (Array.isArray(token.items) ? token.items : []) as Array<Record<string, unknown>>;
const items = tokenItems.map((item) => ({
inlineTokens: parseInlineTokens(String(item.text || '')),
raw: String(item.text || ''),
+8 -12
View File
@@ -1,4 +1,5 @@
import type { SortingState, VisibilityState } from '@tanstack/react-table';
import { z } from 'zod';
const sortingSchema = z.array(z.object({ desc: z.boolean(), id: z.string() }));
@@ -9,7 +10,7 @@ const pageStateSchema = z.object({ page: z.number(), pageSize: z.number() });
export type StoredPageState = z.infer<typeof pageStateSchema>;
function loadFromStorage<T>(key: string, schema: z.ZodType<T>): T | null {
function loadFromStorage<T>(key: string, schema: z.ZodType<T>): null | T {
try {
const raw = localStorage.getItem(key);
@@ -33,19 +34,14 @@ function saveToStorage(key: string, value: unknown): void {
}
}
export const loadSorting = (key: string): SortingState | null => loadFromStorage(key, sortingSchema);
export const loadSorting = (key: string): null | SortingState => loadFromStorage(key, sortingSchema);
export const loadColumnVisibility = (key: string): VisibilityState | null =>
loadFromStorage(key, visibilitySchema);
export const loadColumnVisibility = (key: string): null | VisibilityState => loadFromStorage(key, visibilitySchema);
export const loadPageState = (key: string): StoredPageState | null =>
loadFromStorage(key, pageStateSchema);
export const loadPageState = (key: string): null | StoredPageState => loadFromStorage(key, pageStateSchema);
export const saveSorting = (key: string, sorting: SortingState): void =>
saveToStorage(key, sorting);
export const saveSorting = (key: string, sorting: SortingState): void => saveToStorage(key, sorting);
export const saveColumnVisibility = (key: string, visibility: VisibilityState): void =>
saveToStorage(key, visibility);
export const saveColumnVisibility = (key: string, visibility: VisibilityState): void => saveToStorage(key, visibility);
export const savePageState = (key: string, state: StoredPageState): void =>
saveToStorage(key, state);
export const savePageState = (key: string, state: StoredPageState): void => saveToStorage(key, state);
+1 -9
View File
@@ -18,7 +18,7 @@ export interface CopyableMessage {
* This removes ANSI escape codes and returns formatted text as it appears in UI
*/
export const getCleanTerminalText = (terminalContent: string): Promise<string> => {
return new Promise((resolve, reject) => {
return new Promise((resolve) => {
let hiddenTerminal: null | XTerminal = null;
let hiddenDiv: HTMLDivElement | null = null;
let timeoutId: NodeJS.Timeout | null = null;
@@ -68,14 +68,6 @@ export const getCleanTerminalText = (terminalContent: string): Promise<string> =
}
};
const safeReject = (error: any) => {
if (!isResolved) {
isResolved = true;
cleanup();
reject(error);
}
};
try {
// Create a hidden terminal instance
hiddenTerminal = new XTerminal({
+54 -44
View File
@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useParams, useSearchParams } from 'react-router-dom';
import Logo from '@/components/icons/logo';
@@ -7,6 +7,7 @@ import { useFlowReportQuery } from '@/graphql/types';
import { Log } from '@/lib/log';
import { generateFileName, generatePDFFromMarkdown, generateReport } from '@/lib/report';
type PdfPhase = 'done' | 'error' | 'idle';
type ReportState = 'content' | 'error' | 'generating' | 'loading';
const FlowReport = () => {
@@ -15,9 +16,17 @@ const FlowReport = () => {
const download = searchParams.has('download');
const silent = searchParams.has('silent');
const [state, setState] = useState<ReportState>('loading');
const [error, setError] = useState<null | string>(null);
const [reportContent, setReportContent] = useState<string>('');
const [pdfPhase, setPdfPhase] = useState<PdfPhase>('idle');
const [pdfError, setPdfError] = useState<null | string>(null);
const pdfTriggered = useRef(false);
const [prevFlowId, setPrevFlowId] = useState(flowId);
if (flowId !== prevFlowId) {
setPrevFlowId(flowId);
setPdfPhase('idle');
setPdfError(null);
}
const {
data,
@@ -29,55 +38,58 @@ const FlowReport = () => {
variables: { id: flowId! },
});
// Reset state when component mounts or flowId changes
const dataReady = !loading && !queryError && !!data?.flow;
const reportContent = useMemo(
() => (dataReady ? generateReport(data.tasks || [], data.flow!) : ''),
[dataReady, data],
);
useEffect(() => {
setState('loading');
setError(null);
setReportContent('');
pdfTriggered.current = false;
}, [flowId]);
useEffect(() => {
if (loading) {
if (!dataReady || !download || pdfTriggered.current || !data?.flow) {
return;
}
if (queryError || !data?.flow) {
setError('Failed to load flow data');
setState('error');
pdfTriggered.current = true;
return;
}
const fileName = `${generateFileName(data.flow)}.pdf`;
// Generate report content using flow and tasks from GraphQL response
const content = generateReport(data.tasks || [], data.flow);
setReportContent(content);
generatePDFFromMarkdown(reportContent, fileName)
.then(() => {
if (silent) {
setTimeout(() => window.close(), 1000);
} else {
setPdfPhase('done');
}
})
.catch((err) => {
Log.error('PDF generation failed:', err);
setPdfError('Failed to generate PDF');
setPdfPhase('error');
});
}, [dataReady, download, silent, reportContent, data]);
if (download) {
// Download mode - generate PDF and download it
setState('generating');
const fileName = `${generateFileName(data.flow)}.pdf`;
let state: ReportState;
let errorMessage: null | string = null;
generatePDFFromMarkdown(content, fileName)
.then(() => {
if (silent) {
// Silent download - close window after successful download
setTimeout(() => window.close(), 1000);
} else {
// Normal download - show content after download
setState('content');
}
})
.catch((err) => {
Log.error('PDF generation failed:', err);
setError('Failed to generate PDF');
setState('error');
});
} else {
setState('content');
}
}, [data, loading, queryError, download, silent]);
if (loading) {
state = 'loading';
} else if (queryError || !data?.flow) {
state = 'error';
errorMessage = 'Failed to load flow data';
} else if (pdfPhase === 'error') {
state = 'error';
errorMessage = pdfError;
} else if (download && pdfPhase !== 'done') {
state = 'generating';
} else {
state = 'content';
}
// Loading state (for all modes during initial loading and PDF generation)
if (state === 'loading' || state === 'generating') {
return (
<div className="min-h-screen bg-linear-to-br from-blue-50 via-white to-purple-50 dark:from-gray-900 dark:via-gray-800 dark:to-gray-900">
@@ -99,7 +111,6 @@ const FlowReport = () => {
);
}
// Error state
if (state === 'error') {
return (
<div className="min-h-screen bg-linear-to-br from-red-50 via-white to-orange-50 dark:from-gray-900 dark:via-gray-800 dark:to-gray-900">
@@ -108,7 +119,7 @@ const FlowReport = () => {
<div className="flex flex-col gap-4 text-center">
<h1 className="text-2xl font-semibold text-red-600 dark:text-red-400">Error Loading Report</h1>
<p className="max-w-md text-gray-600 dark:text-gray-400">
{error || 'An unexpected error occurred while loading the report.'}
{errorMessage || 'An unexpected error occurred while loading the report.'}
</p>
<button
className="mt-4 rounded-md bg-red-600 px-4 py-2 text-white transition-colors hover:bg-red-700"
@@ -122,7 +133,6 @@ const FlowReport = () => {
);
}
// Content viewing state (normal mode without download)
return (
<div className="min-h-screen bg-white dark:bg-gray-900">
<div className="h-screen w-full overflow-auto p-8">
+1 -4
View File
@@ -13,10 +13,7 @@ const Login = () => {
const authProviders = authInfo?.providers || [];
// Extract the return URL from either location state or query parameters
const returnUrl = getSafeReturnUrl(
(location.state?.from as string) || searchParams.get('returnUrl'),
'/flows/new',
);
const returnUrl = getSafeReturnUrl((location.state?.from as string) || searchParams.get('returnUrl'), '/flows/new');
return (
<div className="flex h-dvh w-full items-center justify-center">
@@ -105,7 +105,6 @@ const formatFullDateTime = (dateString: string) => {
const SettingsMcpServers = () => {
const navigate = useNavigate();
// Mocked data stored locally. This can be replaced by a real query later.
const initialData: McpServerItem[] = useMemo(
() => [
@@ -533,7 +533,6 @@ const SettingsPrompt = () => {
// For creation, check if the template is identical to the default
if (!isUpdate && formData.template === promptInfo.defaultSystemTemplate) {
return;
}
@@ -585,7 +584,6 @@ const SettingsPrompt = () => {
// For creation, check if the template is identical to the default
if (!isUpdate && formData.template === promptInfo.defaultHumanTemplate) {
return;
}
@@ -78,7 +78,6 @@ const SettingsPrompts = () => {
type: 'all' | 'human' | 'system' | 'tool';
}>(null);
// Three-way sorting handler: null -> asc -> desc -> null
const handleColumnSort = (column: {
clearSorting: () => void;
@@ -328,7 +328,9 @@ const FormModelComboboxItem: React.FC<FormModelComboboxItemProps> = ({
const displayValue = field.value ?? '';
// Format price for display
const formatPrice = (price?: null | { cacheRead: number; cacheWrite: number; input: number; output: number }): string => {
const formatPrice = (
price?: null | { cacheRead: number; cacheWrite: number; input: number; output: number },
): string => {
if (!price || ((!price.input || price.input === 0) && (!price.output || price.output === 0))) {
return 'free';
}
@@ -338,7 +340,7 @@ const FormModelComboboxItem: React.FC<FormModelComboboxItemProps> = ({
};
const basePrice = `$${formatValue(price.input)}/$${formatValue(price.output)}`;
// Add cache prices if available
const hasCachePrices = (price.cacheRead && price.cacheRead > 0) || (price.cacheWrite && price.cacheWrite > 0);
@@ -142,7 +142,6 @@ const SettingsProviders = () => {
const [deletingProvider, setDeletingProvider] = useState<null | Provider>(null);
const navigate = useNavigate();
// Get current page from URL
const currentPage = useMemo(() => {
const page = searchParams.get('page');
@@ -66,6 +66,7 @@ export const ThemeProvider = ({
// Store only light or dark themes
localStorage.setItem(storageKey, theme);
}
setTheme(theme);
},
theme,
+6 -6
View File
@@ -286,8 +286,8 @@
--ring: oklch(0.25 0.14 245);
--chart-1: oklch(0.25 0.14 245);
--chart-2: oklch(0.38 0.18 245);
--chart-3: oklch(0.50 0.22 245);
--chart-4: oklch(0.42 0.10 245);
--chart-3: oklch(0.5 0.22 245);
--chart-4: oklch(0.42 0.1 245);
--chart-5: oklch(0.55 0.14 245);
--sidebar: oklch(0.98 0 240);
--sidebar-foreground: oklch(0.32 0 0);
@@ -339,10 +339,10 @@
--border: oklch(0.3 0.04 245);
--input: oklch(0.3 0.04 245);
--ring: oklch(0.5 0.16 245);
--chart-1: oklch(0.50 0.16 245);
--chart-2: oklch(0.60 0.20 245);
--chart-3: oklch(0.70 0.22 245);
--chart-4: oklch(0.58 0.10 245);
--chart-1: oklch(0.5 0.16 245);
--chart-2: oklch(0.6 0.2 245);
--chart-3: oklch(0.7 0.22 245);
--chart-4: oklch(0.58 0.1 245);
--chart-5: oklch(0.74 0.14 245);
--sidebar: oklch(0.15 0.02 245);
--sidebar-foreground: oklch(0.92 0 0);