diff --git a/.gitignore b/.gitignore
index a547bf36..ffae558d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,10 @@ dist
dist-ssr
*.local
+# typescript
+*.tsbuildinfo
+next-env.d.ts
+
# Editor directories and files
.vscode/*
!.vscode/extensions.json
diff --git a/package.json b/package.json
index 40fb28bc..4818e0be 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,8 @@
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
- "preview": "vite preview"
+ "preview": "vite preview",
+ "type-check": "tsc --noEmit -p ./tsconfig.app.json"
},
"dependencies": {
"@radix-ui/react-avatar": "^1.1.1",
@@ -55,4 +56,4 @@
"typescript-eslint": "^8.15.0",
"vite": "^6.0.1"
}
-}
+}
\ No newline at end of file
diff --git a/public/help/copilot-setup.md b/public/help/copilot-setup.md
index 9979303c..e88e142e 100644
--- a/public/help/copilot-setup.md
+++ b/public/help/copilot-setup.md
@@ -37,6 +37,7 @@ settings (Ctrl+Shift+P) + "Preferences: Open User Settings (JSON)":
"debug.testOverrideProxyUrl": "https://localhost:8990",
"debug.overrideProxyUrl": "https://localhost:8990",
}
+}
```
> **_NOTE:_** CoPilot may need a refresh after creating the proxy config. Restart VS-Code or open the command palate (Ctrl+Shift+P) and select "Developer: Reload Window".
@@ -54,4 +55,3 @@ If there is any sort of failure, you will see the following:
If you experience a failure, click on the CoPilot avatar and select "Show Diagnostics"
, copy the text and post it to the CoPilot [CodeGate Discussions](https://github.com/stacklok/codegate/discussions/categories/copilot)
-
diff --git a/src/App.tsx b/src/App.tsx
index 9b7822f8..fafa5bf0 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -2,7 +2,7 @@ import { Header } from "./components/Header";
import { PromptList } from "./components/PromptList";
import { useEffect } from "react";
import { Dashboard } from "./components/Dashboard";
-import { Routes, Route } from "react-router-dom";
+import { Routes, Route, Link } from "react-router-dom";
import { Chat } from "./components/Chat";
import { usePromptsStore } from "./hooks/usePromptsStore";
import { Sidebar } from "./components/Sidebar";
@@ -10,37 +10,66 @@ import { useSse } from "./hooks/useSse";
import { Help } from "./components/Help";
import { Certificates } from "./components/Certificates";
import { CertificateSecurity } from "./components/CertificateSecurity";
+import {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbSeparator,
+ BreadcrumbPage,
+} from "./components/ui/breadcrumb";
+import { useBreadcrumb } from "./hooks/useBreadcrumb";
function App() {
const { prompts, loading, fetchPrompts } = usePromptsStore();
useSse();
+ const breadcrumb = useBreadcrumb();
useEffect(() => {
fetchPrompts();
}, [fetchPrompts]);
return (
- <>
-
-
-
-
-
-
-
-
-
- } />
- } />
- } />
- } />
- } />
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ Dashboard
+
+ {breadcrumb && (
+ <>
+
+
+
+ {breadcrumb}
+
+
+ >
+ )}
+
+
+
+
+
+
+ } />
+ } />
+ } />
+ } />
+ }
+ />
+
- >
+
);
}
diff --git a/src/components/CertificateSecurity.tsx b/src/components/CertificateSecurity.tsx
index ac5eb748..0468a3c0 100644
--- a/src/components/CertificateSecurity.tsx
+++ b/src/components/CertificateSecurity.tsx
@@ -1,11 +1,3 @@
-import { Link } from "react-router-dom";
-import {
- Breadcrumb,
- BreadcrumbList,
- BreadcrumbItem,
- BreadcrumbSeparator,
- BreadcrumbPage,
-} from "./ui/breadcrumb";
import { Card } from "./ui/card";
const SecurityShieldIcon = () => (
@@ -67,23 +59,8 @@ const OpenSourceIcon = () => (
export function CertificateSecurity() {
return (
- <>
-
-
-
-
- Dashboard
-
-
-
-
- Certificate Security
-
-
-
-
-
-
+
+
Certificate Security
@@ -209,6 +186,6 @@ export function CertificateSecurity() {
- >
+
);
}
diff --git a/src/components/Certificates.tsx b/src/components/Certificates.tsx
index 95f7db62..782bcdfb 100644
--- a/src/components/Certificates.tsx
+++ b/src/components/Certificates.tsx
@@ -2,13 +2,6 @@ import { Button } from "./ui/button";
import { Card } from "./ui/card";
import { Link } from "react-router-dom";
import { useState, ReactNode } from "react";
-import {
- Breadcrumb,
- BreadcrumbList,
- BreadcrumbItem,
- BreadcrumbSeparator,
- BreadcrumbPage,
-} from "./ui/breadcrumb";
type OS = "macos" | "windows" | "linux";
type Action = "install" | "remove";
@@ -150,163 +143,144 @@ export function Certificates() {
const currentSteps = steps[activeOS][activeAction];
return (
- <>
-
-
-
-
- Dashboard
-
-
-
-
- Certificate Download
-
-
-
-
-
-
-
-
Certificates
-
-
-
-
-
-
-
-
- CodeGate SSL Certificate
-
-
- This certificate allows CodeGate to act as a proxy for certain
- software such as CoPilot.
-
-
- Download Certificate
-
-
-
-
-
-
-
- Is this certificate safe to install on my machine?
-
-
-
-
-
- Local Only: CodeGate runs entirely on your
- machine within an isolated container, ensuring all data
- processing stays local without any external transmissions.
-
-
-
-
-
-
- Secure Certificate Handling: This custom CA is
- locally generated and managed, the developers of CodeGate have
- no access to it.
-
-
+
+
Certificates
-
-
-
- No External Communications: CodeGate is
- designed with no capability to call home or communicate with
- external servers, outside of those requested by the IDE or
- Agent.
-
-
+
+
+
+
-
-
+
+ CodeGate SSL Certificate
+
+
+ This certificate allows CodeGate to act as a proxy for certain
+ software such as CoPilot.
+
+
- Learn More
-
-
+ Download Certificate
+
-
+
+
-
- Certificate Management
+
+
+ Is this certificate safe to install on my machine?
+
+
+
+
+
+ Local Only: CodeGate runs entirely on your
+ machine within an isolated container, ensuring all data processing
+ stays local without any external transmissions.
+
+
- {/* OS Selection Tabs */}
-
-
setActiveOS("macos")}
- className={`flex-1 px-4 py-2 text-sm font-medium rounded-md transition-colors ${
- activeOS === "macos"
- ? "bg-white text-teal-700 shadow-sm"
- : "text-gray-500 hover:text-gray-700"
- }`}
- >
- macOS
-
-
setActiveOS("windows")}
- className={`flex-1 px-4 py-2 text-sm font-medium rounded-md transition-colors ${
- activeOS === "windows"
- ? "bg-white text-teal-700 shadow-sm"
- : "text-gray-500 hover:text-gray-700"
- }`}
- >
- Windows
-
-
setActiveOS("linux")}
- className={`flex-1 px-4 py-2 text-sm font-medium rounded-md transition-colors ${
- activeOS === "linux"
- ? "bg-white text-teal-700 shadow-sm"
- : "text-gray-500 hover:text-gray-700"
- }`}
- >
- Linux
-
+
+
+
+ Secure Certificate Handling: This custom CA is
+ locally generated and managed, the developers of CodeGate have no
+ access to it.
+
- {/* Action Selection Tabs */}
-
-
setActiveAction("install")}
- className={`px-4 py-2 text-sm font-medium rounded-md transition-colors border ${
- activeAction === "install"
- ? "border-teal-200 bg-teal-50 text-teal-700"
- : "border-gray-200 text-gray-500 hover:text-gray-700"
- }`}
- >
- Install Certificate
-
-
setActiveAction("remove")}
- className={`px-4 py-2 text-sm font-medium rounded-md transition-colors border ${
- activeAction === "remove"
- ? "border-teal-200 bg-teal-50 text-teal-700"
- : "border-gray-200 text-gray-500 hover:text-gray-700"
- }`}
- >
- Remove Certificate
-
+
+
+
+ No External Communications: CodeGate is designed
+ with no capability to call home or communicate with external
+ servers, outside of those requested by the IDE or Agent.
+
+
+
+
+
+
+ Certificate Management
+
+ {/* OS Selection Tabs */}
+
+ setActiveOS("macos")}
+ className={`flex-1 px-4 py-2 text-sm font-medium rounded-md transition-colors ${
+ activeOS === "macos"
+ ? "bg-white text-teal-700 shadow-sm"
+ : "text-gray-500 hover:text-gray-700"
+ }`}
+ >
+ macOS
+
+ setActiveOS("windows")}
+ className={`flex-1 px-4 py-2 text-sm font-medium rounded-md transition-colors ${
+ activeOS === "windows"
+ ? "bg-white text-teal-700 shadow-sm"
+ : "text-gray-500 hover:text-gray-700"
+ }`}
+ >
+ Windows
+
+ setActiveOS("linux")}
+ className={`flex-1 px-4 py-2 text-sm font-medium rounded-md transition-colors ${
+ activeOS === "linux"
+ ? "bg-white text-teal-700 shadow-sm"
+ : "text-gray-500 hover:text-gray-700"
+ }`}
+ >
+ Linux
+
+
-
-
- {currentSteps.map((step, index) => (
-
- ))}
-
+ {/* Action Selection Tabs */}
+
+ setActiveAction("install")}
+ className={`px-4 py-2 text-sm font-medium rounded-md transition-colors border ${
+ activeAction === "install"
+ ? "border-teal-200 bg-teal-50 text-teal-700"
+ : "border-gray-200 text-gray-500 hover:text-gray-700"
+ }`}
+ >
+ Install Certificate
+
+ setActiveAction("remove")}
+ className={`px-4 py-2 text-sm font-medium rounded-md transition-colors border ${
+ activeAction === "remove"
+ ? "border-teal-200 bg-teal-50 text-teal-700"
+ : "border-gray-200 text-gray-500 hover:text-gray-700"
+ }`}
+ >
+ Remove Certificate
+
+
+
+
+
+ {currentSteps.map((step, index) => (
+
+ ))}
-
-
- >
+
+
+
);
}
diff --git a/src/components/Chat.tsx b/src/components/Chat.tsx
index b02aace2..bde08b9e 100644
--- a/src/components/Chat.tsx
+++ b/src/components/Chat.tsx
@@ -4,43 +4,27 @@ import {
ChatBubbleMessage,
} from "./ui/chat/chat-bubble";
import { ChatMessageList } from "./ui/chat/chat-message-list";
-import { Link, useParams } from "react-router-dom";
-import {
- Breadcrumb,
- BreadcrumbList,
- BreadcrumbItem,
- BreadcrumbSeparator,
- BreadcrumbPage,
-} from "./ui/breadcrumb";
+import { useParams } from "react-router-dom";
import { usePromptsStore } from "@/hooks/usePromptsStore";
import { Markdown } from "./Markdown";
-import { extractTitleFromMessage } from "@/lib/utils";
+import { useEffect } from "react";
export function Chat() {
const { id } = useParams();
- const chat = usePromptsStore((state) =>
- state.prompts.find((prompt) => prompt.chat_id === id)
- );
- const title = chat?.question_answers?.[0].question.message ?? "";
+ const { prompts, setCurrentPromptId } = usePromptsStore();
+ const chat = prompts.find((prompt) => prompt.chat_id === id);
+
+ useEffect(() => {
+ if (id) {
+ setCurrentPromptId(id);
+ }
+
+ return () => setCurrentPromptId("");
+ }, [prompts, id, setCurrentPromptId]);
return (
-
-
-
-
-
- Dashboard
-
-
-
-
- {extractTitleFromMessage(title)}
-
-
-
-
-
-
+
+
{(chat?.question_answers ?? []).map(({ question, answer }, index) => (
diff --git a/src/components/Dashboard.tsx b/src/components/Dashboard.tsx
index 60e6c97e..622b1ca8 100644
--- a/src/components/Dashboard.tsx
+++ b/src/components/Dashboard.tsx
@@ -103,7 +103,7 @@ export function Dashboard() {
if (searchFilterParam && alerts.length > 0) {
setSearch(searchFilterParam);
}
- }, [searchParams, alerts]);
+ }, [searchParams, toggleMaliciousFilter, setSearch, alerts]);
const maliciousPackages = getMaliciousPackagesChart();
@@ -119,26 +119,29 @@ export function Dashboard() {
setSearchParams(searchParams);
toggleMaliciousFilter(isChecked);
},
- [setSearchParams, searchParams, toggleMaliciousFilter]
+ [setSearchParams, setSearch, searchParams, toggleMaliciousFilter]
);
- const handleSearch = useCallback((value: string) => {
- if (value) {
- searchParams.set("search", value);
- searchParams.delete("maliciousPkg");
- setSearch(value);
- toggleMaliciousFilter(false);
- } else {
- searchParams.delete("search");
- setSearch("");
- }
- setSearchParams(searchParams);
- }, []);
+ const handleSearch = useCallback(
+ (value: string) => {
+ if (value) {
+ searchParams.set("search", value);
+ searchParams.delete("maliciousPkg");
+ setSearch(value);
+ toggleMaliciousFilter(false);
+ } else {
+ searchParams.delete("search");
+ setSearch("");
+ }
+ setSearchParams(searchParams);
+ },
+ [searchParams, setSearch, setSearchParams, toggleMaliciousFilter]
+ );
return (
-
+
-
+
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
index 759f5da0..d97da9ae 100644
--- a/src/components/Header.tsx
+++ b/src/components/Header.tsx
@@ -18,20 +18,20 @@ export function Header() {
-
+
Certificates
-
Download
-
Certificate Security
@@ -39,26 +39,38 @@ export function Header() {
-
+
Help
-
Continue Setup
-
CoPilot Setup
+
+
);
diff --git a/src/components/Help.tsx b/src/components/Help.tsx
index c5fbca05..3a4a87c5 100644
--- a/src/components/Help.tsx
+++ b/src/components/Help.tsx
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
-import { Link, useParams } from "react-router-dom";
+import { useParams } from "react-router-dom";
import { Markdown } from "./Markdown";
import Prism from "prismjs";
import "prismjs/themes/prism-tomorrow.css";
@@ -8,13 +8,6 @@ import "prismjs/components/prism-javascript";
import "prismjs/components/prism-python";
import "prismjs/components/prism-json";
import "prismjs/components/prism-yaml";
-import {
- Breadcrumb,
- BreadcrumbList,
- BreadcrumbItem,
- BreadcrumbSeparator,
- BreadcrumbPage,
-} from "./ui/breadcrumb";
export function Help() {
const { section } = useParams();
@@ -46,42 +39,9 @@ export function Help() {
}, [section]);
return (
-
-
-
-
-
- Dashboard
-
-
-
-
- {section === "copilot-setup"
- ? "CoPilot Setup"
- : "Continue Setup"}
-
-
-
-
-
-
-
-
+
- {content}
-
-
+ >
+ {content}
+
);
}
diff --git a/src/components/PromptList.tsx b/src/components/PromptList.tsx
index 8bbf67c9..224a662f 100644
--- a/src/components/PromptList.tsx
+++ b/src/components/PromptList.tsx
@@ -4,8 +4,12 @@ import {
extractTitleFromMessage,
groupPromptsByRelativeDate,
} from "@/lib/utils";
+import { usePromptsStore } from "@/hooks/usePromptsStore";
+import clsx from "clsx";
export function PromptList({ prompts }: { prompts: Prompt[] }) {
+ const { currentPromptId, setCurrentPromptId } = usePromptsStore();
+
const groupedPrompts = groupPromptsByRelativeDate(prompts);
return (
@@ -16,8 +20,12 @@ export function PromptList({ prompts }: { prompts: Prompt[] }) {
{prompts.map((prompt) => (
setCurrentPromptId(prompt.chat_id)}
to={`/prompt/${prompt.chat_id}`}
- className="text-gray-800 text-sm truncate hover:text-gray-500"
+ className={clsx(
+ `text-gray-800 text-sm truncate hover:text-gray-500`,
+ { "font-bold": currentPromptId === prompt.chat_id }
+ )}
>
{extractTitleFromMessage(
prompt.question_answers?.[0].question.message ??
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
index c79f0e38..cf6ac142 100644
--- a/src/components/Sidebar.tsx
+++ b/src/components/Sidebar.tsx
@@ -44,7 +44,6 @@ export function Sidebar({
{children}
-
);
}
diff --git a/src/hooks/useBreadcrumb.ts b/src/hooks/useBreadcrumb.ts
new file mode 100644
index 00000000..720a1f41
--- /dev/null
+++ b/src/hooks/useBreadcrumb.ts
@@ -0,0 +1,30 @@
+import { extractTitleFromMessage } from "@/lib/utils";
+import { useLocation } from "react-router-dom";
+import { usePromptsStore } from "./usePromptsStore";
+
+const routes = [
+ { path: "/certificates/security", breadcrumb: "Certificate Security" },
+ { path: "/certificates", breadcrumb: "Certificate Download" },
+ { path: "/help/continue-setup", breadcrumb: "Continue Setup" },
+ { path: "/help/copilot-setup", breadcrumb: "CoPilot Setup" },
+ { path: "/prompt/", breadcrumb: "Dashboard" },
+ { path: "/", breadcrumb: "" },
+];
+
+export function useBreadcrumb() {
+ const { pathname } = useLocation();
+ const { currentPromptId, prompts } = usePromptsStore();
+
+ const match = routes.find((route) => pathname.startsWith(route.path));
+ if (match?.path === "/prompt/") {
+ try {
+ const chat = prompts.find((prompt) => prompt.chat_id === currentPromptId);
+ const title = chat?.question_answers?.[0].question.message ?? "";
+ return extractTitleFromMessage(title) ?? "";
+ } catch {
+ return "";
+ }
+ }
+
+ return match ? match.breadcrumb : "";
+}
diff --git a/src/hooks/usePromptsStore.ts b/src/hooks/usePromptsStore.ts
index 72564c94..ab3e9fd4 100644
--- a/src/hooks/usePromptsStore.ts
+++ b/src/hooks/usePromptsStore.ts
@@ -5,6 +5,8 @@ import { getPrompts } from "@/service";
export const usePromptsStore = create((set) => ({
prompts: [],
loading: false,
+ currentPromptId: "",
+ setCurrentPromptId: (id: string) => set({ currentPromptId: id }),
fetchPrompts: async () => {
set({ loading: true });
const prompts = await getPrompts();
diff --git a/src/hooks/useSse.ts b/src/hooks/useSse.ts
index 9d6e1517..6de6bd53 100644
--- a/src/hooks/useSse.ts
+++ b/src/hooks/useSse.ts
@@ -26,9 +26,7 @@ export function useSse() {
};
return () => {
- if (location.pathname.includes("/prompt")) {
- eventSource.close();
- }
+ eventSource.close();
};
- }, [BASE_URL]);
+ }, [location.pathname, sendNotification]);
}
diff --git a/src/types.ts b/src/types.ts
index ebf7d481..ce7eaa87 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,6 +1,8 @@
export type PromptState = {
prompts: Prompt[];
loading: boolean;
+ currentPromptId: string;
+ setCurrentPromptId: (id: string) => void;
fetchPrompts: () => void;
};
diff --git a/tsconfig.app.json b/tsconfig.app.json
index caf4270b..231a4ec9 100644
--- a/tsconfig.app.json
+++ b/tsconfig.app.json
@@ -13,6 +13,7 @@
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
+ "incremental": true,
/* Linting */
"strict": true,