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. -

- -
-
-
- - -

- 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. +

+
- +
+
- -

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 */} -
- - - +
+ +

+ Secure Certificate Handling: This custom CA is + locally generated and managed, the developers of CodeGate have no + access to it. +

- {/* Action Selection Tabs */} -
- - +
+ +

+ 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. +

+
+
+ + Learn More + + +
+ + + +

Certificate Management

+ + {/* OS Selection Tabs */} +
+ + + +
-
-
- {currentSteps.map((step, index) => ( - - ))} -
+ {/* Action Selection Tabs */} +
+ + +
+ +
+
+ {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,