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
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
# Vite
|
||||
/dist/
|
||||
/dist-electron/
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
@@ -33,12 +30,8 @@ yarn-error.log*
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# Playwright
|
||||
/test-results/
|
||||
@@ -47,5 +40,8 @@ next-env.d.ts
|
||||
/playwright/.cache/
|
||||
|
||||
# Electron
|
||||
/dist/
|
||||
/release/
|
||||
/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",
|
||||
"description": "An autonomous AI development studio that helps you build software faster using AI-powered agents",
|
||||
"homepage": "https://github.com/AutoMaker-Org/automaker",
|
||||
@@ -13,25 +13,25 @@
|
||||
},
|
||||
"private": true,
|
||||
"license": "Unlicense",
|
||||
"main": "electron/main.js",
|
||||
"main": "dist-electron/main.js",
|
||||
"scripts": {
|
||||
"dev": "next dev -p 3007",
|
||||
"dev:web": "next dev -p 3007",
|
||||
"dev:electron": "concurrently \"next dev -p 3007\" \"wait-on http://localhost:3007 && electron .\"",
|
||||
"dev:electron:debug": "concurrently \"next dev -p 3007\" \"wait-on http://localhost:3007 && OPEN_DEVTOOLS=true electron .\"",
|
||||
"build": "next build",
|
||||
"build:electron": "node scripts/prepare-server.js && next build && electron-builder",
|
||||
"build:electron:win": "node scripts/prepare-server.js && next build && electron-builder --win",
|
||||
"build:electron:mac": "node scripts/prepare-server.js && next build && electron-builder --mac",
|
||||
"build:electron:linux": "node scripts/prepare-server.js && next build && electron-builder --linux",
|
||||
"dev": "vite",
|
||||
"dev:web": "vite",
|
||||
"dev:electron": "vite",
|
||||
"dev:electron:debug": "cross-env OPEN_DEVTOOLS=true vite",
|
||||
"build": "vite build",
|
||||
"build:electron": "node scripts/prepare-server.js && vite build && electron-builder",
|
||||
"build:electron:win": "node scripts/prepare-server.js && vite build && electron-builder --win",
|
||||
"build:electron:mac": "node scripts/prepare-server.js && vite build && electron-builder --mac",
|
||||
"build:electron:linux": "node scripts/prepare-server.js && vite build && electron-builder --linux",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"start": "next start",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint",
|
||||
"pretest": "node scripts/setup-e2e-fixtures.js",
|
||||
"test": "playwright test",
|
||||
"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: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": "cross-env vite",
|
||||
"dev:electron:wsl:gpu": "cross-env MESA_D3D12_DEFAULT_ADAPTER_NAME=NVIDIA vite"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/lang-xml": "^6.1.0",
|
||||
@@ -50,6 +50,7 @@
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@tanstack/react-query": "^5.90.12",
|
||||
"@tanstack/react-router": "^1.132.41",
|
||||
"@uiw/react-codemirror": "^4.25.4",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/addon-webgl": "^0.18.0",
|
||||
@@ -60,7 +61,6 @@
|
||||
"dotenv": "^17.2.3",
|
||||
"geist": "^1.5.1",
|
||||
"lucide-react": "^0.556.0",
|
||||
"next": "^16.0.10",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
@@ -82,20 +82,26 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron/rebuild": "^4.0.2",
|
||||
"@eslint/js": "^9.0.0",
|
||||
"@playwright/test": "^1.57.0",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@tailwindcss/vite": "^4.1.13",
|
||||
"@tanstack/router-plugin": "^1.132.41",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^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-builder": "^26.0.12",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.0.7",
|
||||
"tailwindcss": "^4",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"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": {
|
||||
"appId": "com.automaker.app",
|
||||
@@ -103,11 +109,11 @@
|
||||
"artifactName": "${productName}-${version}-${arch}.${ext}",
|
||||
"afterPack": "./scripts/rebuild-server-natives.js",
|
||||
"directories": {
|
||||
"output": "dist"
|
||||
"output": "release"
|
||||
},
|
||||
"files": [
|
||||
"electron/**/*",
|
||||
"out/**/*",
|
||||
"dist/**/*",
|
||||
"dist-electron/**/*",
|
||||
"public/**/*",
|
||||
"!node_modules/**/*"
|
||||
],
|
||||
@@ -1,6 +1,6 @@
|
||||
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 reuseServer = process.env.TEST_REUSE_SERVER === "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",
|
||||
},
|
||||
},
|
||||
// Frontend Next.js server
|
||||
// Frontend Vite dev server
|
||||
{
|
||||
command: `npx next dev -p ${port}`,
|
||||
command: `npm run dev`,
|
||||
url: `http://localhost:${port}`,
|
||||
reuseExistingServer: true,
|
||||
timeout: 120000,
|
||||
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 {
|
||||
Dialog,
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useRef, useCallback, useEffect } from "react";
|
||||
import { ImageIcon, Upload, Loader2, Trash2 } from "lucide-react";
|
||||
@@ -72,7 +71,7 @@ export function BoardBackgroundModal({
|
||||
useEffect(() => {
|
||||
if (currentProject && backgroundSettings.imagePath) {
|
||||
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
|
||||
const cacheBuster = imageVersion
|
||||
? `&v=${imageVersion}`
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import {
|
||||
@@ -71,7 +70,7 @@ export function FileBrowserDialog({
|
||||
try {
|
||||
// Get server URL from environment or default
|
||||
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`, {
|
||||
method: "POST",
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { Sparkles, Clock } from "lucide-react";
|
||||
import {
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useMemo, useEffect, useCallback, useRef } from "react";
|
||||
import { useNavigate, useLocation } from "@tanstack/react-router";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useAppStore, formatShortcut, type ThemeMode } from "@/store/app-store";
|
||||
import { CoursePromoBadge } from "@/components/ui/course-promo-badge";
|
||||
@@ -223,16 +222,17 @@ const BugReportButton = ({
|
||||
};
|
||||
|
||||
export function Sidebar() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
const {
|
||||
projects,
|
||||
trashedProjects,
|
||||
currentProject,
|
||||
currentView,
|
||||
sidebarOpen,
|
||||
projectHistory,
|
||||
upsertAndSetCurrentProject,
|
||||
setCurrentProject,
|
||||
setCurrentView,
|
||||
toggleSidebar,
|
||||
restoreTrashedProject,
|
||||
deleteTrashedProject,
|
||||
@@ -251,14 +251,13 @@ export function Sidebar() {
|
||||
} = useAppStore();
|
||||
|
||||
// Environment variable flags for hiding sidebar items
|
||||
// Note: Next.js requires static access to process.env variables (no dynamic keys)
|
||||
const hideTerminal = process.env.NEXT_PUBLIC_HIDE_TERMINAL === "true";
|
||||
const hideWiki = process.env.NEXT_PUBLIC_HIDE_WIKI === "true";
|
||||
const hideTerminal = import.meta.env.VITE_HIDE_TERMINAL === "true";
|
||||
const hideWiki = import.meta.env.VITE_HIDE_WIKI === "true";
|
||||
const hideRunningAgents =
|
||||
process.env.NEXT_PUBLIC_HIDE_RUNNING_AGENTS === "true";
|
||||
const hideContext = process.env.NEXT_PUBLIC_HIDE_CONTEXT === "true";
|
||||
const hideSpecEditor = process.env.NEXT_PUBLIC_HIDE_SPEC_EDITOR === "true";
|
||||
const hideAiProfiles = process.env.NEXT_PUBLIC_HIDE_AI_PROFILES === "true";
|
||||
import.meta.env.VITE_HIDE_RUNNING_AGENTS === "true";
|
||||
const hideContext = import.meta.env.VITE_HIDE_CONTEXT === "true";
|
||||
const hideSpecEditor = import.meta.env.VITE_HIDE_SPEC_EDITOR === "true";
|
||||
const hideAiProfiles = import.meta.env.VITE_HIDE_AI_PROFILES === "true";
|
||||
|
||||
// Get customizable keyboard shortcuts
|
||||
const shortcuts = useKeyboardShortcutsConfig();
|
||||
@@ -429,7 +428,6 @@ export function Sidebar() {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [
|
||||
setCurrentView,
|
||||
creatingSpecProjectPath,
|
||||
setupProjectPath,
|
||||
setSpecCreatingForProject,
|
||||
@@ -1177,7 +1175,7 @@ export function Sidebar() {
|
||||
if (item.shortcut) {
|
||||
shortcutsList.push({
|
||||
key: item.shortcut,
|
||||
action: () => setCurrentView(item.id as any),
|
||||
action: () => navigate({ to: `/${item.id}` as const }),
|
||||
description: `Navigate to ${item.label}`,
|
||||
});
|
||||
}
|
||||
@@ -1187,7 +1185,7 @@ export function Sidebar() {
|
||||
// Add settings shortcut
|
||||
shortcutsList.push({
|
||||
key: shortcuts.settings,
|
||||
action: () => setCurrentView("settings"),
|
||||
action: () => navigate({ to: "/settings" }),
|
||||
description: "Navigate to Settings",
|
||||
});
|
||||
}
|
||||
@@ -1196,7 +1194,7 @@ export function Sidebar() {
|
||||
}, [
|
||||
shortcuts,
|
||||
currentProject,
|
||||
setCurrentView,
|
||||
navigate,
|
||||
toggleSidebar,
|
||||
projects.length,
|
||||
handleOpenFolder,
|
||||
@@ -1210,7 +1208,9 @@ export function Sidebar() {
|
||||
useKeyboardShortcuts(navigationShortcuts);
|
||||
|
||||
const isActiveRoute = (id: string) => {
|
||||
return currentView === id;
|
||||
// Map view IDs to route paths
|
||||
const routePath = id === "welcome" ? "/" : `/${id}`;
|
||||
return location.pathname === routePath;
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -1289,7 +1289,7 @@ export function Sidebar() {
|
||||
"flex items-center gap-3 titlebar-no-drag cursor-pointer group",
|
||||
!sidebarOpen && "flex-col gap-1"
|
||||
)}
|
||||
onClick={() => setCurrentView("welcome")}
|
||||
onClick={() => navigate({ to: "/" })}
|
||||
data-testid="logo-button"
|
||||
>
|
||||
{!sidebarOpen ? (
|
||||
@@ -1847,7 +1847,7 @@ export function Sidebar() {
|
||||
return (
|
||||
<button
|
||||
key={item.id}
|
||||
onClick={() => setCurrentView(item.id as any)}
|
||||
onClick={() => navigate({ to: `/${item.id}` as const })}
|
||||
className={cn(
|
||||
"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",
|
||||
@@ -1951,7 +1951,7 @@ export function Sidebar() {
|
||||
{!hideWiki && (
|
||||
<div className="p-2 pb-0">
|
||||
<button
|
||||
onClick={() => setCurrentView("wiki")}
|
||||
onClick={() => navigate({ to: "/wiki" })}
|
||||
className={cn(
|
||||
"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",
|
||||
@@ -2014,7 +2014,7 @@ export function Sidebar() {
|
||||
{!hideRunningAgents && (
|
||||
<div className="p-2 pb-0">
|
||||
<button
|
||||
onClick={() => setCurrentView("running-agents")}
|
||||
onClick={() => navigate({ to: "/running-agents" })}
|
||||
className={cn(
|
||||
"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",
|
||||
@@ -2112,7 +2112,7 @@ export function Sidebar() {
|
||||
{/* Settings Link */}
|
||||
<div className="p-2">
|
||||
<button
|
||||
onClick={() => setCurrentView("settings")}
|
||||
onClick={() => navigate({ to: "/settings" })}
|
||||
className={cn(
|
||||
"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",
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { Check, ChevronsUpDown, LucideIcon } from "lucide-react";
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { GitBranch } from "lucide-react";
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { Autocomplete } from "@/components/ui/autocomplete";
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Command as CommandPrimitive } from "cmdk"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { Clock } from "lucide-react";
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { Sparkles, X } from "lucide-react";
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useRef, useCallback } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -85,7 +84,7 @@ export function DescriptionImageDropZone({
|
||||
|
||||
// Construct server URL for loading saved images
|
||||
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 || "";
|
||||
return `${serverUrl}/api/fs/image?path=${encodeURIComponent(imagePath)}&projectPath=${encodeURIComponent(projectPath)}`;
|
||||
}, [currentProject?.path]);
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useRef, useCallback } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useMemo, useCallback } from "react";
|
||||
import { getElectronAPI } from "@/lib/electron";
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { useEffect, useCallback, useRef } from "react";
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useRef, useCallback } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { useAppStore, DEFAULT_KEYBOARD_SHORTCUTS, parseShortcut, formatShortcut } from "@/store/app-store";
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useMemo, useEffect, useRef } from "react";
|
||||
import {
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as SliderPrimitive from "@radix-ui/react-slider";
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import CodeMirror from "@uiw/react-codemirror";
|
||||
import { xml } from "@codemirror/lang-xml";
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useCallback } from "react";
|
||||
import { useAppStore } from "@/store/app-store";
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useCallback, useRef, useEffect, useMemo } from "react";
|
||||
import { useAppStore, type AgentModel } from "@/store/app-store";
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useState } from "react";
|
||||
import {
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState, useCallback, useMemo } from "react";
|
||||
import {
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { HotkeyButton } from "@/components/ui/hotkey-button";
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useRef, useEffect } from "react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useMemo, memo } from "react";
|
||||
import { useSortable } from "@dnd-kit/sortable";
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { useDroppable } from "@dnd-kit/core";
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useCallback, useMemo } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
@@ -50,6 +49,7 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
|
||||
interface AddFeatureDialogProps {
|
||||
open: boolean;
|
||||
@@ -87,6 +87,7 @@ export function AddFeatureDialog({
|
||||
showProfilesOnly,
|
||||
aiProfiles,
|
||||
}: AddFeatureDialogProps) {
|
||||
const navigate = useNavigate();
|
||||
const [newFeature, setNewFeature] = useState({
|
||||
category: "",
|
||||
description: "",
|
||||
@@ -424,7 +425,7 @@ export function AddFeatureDialog({
|
||||
showManageLink
|
||||
onManageLinkClick={() => {
|
||||
onOpenChange(false);
|
||||
useAppStore.getState().setCurrentView("profiles");
|
||||
navigate({ to: "/profiles" });
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import {
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import {
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import {
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState, useCallback } from "react";
|
||||
import {
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import {
|
||||