diff --git a/artifacts/sandbox-ai/src/App.tsx b/artifacts/sandbox-ai/src/App.tsx index 3feaa26..a68e114 100644 --- a/artifacts/sandbox-ai/src/App.tsx +++ b/artifacts/sandbox-ai/src/App.tsx @@ -22,11 +22,17 @@ import AuthCheck from "@/pages/auth-check"; import Templates from "@/pages/templates"; import ImportGitHub from "@/pages/import-github"; import { usePermissions } from "@/hooks/use-permissions"; +import HealthConfigPage from "@/pages/health-config"; +import { clerkConfig, isAuthEnabled } from "@/config/auth-mode"; +import AuthPortal from "@/pages/auth-portal"; +import MobileDashboard from "@/pages/mobile-dashboard"; +import ChatStaticPage from "@/pages/chat-static"; +import TermuxLab from "@/pages/termux-lab"; +import ToolsHub from "@/pages/tools-hub"; const queryClient = new QueryClient(); -const clerkPubKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY; -const clerkProxyUrl = import.meta.env.VITE_CLERK_PROXY_URL; +const { clerkPubKey, clerkProxyUrl } = clerkConfig; const basePath = import.meta.env.BASE_URL.replace(/\/$/, ""); function stripBase(path: string): string { @@ -198,15 +204,22 @@ function ClerkAuthTokenSetter() { return null; } + function Router() { return ( + + + + + + @@ -258,14 +271,61 @@ function ClerkProviderWithRoutes() { ); } + +function PublicOnlyRouter() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} + +function PublicAppWithoutAuth() { + return ( + + +
+ Auth is running in public mode. Open /health-config to inspect environment readiness. +
+ + +
+
+ ); +} function App() { const [showSplash, setShowSplash] = useState(true); - if (!clerkPubKey) { + if (!isAuthEnabled) { return ( -
- Missing VITE_CLERK_PUBLISHABLE_KEY — please check environment variables. -
+ + + ); } diff --git a/artifacts/sandbox-ai/src/components/chat-area.tsx b/artifacts/sandbox-ai/src/components/chat-area.tsx index cac55e8..eddf211 100644 --- a/artifacts/sandbox-ai/src/components/chat-area.tsx +++ b/artifacts/sandbox-ai/src/components/chat-area.tsx @@ -282,7 +282,7 @@ export function ChatArea({ conversationId, onToggleSidebar, isSidebarOpen }: Cha createConversation.mutate( { data: { title: content.slice(0, 60), mode: mode as any, model } }, { - onSuccess: (conv) => { + onSuccess: (conv: any) => { queryClient.invalidateQueries({ queryKey: getListOpenaiConversationsQueryKey() }); pendingRef.current = { message: content, forConvId: conv.id }; navigate(`/chat/${conv.id}`); diff --git a/artifacts/sandbox-ai/src/components/chat-sidebar.tsx b/artifacts/sandbox-ai/src/components/chat-sidebar.tsx index f18df27..ce4d748 100644 --- a/artifacts/sandbox-ai/src/components/chat-sidebar.tsx +++ b/artifacts/sandbox-ai/src/components/chat-sidebar.tsx @@ -103,7 +103,7 @@ export function ChatSidebar({ isOpen, onToggle, activeId, isMobile = false, onCl createConversation.mutate( { data: { title: "New Conversation", mode: "chat", model: "gpt-5.2" } }, { - onSuccess: (conv) => { + onSuccess: (conv: any) => { queryClient.invalidateQueries({ queryKey: getListOpenaiConversationsQueryKey() }); setLocation(`/chat/${conv.id}`); onClose?.(); @@ -130,7 +130,7 @@ export function ChatSidebar({ isOpen, onToggle, activeId, isMobile = false, onCl if (!conversations) return []; if (!search.trim()) return conversations; const q = search.toLowerCase(); - return conversations.filter((c) => c.title?.toLowerCase().includes(q) || c.mode?.toLowerCase().includes(q)); + return conversations.filter((c: any) => c.title?.toLowerCase().includes(q) || c.mode?.toLowerCase().includes(q)); }, [conversations, search]); const grouped = useMemo(() => groupConversations(filtered as any), [filtered]); diff --git a/artifacts/sandbox-ai/src/config/auth-mode.ts b/artifacts/sandbox-ai/src/config/auth-mode.ts new file mode 100644 index 0000000..12e7ff7 --- /dev/null +++ b/artifacts/sandbox-ai/src/config/auth-mode.ts @@ -0,0 +1,38 @@ +export type AuthMode = "clerk" | "public"; + +const clerkPubKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY; +const clerkProxyUrl = import.meta.env.VITE_CLERK_PROXY_URL; +const requestedAuthMode = (import.meta.env.VITE_AUTH_MODE ?? "clerk").toLowerCase(); + +export const authMode: AuthMode = requestedAuthMode === "public" ? "public" : "clerk"; +export const isClerkConfigured = Boolean(clerkPubKey); +export const isAuthEnabled = authMode === "clerk" && isClerkConfigured; + +export const clerkConfig = { + clerkPubKey, + clerkProxyUrl, +}; + +export const envChecklist = [ + { + key: "VITE_AUTH_MODE", + value: import.meta.env.VITE_AUTH_MODE, + required: false, + status: "info" as const, + hint: "Optional. Use 'clerk' (default) or 'public'.", + }, + { + key: "VITE_CLERK_PUBLISHABLE_KEY", + value: clerkPubKey, + required: authMode === "clerk", + status: isClerkConfigured ? ("ok" as const) : ("missing" as const), + hint: "Required when VITE_AUTH_MODE=clerk.", + }, + { + key: "VITE_CLERK_PROXY_URL", + value: clerkProxyUrl, + required: false, + status: "info" as const, + hint: "Optional proxy URL for Clerk.", + }, +]; diff --git a/artifacts/sandbox-ai/src/pages/auth-portal.tsx b/artifacts/sandbox-ai/src/pages/auth-portal.tsx new file mode 100644 index 0000000..29f3f09 --- /dev/null +++ b/artifacts/sandbox-ai/src/pages/auth-portal.tsx @@ -0,0 +1,18 @@ +import { Link } from "wouter"; + +export default function AuthPortal() { + return ( +
+
+

Đăng nhập / Đăng ký

+

+ Quy trình OAuth (Google/GitHub) được xử lý qua Clerk. Bấm nút bên dưới để vào trang xác thực chính thức. +

+
+ Tiếp tục với Google / GitHub + Tạo tài khoản mới +
+
+
+ ); +} diff --git a/artifacts/sandbox-ai/src/pages/chat-static.tsx b/artifacts/sandbox-ai/src/pages/chat-static.tsx new file mode 100644 index 0000000..9ec1972 --- /dev/null +++ b/artifacts/sandbox-ai/src/pages/chat-static.tsx @@ -0,0 +1,22 @@ +export default function ChatStaticPage() { + const messages = [ + { role: "user", content: "Tạo API login bằng Node.js" }, + { role: "assistant", content: "Dưới đây là skeleton theo phong cách Codex, gồm route, service và validator..." }, + ]; + return ( +
+
Codex-style Chat (Static UI)
+
+ {messages.map((m, i) => ( +
+
{m.role}
+
{m.content}
+
+ ))} +
+
+ +
+
+ ); +} diff --git a/artifacts/sandbox-ai/src/pages/health-config.tsx b/artifacts/sandbox-ai/src/pages/health-config.tsx new file mode 100644 index 0000000..c7db80c --- /dev/null +++ b/artifacts/sandbox-ai/src/pages/health-config.tsx @@ -0,0 +1,27 @@ +import { authMode, envChecklist, isAuthEnabled } from "@/config/auth-mode"; + +export default function HealthConfigPage() { + return ( +
+

Health Config

+

+ Runtime auth mode: {authMode}. Auth enabled: {String(isAuthEnabled)}. +

+
+ {envChecklist.map((item) => ( +
+
+ {item.key} + + {item.status === "ok" ? "✅ configured" : item.status === "missing" ? "❌ missing" : "ℹ️ optional"} + +
+
required: {String(item.required)}
+
value: {item.value ? "set" : "empty"}
+
{item.hint}
+
+ ))} +
+
+ ); +} diff --git a/artifacts/sandbox-ai/src/pages/mobile-dashboard.tsx b/artifacts/sandbox-ai/src/pages/mobile-dashboard.tsx new file mode 100644 index 0000000..ac0b499 --- /dev/null +++ b/artifacts/sandbox-ai/src/pages/mobile-dashboard.tsx @@ -0,0 +1,36 @@ +import { useState } from "react"; +import { Link } from "wouter"; +import { Menu, X, MessageSquare, Wrench, TerminalSquare, User } from "lucide-react"; + +export default function MobileDashboard() { + const [open, setOpen] = useState(false); + return ( +
+
+ +

Sandbox Dashboard

+ +
+ + {open && ( + + )} + +
+
+

Mobile-first layout với menu 3 gạch như bạn yêu cầu.

+
+
+ Mở Chat + Mở Tools +
+
+
+ ); +} diff --git a/artifacts/sandbox-ai/src/pages/termux-lab.tsx b/artifacts/sandbox-ai/src/pages/termux-lab.tsx new file mode 100644 index 0000000..8956dc3 --- /dev/null +++ b/artifacts/sandbox-ai/src/pages/termux-lab.tsx @@ -0,0 +1,18 @@ +import { useState } from "react"; + +export default function TermuxLab() { + const [cmd, setCmd] = useState("npm run build"); + const [logs, setLogs] = useState(["[termux] session booted", "[ai] ready to suggest commands"]); + return ( +
+

Termux nền + AI command assistant (mock)

+
+ {logs.map((l, i) =>
{l}
)} +
+
+ setCmd(e.target.value)} className="flex-1 bg-white/10 text-white px-3 py-2 rounded" aria-label="Command" /> + +
+
+ ); +} diff --git a/artifacts/sandbox-ai/src/pages/tools-hub.tsx b/artifacts/sandbox-ai/src/pages/tools-hub.tsx new file mode 100644 index 0000000..30c5ef0 --- /dev/null +++ b/artifacts/sandbox-ai/src/pages/tools-hub.tsx @@ -0,0 +1,20 @@ +const tools = [ + "Prompt Optimizer", + "API Schema Checker", + "Log Analyzer", + "Release Checklist", + "Env Validator", +]; + +export default function ToolsHub() { + return ( +
+

Công cụ hỗ trợ

+
+ {tools.map((t) => ( +
{t}
+ ))} +
+
+ ); +} diff --git a/artifacts/sandbox-ai/tsconfig.json b/artifacts/sandbox-ai/tsconfig.json index 4b28e98..d968170 100644 --- a/artifacts/sandbox-ai/tsconfig.json +++ b/artifacts/sandbox-ai/tsconfig.json @@ -1,17 +1,33 @@ { "extends": "../../tsconfig.base.json", - "include": ["src/**/*"], - "exclude": ["node_modules", "build", "dist", "**/*.test.ts"], + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "build", + "dist", + "**/*.test.ts" + ], "compilerOptions": { "noEmit": true, "jsx": "preserve", - "lib": ["esnext", "dom", "dom.iterable"], + "lib": [ + "esnext", + "dom", + "dom.iterable" + ], "resolveJsonModule": true, "allowImportingTsExtensions": true, "moduleResolution": "bundler", - "types": ["node", "vite/client"], + "types": [ + "node", + "vite/client" + ], "paths": { - "@/*": ["./src/*"] + "@/*": [ + "./src/*" + ] } }, "references": [ diff --git a/docs_self_healing_process.md b/docs_self_healing_process.md new file mode 100644 index 0000000..0403d9f --- /dev/null +++ b/docs_self_healing_process.md @@ -0,0 +1,27 @@ +# Self-healing reminder workflow (OpenClaw/Eios style) + +## Goal +Automatically remind maintainers when deployment-critical configuration or code artifacts are missing. + +## 1) Detect +- On every deploy, open `/health-config` and validate required env keys. +- On CI, run `pnpm --filter @workspace/sandbox-ai run build` and fail on errors. + +## 2) Classify +- **Config missing** (e.g. `VITE_CLERK_PUBLISHABLE_KEY` when `VITE_AUTH_MODE=clerk`). +- **Artifact missing** (route/page/module referenced but not found). +- **Type/build breakage** (TypeScript/build errors). + +## 3) Notify owner +- Config missing -> notify DevOps/release owner. +- Artifact missing -> notify feature owner/repo maintainer. +- Build/type breakage -> notify author of latest PR and reviewer. + +## 4) Auto-remediation checklist +- If auth env missing in non-production: set `VITE_AUTH_MODE=public` as temporary fallback. +- If auth env missing in production: block release and require secret injection. +- If artifact missing: create placeholder file + TODO with owner and deadline. + +## 5) Prevent regressions +- Add a PR checklist item: “Did you verify `/health-config` and auth mode behavior?” +- Keep a short runbook for required variables per environment.