diff --git a/archon-ui-main/package-lock.json b/archon-ui-main/package-lock.json index 245127d5..37b3e9a7 100644 --- a/archon-ui-main/package-lock.json +++ b/archon-ui-main/package-lock.json @@ -48,6 +48,7 @@ "@testing-library/react": "^14.3.1", "@testing-library/user-event": "^14.5.2", "@types/node": "^20.19.0", + "@types/prismjs": "^1.26.5", "@types/react": "^18.3.1", "@types/react-dom": "^18.3.1", "@typescript-eslint/eslint-plugin": "^6.21.0", @@ -4374,6 +4375,13 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/prismjs": { + "version": "1.26.5", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz", + "integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/prop-types": { "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", diff --git a/archon-ui-main/package.json b/archon-ui-main/package.json index bf4dcdb8..576b78ae 100644 --- a/archon-ui-main/package.json +++ b/archon-ui-main/package.json @@ -62,10 +62,13 @@ }, "devDependencies": { "@biomejs/biome": "2.2.2", + "@tailwindcss/postcss": "4.1.2", + "@tailwindcss/vite": "4.1.2", "@testing-library/jest-dom": "^6.4.6", "@testing-library/react": "^14.3.1", "@testing-library/user-event": "^14.5.2", "@types/node": "^20.19.0", + "@types/prismjs": "^1.26.5", "@types/react": "^18.3.1", "@types/react-dom": "^18.3.1", "@typescript-eslint/eslint-plugin": "^6.21.0", @@ -78,8 +81,6 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.1", "jsdom": "^24.1.0", - "@tailwindcss/postcss": "4.1.2", - "@tailwindcss/vite": "4.1.2", "postcss": "latest", "tailwindcss": "4.1.2", "ts-node": "^10.9.1", diff --git a/archon-ui-main/src/features/knowledge/components/AddKnowledgeDialog.tsx b/archon-ui-main/src/features/knowledge/components/AddKnowledgeDialog.tsx index 3788affd..bcf01bdd 100644 --- a/archon-ui-main/src/features/knowledge/components/AddKnowledgeDialog.tsx +++ b/archon-ui-main/src/features/knowledge/components/AddKnowledgeDialog.tsx @@ -8,8 +8,8 @@ import { useId, useState } from "react"; import { useToast } from "@/features/shared/hooks/useToast"; import { Button, Input, Label } from "../../ui/primitives"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "../../ui/primitives/dialog"; -import { cn } from "../../ui/primitives/styles"; -import { Tabs, TabsContent } from "../../ui/primitives/tabs"; +import { cn, glassCard } from "../../ui/primitives/styles"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../ui/primitives/tabs"; import { useCrawlUrl, useUploadDocument } from "../hooks"; import type { CrawlRequest, UploadMetadata } from "../types"; import { KnowledgeTypeSelector } from "./KnowledgeTypeSelector"; @@ -134,59 +134,17 @@ export const AddKnowledgeDialog: React.FC = ({ setActiveTab(v as "crawl" | "upload")}> - {/* Enhanced Tab Buttons */} -
- {/* Crawl Website Tab */} - - - {/* Upload Document Tab */} - +
+ + + + Crawl Website + + + + Upload Document + +
{/* Crawl Tab */} @@ -207,12 +165,14 @@ export const AddKnowledgeDialog: React.FC = ({ value={crawlUrl} onChange={(e) => setCrawlUrl(e.target.value)} disabled={isProcessing} - className="pl-10 h-12 backdrop-blur-md bg-gradient-to-r from-white/60 to-white/50 dark:from-black/60 dark:to-black/50 border-gray-300/60 dark:border-gray-600/60 focus:border-cyan-400/70 focus:shadow-[0_0_20px_rgba(34,211,238,0.15)]" + className={cn( + "pl-10 h-12", + glassCard.blur.md, + glassCard.transparency.medium, + "border-gray-300/60 dark:border-gray-600/60 focus:border-cyan-400/70", + )} />
-

- Enter the URL of a website you want to crawl for knowledge -

@@ -231,7 +191,13 @@ export const AddKnowledgeDialog: React.FC = ({ - )} - {chunk.metadata.title} - - )} - -
- {isExpanded || !needsExpansion ? ( - chunk.content - ) : ( - <> - {preview}... - - - )} -
- - {chunk.metadata?.tags && chunk.metadata.tags.length > 0 && ( -
- {chunk.metadata.tags.map((tag: string) => ( - - {tag} - - ))} -
- )} -
- ); - })} - - )} - - - - {/* Code Examples Tab */} - -
- {codeLoading ? ( -
Loading code examples...
- ) : codeError ? ( -
- Failed to load code examples for source {sourceId}. - {codeErrorObj?.message && ` ${codeErrorObj.message}`} -
- ) : filteredCode.length === 0 ? ( -
- {searchQuery ? "No code examples match your search" : "No code examples available"} -
- ) : ( -
- {filteredCode.map((example) => ( -
-
-
- - {example.language && ( - - {example.language} - - )} -
- {example.file_path && {example.file_path}} -
- - {example.summary && ( -
{example.summary}
- )} - -
-                        
-                          {example.code || example.content || ""}
-                        
-                      
-
- ))} -
- )} -
-
-
- - - ); -}; diff --git a/archon-ui-main/src/features/knowledge/components/KnowledgeCard.tsx b/archon-ui-main/src/features/knowledge/components/KnowledgeCard.tsx index 43deeb24..0769e708 100644 --- a/archon-ui-main/src/features/knowledge/components/KnowledgeCard.tsx +++ b/archon-ui-main/src/features/knowledge/components/KnowledgeCard.tsx @@ -12,6 +12,7 @@ import { isOptimistic } from "@/features/shared/utils/optimistic"; import { KnowledgeCardProgress } from "../../progress/components/KnowledgeCardProgress"; import type { ActiveOperation } from "../../progress/types"; import { StatPill } from "../../ui/primitives"; +import { DataCard, DataCardContent, DataCardFooter, DataCardHeader } from "../../ui/primitives/data-card"; import { OptimisticIndicator } from "../../ui/primitives/OptimisticIndicator"; import { cn } from "../../ui/primitives/styles"; import { SimpleTooltip } from "../../ui/primitives/tooltip"; @@ -79,37 +80,16 @@ export const KnowledgeCard: React.FC = ({ } }; - const getCardGradient = () => { - if (activeOperation) { - return "from-cyan-100/60 via-cyan-50/30 to-white/70 dark:from-cyan-900/30 dark:via-cyan-900/15 dark:to-black/40"; - } - if (hasError) { - return "from-red-100/50 via-red-50/25 to-white/60 dark:from-red-900/20 dark:via-red-900/10 dark:to-black/30"; - } - if (isProcessing) { - return "from-yellow-100/50 via-yellow-50/25 to-white/60 dark:from-yellow-900/20 dark:via-yellow-900/10 dark:to-black/30"; - } - if (isTechnical) { - return isUrl - ? "from-cyan-100/50 via-cyan-50/25 to-white/60 dark:from-cyan-900/20 dark:via-cyan-900/10 dark:to-black/30" - : "from-purple-100/50 via-purple-50/25 to-white/60 dark:from-purple-900/20 dark:via-purple-900/10 dark:to-black/30"; - } - return isUrl - ? "from-blue-100/50 via-blue-50/25 to-white/60 dark:from-blue-900/20 dark:via-blue-900/10 dark:to-black/30" - : "from-pink-100/50 via-pink-50/25 to-white/60 dark:from-pink-900/20 dark:via-pink-900/10 dark:to-black/30"; + // Determine edge color for DataCard primitive + const getEdgeColor = (): "cyan" | "purple" | "blue" | "pink" | "red" | "orange" => { + if (activeOperation) return "cyan"; + if (hasError) return "red"; + if (isProcessing) return "orange"; + if (isTechnical) return isUrl ? "cyan" : "purple"; + return isUrl ? "blue" : "pink"; }; - const getBorderColor = () => { - if (activeOperation) return "border-cyan-600/40 dark:border-cyan-500/50"; - if (hasError) return "border-red-600/30 dark:border-red-500/30"; - if (isProcessing) return "border-yellow-600/30 dark:border-yellow-500/30"; - if (isTechnical) { - return isUrl ? "border-cyan-600/30 dark:border-cyan-500/30" : "border-purple-600/30 dark:border-purple-500/30"; - } - return isUrl ? "border-blue-600/30 dark:border-blue-500/30" : "border-pink-600/30 dark:border-pink-500/30"; - }; - - // Accent color used for the top glow bar + // Accent color name for title component const getAccentColorName = () => { if (activeOperation) return "cyan" as const; if (hasError) return "red" as const; @@ -118,26 +98,6 @@ export const KnowledgeCard: React.FC = ({ return isUrl ? ("blue" as const) : ("pink" as const); }; - const accent = (() => { - const name = getAccentColorName(); - switch (name) { - case "cyan": - return { bar: "bg-cyan-500", smear: "from-cyan-500/25" }; - case "purple": - return { bar: "bg-purple-500", smear: "from-purple-500/25" }; - case "blue": - return { bar: "bg-blue-500", smear: "from-blue-500/25" }; - case "pink": - return { bar: "bg-pink-500", smear: "from-pink-500/25" }; - case "red": - return { bar: "bg-red-500", smear: "from-red-500/25" }; - case "yellow": - return { bar: "bg-yellow-400", smear: "from-yellow-400/25" }; - default: - return { bar: "bg-cyan-500", smear: "from-cyan-500/25" }; - } - })(); - const getSourceIcon = () => { if (isUrl) return ; return ; @@ -146,7 +106,7 @@ export const KnowledgeCard: React.FC = ({ return ( // biome-ignore lint/a11y/useSemanticElements: Card contains nested interactive elements (buttons, links) - using div to avoid invalid HTML nesting setIsHovered(true)} @@ -161,33 +121,17 @@ export const KnowledgeCard: React.FC = ({ whileHover={{ scale: 1.02 }} transition={{ duration: 0.2 }} > -
- {/* Top accent glow tied to type (does not change size) */} -
- {/* Hairline highlight */} -
- {/* Soft glow smear fading downward */} -
-
- {/* Glow effect on hover */} - {isHovered && ( -
-
-
- )} - - {/* Header with Type Badge */} -
+
{/* Type and Source Badge */}
@@ -248,7 +192,10 @@ export const KnowledgeCard: React.FC = ({ target="_blank" rel="noopener noreferrer" onClick={(e) => e.stopPropagation()} - className="inline-flex items-center gap-1 text-xs text-gray-600 dark:text-gray-400 hover:text-cyan-600 dark:hover:text-cyan-400 transition-colors mt-2" + className={[ + "inline-flex items-center gap-1 text-xs mt-2", + "text-gray-600 dark:text-gray-400 hover:text-cyan-600 dark:hover:text-cyan-400 transition-colors", + ].join(" ")} > {extractDomain(item.url)} @@ -273,16 +220,14 @@ export const KnowledgeCard: React.FC = ({ >
-
+
- {/* Spacer to push footer to bottom */} -
+ + {/* Progress tracking for active operations - using simplified component */} + {activeOperation && } + - {/* Progress tracking for active operations - using simplified component */} - {activeOperation && } - - {/* Fixed Footer with Stats */} -
+
{/* Left: date */}
@@ -342,8 +287,8 @@ export const KnowledgeCard: React.FC = ({
-
-
+ + ); }; diff --git a/archon-ui-main/src/features/knowledge/components/KnowledgeCardTags.tsx b/archon-ui-main/src/features/knowledge/components/KnowledgeCardTags.tsx index de0a1ea1..e6061d62 100644 --- a/archon-ui-main/src/features/knowledge/components/KnowledgeCardTags.tsx +++ b/archon-ui-main/src/features/knowledge/components/KnowledgeCardTags.tsx @@ -215,7 +215,10 @@ export const KnowledgeCardTags: React.FC = ({ sourceId, { setIsEditing(true); // Load this specific tag for editing @@ -255,7 +258,10 @@ export const KnowledgeCardTags: React.FC = ({ sourceId, @@ -346,7 +360,10 @@ export const KnowledgeCardTags: React.FC = ({ sourceId, type="button" onClick={handleCancelEdit} disabled={updateMutation.isPending} - className="px-2 py-1 text-xs bg-gray-500 text-white rounded hover:bg-gray-600 disabled:opacity-50 transition-colors" + className={[ + "px-2 py-1 text-xs bg-gray-500 dark:bg-gray-500 text-white", + "hover:bg-gray-600 dark:hover:bg-gray-600 disabled:opacity-50 transition-colors", + ].join(" ")} > Cancel diff --git a/archon-ui-main/src/features/knowledge/components/KnowledgeCardType.tsx b/archon-ui-main/src/features/knowledge/components/KnowledgeCardType.tsx index ac2f8afe..1c0a4ecd 100644 --- a/archon-ui-main/src/features/knowledge/components/KnowledgeCardType.tsx +++ b/archon-ui-main/src/features/knowledge/components/KnowledgeCardType.tsx @@ -72,8 +72,8 @@ export const KnowledgeCardType: React.FC = ({ sourceId, "border-cyan-400 dark:border-cyan-600", "focus:ring-1 focus:ring-cyan-400", isTechnical - ? "bg-blue-100 text-blue-700 dark:bg-blue-500/10 dark:text-blue-400" - : "bg-pink-100 text-pink-700 dark:bg-pink-500/10 dark:text-pink-400", + ? "bg-cyan-500/10 text-cyan-600 dark:text-cyan-400" + : "bg-purple-500/10 text-purple-600 dark:text-purple-400", )} > @@ -111,8 +111,8 @@ export const KnowledgeCardType: React.FC = ({ sourceId, "flex items-center gap-1.5 px-2 py-1 rounded-md text-xs font-medium cursor-pointer", "hover:ring-1 hover:ring-cyan-400/50 transition-all", isTechnical - ? "bg-blue-100 text-blue-700 dark:bg-blue-500/10 dark:text-blue-400" - : "bg-pink-100 text-pink-700 dark:bg-pink-500/10 dark:text-pink-400", + ? "bg-cyan-500/10 text-cyan-600 dark:text-cyan-400" + : "bg-purple-500/10 text-purple-600 dark:text-purple-400", updateMutation.isPending && "opacity-50 cursor-not-allowed", )} onClick={handleClick} diff --git a/archon-ui-main/src/features/knowledge/components/KnowledgeHeader.tsx b/archon-ui-main/src/features/knowledge/components/KnowledgeHeader.tsx index 8b392699..4cb6dd55 100644 --- a/archon-ui-main/src/features/knowledge/components/KnowledgeHeader.tsx +++ b/archon-ui-main/src/features/knowledge/components/KnowledgeHeader.tsx @@ -32,8 +32,8 @@ export const KnowledgeHeader: React.FC = ({ }) => { return (
-
- {/* Left: Title */} + {/* Row 1: Title and Add Button (always on same line) */} +

Knowledge Base

@@ -42,85 +42,89 @@ export const KnowledgeHeader: React.FC = ({
- {/* Right: Search, Filters, View toggle, CTA */} -
- {/* Search on title row */} -
- - onSearchChange(e.target.value)} - className="pl-10 bg-black/30 border-white/10 focus:border-cyan-500/50" - /> -
+ {/* Add knowledge button - stays on top line */} + +
- {/* Segmented type filters */} - v && onTypeFilterChange(v as "all" | "technical" | "business")} - aria-label="Filter knowledge type" + {/* Row 2: Search and Filters (wraps on smaller screens) */} +
+ {/* Search */} +
+ + onSearchChange(e.target.value)} + className="pl-10 bg-black/30 dark:bg-black/30 border-white/10 dark:border-white/10 focus:border-cyan-500/50" + /> +
+ + {/* Segmented type filters */} + v && onTypeFilterChange(v as "all" | "technical" | "business")} + aria-label="Filter knowledge type" + > + + + - - - - - - - +
diff --git a/archon-ui-main/src/features/knowledge/components/KnowledgeList.tsx b/archon-ui-main/src/features/knowledge/components/KnowledgeList.tsx index 39d7db41..0358f67c 100644 --- a/archon-ui-main/src/features/knowledge/components/KnowledgeList.tsx +++ b/archon-ui-main/src/features/knowledge/components/KnowledgeList.tsx @@ -107,7 +107,7 @@ export const KnowledgeList: React.FC = ({ className="flex items-center justify-center py-12" >
-
+

Failed to Load Knowledge Base

@@ -130,7 +130,7 @@ export const KnowledgeList: React.FC = ({ className="flex items-center justify-center py-12" >
-
+

No Knowledge Items

diff --git a/archon-ui-main/src/features/knowledge/components/KnowledgeTable.tsx b/archon-ui-main/src/features/knowledge/components/KnowledgeTable.tsx index 63844333..614d2276 100644 --- a/archon-ui-main/src/features/knowledge/components/KnowledgeTable.tsx +++ b/archon-ui-main/src/features/knowledge/components/KnowledgeTable.tsx @@ -60,150 +60,171 @@ export const KnowledgeTable: React.FC = ({ items, onViewDoc const getTypeColor = (type?: string) => { if (type === "technical") { - return "text-cyan-400 bg-cyan-500/10 border-cyan-500/20"; + return "bg-cyan-500/10 text-cyan-600 dark:text-cyan-400"; + } + return "bg-purple-500/10 text-purple-600 dark:text-purple-400"; + }; + + const getHostname = (url: string): string => { + try { + return new URL(url).hostname; + } catch { + return url; + } + }; + + const isSafeProtocol = (url: string): boolean => { + try { + const protocol = new URL(url).protocol; + return protocol === "http:" || protocol === "https:"; + } catch { + return false; + } + }; + + const formatCreatedDate = (dateString: string): string => { + try { + const date = new Date(dateString); + if (Number.isNaN(date.getTime())) { + return "N/A"; + } + return formatDistanceToNowStrict(date, { addSuffix: true }); + } catch { + return "N/A"; } - return "text-blue-400 bg-blue-500/10 border-blue-500/20"; }; return ( -
- - - - - - - - - - - - - - {items.map((item) => { - const isDeleting = deletingIds.has(item.source_id); +
+
+
TitleTypeSourceDocsExamplesCreatedActions
+ + + + + + + + + + + + + {items.map((item, index) => { + const isDeleting = deletingIds.has(item.source_id); - return ( - - {/* Title */} - + return ( + + {/* Title */} + - {/* Type */} - - - {/* Source URL */} - - - {/* Document Count */} - - - {/* Code Examples Count */} - - - {/* Created Date */} - - - {/* Actions */} - - - - - - - onViewDocument(item.source_id)}> - - View Documents - - - handleDelete(item)} - className="text-red-400 focus:text-red-400" - > - - Delete - - - - - - - ); - })} - -
TitleTypeSourceDocsExamplesCreatedActions
-
- {item.title} -
-
+
+ + {item.title} + +
+
- - {getTypeIcon(item.metadata?.knowledge_type)} - {item.metadata?.knowledge_type || "general"} - - - - - - {(() => { - try { - return new URL(item.url).hostname; - } catch { - return item.url; - } - })()} - - - -
- - - {item.document_count || item.metadata?.document_count || 0} - -
-
-
- - - {item.code_examples_count || item.metadata?.code_examples_count || 0} - -
-
- - {formatDistanceToNowStrict(new Date(item.created_at), { addSuffix: true })} - - -
-
+ - - + {getTypeIcon(item.metadata?.knowledge_type)} + {item.metadata?.knowledge_type || "general"} + +
+ {/* Source URL */} + + {isSafeProtocol(item.url) ? ( + + + {getHostname(item.url)} + + ) : ( + + + {getHostname(item.url)} + + )} + + + {/* Document Count */} + + {item.document_count || item.metadata?.document_count || 0} + + + {/* Code Examples Count */} + + {item.code_examples_count || item.metadata?.code_examples_count || 0} + + + {/* Created Date */} + + {formatCreatedDate(item.created_at)} + + + {/* Actions */} + +
+ + + + + + + + onViewDocument(item.source_id)}> + + View Documents + + + handleDelete(item)} + className="text-red-600 dark:text-red-400 focus:text-red-600 dark:focus:text-red-400" + > + + Delete + + + +
+ + + ); + })} + + +
); }; diff --git a/archon-ui-main/src/features/knowledge/components/KnowledgeTypeSelector.tsx b/archon-ui-main/src/features/knowledge/components/KnowledgeTypeSelector.tsx index 2d1f6e4e..faa34dda 100644 --- a/archon-ui-main/src/features/knowledge/components/KnowledgeTypeSelector.tsx +++ b/archon-ui-main/src/features/knowledge/components/KnowledgeTypeSelector.tsx @@ -4,8 +4,8 @@ */ import { motion } from "framer-motion"; -import { Briefcase, Check, Terminal } from "lucide-react"; -import { cn } from "../../ui/primitives/styles"; +import { Briefcase, Terminal } from "lucide-react"; +import { cn, glassCard } from "../../ui/primitives/styles"; interface KnowledgeTypeSelectorProps { value: "technical" | "business"; @@ -19,54 +19,24 @@ const TYPES = [ label: "Technical", description: "Code, APIs, dev docs", icon: Terminal, - gradient: { - selected: - "from-cyan-100/60 via-cyan-50/30 to-white/70 dark:from-cyan-900/30 dark:via-cyan-900/15 dark:to-black/40", - unselected: - "from-gray-50/50 via-gray-25/25 to-white/60 dark:from-gray-800/20 dark:via-gray-800/10 dark:to-black/30", - }, - border: { - selected: "border-cyan-500/60", - unselected: "border-gray-300/50 dark:border-gray-700/50", - hover: "hover:border-cyan-400/50", - }, + edgeColor: "cyan" as const, colors: { - selected: "text-cyan-700 dark:text-cyan-400", - unselected: "text-gray-700 dark:text-gray-300", - description: { - selected: "text-cyan-600 dark:text-cyan-400", - unselected: "text-gray-500 dark:text-gray-400", - }, + icon: "text-cyan-700 dark:text-cyan-400", + label: "text-cyan-700 dark:text-cyan-400", + description: "text-cyan-600 dark:text-cyan-400", }, - accent: "bg-cyan-500", - smear: "from-cyan-500/25", }, { value: "business" as const, label: "Business", description: "Guides, policies, general", icon: Briefcase, - gradient: { - selected: - "from-pink-100/60 via-pink-50/30 to-white/70 dark:from-pink-900/30 dark:via-pink-900/15 dark:to-black/40", - unselected: - "from-gray-50/50 via-gray-25/25 to-white/60 dark:from-gray-800/20 dark:via-gray-800/10 dark:to-black/30", - }, - border: { - selected: "border-pink-500/60", - unselected: "border-gray-300/50 dark:border-gray-700/50", - hover: "hover:border-pink-400/50", - }, + edgeColor: "purple" as const, colors: { - selected: "text-pink-700 dark:text-pink-400", - unselected: "text-gray-700 dark:text-gray-300", - description: { - selected: "text-pink-600 dark:text-pink-400", - unselected: "text-gray-500 dark:text-gray-400", - }, + icon: "text-purple-700 dark:text-purple-400", + label: "text-purple-700 dark:text-purple-400", + description: "text-purple-600 dark:text-purple-400", }, - accent: "bg-pink-500", - smear: "from-pink-500/25", }, ]; @@ -94,47 +64,50 @@ export const KnowledgeTypeSelector: React.FC = ({ onClick={() => !disabled && onValueChange(type.value)} disabled={disabled} className={cn( - "relative w-full h-24 rounded-xl transition-all duration-200 border-2", + "relative w-full h-24 rounded-xl transition-all duration-200", "flex flex-col items-center justify-center gap-2 p-4", - "backdrop-blur-md", + glassCard.base, isSelected - ? `${type.border.selected} bg-gradient-to-b ${type.gradient.selected}` - : `${type.border.unselected} bg-gradient-to-b ${type.gradient.unselected}`, - !disabled && !isSelected && type.border.hover, - !disabled && - !isSelected && - "hover:shadow-[0_0_15px_rgba(0,0,0,0.05)] dark:hover:shadow-[0_0_15px_rgba(255,255,255,0.05)]", - isSelected && "shadow-[0_0_20px_rgba(6,182,212,0.15)]", + ? glassCard.edgeColors[type.edgeColor].border + : "border border-gray-300/50 dark:border-gray-700/50", + isSelected ? glassCard.tints[type.edgeColor].light : glassCard.transparency.light, + !isSelected && "hover:border-gray-400/60 dark:hover:border-gray-600/60", disabled && "opacity-50 cursor-not-allowed", )} aria-label={`Select ${type.label}: ${type.description}`} > - {/* Top accent glow for selected state */} + {/* Top edge-lit effect for selected state */} {isSelected && ( -
-
-
-
- )} - - {/* Selection indicator */} - {isSelected && ( -
- -
+ <> +
+
+ )} {/* Icon */} - +
); }; diff --git a/archon-ui-main/src/features/knowledge/components/LevelSelector.tsx b/archon-ui-main/src/features/knowledge/components/LevelSelector.tsx index 42832adb..0bb9afa4 100644 --- a/archon-ui-main/src/features/knowledge/components/LevelSelector.tsx +++ b/archon-ui-main/src/features/knowledge/components/LevelSelector.tsx @@ -4,9 +4,9 @@ */ import { motion } from "framer-motion"; -import { Check, Info } from "lucide-react"; -import { cn } from "../../ui/primitives/styles"; -import { SimpleTooltip } from "../../ui/primitives/tooltip"; +import { Info } from "lucide-react"; +import { cn, glassCard } from "../../ui/primitives/styles"; +import { SimpleTooltip, Tooltip, TooltipContent, TooltipTrigger } from "../../ui/primitives/tooltip"; interface LevelSelectorProps { value: string; @@ -43,36 +43,47 @@ const LEVELS = [ export const LevelSelector: React.FC = ({ value, onValueChange, disabled = false }) => { const tooltipContent = ( -
-
Crawl Depth Level Explanations:
+
+
Crawl Depth Levels:
{LEVELS.map((level) => ( -
-
- Level {level.value}: "{level.description}" +
+
+ Level {level.value}: {level.description}
-
{level.details}
+
{level.details}
))} -
-
- 💡 - More data isn't always better. Choose based on your needs. -
+
+ 💡 More data isn't always better. Choose based on your needs.
); return (
-
-
- Crawl Depth +
+
+
+ Crawl Depth +
+ + + + + {tooltipContent} + +
+
+ Higher levels crawl deeper into the website structure
- - -
-
+
{LEVELS.map((level) => { const isSelected = value === level.value; @@ -98,29 +109,34 @@ export const LevelSelector: React.FC = ({ value, onValueChan }} disabled={disabled} className={cn( - "relative w-full h-16 rounded-xl transition-all duration-200 border-2", + "relative w-full h-16 rounded-xl transition-all duration-200", "flex flex-col items-center justify-center gap-1", - "backdrop-blur-md focus:outline-none focus:ring-2 focus:ring-cyan-500 focus:ring-offset-2", - isSelected - ? "border-cyan-500/60 bg-gradient-to-b from-cyan-100/60 via-cyan-50/30 to-white/70 dark:from-cyan-900/30 dark:via-cyan-900/15 dark:to-black/40" - : "border-gray-300/50 dark:border-gray-700/50 bg-gradient-to-b from-gray-50/50 via-gray-25/25 to-white/60 dark:from-gray-800/20 dark:via-gray-800/10 dark:to-black/30", - !disabled && "hover:border-cyan-400/50 hover:shadow-[0_0_15px_rgba(6,182,212,0.15)]", + glassCard.base, + "focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-500 focus-visible:ring-offset-2", + isSelected ? glassCard.edgeColors.cyan.border : "border border-gray-300/50 dark:border-gray-700/50", + isSelected ? glassCard.tints.cyan.light : glassCard.transparency.light, + !disabled && !isSelected && "hover:border-cyan-400/50", disabled && "opacity-50 cursor-not-allowed", )} > - {/* Top accent glow for selected state */} + {/* Top edge-lit effect for selected state */} {isSelected && ( -
-
-
-
- )} - - {/* Selection indicator */} - {isSelected && ( -
- -
+ <> +
+
+ )} {/* Level number */} @@ -148,11 +164,6 @@ export const LevelSelector: React.FC = ({ value, onValueChan ); })}
- - {/* Help text */} -
- Higher levels crawl deeper into the website structure -
); }; diff --git a/archon-ui-main/src/features/knowledge/components/TagInput.tsx b/archon-ui-main/src/features/knowledge/components/TagInput.tsx index d4cde034..4c2dd397 100644 --- a/archon-ui-main/src/features/knowledge/components/TagInput.tsx +++ b/archon-ui-main/src/features/knowledge/components/TagInput.tsx @@ -7,7 +7,7 @@ import { motion } from "framer-motion"; import { Plus, X } from "lucide-react"; import { useState } from "react"; import { Input } from "../../ui/primitives"; -import { cn } from "../../ui/primitives/styles"; +import { cn, glassCard } from "../../ui/primitives/styles"; interface TagInputProps { tags: string[]; @@ -75,12 +75,17 @@ export const TagInput: React.FC = ({ return (
-
Tags
+
+
Tags
+
+ Press Enter or comma to add tags • Backspace to remove last tag +
+
{/* Tag Display */} {tags.length > 0 && (
- {tags.map((tag, index) => ( + {tags.map((tag) => ( = ({ exit={{ opacity: 0, scale: 0.8 }} className={cn( "inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-xs font-medium", - "backdrop-blur-md bg-gradient-to-r from-blue-100/80 to-blue-50/60 dark:from-blue-900/40 dark:to-blue-800/30", - "border border-blue-300/50 dark:border-blue-700/50", - "text-blue-700 dark:text-blue-300", + glassCard.blur.md, + glassCard.tints.blue.medium, + "border border-blue-400/30", "transition-all duration-200", )} > @@ -102,7 +107,7 @@ export const TagInput: React.FC = ({ className="ml-0.5 text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-200 transition-colors" aria-label={`Remove ${tag} tag`} > - + @@ -122,19 +127,21 @@ export const TagInput: React.FC = ({ onKeyDown={handleKeyDown} placeholder={tags.length >= maxTags ? "Maximum tags reached" : placeholder} disabled={disabled || tags.length >= maxTags} - className="pl-9 backdrop-blur-md bg-gradient-to-r from-white/60 to-white/50 dark:from-black/60 dark:to-black/50 border-gray-300/60 dark:border-gray-600/60 focus:border-blue-400/70 focus:shadow-[0_0_15px_rgba(59,130,246,0.15)]" + className={cn( + "pl-9", + glassCard.blur.md, + glassCard.transparency.medium, + "border-gray-300/60 dark:border-gray-600/60 focus:border-blue-400/70", + )} />
- {/* Help Text */} -
-

Press Enter or comma to add tags • Backspace to remove last tag

- {maxTags && ( -

- {tags.length}/{maxTags} tags used -

- )} -
+ {/* Tag count */} + {maxTags && ( +
+ {tags.length}/{maxTags} tags used +
+ )}
); }; diff --git a/archon-ui-main/src/features/knowledge/components/index.ts b/archon-ui-main/src/features/knowledge/components/index.ts index e9174d5b..a7f9ff55 100644 --- a/archon-ui-main/src/features/knowledge/components/index.ts +++ b/archon-ui-main/src/features/knowledge/components/index.ts @@ -1,5 +1,4 @@ export * from "./AddKnowledgeDialog"; -export * from "./DocumentBrowser"; export * from "./KnowledgeCard"; export * from "./KnowledgeList"; export * from "./KnowledgeTypeSelector"; diff --git a/archon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.ts b/archon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.ts index bf2ab907..568b834d 100644 --- a/archon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.ts +++ b/archon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.ts @@ -228,7 +228,7 @@ export function useCrawlUrl() { }); // Return context for rollback and replacement - return { previousSummaries, previousOperations, tempProgressId }; + return { previousSummaries, previousOperations, tempProgressId, tempItemId: tempProgressId }; }, onSuccess: (response, _variables, context) => { // Replace temporary IDs with real ones from the server @@ -407,7 +407,7 @@ export function useUploadDocument() { }; }); - return { previousSummaries, previousOperations, tempProgressId }; + return { previousSummaries, previousOperations, tempProgressId, tempItemId: tempProgressId }; }, onSuccess: (response, _variables, context) => { // Replace temporary IDs with real ones from the server diff --git a/archon-ui-main/src/features/knowledge/inspector/components/ContentViewer.tsx b/archon-ui-main/src/features/knowledge/inspector/components/ContentViewer.tsx index d3f91a3a..4a3a9c05 100644 --- a/archon-ui-main/src/features/knowledge/inspector/components/ContentViewer.tsx +++ b/archon-ui-main/src/features/knowledge/inspector/components/ContentViewer.tsx @@ -4,9 +4,20 @@ */ import { Check, Code, Copy, FileText, Layers } from "lucide-react"; +import Prism from "prismjs"; +import ReactMarkdown from "react-markdown"; import { Button } from "../../../ui/primitives"; import type { InspectorSelectedItem } from "../../types"; +// Import Prism theme and languages +import "prismjs/themes/prism-tomorrow.css"; +import "prismjs/components/prism-javascript"; +import "prismjs/components/prism-typescript"; +import "prismjs/components/prism-python"; +import "prismjs/components/prism-java"; +import "prismjs/components/prism-bash"; +import "prismjs/components/prism-json"; + interface ContentViewerProps { selectedItem: InspectorSelectedItem | null; onCopy: (text: string, id: string) => void; @@ -25,6 +36,51 @@ export const ContentViewer: React.FC = ({ selectedItem, onCo ); } + // Highlight code with Prism + const highlightCode = (code: string, language?: string): string => { + try { + // Escape HTML entities FIRST per Prism documentation requirement + // Prism expects pre-escaped input to prevent XSS + const escaped = code + .replace(/&/g, "&") + .replace(//g, ">"); + + const lang = language?.toLowerCase() || "javascript"; + const grammar = Prism.languages[lang] || Prism.languages.javascript; + return Prism.highlight(escaped, grammar, lang); + } catch (error) { + console.error("Prism highlighting failed:", error); + // Return escaped code on error + return code.replace(/&/g, "&").replace(//g, ">"); + } + }; + + // Strip leading/trailing backticks from document content + const stripOuterBackticks = (content: string) => { + let cleaned = content.trim(); + + // Remove opening triple backticks (with optional language identifier) + if (cleaned.startsWith("```")) { + const firstNewline = cleaned.indexOf("\n"); + if (firstNewline > 0) { + cleaned = cleaned.substring(firstNewline + 1); + } + } + + // Remove closing triple backticks + if (cleaned.endsWith("```")) { + const lastBackticks = cleaned.lastIndexOf("\n```"); + if (lastBackticks > 0) { + cleaned = cleaned.substring(0, lastBackticks); + } else { + cleaned = cleaned.substring(0, cleaned.length - 3); + } + } + + return cleaned.trim(); + }; + return (
{/* Content Header - Fixed with proper overflow handling */} @@ -56,7 +112,12 @@ export const ContentViewer: React.FC = ({ selectedItem, onCo ) : ( <>
- + {selectedItem.type === "code" && selectedItem.metadata && "language" in selectedItem.metadata ? selectedItem.metadata.language || "unknown" : "unknown"} @@ -105,19 +166,48 @@ export const ContentViewer: React.FC = ({ selectedItem, onCo {/* Content Body */}
{selectedItem.type === "document" ? ( -
-
-              {selectedItem.content || "No content available"}
-            
+
+

{children}

, + h1: ({ children }) =>

{children}

, + h2: ({ children }) =>

{children}

, + h3: ({ children }) =>

{children}

, + ul: ({ children }) =>
    {children}
, + ol: ({ children }) =>
    {children}
, + li: ({ children }) =>
  • {children}
  • , + code: ({ children }) => {children}, + }} + > + {stripOuterBackticks(selectedItem.content || "No content available")} +
    ) : ( -
    -
    -              
    -                {selectedItem.content || "// No code content available"}
    -              
    -            
    -
    + (() => { + // Extract language once + const language = + selectedItem.metadata && "language" in selectedItem.metadata + ? selectedItem.metadata.language || "javascript" + : "javascript"; + + return ( +
    +
    +                  
    +                
    +
    + ); + })() )}
    @@ -131,16 +221,19 @@ export const ContentViewer: React.FC = ({ selectedItem, onCo {(selectedItem.metadata.relevance_score * 100).toFixed(0)}% )} - {selectedItem.type === "document" && "url" in selectedItem.metadata && selectedItem.metadata.url && ( - - View Source - - )} + {selectedItem.type === "document" && + selectedItem.metadata && + "url" in selectedItem.metadata && + selectedItem.metadata.url && ( + + View Source + + )}
    {selectedItem.type === "document" ? "Document Chunk" : "Code Example"}
    diff --git a/archon-ui-main/src/features/knowledge/inspector/components/InspectorHeader.tsx b/archon-ui-main/src/features/knowledge/inspector/components/InspectorHeader.tsx index a3dc9615..ecdc90af 100644 --- a/archon-ui-main/src/features/knowledge/inspector/components/InspectorHeader.tsx +++ b/archon-ui-main/src/features/knowledge/inspector/components/InspectorHeader.tsx @@ -38,8 +38,8 @@ export const InspectorHeader: React.FC = ({ className={cn( "inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium", item.source_type === "url" - ? "bg-blue-500/10 text-blue-400 border border-blue-500/20" - : "bg-purple-500/10 text-purple-400 border border-purple-500/20", + ? "bg-blue-500/10 text-blue-600 dark:text-blue-400 border border-blue-500/20" + : "bg-purple-500/10 text-purple-600 dark:text-purple-400 border border-purple-500/20", )} > {item.source_type === "url" ? ( @@ -60,8 +60,8 @@ export const InspectorHeader: React.FC = ({ className={cn( "inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium", item.knowledge_type === "technical" - ? "bg-green-500/10 text-green-400 border border-green-500/20" - : "bg-orange-500/10 text-orange-400 border border-orange-500/20", + ? "bg-green-500/10 text-green-600 dark:text-green-400 border border-green-500/20" + : "bg-orange-500/10 text-orange-600 dark:text-orange-400 border border-orange-500/20", )} > {item.knowledge_type === "technical" ? ( diff --git a/archon-ui-main/src/features/knowledge/inspector/components/InspectorSidebar.tsx b/archon-ui-main/src/features/knowledge/inspector/components/InspectorSidebar.tsx index 09b9e441..2dfa5119 100644 --- a/archon-ui-main/src/features/knowledge/inspector/components/InspectorSidebar.tsx +++ b/archon-ui-main/src/features/knowledge/inspector/components/InspectorSidebar.tsx @@ -102,9 +102,9 @@ export const InspectorSidebar: React.FC = ({ onClick={() => onItemSelect(item)} className={cn( "w-full text-left p-3 rounded-lg mb-1 transition-all", - "hover:bg-white/5 focus:outline-none focus:ring-2 focus:ring-cyan-500/50", + "hover:bg-white/5 dark:hover:bg-white/5 focus:outline-none focus:ring-2 focus:ring-cyan-500/50", selectedItemId === item.id - ? "bg-cyan-500/10 border border-cyan-500/30 ring-1 ring-cyan-500/20" + ? "bg-cyan-500/10 dark:bg-cyan-500/10 border border-cyan-500/30 dark:border-cyan-500/30 ring-1 ring-cyan-500/20" : "border border-transparent", )} role="option" @@ -128,7 +128,7 @@ export const InspectorSidebar: React.FC = ({ {getItemTitle(item)} {viewMode === "code" && (item as CodeExample).language && ( - + {(item as CodeExample).language} )} @@ -157,7 +157,7 @@ export const InspectorSidebar: React.FC = ({ size="sm" onClick={onLoadMore} disabled={isFetchingNextPage} - className="w-full text-cyan-400 hover:text-white hover:bg-cyan-500/10 transition-all" + className="w-full text-cyan-600 dark:text-cyan-400 hover:text-white dark:hover:text-white hover:bg-cyan-500/10 transition-all" aria-label={`Load more ${viewMode}`} > {isFetchingNextPage ? ( diff --git a/archon-ui-main/src/features/knowledge/views/KnowledgeView.tsx b/archon-ui-main/src/features/knowledge/views/KnowledgeView.tsx index 0bedc7b2..c9a9a3af 100644 --- a/archon-ui-main/src/features/knowledge/views/KnowledgeView.tsx +++ b/archon-ui-main/src/features/knowledge/views/KnowledgeView.tsx @@ -73,7 +73,7 @@ export const KnowledgeView = () => { // Check if it was an error or success if (op.status === "error" || op.status === "failed") { // Show error message with details - const errorMessage = op.message || op.error || "Operation failed"; + const errorMessage = op.message || "Operation failed"; showToast(`❌ ${errorMessage}`, "error", 7000); } else if (op.status === "completed") { // Show success message @@ -141,7 +141,7 @@ export const KnowledgeView = () => {

    Active Operations ({activeOperations.length})

    -
    +
    Live Updates
    diff --git a/archon-ui-main/src/features/ui/primitives/switch.tsx b/archon-ui-main/src/features/ui/primitives/switch.tsx index f621dd0a..714fde18 100644 --- a/archon-ui-main/src/features/ui/primitives/switch.tsx +++ b/archon-ui-main/src/features/ui/primitives/switch.tsx @@ -105,7 +105,21 @@ const switchVariants = { * - Uncontrolled: Pass defaultChecked, component manages own state */ const Switch = React.forwardRef, SwitchProps>( - ({ className, size = "md", color = "cyan", icon, iconOn, iconOff, checked, defaultChecked, onCheckedChange, ...props }, ref) => { + ( + { + className, + size = "md", + color = "cyan", + icon, + iconOn, + iconOff, + checked, + defaultChecked, + onCheckedChange, + ...props + }, + ref, + ) => { const sizeStyles = switchVariants.size[size]; const colorStyles = switchVariants.color[color]; @@ -128,7 +142,7 @@ const Switch = React.forwardRef, // Call parent's handler if provided onCheckedChange?.(newChecked); }, - [isControlled, onCheckedChange] + [isControlled, onCheckedChange], ); const displayIcon = React.useMemo(() => {