refactor: move from next js to vite and tanstack router
@@ -1,5 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
rules: {
|
|
||||||
"@typescript-eslint/no-require-imports": "off",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
/**
|
|
||||||
* Simplified Electron preload script
|
|
||||||
*
|
|
||||||
* Only exposes native features (dialogs, shell) and server URL.
|
|
||||||
* All other operations go through HTTP API.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const { contextBridge, ipcRenderer } = require("electron");
|
|
||||||
|
|
||||||
// Expose minimal API for native features
|
|
||||||
contextBridge.exposeInMainWorld("electronAPI", {
|
|
||||||
// Platform info
|
|
||||||
platform: process.platform,
|
|
||||||
isElectron: true,
|
|
||||||
|
|
||||||
// Connection check
|
|
||||||
ping: () => ipcRenderer.invoke("ping"),
|
|
||||||
|
|
||||||
// Get server URL for HTTP client
|
|
||||||
getServerUrl: () => ipcRenderer.invoke("server:getUrl"),
|
|
||||||
|
|
||||||
// Native dialogs - better UX than prompt()
|
|
||||||
openDirectory: () => ipcRenderer.invoke("dialog:openDirectory"),
|
|
||||||
openFile: (options) => ipcRenderer.invoke("dialog:openFile", options),
|
|
||||||
saveFile: (options) => ipcRenderer.invoke("dialog:saveFile", options),
|
|
||||||
|
|
||||||
// Shell operations
|
|
||||||
openExternalLink: (url) => ipcRenderer.invoke("shell:openExternal", url),
|
|
||||||
openPath: (filePath) => ipcRenderer.invoke("shell:openPath", filePath),
|
|
||||||
|
|
||||||
// App info
|
|
||||||
getPath: (name) => ipcRenderer.invoke("app:getPath", name),
|
|
||||||
getVersion: () => ipcRenderer.invoke("app:getVersion"),
|
|
||||||
isPackaged: () => ipcRenderer.invoke("app:isPackaged"),
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("[Preload] Electron API exposed (simplified mode)");
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { defineConfig, globalIgnores } from "eslint/config";
|
|
||||||
import nextVitals from "eslint-config-next/core-web-vitals";
|
|
||||||
import nextTs from "eslint-config-next/typescript";
|
|
||||||
|
|
||||||
const eslintConfig = defineConfig([
|
|
||||||
...nextVitals,
|
|
||||||
...nextTs,
|
|
||||||
// Override default ignores of eslint-config-next.
|
|
||||||
globalIgnores([
|
|
||||||
// Default ignores of eslint-config-next:
|
|
||||||
".next/**",
|
|
||||||
"out/**",
|
|
||||||
"build/**",
|
|
||||||
"next-env.d.ts",
|
|
||||||
// Electron files use CommonJS
|
|
||||||
"electron/**",
|
|
||||||
]),
|
|
||||||
]);
|
|
||||||
|
|
||||||
export default eslintConfig;
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import type { NextConfig } from "next";
|
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
|
||||||
output: "export",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default nextConfig;
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
const config = {
|
|
||||||
plugins: {
|
|
||||||
"@tailwindcss/postcss": {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
|
||||||
|
|
||||||
interface AnthropicResponse {
|
|
||||||
content?: Array<{ type: string; text?: string }>;
|
|
||||||
model?: string;
|
|
||||||
error?: { message?: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
|
||||||
try {
|
|
||||||
const { apiKey } = await request.json();
|
|
||||||
|
|
||||||
// Use provided API key or fall back to environment variable
|
|
||||||
const effectiveApiKey = apiKey || process.env.ANTHROPIC_API_KEY;
|
|
||||||
|
|
||||||
if (!effectiveApiKey) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ success: false, error: "No API key provided or configured in environment" },
|
|
||||||
{ status: 400 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send a simple test prompt to the Anthropic API
|
|
||||||
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"x-api-key": effectiveApiKey,
|
|
||||||
"anthropic-version": "2023-06-01",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
model: "claude-sonnet-4-20250514",
|
|
||||||
max_tokens: 100,
|
|
||||||
messages: [
|
|
||||||
{
|
|
||||||
role: "user",
|
|
||||||
content: "Respond with exactly: 'Claude API connection successful!' and nothing else.",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorData = (await response.json()) as AnthropicResponse;
|
|
||||||
const errorMessage = errorData.error?.message || `HTTP ${response.status}`;
|
|
||||||
|
|
||||||
if (response.status === 401) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ success: false, error: "Invalid API key. Please check your Anthropic API key." },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.status === 429) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ success: false, error: "Rate limit exceeded. Please try again later." },
|
|
||||||
{ status: 429 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{ success: false, error: `API error: ${errorMessage}` },
|
|
||||||
{ status: response.status }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = (await response.json()) as AnthropicResponse;
|
|
||||||
|
|
||||||
// Check if we got a valid response
|
|
||||||
if (data.content && data.content.length > 0) {
|
|
||||||
const textContent = data.content.find((block) => block.type === "text");
|
|
||||||
if (textContent && textContent.type === "text" && textContent.text) {
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
message: `Connection successful! Response: "${textContent.text}"`,
|
|
||||||
model: data.model,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
message: "Connection successful! Claude responded.",
|
|
||||||
model: data.model,
|
|
||||||
});
|
|
||||||
} catch (error: unknown) {
|
|
||||||
console.error("Claude API test error:", error);
|
|
||||||
|
|
||||||
const errorMessage =
|
|
||||||
error instanceof Error ? error.message : "Failed to connect to Claude API";
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{ success: false, error: errorMessage },
|
|
||||||
{ status: 500 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
|
||||||
|
|
||||||
interface GeminiContent {
|
|
||||||
parts: Array<{
|
|
||||||
text?: string;
|
|
||||||
inlineData?: {
|
|
||||||
mimeType: string;
|
|
||||||
data: string;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
role?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GeminiRequest {
|
|
||||||
contents: GeminiContent[];
|
|
||||||
generationConfig?: {
|
|
||||||
maxOutputTokens?: number;
|
|
||||||
temperature?: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GeminiResponse {
|
|
||||||
candidates?: Array<{
|
|
||||||
content: {
|
|
||||||
parts: Array<{
|
|
||||||
text: string;
|
|
||||||
}>;
|
|
||||||
role: string;
|
|
||||||
};
|
|
||||||
finishReason: string;
|
|
||||||
safetyRatings?: Array<{
|
|
||||||
category: string;
|
|
||||||
probability: string;
|
|
||||||
}>;
|
|
||||||
}>;
|
|
||||||
promptFeedback?: {
|
|
||||||
safetyRatings?: Array<{
|
|
||||||
category: string;
|
|
||||||
probability: string;
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
error?: {
|
|
||||||
code: number;
|
|
||||||
message: string;
|
|
||||||
status: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
|
||||||
try {
|
|
||||||
const { apiKey, imageData, mimeType, prompt } = await request.json();
|
|
||||||
|
|
||||||
// Use provided API key or fall back to environment variable
|
|
||||||
const effectiveApiKey = apiKey || process.env.GOOGLE_API_KEY;
|
|
||||||
|
|
||||||
if (!effectiveApiKey) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ success: false, error: "No API key provided or configured in environment" },
|
|
||||||
{ status: 400 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the request body
|
|
||||||
const requestBody: GeminiRequest = {
|
|
||||||
contents: [
|
|
||||||
{
|
|
||||||
parts: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
generationConfig: {
|
|
||||||
maxOutputTokens: 150,
|
|
||||||
temperature: 0.4,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add image if provided
|
|
||||||
if (imageData && mimeType) {
|
|
||||||
requestBody.contents[0].parts.push({
|
|
||||||
inlineData: {
|
|
||||||
mimeType: mimeType,
|
|
||||||
data: imageData,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add text prompt
|
|
||||||
const textPrompt = prompt || (imageData
|
|
||||||
? "Describe what you see in this image briefly."
|
|
||||||
: "Respond with exactly: 'Gemini SDK connection successful!' and nothing else.");
|
|
||||||
|
|
||||||
requestBody.contents[0].parts.push({
|
|
||||||
text: textPrompt,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Call Gemini API - using gemini-1.5-flash as it supports both text and vision
|
|
||||||
const model = imageData ? "gemini-1.5-flash" : "gemini-1.5-flash";
|
|
||||||
const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${effectiveApiKey}`;
|
|
||||||
|
|
||||||
const response = await fetch(geminiUrl, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(requestBody),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data: GeminiResponse = await response.json();
|
|
||||||
|
|
||||||
// Check for API errors
|
|
||||||
if (data.error) {
|
|
||||||
const errorMessage = data.error.message || "Unknown Gemini API error";
|
|
||||||
const statusCode = data.error.code || 500;
|
|
||||||
|
|
||||||
if (statusCode === 400 && errorMessage.includes("API key")) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ success: false, error: "Invalid API key. Please check your Google API key." },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (statusCode === 429) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ success: false, error: "Rate limit exceeded. Please try again later." },
|
|
||||||
{ status: 429 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{ success: false, error: `API error: ${errorMessage}` },
|
|
||||||
{ status: statusCode }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for valid response
|
|
||||||
if (!response.ok) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ success: false, error: `HTTP error: ${response.status} ${response.statusText}` },
|
|
||||||
{ status: response.status }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract response text
|
|
||||||
if (data.candidates && data.candidates.length > 0 && data.candidates[0].content?.parts?.length > 0) {
|
|
||||||
const responseText = data.candidates[0].content.parts
|
|
||||||
.filter((part) => part.text)
|
|
||||||
.map((part) => part.text)
|
|
||||||
.join("");
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
message: `Connection successful! Response: "${responseText.substring(0, 200)}${responseText.length > 200 ? '...' : ''}"`,
|
|
||||||
model: model,
|
|
||||||
hasImage: !!imageData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle blocked responses
|
|
||||||
if (data.promptFeedback?.safetyRatings) {
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
message: "Connection successful! Gemini responded (response may have been filtered).",
|
|
||||||
model: model,
|
|
||||||
hasImage: !!imageData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
message: "Connection successful! Gemini responded.",
|
|
||||||
model: model,
|
|
||||||
hasImage: !!imageData,
|
|
||||||
});
|
|
||||||
} catch (error: unknown) {
|
|
||||||
console.error("Gemini API test error:", error);
|
|
||||||
|
|
||||||
if (error instanceof TypeError && error.message.includes("fetch")) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ success: false, error: "Network error. Unable to reach Gemini API." },
|
|
||||||
{ status: 503 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const errorMessage =
|
|
||||||
error instanceof Error ? error.message : "Failed to connect to Gemini API";
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{ success: false, error: errorMessage },
|
|
||||||
{ status: 500 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 25 KiB |
@@ -1,26 +0,0 @@
|
|||||||
import type { Metadata } from "next";
|
|
||||||
import { GeistSans } from "geist/font/sans";
|
|
||||||
import { GeistMono } from "geist/font/mono";
|
|
||||||
import { Toaster } from "sonner";
|
|
||||||
import "./globals.css";
|
|
||||||
export const metadata: Metadata = {
|
|
||||||
title: "Automaker - Autonomous AI Development Studio",
|
|
||||||
description: "Build software autonomously with intelligent orchestration",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function RootLayout({
|
|
||||||
children,
|
|
||||||
}: Readonly<{
|
|
||||||
children: React.ReactNode;
|
|
||||||
}>) {
|
|
||||||
return (
|
|
||||||
<html lang="en" suppressHydrationWarning>
|
|
||||||
<body
|
|
||||||
className={`${GeistSans.variable} ${GeistMono.variable} antialiased`}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
<Toaster richColors position="bottom-right" />
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,255 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useEffect, useState, useCallback } from "react";
|
|
||||||
import { Sidebar } from "@/components/layout/sidebar";
|
|
||||||
import { WelcomeView } from "@/components/views/welcome-view";
|
|
||||||
import { BoardView } from "@/components/views/board-view";
|
|
||||||
import { SpecView } from "@/components/views/spec-view";
|
|
||||||
import { AgentView } from "@/components/views/agent-view";
|
|
||||||
import { SettingsView } from "@/components/views/settings-view";
|
|
||||||
import { InterviewView } from "@/components/views/interview-view";
|
|
||||||
import { ContextView } from "@/components/views/context-view";
|
|
||||||
import { ProfilesView } from "@/components/views/profiles-view";
|
|
||||||
import { SetupView } from "@/components/views/setup-view";
|
|
||||||
import { RunningAgentsView } from "@/components/views/running-agents-view";
|
|
||||||
import { TerminalView } from "@/components/views/terminal-view";
|
|
||||||
import { WikiView } from "@/components/views/wiki-view";
|
|
||||||
import { useAppStore } from "@/store/app-store";
|
|
||||||
import { useSetupStore } from "@/store/setup-store";
|
|
||||||
import { getElectronAPI, isElectron } from "@/lib/electron";
|
|
||||||
import {
|
|
||||||
FileBrowserProvider,
|
|
||||||
useFileBrowser,
|
|
||||||
setGlobalFileBrowser,
|
|
||||||
} from "@/contexts/file-browser-context";
|
|
||||||
|
|
||||||
function HomeContent() {
|
|
||||||
const {
|
|
||||||
currentView,
|
|
||||||
setCurrentView,
|
|
||||||
setIpcConnected,
|
|
||||||
theme,
|
|
||||||
currentProject,
|
|
||||||
previewTheme,
|
|
||||||
getEffectiveTheme,
|
|
||||||
} = useAppStore();
|
|
||||||
const { isFirstRun, setupComplete } = useSetupStore();
|
|
||||||
const [isMounted, setIsMounted] = useState(false);
|
|
||||||
const [streamerPanelOpen, setStreamerPanelOpen] = useState(false);
|
|
||||||
const { openFileBrowser } = useFileBrowser();
|
|
||||||
|
|
||||||
// Hidden streamer panel - opens with "\" key
|
|
||||||
const handleStreamerPanelShortcut = useCallback((event: KeyboardEvent) => {
|
|
||||||
// Don't trigger when typing in inputs
|
|
||||||
const activeElement = document.activeElement;
|
|
||||||
if (activeElement) {
|
|
||||||
const tagName = activeElement.tagName.toLowerCase();
|
|
||||||
if (
|
|
||||||
tagName === "input" ||
|
|
||||||
tagName === "textarea" ||
|
|
||||||
tagName === "select"
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (activeElement.getAttribute("contenteditable") === "true") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const role = activeElement.getAttribute("role");
|
|
||||||
if (role === "textbox" || role === "searchbox" || role === "combobox") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't trigger with modifier keys
|
|
||||||
if (event.ctrlKey || event.altKey || event.metaKey) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for "\" key (backslash)
|
|
||||||
if (event.key === "\\") {
|
|
||||||
event.preventDefault();
|
|
||||||
setStreamerPanelOpen((prev) => !prev);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Register the "\" shortcut for streamer panel
|
|
||||||
useEffect(() => {
|
|
||||||
window.addEventListener("keydown", handleStreamerPanelShortcut);
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("keydown", handleStreamerPanelShortcut);
|
|
||||||
};
|
|
||||||
}, [handleStreamerPanelShortcut]);
|
|
||||||
|
|
||||||
// Compute the effective theme: previewTheme takes priority, then project theme, then global theme
|
|
||||||
// This is reactive because it depends on previewTheme, currentProject, and theme from the store
|
|
||||||
const effectiveTheme = getEffectiveTheme();
|
|
||||||
|
|
||||||
// Prevent hydration issues
|
|
||||||
useEffect(() => {
|
|
||||||
setIsMounted(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Initialize global file browser for HttpApiClient
|
|
||||||
useEffect(() => {
|
|
||||||
setGlobalFileBrowser(openFileBrowser);
|
|
||||||
}, [openFileBrowser]);
|
|
||||||
|
|
||||||
// Check if this is first run and redirect to setup if needed
|
|
||||||
useEffect(() => {
|
|
||||||
console.log("[Setup Flow] Checking setup state:", {
|
|
||||||
isMounted,
|
|
||||||
isFirstRun,
|
|
||||||
setupComplete,
|
|
||||||
currentView,
|
|
||||||
shouldShowSetup: isMounted && isFirstRun && !setupComplete,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isMounted && isFirstRun && !setupComplete) {
|
|
||||||
console.log(
|
|
||||||
"[Setup Flow] Redirecting to setup wizard (first run, not complete)"
|
|
||||||
);
|
|
||||||
setCurrentView("setup");
|
|
||||||
} else if (isMounted && setupComplete) {
|
|
||||||
console.log("[Setup Flow] Setup already complete, showing normal view");
|
|
||||||
}
|
|
||||||
}, [isMounted, isFirstRun, setupComplete, setCurrentView, currentView]);
|
|
||||||
|
|
||||||
// Test IPC connection on mount
|
|
||||||
useEffect(() => {
|
|
||||||
const testConnection = async () => {
|
|
||||||
try {
|
|
||||||
const api = getElectronAPI();
|
|
||||||
const result = await api.ping();
|
|
||||||
setIpcConnected(result === "pong");
|
|
||||||
} catch (error) {
|
|
||||||
console.error("IPC connection failed:", error);
|
|
||||||
setIpcConnected(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
testConnection();
|
|
||||||
}, [setIpcConnected]);
|
|
||||||
|
|
||||||
// Apply theme class to document (uses effective theme - preview, project-specific, or global)
|
|
||||||
useEffect(() => {
|
|
||||||
const root = document.documentElement;
|
|
||||||
root.classList.remove(
|
|
||||||
"dark",
|
|
||||||
"retro",
|
|
||||||
"light",
|
|
||||||
"dracula",
|
|
||||||
"nord",
|
|
||||||
"monokai",
|
|
||||||
"tokyonight",
|
|
||||||
"solarized",
|
|
||||||
"gruvbox",
|
|
||||||
"catppuccin",
|
|
||||||
"onedark",
|
|
||||||
"synthwave",
|
|
||||||
"red"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (effectiveTheme === "dark") {
|
|
||||||
root.classList.add("dark");
|
|
||||||
} else if (effectiveTheme === "retro") {
|
|
||||||
root.classList.add("retro");
|
|
||||||
} else if (effectiveTheme === "dracula") {
|
|
||||||
root.classList.add("dracula");
|
|
||||||
} else if (effectiveTheme === "nord") {
|
|
||||||
root.classList.add("nord");
|
|
||||||
} else if (effectiveTheme === "monokai") {
|
|
||||||
root.classList.add("monokai");
|
|
||||||
} else if (effectiveTheme === "tokyonight") {
|
|
||||||
root.classList.add("tokyonight");
|
|
||||||
} else if (effectiveTheme === "solarized") {
|
|
||||||
root.classList.add("solarized");
|
|
||||||
} else if (effectiveTheme === "gruvbox") {
|
|
||||||
root.classList.add("gruvbox");
|
|
||||||
} else if (effectiveTheme === "catppuccin") {
|
|
||||||
root.classList.add("catppuccin");
|
|
||||||
} else if (effectiveTheme === "onedark") {
|
|
||||||
root.classList.add("onedark");
|
|
||||||
} else if (effectiveTheme === "synthwave") {
|
|
||||||
root.classList.add("synthwave");
|
|
||||||
} else if (effectiveTheme === "red") {
|
|
||||||
root.classList.add("red");
|
|
||||||
} else if (effectiveTheme === "light") {
|
|
||||||
root.classList.add("light");
|
|
||||||
} else if (effectiveTheme === "system") {
|
|
||||||
// System theme
|
|
||||||
const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
||||||
if (isDark) {
|
|
||||||
root.classList.add("dark");
|
|
||||||
} else {
|
|
||||||
root.classList.add("light");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [effectiveTheme, previewTheme, currentProject, theme]);
|
|
||||||
|
|
||||||
const renderView = () => {
|
|
||||||
switch (currentView) {
|
|
||||||
case "welcome":
|
|
||||||
return <WelcomeView />;
|
|
||||||
case "setup":
|
|
||||||
return <SetupView />;
|
|
||||||
case "board":
|
|
||||||
return <BoardView />;
|
|
||||||
case "spec":
|
|
||||||
return <SpecView />;
|
|
||||||
case "agent":
|
|
||||||
return <AgentView />;
|
|
||||||
case "settings":
|
|
||||||
return <SettingsView />;
|
|
||||||
case "interview":
|
|
||||||
return <InterviewView />;
|
|
||||||
case "context":
|
|
||||||
return <ContextView />;
|
|
||||||
case "profiles":
|
|
||||||
return <ProfilesView />;
|
|
||||||
case "running-agents":
|
|
||||||
return <RunningAgentsView />;
|
|
||||||
case "terminal":
|
|
||||||
return <TerminalView />;
|
|
||||||
case "wiki":
|
|
||||||
return <WikiView />;
|
|
||||||
default:
|
|
||||||
return <WelcomeView />;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Setup view is full-screen without sidebar
|
|
||||||
if (currentView === "setup") {
|
|
||||||
return (
|
|
||||||
<main className="h-screen overflow-hidden" data-testid="app-container">
|
|
||||||
<SetupView />
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<main className="flex h-screen overflow-hidden" data-testid="app-container">
|
|
||||||
<Sidebar />
|
|
||||||
<div
|
|
||||||
className="flex-1 flex flex-col overflow-hidden transition-all duration-300"
|
|
||||||
style={{ marginRight: streamerPanelOpen ? "250px" : "0" }}
|
|
||||||
>
|
|
||||||
{renderView()}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Hidden streamer panel - opens with "\" key, pushes content */}
|
|
||||||
<div
|
|
||||||
className={`fixed top-0 right-0 h-full w-[250px] bg-background border-l border-border transition-transform duration-300 ${
|
|
||||||
streamerPanelOpen ? "translate-x-0" : "translate-x-full"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Home() {
|
|
||||||
return (
|
|
||||||
<FileBrowserProvider>
|
|
||||||
<HomeContent />
|
|
||||||
</FileBrowserProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ES2017",
|
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
|
||||||
"allowJs": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"strict": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
"incremental": true,
|
|
||||||
"plugins": [
|
|
||||||
{
|
|
||||||
"name": "next"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["./src/*"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"next-env.d.ts",
|
|
||||||
"**/*.ts",
|
|
||||||
"**/*.tsx",
|
|
||||||
".next/types/**/*.ts",
|
|
||||||
".next/dev/types/**/*.ts",
|
|
||||||
"**/*.mts"
|
|
||||||
],
|
|
||||||
"exclude": ["node_modules"]
|
|
||||||
}
|
|
||||||
18
apps/app/.gitignore → apps/ui/.gitignore
vendored
@@ -13,12 +13,9 @@
|
|||||||
# testing
|
# testing
|
||||||
/coverage
|
/coverage
|
||||||
|
|
||||||
# next.js
|
# Vite
|
||||||
/.next/
|
/dist/
|
||||||
/out/
|
/dist-electron/
|
||||||
|
|
||||||
# production
|
|
||||||
/build
|
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
@@ -33,12 +30,8 @@ yarn-error.log*
|
|||||||
# env files (can opt-in for committing if needed)
|
# env files (can opt-in for committing if needed)
|
||||||
.env*
|
.env*
|
||||||
|
|
||||||
# vercel
|
|
||||||
.vercel
|
|
||||||
|
|
||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
next-env.d.ts
|
|
||||||
|
|
||||||
# Playwright
|
# Playwright
|
||||||
/test-results/
|
/test-results/
|
||||||
@@ -47,5 +40,8 @@ next-env.d.ts
|
|||||||
/playwright/.cache/
|
/playwright/.cache/
|
||||||
|
|
||||||
# Electron
|
# Electron
|
||||||
/dist/
|
/release/
|
||||||
/server-bundle/
|
/server-bundle/
|
||||||
|
|
||||||
|
# TanStack Router generated
|
||||||
|
src/routeTree.gen.ts
|
||||||
36
apps/ui/eslint.config.mjs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { defineConfig, globalIgnores } from "eslint/config";
|
||||||
|
import js from "@eslint/js";
|
||||||
|
import ts from "@typescript-eslint/eslint-plugin";
|
||||||
|
import tsParser from "@typescript-eslint/parser";
|
||||||
|
|
||||||
|
const eslintConfig = defineConfig([
|
||||||
|
js.configs.recommended,
|
||||||
|
{
|
||||||
|
files: ["**/*.ts", "**/*.tsx"],
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsParser,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
sourceType: "module",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
"@typescript-eslint": ts,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...ts.configs.recommended.rules,
|
||||||
|
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
|
||||||
|
"@typescript-eslint/no-explicit-any": "warn",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
globalIgnores([
|
||||||
|
"dist/**",
|
||||||
|
"dist-electron/**",
|
||||||
|
"node_modules/**",
|
||||||
|
"server-bundle/**",
|
||||||
|
"release/**",
|
||||||
|
"src/routeTree.gen.ts",
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export default eslintConfig;
|
||||||
31
apps/ui/index.html
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en" suppressHydrationWarning>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>Automaker - Autonomous AI Development Studio</title>
|
||||||
|
<meta name="description" content="Build software autonomously with AI agents" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||||
|
<script>
|
||||||
|
// Prevent dark mode flash
|
||||||
|
(function() {
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem('automaker-storage');
|
||||||
|
if (stored) {
|
||||||
|
const data = JSON.parse(stored);
|
||||||
|
const theme = data.state?.theme;
|
||||||
|
if (theme === 'dark') {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
} else if (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body class="antialiased">
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/renderer.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@automaker/app",
|
"name": "@automaker/ui",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "An autonomous AI development studio that helps you build software faster using AI-powered agents",
|
"description": "An autonomous AI development studio that helps you build software faster using AI-powered agents",
|
||||||
"homepage": "https://github.com/AutoMaker-Org/automaker",
|
"homepage": "https://github.com/AutoMaker-Org/automaker",
|
||||||
@@ -13,25 +13,25 @@
|
|||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Unlicense",
|
"license": "Unlicense",
|
||||||
"main": "electron/main.js",
|
"main": "dist-electron/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -p 3007",
|
"dev": "vite",
|
||||||
"dev:web": "next dev -p 3007",
|
"dev:web": "vite",
|
||||||
"dev:electron": "concurrently \"next dev -p 3007\" \"wait-on http://localhost:3007 && electron .\"",
|
"dev:electron": "vite",
|
||||||
"dev:electron:debug": "concurrently \"next dev -p 3007\" \"wait-on http://localhost:3007 && OPEN_DEVTOOLS=true electron .\"",
|
"dev:electron:debug": "cross-env OPEN_DEVTOOLS=true vite",
|
||||||
"build": "next build",
|
"build": "vite build",
|
||||||
"build:electron": "node scripts/prepare-server.js && next build && electron-builder",
|
"build:electron": "node scripts/prepare-server.js && vite build && electron-builder",
|
||||||
"build:electron:win": "node scripts/prepare-server.js && next build && electron-builder --win",
|
"build:electron:win": "node scripts/prepare-server.js && vite build && electron-builder --win",
|
||||||
"build:electron:mac": "node scripts/prepare-server.js && next build && electron-builder --mac",
|
"build:electron:mac": "node scripts/prepare-server.js && vite build && electron-builder --mac",
|
||||||
"build:electron:linux": "node scripts/prepare-server.js && next build && electron-builder --linux",
|
"build:electron:linux": "node scripts/prepare-server.js && vite build && electron-builder --linux",
|
||||||
"postinstall": "electron-builder install-app-deps",
|
"postinstall": "electron-builder install-app-deps",
|
||||||
"start": "next start",
|
"preview": "vite preview",
|
||||||
"lint": "eslint",
|
"lint": "eslint",
|
||||||
"pretest": "node scripts/setup-e2e-fixtures.js",
|
"pretest": "node scripts/setup-e2e-fixtures.js",
|
||||||
"test": "playwright test",
|
"test": "playwright test",
|
||||||
"test:headed": "playwright test --headed",
|
"test:headed": "playwright test --headed",
|
||||||
"dev:electron:wsl": "concurrently \"next dev -p 3007\" \"wait-on http://localhost:3007 && electron . --no-sandbox --disable-gpu\"",
|
"dev:electron:wsl": "cross-env vite",
|
||||||
"dev:electron:wsl:gpu": "concurrently \"next dev -p 3007\" \"wait-on http://localhost:3007 && MESA_D3D12_DEFAULT_ADAPTER_NAME=NVIDIA electron . --no-sandbox --disable-gpu-sandbox\""
|
"dev:electron:wsl:gpu": "cross-env MESA_D3D12_DEFAULT_ADAPTER_NAME=NVIDIA vite"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/lang-xml": "^6.1.0",
|
"@codemirror/lang-xml": "^6.1.0",
|
||||||
@@ -50,6 +50,7 @@
|
|||||||
"@radix-ui/react-tabs": "^1.1.13",
|
"@radix-ui/react-tabs": "^1.1.13",
|
||||||
"@radix-ui/react-tooltip": "^1.2.8",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
"@tanstack/react-query": "^5.90.12",
|
"@tanstack/react-query": "^5.90.12",
|
||||||
|
"@tanstack/react-router": "^1.132.41",
|
||||||
"@uiw/react-codemirror": "^4.25.4",
|
"@uiw/react-codemirror": "^4.25.4",
|
||||||
"@xterm/addon-fit": "^0.10.0",
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
"@xterm/addon-webgl": "^0.18.0",
|
"@xterm/addon-webgl": "^0.18.0",
|
||||||
@@ -60,7 +61,6 @@
|
|||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"geist": "^1.5.1",
|
"geist": "^1.5.1",
|
||||||
"lucide-react": "^0.556.0",
|
"lucide-react": "^0.556.0",
|
||||||
"next": "^16.0.10",
|
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
@@ -82,20 +82,26 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@electron/rebuild": "^4.0.2",
|
"@electron/rebuild": "^4.0.2",
|
||||||
|
"@eslint/js": "^9.0.0",
|
||||||
"@playwright/test": "^1.57.0",
|
"@playwright/test": "^1.57.0",
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/vite": "^4.1.13",
|
||||||
|
"@tanstack/router-plugin": "^1.132.41",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"concurrently": "^9.2.1",
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||||
|
"@typescript-eslint/parser": "^8.0.0",
|
||||||
|
"@vitejs/plugin-react": "^4.5.2",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
"electron": "39.2.7",
|
"electron": "39.2.7",
|
||||||
"electron-builder": "^26.0.12",
|
"electron-builder": "^26.0.12",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "16.0.7",
|
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"wait-on": "^9.0.3"
|
"vite": "^6.3.5",
|
||||||
|
"vite-plugin-electron": "^0.29.0",
|
||||||
|
"vite-plugin-electron-renderer": "^0.14.6"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "com.automaker.app",
|
"appId": "com.automaker.app",
|
||||||
@@ -103,11 +109,11 @@
|
|||||||
"artifactName": "${productName}-${version}-${arch}.${ext}",
|
"artifactName": "${productName}-${version}-${arch}.${ext}",
|
||||||
"afterPack": "./scripts/rebuild-server-natives.js",
|
"afterPack": "./scripts/rebuild-server-natives.js",
|
||||||
"directories": {
|
"directories": {
|
||||||
"output": "dist"
|
"output": "release"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"electron/**/*",
|
"dist/**/*",
|
||||||
"out/**/*",
|
"dist-electron/**/*",
|
||||||
"public/**/*",
|
"public/**/*",
|
||||||
"!node_modules/**/*"
|
"!node_modules/**/*"
|
||||||
],
|
],
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { defineConfig, devices } from "@playwright/test";
|
import { defineConfig, devices } from "@playwright/test";
|
||||||
|
|
||||||
const port = process.env.TEST_PORT || 3007;
|
const port = process.env.TEST_PORT || 5173;
|
||||||
const serverPort = process.env.TEST_SERVER_PORT || 3008;
|
const serverPort = process.env.TEST_SERVER_PORT || 3008;
|
||||||
const reuseServer = process.env.TEST_REUSE_SERVER === "true";
|
const reuseServer = process.env.TEST_REUSE_SERVER === "true";
|
||||||
const mockAgent = process.env.CI === "true" || process.env.AUTOMAKER_MOCK_AGENT === "true";
|
const mockAgent = process.env.CI === "true" || process.env.AUTOMAKER_MOCK_AGENT === "true";
|
||||||
@@ -43,15 +43,15 @@ export default defineConfig({
|
|||||||
ALLOWED_PROJECT_DIRS: "/Users,/home,/tmp,/var/folders",
|
ALLOWED_PROJECT_DIRS: "/Users,/home,/tmp,/var/folders",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Frontend Next.js server
|
// Frontend Vite dev server
|
||||||
{
|
{
|
||||||
command: `npx next dev -p ${port}`,
|
command: `npm run dev`,
|
||||||
url: `http://localhost:${port}`,
|
url: `http://localhost:${port}`,
|
||||||
reuseExistingServer: true,
|
reuseExistingServer: true,
|
||||||
timeout: 120000,
|
timeout: 120000,
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
NEXT_PUBLIC_SKIP_SETUP: "true",
|
VITE_SKIP_SETUP: "true",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 391 B After Width: | Height: | Size: 391 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 317 KiB After Width: | Height: | Size: 317 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 128 B After Width: | Height: | Size: 128 B |
|
Before Width: | Height: | Size: 385 B After Width: | Height: | Size: 385 B |
7
apps/ui/src/App.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { RouterProvider } from "@tanstack/react-router";
|
||||||
|
import { router } from "./utils/router";
|
||||||
|
import "./styles/global.css";
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
return <RouterProvider router={router} />;
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState, useRef, useCallback, useEffect } from "react";
|
import { useState, useRef, useCallback, useEffect } from "react";
|
||||||
import { ImageIcon, Upload, Loader2, Trash2 } from "lucide-react";
|
import { ImageIcon, Upload, Loader2, Trash2 } from "lucide-react";
|
||||||
@@ -72,7 +71,7 @@ export function BoardBackgroundModal({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentProject && backgroundSettings.imagePath) {
|
if (currentProject && backgroundSettings.imagePath) {
|
||||||
const serverUrl =
|
const serverUrl =
|
||||||
process.env.NEXT_PUBLIC_SERVER_URL || "http://localhost:3008";
|
import.meta.env.VITE_SERVER_URL || "http://localhost:3008";
|
||||||
// Add cache-busting query parameter to force browser to reload image
|
// Add cache-busting query parameter to force browser to reload image
|
||||||
const cacheBuster = imageVersion
|
const cacheBuster = imageVersion
|
||||||
? `&v=${imageVersion}`
|
? `&v=${imageVersion}`
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -71,7 +70,7 @@ export function FileBrowserDialog({
|
|||||||
try {
|
try {
|
||||||
// Get server URL from environment or default
|
// Get server URL from environment or default
|
||||||
const serverUrl =
|
const serverUrl =
|
||||||
process.env.NEXT_PUBLIC_SERVER_URL || "http://localhost:3008";
|
import.meta.env.VITE_SERVER_URL || "http://localhost:3008";
|
||||||
|
|
||||||
const response = await fetch(`${serverUrl}/api/fs/browse`, {
|
const response = await fetch(`${serverUrl}/api/fs/browse`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { Sparkles, Clock } from "lucide-react";
|
import { Sparkles, Clock } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState, useMemo, useEffect, useCallback, useRef } from "react";
|
import { useState, useMemo, useEffect, useCallback, useRef } from "react";
|
||||||
|
import { useNavigate, useLocation } from "@tanstack/react-router";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useAppStore, formatShortcut, type ThemeMode } from "@/store/app-store";
|
import { useAppStore, formatShortcut, type ThemeMode } from "@/store/app-store";
|
||||||
import { CoursePromoBadge } from "@/components/ui/course-promo-badge";
|
import { CoursePromoBadge } from "@/components/ui/course-promo-badge";
|
||||||
@@ -223,16 +222,17 @@ const BugReportButton = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function Sidebar() {
|
export function Sidebar() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
projects,
|
projects,
|
||||||
trashedProjects,
|
trashedProjects,
|
||||||
currentProject,
|
currentProject,
|
||||||
currentView,
|
|
||||||
sidebarOpen,
|
sidebarOpen,
|
||||||
projectHistory,
|
projectHistory,
|
||||||
upsertAndSetCurrentProject,
|
upsertAndSetCurrentProject,
|
||||||
setCurrentProject,
|
setCurrentProject,
|
||||||
setCurrentView,
|
|
||||||
toggleSidebar,
|
toggleSidebar,
|
||||||
restoreTrashedProject,
|
restoreTrashedProject,
|
||||||
deleteTrashedProject,
|
deleteTrashedProject,
|
||||||
@@ -251,14 +251,13 @@ export function Sidebar() {
|
|||||||
} = useAppStore();
|
} = useAppStore();
|
||||||
|
|
||||||
// Environment variable flags for hiding sidebar items
|
// Environment variable flags for hiding sidebar items
|
||||||
// Note: Next.js requires static access to process.env variables (no dynamic keys)
|
const hideTerminal = import.meta.env.VITE_HIDE_TERMINAL === "true";
|
||||||
const hideTerminal = process.env.NEXT_PUBLIC_HIDE_TERMINAL === "true";
|
const hideWiki = import.meta.env.VITE_HIDE_WIKI === "true";
|
||||||
const hideWiki = process.env.NEXT_PUBLIC_HIDE_WIKI === "true";
|
|
||||||
const hideRunningAgents =
|
const hideRunningAgents =
|
||||||
process.env.NEXT_PUBLIC_HIDE_RUNNING_AGENTS === "true";
|
import.meta.env.VITE_HIDE_RUNNING_AGENTS === "true";
|
||||||
const hideContext = process.env.NEXT_PUBLIC_HIDE_CONTEXT === "true";
|
const hideContext = import.meta.env.VITE_HIDE_CONTEXT === "true";
|
||||||
const hideSpecEditor = process.env.NEXT_PUBLIC_HIDE_SPEC_EDITOR === "true";
|
const hideSpecEditor = import.meta.env.VITE_HIDE_SPEC_EDITOR === "true";
|
||||||
const hideAiProfiles = process.env.NEXT_PUBLIC_HIDE_AI_PROFILES === "true";
|
const hideAiProfiles = import.meta.env.VITE_HIDE_AI_PROFILES === "true";
|
||||||
|
|
||||||
// Get customizable keyboard shortcuts
|
// Get customizable keyboard shortcuts
|
||||||
const shortcuts = useKeyboardShortcutsConfig();
|
const shortcuts = useKeyboardShortcutsConfig();
|
||||||
@@ -429,7 +428,6 @@ export function Sidebar() {
|
|||||||
unsubscribe();
|
unsubscribe();
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
setCurrentView,
|
|
||||||
creatingSpecProjectPath,
|
creatingSpecProjectPath,
|
||||||
setupProjectPath,
|
setupProjectPath,
|
||||||
setSpecCreatingForProject,
|
setSpecCreatingForProject,
|
||||||
@@ -1177,7 +1175,7 @@ export function Sidebar() {
|
|||||||
if (item.shortcut) {
|
if (item.shortcut) {
|
||||||
shortcutsList.push({
|
shortcutsList.push({
|
||||||
key: item.shortcut,
|
key: item.shortcut,
|
||||||
action: () => setCurrentView(item.id as any),
|
action: () => navigate({ to: `/${item.id}` as const }),
|
||||||
description: `Navigate to ${item.label}`,
|
description: `Navigate to ${item.label}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1187,7 +1185,7 @@ export function Sidebar() {
|
|||||||
// Add settings shortcut
|
// Add settings shortcut
|
||||||
shortcutsList.push({
|
shortcutsList.push({
|
||||||
key: shortcuts.settings,
|
key: shortcuts.settings,
|
||||||
action: () => setCurrentView("settings"),
|
action: () => navigate({ to: "/settings" }),
|
||||||
description: "Navigate to Settings",
|
description: "Navigate to Settings",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1196,7 +1194,7 @@ export function Sidebar() {
|
|||||||
}, [
|
}, [
|
||||||
shortcuts,
|
shortcuts,
|
||||||
currentProject,
|
currentProject,
|
||||||
setCurrentView,
|
navigate,
|
||||||
toggleSidebar,
|
toggleSidebar,
|
||||||
projects.length,
|
projects.length,
|
||||||
handleOpenFolder,
|
handleOpenFolder,
|
||||||
@@ -1210,7 +1208,9 @@ export function Sidebar() {
|
|||||||
useKeyboardShortcuts(navigationShortcuts);
|
useKeyboardShortcuts(navigationShortcuts);
|
||||||
|
|
||||||
const isActiveRoute = (id: string) => {
|
const isActiveRoute = (id: string) => {
|
||||||
return currentView === id;
|
// Map view IDs to route paths
|
||||||
|
const routePath = id === "welcome" ? "/" : `/${id}`;
|
||||||
|
return location.pathname === routePath;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -1289,7 +1289,7 @@ export function Sidebar() {
|
|||||||
"flex items-center gap-3 titlebar-no-drag cursor-pointer group",
|
"flex items-center gap-3 titlebar-no-drag cursor-pointer group",
|
||||||
!sidebarOpen && "flex-col gap-1"
|
!sidebarOpen && "flex-col gap-1"
|
||||||
)}
|
)}
|
||||||
onClick={() => setCurrentView("welcome")}
|
onClick={() => navigate({ to: "/" })}
|
||||||
data-testid="logo-button"
|
data-testid="logo-button"
|
||||||
>
|
>
|
||||||
{!sidebarOpen ? (
|
{!sidebarOpen ? (
|
||||||
@@ -1847,7 +1847,7 @@ export function Sidebar() {
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={item.id}
|
key={item.id}
|
||||||
onClick={() => setCurrentView(item.id as any)}
|
onClick={() => navigate({ to: `/${item.id}` as const })}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group flex items-center w-full px-3 py-2.5 rounded-xl relative overflow-hidden titlebar-no-drag",
|
"group flex items-center w-full px-3 py-2.5 rounded-xl relative overflow-hidden titlebar-no-drag",
|
||||||
"transition-all duration-200 ease-out",
|
"transition-all duration-200 ease-out",
|
||||||
@@ -1951,7 +1951,7 @@ export function Sidebar() {
|
|||||||
{!hideWiki && (
|
{!hideWiki && (
|
||||||
<div className="p-2 pb-0">
|
<div className="p-2 pb-0">
|
||||||
<button
|
<button
|
||||||
onClick={() => setCurrentView("wiki")}
|
onClick={() => navigate({ to: "/wiki" })}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group flex items-center w-full px-3 py-2.5 rounded-xl relative overflow-hidden titlebar-no-drag",
|
"group flex items-center w-full px-3 py-2.5 rounded-xl relative overflow-hidden titlebar-no-drag",
|
||||||
"transition-all duration-200 ease-out",
|
"transition-all duration-200 ease-out",
|
||||||
@@ -2014,7 +2014,7 @@ export function Sidebar() {
|
|||||||
{!hideRunningAgents && (
|
{!hideRunningAgents && (
|
||||||
<div className="p-2 pb-0">
|
<div className="p-2 pb-0">
|
||||||
<button
|
<button
|
||||||
onClick={() => setCurrentView("running-agents")}
|
onClick={() => navigate({ to: "/running-agents" })}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group flex items-center w-full px-3 py-2.5 rounded-xl relative overflow-hidden titlebar-no-drag",
|
"group flex items-center w-full px-3 py-2.5 rounded-xl relative overflow-hidden titlebar-no-drag",
|
||||||
"transition-all duration-200 ease-out",
|
"transition-all duration-200 ease-out",
|
||||||
@@ -2112,7 +2112,7 @@ export function Sidebar() {
|
|||||||
{/* Settings Link */}
|
{/* Settings Link */}
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => setCurrentView("settings")}
|
onClick={() => navigate({ to: "/settings" })}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group flex items-center w-full px-3 py-2.5 rounded-xl relative overflow-hidden titlebar-no-drag",
|
"group flex items-center w-full px-3 py-2.5 rounded-xl relative overflow-hidden titlebar-no-drag",
|
||||||
"transition-all duration-200 ease-out",
|
"transition-all duration-200 ease-out",
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { ChevronDown } from "lucide-react";
|
import { ChevronDown } from "lucide-react";
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Check, ChevronsUpDown, LucideIcon } from "lucide-react";
|
import { Check, ChevronsUpDown, LucideIcon } from "lucide-react";
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { GitBranch } from "lucide-react";
|
import { GitBranch } from "lucide-react";
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Autocomplete } from "@/components/ui/autocomplete";
|
import { Autocomplete } from "@/components/ui/autocomplete";
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { Command as CommandPrimitive } from "cmdk"
|
import { Command as CommandPrimitive } from "cmdk"
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Clock } from "lucide-react";
|
import { Clock } from "lucide-react";
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Sparkles, X } from "lucide-react";
|
import { Sparkles, X } from "lucide-react";
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import React, { useState, useRef, useCallback } from "react";
|
import React, { useState, useRef, useCallback } from "react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@@ -85,7 +84,7 @@ export function DescriptionImageDropZone({
|
|||||||
|
|
||||||
// Construct server URL for loading saved images
|
// Construct server URL for loading saved images
|
||||||
const getImageServerUrl = useCallback((imagePath: string): string => {
|
const getImageServerUrl = useCallback((imagePath: string): string => {
|
||||||
const serverUrl = process.env.NEXT_PUBLIC_SERVER_URL || "http://localhost:3008";
|
const serverUrl = import.meta.env.VITE_SERVER_URL || "http://localhost:3008";
|
||||||
const projectPath = currentProject?.path || "";
|
const projectPath = currentProject?.path || "";
|
||||||
return `${serverUrl}/api/fs/image?path=${encodeURIComponent(imagePath)}&projectPath=${encodeURIComponent(projectPath)}`;
|
return `${serverUrl}/api/fs/image?path=${encodeURIComponent(imagePath)}&projectPath=${encodeURIComponent(projectPath)}`;
|
||||||
}, [currentProject?.path]);
|
}, [currentProject?.path]);
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import React, { useState, useRef, useCallback } from "react";
|
import React, { useState, useRef, useCallback } from "react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState, useEffect, useMemo, useCallback } from "react";
|
import { useState, useEffect, useMemo, useCallback } from "react";
|
||||||
import { getElectronAPI } from "@/lib/electron";
|
import { getElectronAPI } from "@/lib/electron";
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useEffect, useCallback, useRef } from "react";
|
import { useEffect, useCallback, useRef } from "react";
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import React, { useState, useRef, useCallback } from "react";
|
import React, { useState, useRef, useCallback } from "react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useAppStore, DEFAULT_KEYBOARD_SHORTCUTS, parseShortcut, formatShortcut } from "@/store/app-store";
|
import { useAppStore, DEFAULT_KEYBOARD_SHORTCUTS, parseShortcut, formatShortcut } from "@/store/app-store";
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState, useMemo, useEffect, useRef } from "react";
|
import { useState, useMemo, useEffect, useRef } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as SliderPrimitive from "@radix-ui/react-slider";
|
import * as SliderPrimitive from "@radix-ui/react-slider";
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import CodeMirror from "@uiw/react-codemirror";
|
import CodeMirror from "@uiw/react-codemirror";
|
||||||
import { xml } from "@codemirror/lang-xml";
|
import { xml } from "@codemirror/lang-xml";
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState, useCallback } from "react";
|
import { useState, useCallback } from "react";
|
||||||
import { useAppStore } from "@/store/app-store";
|
import { useAppStore } from "@/store/app-store";
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState, useCallback, useRef, useEffect, useMemo } from "react";
|
import { useState, useCallback, useRef, useEffect, useMemo } from "react";
|
||||||
import { useAppStore, type AgentModel } from "@/store/app-store";
|
import { useAppStore, type AgentModel } from "@/store/app-store";
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useEffect, useState, useCallback, useMemo } from "react";
|
import { useEffect, useState, useCallback, useMemo } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { HotkeyButton } from "@/components/ui/hotkey-button";
|
import { HotkeyButton } from "@/components/ui/hotkey-button";
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useRef, useEffect } from "react";
|
import { useRef, useEffect } from "react";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState, useEffect, useMemo, memo } from "react";
|
import { useState, useEffect, useMemo, memo } from "react";
|
||||||
import { useSortable } from "@dnd-kit/sortable";
|
import { useSortable } from "@dnd-kit/sortable";
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
import { useDroppable } from "@dnd-kit/core";
|
import { useDroppable } from "@dnd-kit/core";
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState, useEffect, useCallback, useMemo } from "react";
|
import { useState, useEffect, useCallback, useMemo } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -50,6 +49,7 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
interface AddFeatureDialogProps {
|
interface AddFeatureDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -87,6 +87,7 @@ export function AddFeatureDialog({
|
|||||||
showProfilesOnly,
|
showProfilesOnly,
|
||||||
aiProfiles,
|
aiProfiles,
|
||||||
}: AddFeatureDialogProps) {
|
}: AddFeatureDialogProps) {
|
||||||
|
const navigate = useNavigate();
|
||||||
const [newFeature, setNewFeature] = useState({
|
const [newFeature, setNewFeature] = useState({
|
||||||
category: "",
|
category: "",
|
||||||
description: "",
|
description: "",
|
||||||
@@ -424,7 +425,7 @@ export function AddFeatureDialog({
|
|||||||
showManageLink
|
showManageLink
|
||||||
onManageLinkClick={() => {
|
onManageLinkClick={() => {
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
useAppStore.getState().setCurrentView("profiles");
|
navigate({ to: "/profiles" });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useEffect, useRef, useState, useCallback } from "react";
|
import { useEffect, useRef, useState, useCallback } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import {
|
import {
|
||||||