diff --git a/app/api/github/repositories/route.ts b/app/api/github/repositories/route.ts deleted file mode 100644 index dcf3957..0000000 --- a/app/api/github/repositories/route.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { NextResponse } from 'next/server' - -import { auth } from '@/lib/auth' -import { prisma } from '@/lib/db' -import { createGitHubClient } from '@/lib/github' - -// Get user's GitHub repositories -export async function GET() { - const session = await auth() - - if (!session) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - - try { - // Get GitHub identity with token - const githubIdentity = await prisma.userIdentity.findFirst({ - where: { - userId: session.user.id, - provider: 'GITHUB', - }, - }) - - if (!githubIdentity) { - return NextResponse.json( - { error: 'GitHub account not connected. Please sign in with GitHub.' }, - { status: 400 } - ) - } - - const metadata = githubIdentity.metadata as { token?: string } - const githubToken = metadata.token - - if (!githubToken) { - return NextResponse.json( - { error: 'GitHub token not found. Please reconnect your GitHub account.' }, - { status: 400 } - ) - } - - // Fetch data from GitHub API in parallel - const githubClient = createGitHubClient(githubToken) - const [githubUser, allRepos, organizations] = await Promise.all([ - githubClient.getUser(), - githubClient.listRepos(), - githubClient.listOrganizations(), - ]) - - // Build accounts array (personal account + organizations) - const accounts = [ - { - login: githubUser.login, - type: 'User' as const, - avatarUrl: githubUser.avatar_url, - name: githubUser.name, - }, - ...organizations.map((org) => ({ - login: org.login, - type: 'Organization' as const, - avatarUrl: org.avatar_url, - name: org.login, - })), - ] - - // Format repositories with owner information - const formattedRepos = allRepos.map((repo) => ({ - name: repo.name, - fullName: repo.full_name, - private: repo.private, - description: repo.description, - owner: { - login: repo.owner.login, - type: repo.owner.type, - }, - })) - - return NextResponse.json({ - accounts, - repositories: formattedRepos, - count: formattedRepos.length, - }) - } catch (error: unknown) { - console.error('Error fetching GitHub repositories:', error) - - // Handle GitHub API rate limiting - if (error instanceof Error && 'status' in error && error.status === 403) { - return NextResponse.json( - { error: 'GitHub API rate limit exceeded. Please try again later.' }, - { status: 429 } - ) - } - - // Handle invalid or expired token - if (error instanceof Error && 'status' in error && error.status === 401) { - return NextResponse.json( - { error: 'GitHub token is invalid or expired. Please reconnect your GitHub account.' }, - { status: 401 } - ) - } - - return NextResponse.json({ error: 'Failed to fetch GitHub repositories' }, { status: 500 }) - } -} diff --git a/app/api/projects/[id]/environment/route.ts b/app/api/projects/[id]/environment/route.ts index d5a45b8..47f92f1 100644 --- a/app/api/projects/[id]/environment/route.ts +++ b/app/api/projects/[id]/environment/route.ts @@ -2,13 +2,10 @@ import type { Environment } from '@prisma/client' import { NextResponse } from 'next/server' import { verifyProjectAccess, withAuth } from '@/lib/api-auth' +import { EnvironmentCategory } from '@/lib/const' import { prisma } from '@/lib/db' -type GroupedEnvironments = { - general: Environment[] - auth: Environment[] - payment: Environment[] -} +type GroupedEnvironments = Record type GetEnvironmentsResponse = { error: string } | GroupedEnvironments @@ -25,12 +22,16 @@ export const GET = withAuth(async (_req, context, sessi orderBy: { createdAt: 'asc' }, }) - // Group environment variables by category - const grouped = { - general: environments.filter((e) => !e.category || e.category === 'general'), - auth: environments.filter((e) => e.category === 'auth'), - payment: environments.filter((e) => e.category === 'payment'), - } + // Group environment variables by category (dynamically based on EnvironmentCategory enum) + const grouped: GroupedEnvironments = {} + + // Initialize all categories from enum + Object.values(EnvironmentCategory).forEach((category) => { + grouped[category] = environments.filter((e) => e.category === category) + }) + + // Add general category for null/undefined categories + grouped.general = environments.filter((e) => !e.category) return NextResponse.json(grouped) } catch (error) { diff --git a/app/projects/[id]/github/page.tsx b/app/projects/[id]/github/page.tsx deleted file mode 100644 index 596d602..0000000 --- a/app/projects/[id]/github/page.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { Github as GithubIcon, Link2 } from 'lucide-react'; -import { redirect } from 'next/navigation'; -import { notFound } from 'next/navigation'; - -import { GitHubRepositorySelector } from '@/components/github-repository-selector'; -import { auth } from '@/lib/auth'; -import { prisma } from '@/lib/db'; - -export default async function GitHubRepositoryPage({ - params, -}: { - params: Promise<{ id: string }>; -}) { - const session = await auth(); - - if (!session) { - redirect('/login'); - } - - const { id } = await params; - - const project = await prisma.project.findFirst({ - where: { - id: id, - userId: session.user.id, - }, - }); - - if (!project) { - notFound(); - } - - return ( -
- {/* Header */} -
-
-

- - GitHub Repository -

-

Connect and manage your GitHub repository

-
-
- - {/* Content */} -
-
- {/* Connection Status */} -
-

- - Repository Connection -

- - -
- - {/* GitHub Integration Features */} -
-

- - GitHub Features -

- -
-
-
-
-

Version Control

-

Track changes and collaborate with Git

-
-
-
-
-
-

Automatic Commits

-

- AI commits changes with descriptive messages -

-
-
-
-
-
-

Pull Request Integration

-

Create and manage pull requests

-
-
-
-
-
-

GitHub Actions (Coming Soon)

-

Automated CI/CD workflows

-
-
-
-
-
-
-
- ); -} diff --git a/app/projects/[id]/layout.tsx b/app/projects/[id]/layout.tsx index f6815d4..3a2ed9b 100644 --- a/app/projects/[id]/layout.tsx +++ b/app/projects/[id]/layout.tsx @@ -1,11 +1,8 @@ import { redirect } from 'next/navigation'; import { notFound } from 'next/navigation'; -import ContentWrapper from '@/components/content-wrapper'; -import PersistentTerminal from '@/components/persistent-terminal'; import PrimarySidebar from '@/components/sidebars/primary-sidebar'; import ProjectSidebar from '@/components/sidebars/project-sidebar'; -import { TerminalProvider } from '@/components/terminal-provider'; import { auth } from '@/lib/auth'; import { prisma } from '@/lib/db'; @@ -40,31 +37,23 @@ export default async function ProjectLayout({ notFound(); } - - return ( - -
- {/* Primary Sidebar - VSCode style */} - - - {/* Secondary Sidebar - Project Settings */} - - {/* Main Content Area */} - -
- {/* Regular Page Content */} - {children} -
-
- - {/* Persistent Terminal (hidden by default) - separate from main content */} - + return ( +
+ {/* Primary Sidebar - VSCode style */} + + + {/* Secondary Sidebar - Project Settings */} + + + {/* Main Content Area */} +
+ {children}
- +
); } diff --git a/app/projects/[id]/terminal/page.tsx b/app/projects/[id]/terminal/page.tsx index 8966152..6337f37 100644 --- a/app/projects/[id]/terminal/page.tsx +++ b/app/projects/[id]/terminal/page.tsx @@ -1,92 +1,47 @@ -'use client'; +/** + * Terminal Page + * + * Displays terminal interface for project sandbox + * Uses React Query for automatic state synchronization + */ -import { useEffect, useState } from 'react'; -import type { Prisma } from '@prisma/client'; -import { AlertCircle, ChevronDown, Loader2, Play, Square, Trash2 } from 'lucide-react'; -import { useParams, useRouter } from 'next/navigation'; +'use client'; -import ProjectTerminalView from '@/components/project-terminal-view'; -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, -} from '@/components/ui/alert-dialog'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; -import { GET, POST } from '@/lib/fetch-client'; -import { getAvailableProjectActions, type ProjectAction } from '@/lib/util/action'; -import { cn } from '@/lib/utils'; +import { AlertCircle } from 'lucide-react'; +import { useParams } from 'next/navigation'; -type Project = Prisma.ProjectGetPayload<{ - include: { - sandboxes: true; - databases: true; - }; -}>; +import { TerminalContainer } from '@/components/terminal/terminal-container'; +import { Spinner } from '@/components/ui/spinner'; +import { useProject } from '@/hooks/use-project'; export default function TerminalPage() { const params = useParams(); const projectId = params.id as string; - const [project, setProject] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - // Fetch project data - const fetchProject = async () => { - try { - const data = await GET(`/api/projects/${projectId}`); - setProject(data); - setError(null); - } catch (err) { - console.error('Failed to fetch project:', err); - setError('Failed to load project'); - } finally { - setLoading(false); - } - }; - - // Initial load and polling - useEffect(() => { - // Initial fetch - fetchProject(); + // Fetch project with automatic polling (every 3 seconds) + const { data: project, isLoading, error } = useProject(projectId); - // Polling: refresh every 3 seconds - const interval = setInterval(() => { - fetchProject(); - }, 3000); - - return () => clearInterval(interval); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [projectId]); - - if (loading) { + // Loading state + if (isLoading) { return ( -
-
- - Loading project... +
+
+ + Loading project...
); } + // Error state if (error || !project) { return ( -
-
- - {error || 'Project not found'} +
+
+ + + {error ? 'Failed to load project' : 'Project not found'} +
); @@ -96,234 +51,7 @@ export default function TerminalPage() { return (
- {/* Conditional Terminal View based on Project Status (aggregated) */} -
- {project.status === 'RUNNING' && sandbox ? ( - - ) : ( - - )} -
-
- ); -} - -interface StatusTransitionViewProps { - status: string; - project: Project; -} - -function StatusTransitionView({ status, project }: StatusTransitionViewProps) { - const router = useRouter(); - const [loading, setLoading] = useState(null); - const [showDeleteDialog, setShowDeleteDialog] = useState(false); - - const availableActions = getAvailableProjectActions(project); - - const handleOperation = async (action: ProjectAction) => { - setLoading(action); - - try { - let endpoint = ''; - - switch (action) { - case 'START': - endpoint = `/api/projects/${project.id}/start`; - break; - case 'STOP': - endpoint = `/api/projects/${project.id}/stop`; - break; - case 'DELETE': - endpoint = `/api/projects/${project.id}/delete`; - break; - default: - throw new Error(`Unknown action: ${action}`); - } - - await POST(endpoint); - - if (action === 'DELETE') { - router.push('/projects'); - return; - } - - router.refresh(); - } catch (err) { - console.error(`Failed to ${action.toLowerCase()} project:`, err); - } finally { - setLoading(null); - } - }; - - const handleDeleteClick = () => { - setShowDeleteDialog(true); - }; - - const handleDeleteConfirm = () => { - setShowDeleteDialog(false); - handleOperation('DELETE'); - }; - - // Get status message and icon - let message = ''; - let showSpinner = false; - - switch (status) { - case 'CREATING': - message = 'Creating sandbox...'; - showSpinner = true; - break; - case 'STARTING': - message = 'Starting sandbox...'; - showSpinner = true; - break; - case 'STOPPED': - message = 'Sandbox stopped'; - showSpinner = false; - break; - case 'STOPPING': - message = 'Stopping sandbox...'; - showSpinner = true; - break; - case 'TERMINATING': - message = 'Terminating sandbox...'; - showSpinner = true; - break; - case 'ERROR': - message = 'Sandbox error'; - showSpinner = false; - break; - default: - message = `Status: ${status}`; - showSpinner = true; - } - - return ( -
- {/* Header Bar with Operations */} -
-
- {/* Status Badge */} -
-
- {project.status} -
- - {/* Operations Dropdown */} - - - - - - {availableActions.includes('START') && ( - handleOperation('START')} - disabled={loading !== null} - className="text-xs cursor-pointer focus:bg-accent focus:text-foreground" - > - {loading === 'START' ? ( - <> - - Starting... - - ) : ( - <> - - Start Sandbox - - )} - - )} - {availableActions.includes('STOP') && ( - handleOperation('STOP')} - disabled={loading !== null} - className="text-xs cursor-pointer focus:bg-accent focus:text-foreground" - > - {loading === 'STOP' ? ( - <> - - Stopping... - - ) : ( - <> - - Stop Sandbox - - )} - - )} - {availableActions.includes('DELETE') && ( - <> - - - - Delete Sandbox - - - )} - - -
-
- - {/* Status Content */} -
-
- {showSpinner ? ( - - ) : status === 'ERROR' ? ( - - ) : null} -

{message}

-
-
- - {/* Delete Confirmation Dialog */} - - - - Delete Project - - Are you sure you want to delete this project? This will terminate all resources - (databases, sandboxes) and cannot be undone. - - - - - Cancel - - - Delete - - - - +
); -} +} \ No newline at end of file diff --git a/app/projects/new/page.tsx b/app/projects/new/page.tsx index a783d3a..824040c 100644 --- a/app/projects/new/page.tsx +++ b/app/projects/new/page.tsx @@ -5,7 +5,7 @@ import { Loader2 } from 'lucide-react'; import { useRouter } from 'next/navigation'; import { toast } from 'sonner'; -import SettingsDialog from '@/components/settings-dialog'; +import SettingsDialog from '@/components/dialog/settings-dialog'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; diff --git a/app/projects/page.tsx b/app/projects/page.tsx index 612907a..c1a2539 100644 --- a/app/projects/page.tsx +++ b/app/projects/page.tsx @@ -1,61 +1,28 @@ -'use client'; +/** + * Projects Page + * + * Displays list of user projects with automatic polling + * Uses React Query for state management + */ -import { useCallback, useEffect, useState } from 'react'; -import { Loader2 } from 'lucide-react'; +'use client'; import NoProject from '@/components/features/projectList/NoProject'; import PageHeader from '@/components/features/projectList/PageHeader'; import ProjectCard from '@/components/features/projectList/ProjectCard'; -import { GET } from '@/lib/fetch-client'; -import { Project } from '@/types/project'; - -// TODO: convert this page to ssr, add loading and error status, add a ProjectGrid UI, and handle data fetching there +import { Spinner } from '@/components/ui/spinner'; +import { useProjects } from '@/hooks/use-projects'; export default function ProjectsPage() { - const [projects, setProjects] = useState([]); - const [loading, setLoading] = useState(true); - - // Fetch projects list with AbortController support - const fetchProjects = useCallback(async (signal?: AbortSignal) => { - try { - const data = await GET('/api/projects', { signal }); - setProjects(data); - } catch (error) { - // Ignore AbortError (component was unmounted) - if (error instanceof Error && error.name === 'AbortError') { - return; - } - console.error('Failed to fetch projects:', error); - } finally { - setLoading(false); - } - }, []); - - // Initial load and polling with proper cleanup - useEffect(() => { - const abortController = new AbortController(); - - // Initial fetch - fetchProjects(abortController.signal); - - // Set up polling - const interval = setInterval(() => { - fetchProjects(abortController.signal); - }, 3000); - - // Cleanup function - return () => { - abortController.abort(); // Cancel all ongoing requests - clearInterval(interval); // Clear polling interval - }; - }, [fetchProjects]); + // Fetch projects with automatic polling (every 3 seconds) + const { data: projects, isLoading } = useProjects(); - if (loading) { + if (isLoading) { return (
-
- -

Loading projects...

+
+ + Loading projects...
); @@ -64,11 +31,11 @@ export default function ProjectsPage() { return (
{/* Header Bar */} - + {/* Content */}
- {projects.length === 0 ? ( + {!projects || projects.length === 0 ? ( ) : (
@@ -80,4 +47,4 @@ export default function ProjectsPage() {
); -} +} \ No newline at end of file diff --git a/app/settings/layout.tsx b/app/settings/layout.tsx deleted file mode 100644 index 6f675ee..0000000 --- a/app/settings/layout.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { ReactNode } from 'react'; -import { redirect } from 'next/navigation'; - -import PrimarySidebar from '@/components/sidebars/primary-sidebar'; -import { auth } from '@/lib/auth'; - -export default async function SettingsLayout({ - children, -}: Readonly<{ - children: ReactNode; -}>) { - const session = await auth(); - - if (!session || !session.user?.id) { - redirect('/login'); - } - - return ( -
- {/* Primary Sidebar - VSCode style */} - - - {/* Main Content Area with Settings */} -
{children}
-
- ); -} diff --git a/app/settings/page.tsx b/app/settings/page.tsx deleted file mode 100644 index a657c86..0000000 --- a/app/settings/page.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Settings } from 'lucide-react'; -import { redirect } from 'next/navigation'; - -import { auth } from '@/lib/auth'; -import { prisma } from '@/lib/db'; - -import SettingsClient from './settings-client'; - -export default async function SettingsPage() { - const session = await auth(); - - if (!session || !session.user?.id) { - redirect('/login'); - } - - // Get user info - const user = await prisma.user.findUnique({ - where: { id: session.user.id }, - select: { - id: true, - name: true, - }, - }); - - if (!user) { - redirect('/login'); - } - - // Get projects count for account info - const projectsCount = await prisma.project.count({ - where: { userId: user.id }, - }); - - return ( -
-
-
- -

Settings

-
-

- Configure your development environment and system preferences -

-
- - -
- ); -} diff --git a/app/settings/settings-client.tsx b/app/settings/settings-client.tsx deleted file mode 100644 index e3049e0..0000000 --- a/app/settings/settings-client.tsx +++ /dev/null @@ -1,489 +0,0 @@ -'use client'; - -import { useEffect, useState } from 'react'; -import { Code, Database, Globe, Key, Save, Shield, Terminal } from 'lucide-react'; -import { toast } from 'sonner'; - -import { Button } from '@/components/ui/button'; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; -import { Textarea } from '@/components/ui/textarea'; -import * as fetchClient from '@/lib/fetch-client'; -import { useSealos } from '@/provider/sealos'; - -interface SettingsClientProps { - user: { - id: string; - name: string | null; - }; - projectsCount: number; -} - -const DEFAULT_SYSTEM_PROMPT = `You are an AI full-stack developer working in a Next.js environment. - -## Environment Information -- Framework: Next.js 15 with App Router -- Language: TypeScript -- Database: PostgreSQL with Prisma ORM -- UI Framework: Shadcn/UI with Tailwind CSS -- Authentication: NextAuth v5 - -## Available Environment Variables -- DATABASE_URL: PostgreSQL connection string -- ANTHROPIC_API_KEY: Claude API key -- ANTHROPIC_BASE_URL: Claude API base URL - -## Instructions -- Follow Next.js 15 App Router conventions -- Use TypeScript for type safety -- Implement proper error handling and loading states -- Follow responsive design principles with Tailwind CSS -- Focus on creating production-ready, maintainable code - -## Development Guidelines -- Write clean, readable code with proper documentation -- Implement proper error boundaries and validation -- Use semantic HTML and accessibility best practices -- Optimize for performance and SEO -- Follow modern React patterns and best practices`; - -export default function SettingsClient({ user, projectsCount }: SettingsClientProps) { - // Sealos context - const { isSealos } = useSealos(); - - // System Prompt state - const [systemPrompt, setSystemPrompt] = useState(''); - const [isSystemPromptLoading, setIsSystemPromptLoading] = useState(false); - const [isSystemPromptInitialLoading, setIsSystemPromptInitialLoading] = useState(true); - - // Kubeconfig state - const [kubeconfig, setKubeconfig] = useState(''); - const [kubeconfigNamespace, setKubeconfigNamespace] = useState(null); - const [isKubeconfigLoading, setIsKubeconfigLoading] = useState(false); - const [isKubeconfigInitialLoading, setIsKubeconfigInitialLoading] = useState(true); - - // Anthropic state - const [anthropicApiKey, setAnthropicApiKey] = useState(''); - const [anthropicApiBaseUrl, setAnthropicApiBaseUrl] = useState(''); - const [isAnthropicLoading, setIsAnthropicLoading] = useState(false); - const [isAnthropicInitialLoading, setIsAnthropicInitialLoading] = useState(true); - - // Load system prompt - useEffect(() => { - const loadSystemPrompt = async () => { - try { - const data = await fetchClient.GET<{ systemPrompt: string | null }>( - '/api/user/config/system-prompt' - ); - setSystemPrompt(data.systemPrompt || DEFAULT_SYSTEM_PROMPT); - } catch (error) { - console.error('Failed to load system prompt:', error); - setSystemPrompt(DEFAULT_SYSTEM_PROMPT); - } finally { - setIsSystemPromptInitialLoading(false); - } - }; - - loadSystemPrompt(); - }, []); - - // Load kubeconfig - useEffect(() => { - const loadKubeconfig = async () => { - try { - const data = await fetchClient.GET<{ kubeconfig: string; namespace?: string | null }>( - '/api/user/config/kc' - ); - setKubeconfig(data.kubeconfig); - setKubeconfigNamespace(data.namespace || null); - } catch (error: unknown) { - if (error && typeof error === 'object' && 'status' in error && error.status === 404) { - // No kubeconfig found, that's ok - setKubeconfig(''); - } else { - console.error('Failed to load kubeconfig:', error); - toast.error('Failed to load kubeconfig'); - } - } finally { - setIsKubeconfigInitialLoading(false); - } - }; - - loadKubeconfig(); - }, []); - - // Load Anthropic config - useEffect(() => { - const loadAnthropicConfig = async () => { - try { - const data = await fetchClient.GET<{ apiKey: string | null; apiBaseUrl: string | null }>( - '/api/user/config/anthropic' - ); - setAnthropicApiKey(data.apiKey || ''); - setAnthropicApiBaseUrl(data.apiBaseUrl || ''); - } catch (error) { - console.error('Failed to load Anthropic config:', error); - } finally { - setIsAnthropicInitialLoading(false); - } - }; - - loadAnthropicConfig(); - }, []); - - // Save system prompt - const handleSaveSystemPrompt = async () => { - setIsSystemPromptLoading(true); - try { - await fetchClient.POST('/api/user/config/system-prompt', { - systemPrompt, - }); - toast.success('System prompt saved successfully'); - } catch (error) { - console.error('Failed to save system prompt:', error); - toast.error('Failed to save system prompt'); - } finally { - setIsSystemPromptLoading(false); - } - }; - - // Save kubeconfig - const handleSaveKubeconfig = async () => { - if (!kubeconfig.trim()) { - toast.error('Kubeconfig cannot be empty'); - return; - } - - setIsKubeconfigLoading(true); - try { - const result = await fetchClient.POST<{ - success: boolean; - namespace?: string; - error?: string; - valid?: boolean; - }>('/api/user/config/kc', { - kubeconfig, - }); - - if (result.success) { - setKubeconfigNamespace(result.namespace || null); - toast.success(`Kubeconfig saved successfully (namespace: ${result.namespace})`); - } else { - toast.error(result.error || 'Failed to save kubeconfig'); - } - } catch (error: unknown) { - console.error('Failed to save kubeconfig:', error); - const errorMessage = - error && typeof error === 'object' && 'body' in error && error.body - ? (error.body as { error?: string }).error || 'Failed to save kubeconfig' - : 'Failed to save kubeconfig'; - toast.error(errorMessage); - } finally { - setIsKubeconfigLoading(false); - } - }; - - // Save Anthropic config - const handleSaveAnthropicConfig = async () => { - if (!anthropicApiKey.trim() || !anthropicApiBaseUrl.trim()) { - toast.error('Both API key and base URL are required'); - return; - } - - setIsAnthropicLoading(true); - try { - await fetchClient.POST('/api/user/config/anthropic', { - apiKey: anthropicApiKey, - apiBaseUrl: anthropicApiBaseUrl, - }); - toast.success('Anthropic configuration saved successfully'); - } catch (error: unknown) { - console.error('Failed to save Anthropic config:', error); - const errorMessage = - error && typeof error === 'object' && 'body' in error && error.body - ? (error.body as { error?: string }).error || 'Failed to save Anthropic configuration' - : 'Failed to save Anthropic configuration'; - toast.error(errorMessage); - } finally { - setIsAnthropicLoading(false); - } - }; - - // Reset system prompt to default - const handleResetSystemPrompt = () => { - setSystemPrompt(DEFAULT_SYSTEM_PROMPT); - toast.success('Reset to default system prompt'); - }; - - return ( - - - - - System Prompt - - {!isSealos && ( - - - Kubeconfig - - )} - - - Anthropic - - - - Account - - - - {/* System Prompt Tab */} - - - - - - System Prompt Configuration - - - Define the system instruction set for Claude Code. This helps the AI understand your - project environment, programming languages, frameworks, and available environment - variables. - - - -
- -