diff --git a/.claude/commands/archon/archon-ui-consistency-review.md b/.claude/commands/archon/archon-ui-consistency-review.md new file mode 100644 index 00000000..f44e88ea --- /dev/null +++ b/.claude/commands/archon/archon-ui-consistency-review.md @@ -0,0 +1,59 @@ +--- +description: Analyze UI components for reusability, Radix usage, primitives, and styling consistency +argument-hint: +allowed-tools: Read, Grep, Glob, Write, Bash +thinking: auto +--- + +# UI Consistency Review for Archon + +**Review scope**: $ARGUMENTS + +## Process + +### Step 1: Load Standards +Read `PRPs/ai_docs/UI_STANDARDS.md` - This is the single source of truth for all rules, patterns, and scans. + +### Step 2: Find Files +Glob all `.tsx` files in the provided path. + +### Step 3: Run Automated Scans +Execute ALL scans from **UI_STANDARDS.md - AUTOMATED SCAN REFERENCE** section: +- Critical scans (dynamic classes, non-responsive grids, native HTML, unconstrained scroll) +- High priority scans (keyboard support, dark mode, hardcoded patterns, min-w-0) +- Medium priority scans (TypeScript, color mismatches, props validation) + +### Step 4: Deep Analysis +For each file, check against ALL rules from **UI_STANDARDS.md sections 1-8**: +1. TAILWIND V4 - Static classes, tokens +2. LAYOUT & RESPONSIVE - Grids, scroll, truncation +3. THEMING - Dark mode variants +4. RADIX UI - Primitives usage +5. PRIMITIVES LIBRARY - Card, PillNavigation, styles.ts +6. ACCESSIBILITY - Keyboard, ARIA, focus +7. TYPESCRIPT & API CONTRACTS - Types, props, consistency +8. FUNCTIONAL LOGIC - UI actually works + +**For primitives** (files in `/features/ui/primitives/`): +- Verify all props affect rendering +- Check color variant objects have: checked, glow, focusRing, hover +- Validate prop implementations match interface + +### Step 5: Generate Report +Save to `PRPs/reviews/ui-consistency-review-[feature].md` with: +- Overall scores (use **UI_STANDARDS.md - SCORING VIOLATIONS**) +- Component-by-component analysis +- Violations with file:line, current code, required fix +- Prioritized action items + +### Step 6: Create PRP +Use `/prp-claude-code:prp-claude-code-create ui-consistency-fixes-[feature]` if violations found. + +**PRP should reference:** +- The review report +- Specific UI_STANDARDS.md sections violated +- Automated scan commands to re-run for validation + +--- + +**Note**: Do NOT duplicate rules/patterns from UI_STANDARDS.md. Just reference section numbers. diff --git a/.gitignore b/.gitignore index 96c7a645..35179987 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ __pycache__ PRPs/local PRPs/completed/ PRPs/stories/ +PRPs/reviews/ /logs/ .zed tmp/ diff --git a/CLAUDE.md b/CLAUDE.md index 77673db7..6bac8d57 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -209,11 +209,15 @@ See `python/.env.example` for complete list ### Add a new UI component in features directory +**IMPORTANT**: Review UI design standards in `@PRPs/ai_docs/UI_STANDARDS.md` before creating UI components. + 1. Use Radix UI primitives from `src/features/ui/primitives/` 2. Create component in relevant feature folder under `src/features/[feature]/components/` 3. Define types in `src/features/[feature]/types/` 4. Use TanStack Query hook from `src/features/[feature]/hooks/` 5. Apply Tron-inspired glassmorphism styling with Tailwind +6. Follow responsive design patterns (mobile-first with breakpoints) +7. Ensure no dynamic Tailwind class construction (see UI_STANDARDS.md Section 2) ### Add or modify MCP tools @@ -268,6 +272,8 @@ When connected to Claude/Cursor/Windsurf, the following tools are available: - `archon:rag_search_knowledge_base` - Search knowledge base for relevant content - `archon:rag_search_code_examples` - Find code snippets in the knowledge base - `archon:rag_get_available_sources` - List available knowledge sources +- `archon:rag_list_pages_for_source` - List all pages for a given source (browse documentation structure) +- `archon:rag_read_full_page` - Retrieve full page content by page_id or URL ### Project Management diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9c2f0c6d..d3b27af7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -365,12 +365,21 @@ Test these things using both the UI and the MCP server. This process will be sim archon-ui-main/src/pages/YourPage.tsx ``` -2. **Testing Your Changes** +2. **UI Design Standards** + + Before creating or modifying UI components, review the design standards: + - **UI Standards**: `PRPs/ai_docs/UI_STANDARDS.md` - Complete Tailwind v4, Radix, and responsive design patterns + - **Style Guide**: Enable in Settings → scroll to "Feature Flags" → Enable "Style Guide Page" + - Access at http://localhost:3737/style-guide + - View all available primitives, colors, layouts, and component patterns + - **UI Consistency Review**: Run `/archon:archon-ui-consistency-review ` to automatically check your components for compliance + +3. **Testing Your Changes** ```bash # Using Make (if installed) make test-fe - + # Or manually cd archon-ui-main && npm run test @@ -381,11 +390,11 @@ Test these things using both the UI and the MCP server. This process will be sim npm run test:ui ``` -3. **Development Server** +4. **Development Server** ```bash # Using Make for hybrid mode (if installed) make dev # Backend in Docker, frontend local - + # Or manually for faster iteration cd archon-ui-main && npm run dev # Still connects to Docker backend services diff --git a/PRPs/ai_docs/UI_STANDARDS.md b/PRPs/ai_docs/UI_STANDARDS.md new file mode 100644 index 00000000..a8f67077 --- /dev/null +++ b/PRPs/ai_docs/UI_STANDARDS.md @@ -0,0 +1,797 @@ +# Archon UI Standards + +**Audience**: AI agents performing automated UI audits and refactors +**Purpose**: Single source of truth for UI patterns, violations, and automated detection +**Usage**: Run `/archon:archon-ui-consistency-review` to scan code against these standards + +--- + +## 1. TAILWIND V4 + +### Rules +- **NO dynamic class construction** - Tailwind scans source code as plain text at build time + - NEVER: `` `bg-${color}-500` ``, `` `ring-${color}-500` ``, `` `shadow-${size}` `` + - Use static lookup objects instead +- **Bare HSL values in CSS variables** - NO `hsl()` wrapper + - Correct: `--background: 0 0% 98%;` + - Wrong: `--background: hsl(0 0% 98%);` +- **CSS variables allowed in arbitrary values** - Utility name must be static + - Correct: `bg-[var(--accent)]` + - Wrong: `` `bg-[var(--${colorName})]` `` +- **Use @theme inline** to map CSS vars to Tailwind utilities +- **Define @custom-variant dark** - Required for `dark:` to work in v4 + +### Anti-Patterns +```tsx +// ❌ Dynamic classes (NO CSS GENERATED) +const color = "cyan"; +
+
// Common miss! + +// ❌ Inline styles for visual CSS +
+``` + +### Good Examples +```tsx +// ✅ Static lookup for discrete variants +const colorClasses = { + cyan: "bg-cyan-500 text-cyan-900 ring-cyan-500", + purple: "bg-purple-500 text-purple-900 ring-purple-500", +}; +
+ +// ✅ CSS variables for dynamic values +
+``` + +### Automated Scans +```bash +# All dynamic class construction patterns +grep -rn "className.*\`.*\${.*}\`" [path] --include="*.tsx" +grep -rn "bg-\${.*}\|text-\${.*}\|border-\${.*}" [path] --include="*.tsx" +grep -rn "ring-\${.*}\|shadow-\${.*}\|outline-\${.*}\|opacity-\${.*}" [path] --include="*.tsx" + +# Inline visual styles (not CSS vars) +grep -rn "style={{.*backgroundColor\|color:\|padding:" [path] --include="*.tsx" +``` + +**Fix Pattern**: Add all properties to static variant object (checked, glow, focusRing, hover) + +--- + +## 2. LAYOUT & RESPONSIVE + +### Rules +- **Responsive grids** - NEVER fixed columns without breakpoints + - Use: `grid-cols-1 md:grid-cols-2 lg:grid-cols-4` +- **Constrain horizontal scroll** - Parent must have `w-full` or `max-w-*` +- **Add scrollbar-hide** to all `overflow-x-auto` containers +- **min-w-0 on flex parents** containing scroll containers (prevents page expansion) +- **Text truncation** - Always use `truncate`, `line-clamp-N`, or `break-words` +- **Desktop-primary** - Optimize for desktop, add responsive breakpoints down + +### Anti-Patterns +```tsx +// ❌ Fixed grid (breaks mobile) +
+ +// ❌ Unconstrained scroll (page becomes horizontally scrollable) +
+
+ +// ❌ Flex parent without min-w-0 (page expansion) +
+
{/* MISSING min-w-0! */} +
+``` + +### Good Examples +```tsx +// ✅ Responsive grid +
+ +// ✅ Constrained horizontal scroll +
+
+
+ +// ✅ Flex parent with scroll container +
+
{/* min-w-0 CRITICAL */} +``` + +### Automated Scans +```bash +# Non-responsive grids +grep -rn "grid-cols-[2-9]" [path] --include="*.tsx" | grep -v "md:\|lg:\|xl:" + +# Unconstrained scroll +grep -rn "overflow-x-auto" [path] --include="*.tsx" +# Then manually verify parent has w-full + +# Missing text truncation +grep -rn "`, add responsive breakpoints to grids + +--- + +## 3. THEMING + +### Rules +- **Every visible color needs `dark:` variant** +- **Structure identical** between themes (only colors/opacity change) +- **Use tokens** for both light and dark (`--bg` and redefine in `.dark`) + +### Anti-Patterns +```tsx +// ❌ No dark variant +
+ +// ❌ Different structure in dark mode +{theme === 'dark' ? : } +``` + +### Good Examples +```tsx +// ✅ Both themes +
+``` + +### Automated Scans +```bash +# Colors without dark variants +grep -rn "bg-.*-[0-9]" [path] --include="*.tsx" | grep -v "dark:" +``` + +**Fix Pattern**: Add `dark:` variant for every color, border, shadow + +--- + +## 4. RADIX UI + +### Rules +- **Use Radix primitives** - NEVER native ``, `` +- **Compose with asChild** - Don't wrap, attach behavior to your components +- **Style via data attributes** - `[data-state="open"]`, `[data-disabled]` +- **Use Portal** for overlays with proper z-index +- **Support both controlled and uncontrolled modes** - All form primitives must work in both modes + +### Controlled vs Uncontrolled Form Components + +**CRITICAL RULE**: Form primitives (Switch, Checkbox, Select, etc.) MUST support both controlled and uncontrolled modes. + +**Controlled Mode**: Parent manages state via `value`/`checked` prop + `onChange`/`onCheckedChange` handler +**Uncontrolled Mode**: Component manages own state via `defaultValue`/`defaultChecked` + +### Anti-Patterns +```tsx +// ❌ Native HTML + + + +// ❌ Wrong composition + + +// ❌ Only supports controlled mode (breaks uncontrolled usage) +const Switch = ({ checked, ...props }) => { + const displayIcon = checked ? iconOn : iconOff; // No internal state! + return +}; +``` + +### Good Examples +```tsx +// ✅ Radix with asChild + + + + +// ✅ Radix primitives + + + +// ✅ Supports both controlled and uncontrolled modes +const Switch = ({ checked, defaultChecked, onCheckedChange, ...props }) => { + const isControlled = checked !== undefined; + const [internalChecked, setInternalChecked] = useState(defaultChecked ?? false); + const actualChecked = isControlled ? checked : internalChecked; + + const handleChange = (newChecked: boolean) => { + if (!isControlled) setInternalChecked(newChecked); + onCheckedChange?.(newChecked); + }; + + return +}; +``` + +### Automated Scans +```bash +# Native HTML form elements +grep -rn "\|type=\"checkbox\"" [path]` +- Emerald usage: `grep -rn "emerald" [path] --include="*.tsx" --include="*.ts"` (must use "green") + +### High Priority +- Missing keyboard: `grep -rn "onClick.*role=\"button\"" [path]` (verify onKeyDown) +- Clickable icons: `grep -rn "<[A-Z].*onClick={" [path] --include="*.tsx" | grep -v "&1 | grep "error TS"` +- Line length: `grep -rn ".\{121,\}" [path] --include="*.tsx" | grep className` +- Missing satisfies: `grep -rn "const.*Classes = {" [path]/primitives -A 5 | grep -v "satisfies"` +- Props unused: Manual check interfaces vs usage + +--- + +## QUICK REFERENCE + +### Breakpoints +- sm: 640px | md: 768px | lg: 1024px | xl: 1280px | 2xl: 1536px + +### Color Variant Checklist (for primitives with colors) +Every color object MUST have: +- [ ] `checked` or `active` state classes +- [ ] `glow` effect +- [ ] `focusRing` - STATIC class like `"focus-visible:ring-cyan-500"` +- [ ] `hover` state +- [ ] All 6 colors: purple, blue, cyan, green, orange, pink + +### Common Patterns + +**Horizontal Scroll (Archon Standard)** +```tsx +
+
+
+ {items.map(i => )} +``` + +**Responsive Grid** +```tsx +
+``` + +**Flex + Scroll Container** +```tsx +
+ +
{/* min-w-0 REQUIRED */} + {/* scroll containers here */} +``` + +**Color Variants (Static Lookup)** +```tsx +const variants = { + cyan: { + checked: "data-[state=checked]:bg-cyan-500/20", + glow: "shadow-[0_0_15px_rgba(34,211,238,0.5)]", + focusRing: "focus-visible:ring-cyan-500", // STATIC! + hover: "hover:bg-cyan-500/10", + }, + // ... repeat for all colors +}; +``` + +**Keyboard Support** +```tsx +
(e.key === "Enter" || e.key === " ") && handler()} + aria-selected={isSelected} +> +``` + +--- + +## SCORING VIOLATIONS + +### Critical (-3 points each) +- Dynamic class construction +- Missing keyboard support on interactive +- Non-responsive grids causing horizontal scroll +- TypeScript errors + +### High (-2 points each) +- Unconstrained scroll containers +- Props that do nothing +- Non-functional UI logic (filter/sort/drag-drop) +- Missing dark mode variants + +### Medium (-1 point each) +- Native HTML form elements +- Hardcoded glassmorphism +- Missing text truncation +- Color type inconsistencies + +**Grading Scale:** +- 0 critical violations: A (9-10/10) +- 1 critical: B (7-8/10) +- 2-3 critical: C (5-6/10) +- 4+ critical: F (1-4/10) + +--- + +## ADDING NEW RULES + +When code review finds an issue not caught by automated review: + +1. **Identify which section** it belongs to (Tailwind? Layout? A11y?) +2. **Add to that section**: + - Rule (what to do) + - Anti-Pattern example + - Good example + - Automated scan (if possible) +3. **Add scan to AUTOMATED SCAN REFERENCE** +4. **Done** - Next review will catch it + +**Goal**: Eventually eliminate manual code reviews entirely. diff --git a/archon-ui-main/package-lock.json b/archon-ui-main/package-lock.json index a6653753..37b3e9a7 100644 --- a/archon-ui-main/package-lock.json +++ b/archon-ui-main/package-lock.json @@ -10,10 +10,14 @@ "dependencies": { "@mdxeditor/editor": "^3.42.0", "@radix-ui/react-alert-dialog": "^1.1.15", + "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-radio-group": "^1.3.8", "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-toast": "^1.2.15", "@radix-ui/react-tooltip": "^1.2.8", @@ -30,6 +34,7 @@ "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.3.1", + "react-icons": "^5.5.0", "react-markdown": "^10.1.0", "react-router-dom": "^6.26.2", "tailwind-merge": "latest", @@ -37,10 +42,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", @@ -54,7 +62,7 @@ "eslint-plugin-react-refresh": "^0.4.1", "jsdom": "^24.1.0", "postcss": "latest", - "tailwindcss": "3.4.17", + "tailwindcss": "4.1.2", "ts-node": "^10.9.1", "typescript": "^5.5.4", "vite": "^5.2.0", @@ -1739,53 +1747,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -2428,17 +2389,6 @@ "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", "license": "MIT" }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@polka/url": { "version": "1.0.0-next.29", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", @@ -2515,6 +2465,36 @@ } } }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", + "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-collection": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", @@ -2745,6 +2725,29 @@ } } }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", + "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-menu": { "version": "2.1.16", "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", @@ -2925,6 +2928,38 @@ } } }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", + "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", @@ -3040,6 +3075,35 @@ } } }, + "node_modules/@radix-ui/react-switch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", + "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-tabs": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", @@ -3735,6 +3799,258 @@ "integrity": "sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==", "license": "MIT" }, + "node_modules/@tailwindcss/node": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.2.tgz", + "integrity": "sha512-ZwFnxH+1z8Ehh8bNTMX3YFrYdzAv7JLY5X5X7XSFY+G9QGJVce/P9xb2mh+j5hKt8NceuHmdtllJvAHWKtsNrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "enhanced-resolve": "^5.18.1", + "jiti": "^2.4.2", + "lightningcss": "1.29.2", + "tailwindcss": "4.1.2" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.2.tgz", + "integrity": "sha512-Zwz//1QKo6+KqnCKMT7lA4bspGfwEgcPAHlSthmahtgrpKDfwRGk8PKQrW8Zg/ofCDIlg6EtjSTKSxxSufC+CQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.2", + "@tailwindcss/oxide-darwin-arm64": "4.1.2", + "@tailwindcss/oxide-darwin-x64": "4.1.2", + "@tailwindcss/oxide-freebsd-x64": "4.1.2", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.2", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.2", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.2", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.2", + "@tailwindcss/oxide-linux-x64-musl": "4.1.2", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.2", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.2" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.2.tgz", + "integrity": "sha512-IxkXbntHX8lwGmwURUj4xTr6nezHhLYqeiJeqa179eihGv99pRlKV1W69WByPJDQgSf4qfmwx904H6MkQqTA8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.2.tgz", + "integrity": "sha512-ZRtiHSnFYHb4jHKIdzxlFm6EDfijTCOT4qwUhJ3GWxfDoW2yT3z/y8xg0nE7e72unsmSj6dtfZ9Y5r75FIrlpA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.2.tgz", + "integrity": "sha512-BiKUNZf1A0pBNzndBvnPnBxonCY49mgbOsPfILhcCE5RM7pQlRoOgN7QnwNhY284bDbfQSEOWnFR0zbPo6IDTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.2.tgz", + "integrity": "sha512-Z30VcpUfRGkiddj4l5NRCpzbSGjhmmklVoqkVQdkEC0MOelpY+fJrVhzSaXHmWrmSvnX8yiaEqAbdDScjVujYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.2.tgz", + "integrity": "sha512-w3wsK1ChOLeQ3gFOiwabtWU5e8fY3P1Ss8jR3IFIn/V0va3ir//hZ8AwURveS4oK1Pu6b8i+yxesT4qWnLVUow==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.2.tgz", + "integrity": "sha512-oY/u+xJHpndTj7B5XwtmXGk8mQ1KALMfhjWMMpE8pdVAznjJsF5KkCceJ4Fmn5lS1nHMCwZum5M3/KzdmwDMdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.2.tgz", + "integrity": "sha512-k7G6vcRK/D+JOWqnKzKN/yQq1q4dCkI49fMoLcfs2pVcaUAXEqCP9NmA8Jv+XahBv5DtDjSAY3HJbjosEdKczg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.2.tgz", + "integrity": "sha512-fLL+c678TkYKgkDLLNxSjPPK/SzTec7q/E5pTwvpTqrth867dftV4ezRyhPM5PaiCqX651Y8Yk0wRQMcWUGnmQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.2.tgz", + "integrity": "sha512-0tU1Vjd1WucZ2ooq6y4nI9xyTSaH2g338bhrqk+2yzkMHskBm+pMsOCfY7nEIvALkA1PKPOycR4YVdlV7Czo+A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.2.tgz", + "integrity": "sha512-r8QaMo3QKiHqUcn+vXYCypCEha+R0sfYxmaZSgZshx9NfkY+CHz91aS2xwNV/E4dmUDkTPUag7sSdiCHPzFVTg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.2.tgz", + "integrity": "sha512-lYCdkPxh9JRHXoBsPE8Pu/mppUsC2xihYArNAESub41PKhHTnvn6++5RpmFM+GLSt3ewyS8fwCVvht7ulWm6cw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.2.tgz", + "integrity": "sha512-vgkMo6QRhG6uv97im6Y4ExDdq71y9v2IGZc+0wn7lauQFYJM/1KdUVhrOkexbUso8tUsMOWALxyHVkQEbsM7gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.2", + "@tailwindcss/oxide": "4.1.2", + "postcss": "^8.4.41", + "tailwindcss": "4.1.2" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.2.tgz", + "integrity": "sha512-3r/ZdMW0gxY8uOx1To0lpYa4coq4CzINcCX4laM1rS340Kcn0ac4A/MMFfHN8qba51aorZMYwMcOxYk4wJ9FYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.2", + "@tailwindcss/oxide": "4.1.2", + "tailwindcss": "4.1.2" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6" + } + }, "node_modules/@tanstack/query-core": { "version": "5.87.0", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.87.0.tgz", @@ -4059,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", @@ -4665,34 +4988,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, - "license": "MIT" - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true, - "license": "MIT" - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -4856,19 +5151,6 @@ ], "license": "MIT" }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -5020,16 +5302,6 @@ "node": ">=6" } }, - "node_modules/camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/caniuse-lite": { "version": "1.0.30001720", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001720.tgz", @@ -5150,44 +5422,6 @@ "node": "*" } }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/classnames": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", @@ -5279,16 +5513,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/compute-scroll-into-view": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz", @@ -5351,19 +5575,6 @@ "dev": true, "license": "MIT" }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/cssstyle": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.3.1.tgz", @@ -5573,6 +5784,16 @@ "node": ">=6" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", @@ -5592,13 +5813,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -5632,13 +5846,6 @@ "node": ">=8" } }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true, - "license": "MIT" - }, "node_modules/dnd-core": { "version": "16.0.1", "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.1.tgz", @@ -5713,13 +5920,6 @@ "node": ">= 0.4" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, "node_modules/electron-to-chromium": { "version": "1.5.161", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.161.tgz", @@ -5727,12 +5927,19 @@ "dev": true, "license": "ISC" }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } }, "node_modules/entities": { "version": "6.0.0", @@ -6394,23 +6601,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/form-data": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", @@ -6687,6 +6877,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -7096,19 +7293,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/is-boolean-object": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", @@ -7139,22 +7323,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-date-object": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", @@ -7192,16 +7360,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -7499,30 +7657,14 @@ "node": ">=8" } }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, "node_modules/jiti": { - "version": "1.21.7", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", - "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, "license": "MIT", "bin": { - "jiti": "bin/jiti.js" + "jiti": "lib/jiti-cli.mjs" } }, "node_modules/js-tokens": { @@ -7692,25 +7834,244 @@ "url": "https://github.com/sponsors/dmonad" } }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "node_modules/lightningcss": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz", + "integrity": "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==", "dev": true, - "license": "MIT", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, "engines": { - "node": ">=14" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/antonk52" + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.29.2", + "lightningcss-darwin-x64": "1.29.2", + "lightningcss-freebsd-x64": "1.29.2", + "lightningcss-linux-arm-gnueabihf": "1.29.2", + "lightningcss-linux-arm64-gnu": "1.29.2", + "lightningcss-linux-arm64-musl": "1.29.2", + "lightningcss-linux-x64-gnu": "1.29.2", + "lightningcss-linux-x64-musl": "1.29.2", + "lightningcss-win32-arm64-msvc": "1.29.2", + "lightningcss-win32-x64-msvc": "1.29.2" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "node_modules/lightningcss-darwin-arm64": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.2.tgz", + "integrity": "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.2.tgz", + "integrity": "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.2.tgz", + "integrity": "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.2.tgz", + "integrity": "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.2.tgz", + "integrity": "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.2.tgz", + "integrity": "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.2.tgz", + "integrity": "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.2.tgz", + "integrity": "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.2.tgz", + "integrity": "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.2.tgz", + "integrity": "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, "node_modules/local-pkg": { "version": "0.5.1", @@ -8948,16 +9309,6 @@ "node": "*" } }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/mlly": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", @@ -9018,18 +9369,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, "node_modules/nanoid": { "version": "5.1.5", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", @@ -9068,16 +9407,6 @@ "dev": true, "license": "MIT" }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/normalize-range": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", @@ -9133,16 +9462,6 @@ "node": ">=0.10.0" } }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -9286,13 +9605,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -9374,37 +9686,6 @@ "node": ">=8" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -9452,26 +9733,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/pkg-types": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", @@ -9530,120 +9791,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-js": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", - "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.4.21" - } - }, - "node_modules/postcss-load-config": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", - "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "lilconfig": "^3.0.0", - "yaml": "^2.3.4" - }, - "engines": { - "node": ">= 14" - }, - "peerDependencies": { - "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/postcss-nested": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", - "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.1.1" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", @@ -9900,6 +10047,15 @@ "react": "^16.8.0 || ^17 || ^18 || ^19" } }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -10044,29 +10200,6 @@ } } }, - "node_modules/read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^2.3.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -10151,27 +10284,6 @@ "dev": true, "license": "MIT" }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -10597,76 +10709,6 @@ "integrity": "sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==", "license": "MIT" }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/stringify-entities": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", @@ -10694,20 +10736,6 @@ "node": ">=8" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-final-newline": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", @@ -10791,76 +10819,6 @@ "inline-style-parser": "0.2.4" } }, - "node_modules/sucrase": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", - "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "glob": "^10.3.10", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/sucrase/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/sucrase/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sucrase/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -10874,19 +10832,6 @@ "node": ">=8" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -10911,41 +10856,24 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.17", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", - "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.2.tgz", + "integrity": "sha512-VCsK+fitIbQF7JlxXaibFhxrPq4E2hDcG8apzHUdWFMCQWD8uLdlHg4iSkZ53cgLCCcZ+FZK7vG8VjvLcnBgKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "dev": true, "license": "MIT", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.6.0", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.3.2", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.21.6", - "lilconfig": "^3.1.3", - "micromatch": "^4.0.8", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.4.47", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.2", - "postcss-nested": "^6.2.0", - "postcss-selector-parser": "^6.1.2", - "resolve": "^1.22.8", - "sucrase": "^3.35.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, "engines": { - "node": ">=14.0.0" + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/test-exclude": { @@ -10970,29 +10898,6 @@ "dev": true, "license": "MIT" }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -11105,13 +11010,6 @@ "typescript": ">=4.2.0" } }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -11462,13 +11360,6 @@ } } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, "node_modules/uvu": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", @@ -11850,107 +11741,6 @@ "node": ">=0.10.0" } }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -12004,19 +11794,6 @@ "dev": true, "license": "ISC" }, - "node_modules/yaml": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, "node_modules/yjs": { "version": "13.6.27", "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.27.tgz", diff --git a/archon-ui-main/package.json b/archon-ui-main/package.json index 31c07574..576b78ae 100644 --- a/archon-ui-main/package.json +++ b/archon-ui-main/package.json @@ -30,10 +30,14 @@ "dependencies": { "@mdxeditor/editor": "^3.42.0", "@radix-ui/react-alert-dialog": "^1.1.15", + "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-radio-group": "^1.3.8", "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-toast": "^1.2.15", "@radix-ui/react-tooltip": "^1.2.8", @@ -50,6 +54,7 @@ "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.3.1", + "react-icons": "^5.5.0", "react-markdown": "^10.1.0", "react-router-dom": "^6.26.2", "tailwind-merge": "latest", @@ -57,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", @@ -74,7 +82,7 @@ "eslint-plugin-react-refresh": "^0.4.1", "jsdom": "^24.1.0", "postcss": "latest", - "tailwindcss": "3.4.17", + "tailwindcss": "4.1.2", "ts-node": "^10.9.1", "typescript": "^5.5.4", "vite": "^5.2.0", diff --git a/archon-ui-main/postcss.config.js b/archon-ui-main/postcss.config.js index 2e7af2b7..1c878468 100644 --- a/archon-ui-main/postcss.config.js +++ b/archon-ui-main/postcss.config.js @@ -1,6 +1,6 @@ export default { plugins: { - tailwindcss: {}, + '@tailwindcss/postcss': {}, autoprefixer: {}, }, } diff --git a/archon-ui-main/src/App.tsx b/archon-ui-main/src/App.tsx index ea2539cc..36e0d375 100644 --- a/archon-ui-main/src/App.tsx +++ b/archon-ui-main/src/App.tsx @@ -13,6 +13,7 @@ import { ToastProvider } from './features/ui/components/ToastProvider'; import { SettingsProvider, useSettings } from './contexts/SettingsContext'; import { TooltipProvider } from './features/ui/primitives/tooltip'; import { ProjectPage } from './pages/ProjectPage'; +import StyleGuidePage from './pages/StyleGuidePage'; import { DisconnectScreenOverlay } from './components/DisconnectScreenOverlay'; import { ErrorBoundaryWithBugReport } from './components/bug-report/ErrorBoundaryWithBugReport'; import { MigrationBanner } from './components/ui/MigrationBanner'; @@ -21,14 +22,19 @@ import { useMigrationStatus } from './hooks/useMigrationStatus'; const AppRoutes = () => { - const { projectsEnabled } = useSettings(); - + const { projectsEnabled, styleGuideEnabled } = useSettings(); + return ( } /> } /> } /> } /> + {styleGuideEnabled ? ( + } /> + ) : ( + } /> + )} {projectsEnabled ? ( <> } /> diff --git a/archon-ui-main/src/components/layout/MainLayout.tsx b/archon-ui-main/src/components/layout/MainLayout.tsx index 73fcc1de..ffa31792 100644 --- a/archon-ui-main/src/components/layout/MainLayout.tsx +++ b/archon-ui-main/src/components/layout/MainLayout.tsx @@ -129,11 +129,12 @@ export function MainLayout({ children, className }: MainLayoutProps) { }, [isBackendError, backendError, showToast]); return ( -
+
{/* TEMPORARY: Show backend startup error using old component */} {backendStartupFailed && } - {/* Fixed full-page background grid that doesn't scroll */} + {/* Fixed full-page background - grid pattern on dark background */} +
{/* Floating Navigation */} @@ -143,7 +144,7 @@ export function MainLayout({ children, className }: MainLayoutProps) {
{/* Main Content Area - matches old layout exactly */} -
+
{children}
diff --git a/archon-ui-main/src/components/layout/Navigation.tsx b/archon-ui-main/src/components/layout/Navigation.tsx index e2f1e806..3547b5fb 100644 --- a/archon-ui-main/src/components/layout/Navigation.tsx +++ b/archon-ui-main/src/components/layout/Navigation.tsx @@ -1,4 +1,4 @@ -import { BookOpen, Settings } from "lucide-react"; +import { BookOpen, Palette, Settings } from "lucide-react"; import type React from "react"; import { Link, useLocation } from "react-router-dom"; // TEMPORARY: Use old SettingsContext until settings are migrated @@ -24,7 +24,7 @@ interface NavigationProps { */ export function Navigation({ className }: NavigationProps) { const location = useLocation(); - const { projectsEnabled } = useSettings(); + const { projectsEnabled, styleGuideEnabled } = useSettings(); // Navigation items configuration const navigationItems: NavigationItem[] = [ @@ -54,6 +54,12 @@ export function Navigation({ className }: NavigationProps) { label: "MCP Server", enabled: true, }, + { + path: "/style-guide", + icon: , + label: "Style Guide", + enabled: styleGuideEnabled, + }, { path: "/settings", icon: , @@ -62,6 +68,9 @@ export function Navigation({ className }: NavigationProps) { }, ]; + // Filter out disabled navigation items + const enabledNavigationItems = navigationItems.filter((item) => item.enabled); + const isProjectsActive = location.pathname.startsWith("/projects"); return ( @@ -125,15 +134,14 @@ export function Navigation({ className }: NavigationProps) { {/* Navigation Items */}
@@ -228,16 +265,39 @@ export const FeaturesSection = () => { )}
- } disabled={loading || !projectsSchemaValid} />
+ {/* Style Guide Toggle */} +
+
+

+ Style Guide +

+

+ Show UI style guide and components in navigation +

+
+
+ } + disabled={loading} + /> +
+
+ {/* COMMENTED OUT FOR FUTURE RELEASE - AG-UI Library Toggle */} {/*
@@ -283,10 +343,11 @@ export const FeaturesSection = () => {

- } disabled={loading} /> @@ -304,10 +365,11 @@ export const FeaturesSection = () => {

- } disabled={loading} /> diff --git a/archon-ui-main/src/components/settings/RAGSettings.tsx b/archon-ui-main/src/components/settings/RAGSettings.tsx index ccba61ce..62739fc7 100644 --- a/archon-ui-main/src/components/settings/RAGSettings.tsx +++ b/archon-ui-main/src/components/settings/RAGSettings.tsx @@ -1,9 +1,12 @@ -import React, { useState, useEffect, useRef } from 'react'; -import { Settings, Check, Save, Loader, ChevronDown, ChevronUp, Zap, Database, Trash2 } from 'lucide-react'; +import React, { useState, useEffect, useRef, useCallback } from 'react'; +import { Settings, Check, Save, Loader, ChevronDown, ChevronUp, Zap, Database, Trash2, Cog } from 'lucide-react'; import { Card } from '../ui/Card'; import { Input } from '../ui/Input'; import { Select } from '../ui/Select'; import { Button } from '../ui/Button'; +import { Button as GlowButton } from '../../features/ui/primitives/button'; +import { LuBrainCircuit } from 'react-icons/lu'; +import { PiDatabaseThin } from 'react-icons/pi'; import { useToast } from '../../features/shared/hooks/useToast'; import { credentialsService } from '../../services/credentialsService'; import OllamaModelDiscoveryModal from './OllamaModelDiscoveryModal'; @@ -11,6 +14,9 @@ import OllamaModelSelectionModal from './OllamaModelSelectionModal'; type ProviderKey = 'openai' | 'google' | 'ollama' | 'anthropic' | 'grok' | 'openrouter'; +// Providers that support embedding models +const EMBEDDING_CAPABLE_PROVIDERS: ProviderKey[] = ['openai', 'google', 'ollama']; + interface ProviderModels { chatModel: string; embeddingModel: string; @@ -85,27 +91,53 @@ const colorStyles: Record = { grok: 'border-yellow-500 bg-yellow-500/10', }; -const providerAlertStyles: Record = { - openai: 'bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800 text-green-800 dark:text-green-300', - google: 'bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800 text-blue-800 dark:text-blue-300', - openrouter: 'bg-cyan-50 dark:bg-cyan-900/20 border-cyan-200 dark:border-cyan-800 text-cyan-800 dark:text-cyan-300', - ollama: 'bg-purple-50 dark:bg-purple-900/20 border-purple-200 dark:border-purple-800 text-purple-800 dark:text-purple-300', - anthropic: 'bg-orange-50 dark:bg-orange-900/20 border-orange-200 dark:border-orange-800 text-orange-800 dark:text-orange-300', - grok: 'bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800 text-yellow-800 dark:text-yellow-300', -}; +const providerWarningAlertStyle = 'bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800 text-yellow-800 dark:text-yellow-300'; +const providerErrorAlertStyle = 'bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800 text-red-800 dark:text-red-300'; +const providerMissingAlertStyle = providerErrorAlertStyle; -const providerAlertMessages: Record = { - openai: 'Configure your OpenAI API key in the credentials section to use GPT models.', - google: 'Configure your Google API key in the credentials section to use Gemini models.', - openrouter: 'Configure your OpenRouter API key in the credentials section to use models.', - ollama: 'Configure your Ollama instances in this panel to connect local models.', - anthropic: 'Configure your Anthropic API key in the credentials section to use Claude models.', - grok: 'Configure your Grok API key in the credentials section to use Grok models.', +const providerDisplayNames: Record = { + openai: 'OpenAI', + google: 'Google', + openrouter: 'OpenRouter', + ollama: 'Ollama', + anthropic: 'Anthropic', + grok: 'Grok', }; const isProviderKey = (value: unknown): value is ProviderKey => typeof value === 'string' && ['openai', 'google', 'openrouter', 'ollama', 'anthropic', 'grok'].includes(value); +// Default base URL for Ollama instances when not explicitly configured +const DEFAULT_OLLAMA_URL = 'http://host.docker.internal:11434/v1'; + +const PROVIDER_CREDENTIAL_KEYS = [ + 'OPENAI_API_KEY', + 'GOOGLE_API_KEY', + 'ANTHROPIC_API_KEY', + 'OPENROUTER_API_KEY', + 'GROK_API_KEY', +] as const; + +type ProviderCredentialKey = typeof PROVIDER_CREDENTIAL_KEYS[number]; + +const CREDENTIAL_PROVIDER_MAP: Record = { + OPENAI_API_KEY: 'openai', + GOOGLE_API_KEY: 'google', + ANTHROPIC_API_KEY: 'anthropic', + OPENROUTER_API_KEY: 'openrouter', + GROK_API_KEY: 'grok', +}; + +const normalizeBaseUrl = (url?: string | null): string | null => { + if (!url) return null; + const trimmed = url.trim(); + if (!trimmed) return null; + + let normalized = trimmed.replace(/\/+$/, ''); + normalized = normalized.replace(/\/v1$/i, ''); + return normalized || null; +}; + interface RAGSettingsProps { ragSettings: { MODEL_CHOICE: string; @@ -118,6 +150,7 @@ interface RAGSettingsProps { LLM_BASE_URL?: string; LLM_INSTANCE_NAME?: string; EMBEDDING_MODEL?: string; + EMBEDDING_PROVIDER?: string; OLLAMA_EMBEDDING_URL?: string; OLLAMA_EMBEDDING_INSTANCE_NAME?: string; // Crawling Performance Settings @@ -148,6 +181,7 @@ export const RAGSettings = ({ const [showCrawlingSettings, setShowCrawlingSettings] = useState(false); const [showStorageSettings, setShowStorageSettings] = useState(false); const [showModelDiscoveryModal, setShowModelDiscoveryModal] = useState(false); + const [showOllamaConfig, setShowOllamaConfig] = useState(false); // Edit modals state const [showEditLLMModal, setShowEditLLMModal] = useState(false); @@ -160,6 +194,16 @@ export const RAGSettings = ({ // Provider-specific model persistence state const [providerModels, setProviderModels] = useState(() => loadProviderModels()); + // Independent provider selection state + const [chatProvider, setChatProvider] = useState(() => + (ragSettings.LLM_PROVIDER as ProviderKey) || 'openai' + ); + const [embeddingProvider, setEmbeddingProvider] = useState(() => + // Default to openai if no specific embedding provider is set + (ragSettings.EMBEDDING_PROVIDER as ProviderKey) || 'openai' + ); + const [activeSelection, setActiveSelection] = useState<'chat' | 'embedding'>('chat'); + // Instance configurations const [llmInstanceConfig, setLLMInstanceConfig] = useState({ name: '', @@ -215,16 +259,32 @@ export const RAGSettings = ({ } }, [ragSettings.OLLAMA_EMBEDDING_URL, ragSettings.OLLAMA_EMBEDDING_INSTANCE_NAME]); - // Provider model persistence effects + // Provider model persistence effects - separate for chat and embedding useEffect(() => { - // Update provider models when current models change - const currentProvider = ragSettings.LLM_PROVIDER as ProviderKey; - if (currentProvider && ragSettings.MODEL_CHOICE && ragSettings.EMBEDDING_MODEL) { + // Update chat provider models when chat model changes + if (chatProvider && ragSettings.MODEL_CHOICE) { setProviderModels(prev => { const updated = { ...prev, - [currentProvider]: { - chatModel: ragSettings.MODEL_CHOICE, + [chatProvider]: { + ...prev[chatProvider], + chatModel: ragSettings.MODEL_CHOICE + } + }; + saveProviderModels(updated); + return updated; + }); + } + }, [ragSettings.MODEL_CHOICE, chatProvider]); + + useEffect(() => { + // Update embedding provider models when embedding model changes + if (embeddingProvider && ragSettings.EMBEDDING_MODEL) { + setProviderModels(prev => { + const updated = { + ...prev, + [embeddingProvider]: { + ...prev[embeddingProvider], embeddingModel: ragSettings.EMBEDDING_MODEL } }; @@ -232,93 +292,181 @@ export const RAGSettings = ({ return updated; }); } - }, [ragSettings.MODEL_CHOICE, ragSettings.EMBEDDING_MODEL, ragSettings.LLM_PROVIDER]); + }, [ragSettings.EMBEDDING_MODEL, embeddingProvider]); - // Load API credentials for status checking - useEffect(() => { - const loadApiCredentials = async () => { - try { - // Get decrypted values for the API keys we need for status checking - const keyNames = ['OPENAI_API_KEY', 'GOOGLE_API_KEY', 'ANTHROPIC_API_KEY']; - const statusResults = await credentialsService.checkCredentialStatus(keyNames); - - const credentials: {[key: string]: string} = {}; - - for (const [key, result] of Object.entries(statusResults)) { - if (result.has_value && result.value && result.value.trim().length > 0) { - credentials[key] = result.value; - } - } - - console.log('🔑 Loaded API credentials for status checking:', Object.keys(credentials)); - setApiCredentials(credentials); - } catch (error) { - console.error('Failed to load API credentials for status checking:', error); - } - }; - - loadApiCredentials(); - }, []); - - // Reload API credentials when ragSettings change (e.g., after saving) - // Use a ref to track if we've loaded credentials to prevent infinite loops const hasLoadedCredentialsRef = useRef(false); - - // Manual reload function for external calls - const reloadApiCredentials = async () => { + + const reloadApiCredentials = useCallback(async () => { try { - // Get decrypted values for the API keys we need for status checking - const keyNames = ['OPENAI_API_KEY', 'GOOGLE_API_KEY', 'ANTHROPIC_API_KEY']; - const statusResults = await credentialsService.checkCredentialStatus(keyNames); - - const credentials: {[key: string]: string} = {}; - - for (const [key, result] of Object.entries(statusResults)) { - if (result.has_value && result.value && result.value.trim().length > 0) { - credentials[key] = result.value; - } + const statusResults = await credentialsService.checkCredentialStatus( + Array.from(PROVIDER_CREDENTIAL_KEYS), + ); + + const credentials: { [key: string]: boolean } = {}; + + for (const key of PROVIDER_CREDENTIAL_KEYS) { + const result = statusResults[key]; + credentials[key] = !!result?.has_value; } - - console.log('🔄 Reloaded API credentials for status checking:', Object.keys(credentials)); + + console.log( + '🔑 Updated API credential status snapshot:', + Object.keys(credentials), + ); setApiCredentials(credentials); hasLoadedCredentialsRef.current = true; } catch (error) { - console.error('Failed to reload API credentials:', error); + console.error('Failed to load API credentials for status checking:', error); } - }; - + }, []); + useEffect(() => { - // Only reload if we have ragSettings and haven't loaded yet, or if LLM_PROVIDER changed - if (Object.keys(ragSettings).length > 0 && (!hasLoadedCredentialsRef.current || ragSettings.LLM_PROVIDER)) { - reloadApiCredentials(); + void reloadApiCredentials(); + }, [reloadApiCredentials]); + + useEffect(() => { + if (!hasLoadedCredentialsRef.current) { + return; } - }, [ragSettings.LLM_PROVIDER]); // Only depend on LLM_PROVIDER changes - - // Reload credentials periodically to catch updates from other components (like onboarding) + + void reloadApiCredentials(); + }, [ragSettings.LLM_PROVIDER, reloadApiCredentials]); + useEffect(() => { - // Set up periodic reload every 30 seconds when component is active (reduced from 2s) const interval = setInterval(() => { if (Object.keys(ragSettings).length > 0) { - reloadApiCredentials(); + void reloadApiCredentials(); } - }, 30000); // Changed from 2000ms to 30000ms (30 seconds) + }, 30000); return () => clearInterval(interval); - }, [ragSettings.LLM_PROVIDER]); // Only restart interval if provider changes - + }, [ragSettings.LLM_PROVIDER, reloadApiCredentials]); + + useEffect(() => { + const needsDetection = chatProvider === 'ollama' || embeddingProvider === 'ollama'; + + if (!needsDetection) { + setOllamaServerStatus('unknown'); + return; + } + + const baseUrl = ( + ragSettings.LLM_BASE_URL?.trim() || + llmInstanceConfig.url?.trim() || + ragSettings.OLLAMA_EMBEDDING_URL?.trim() || + embeddingInstanceConfig.url?.trim() || + DEFAULT_OLLAMA_URL + ); + + const normalizedUrl = baseUrl.replace('/v1', '').replace(/\/$/, ''); + + let cancelled = false; + + (async () => { + try { + const response = await fetch( + `/api/ollama/instances/health?instance_urls=${encodeURIComponent(normalizedUrl)}`, + { method: 'GET', headers: { Accept: 'application/json' }, signal: AbortSignal.timeout(10000) } + ); + + if (cancelled) return; + + if (!response.ok) { + setOllamaServerStatus('offline'); + return; + } + + const data = await response.json(); + const instanceStatus = data.instance_status?.[normalizedUrl]; + setOllamaServerStatus(instanceStatus?.is_healthy ? 'online' : 'offline'); + } catch (error) { + if (!cancelled) { + setOllamaServerStatus('offline'); + } + } + })(); + + return () => { + cancelled = true; + }; + }, [chatProvider, embeddingProvider, ragSettings.LLM_BASE_URL, ragSettings.OLLAMA_EMBEDDING_URL, llmInstanceConfig.url, embeddingInstanceConfig.url]); + + // Sync independent provider states with ragSettings (one-way: ragSettings -> local state) + useEffect(() => { + if (ragSettings.LLM_PROVIDER && ragSettings.LLM_PROVIDER !== chatProvider) { + setChatProvider(ragSettings.LLM_PROVIDER as ProviderKey); + } + }, [ragSettings.LLM_PROVIDER]); // Remove chatProvider dependency to avoid loops + + useEffect(() => { + if (ragSettings.EMBEDDING_PROVIDER && ragSettings.EMBEDDING_PROVIDER !== embeddingProvider) { + setEmbeddingProvider(ragSettings.EMBEDDING_PROVIDER as ProviderKey); + } + }, [ragSettings.EMBEDDING_PROVIDER]); // Remove embeddingProvider dependency to avoid loops + + useEffect(() => { + setOllamaManualConfirmed(false); + setOllamaServerStatus('unknown'); + }, [ragSettings.LLM_BASE_URL, ragSettings.OLLAMA_EMBEDDING_URL, chatProvider, embeddingProvider]); + + // Update ragSettings when independent providers change (one-way: local state -> ragSettings) + // Split the “first‐run” guard into two refs so chat and embedding effects don’t interfere. + const updateChatRagSettingsRef = useRef(true); + const updateEmbeddingRagSettingsRef = useRef(true); + + useEffect(() => { + // Only update if this is a user‐initiated change, not a sync from ragSettings + if (updateChatRagSettingsRef.current && chatProvider !== ragSettings.LLM_PROVIDER) { + setRagSettings(prev => ({ + ...prev, + LLM_PROVIDER: chatProvider + })); + } + updateChatRagSettingsRef.current = true; + }, [chatProvider]); + + useEffect(() => { + // Only update if this is a user‐initiated change, not a sync from ragSettings + if (updateEmbeddingRagSettingsRef.current && embeddingProvider && embeddingProvider !== ragSettings.EMBEDDING_PROVIDER) { + setRagSettings(prev => ({ + ...prev, + EMBEDDING_PROVIDER: embeddingProvider + })); + } + updateEmbeddingRagSettingsRef.current = true; + }, [embeddingProvider]); + + // Status tracking const [llmStatus, setLLMStatus] = useState({ online: false, responseTime: null, checking: false }); const [embeddingStatus, setEmbeddingStatus] = useState({ online: false, responseTime: null, checking: false }); + const llmRetryTimeoutRef = useRef(null); + const embeddingRetryTimeoutRef = useRef(null); // API key credentials for status checking - const [apiCredentials, setApiCredentials] = useState<{[key: string]: string}>({}); + const [apiCredentials, setApiCredentials] = useState<{[key: string]: boolean}>({}); // Provider connection status tracking const [providerConnectionStatus, setProviderConnectionStatus] = useState<{ [key: string]: { connected: boolean; checking: boolean; lastChecked?: Date } }>({}); + const [ollamaServerStatus, setOllamaServerStatus] = useState<'unknown' | 'online' | 'offline'>('unknown'); + const [ollamaManualConfirmed, setOllamaManualConfirmed] = useState(false); + + useEffect(() => { + return () => { + if (llmRetryTimeoutRef.current) { + clearTimeout(llmRetryTimeoutRef.current); + llmRetryTimeoutRef.current = null; + } + if (embeddingRetryTimeoutRef.current) { + clearTimeout(embeddingRetryTimeoutRef.current); + embeddingRetryTimeoutRef.current = null; + } + }; + }, []); // Test connection to external providers - const testProviderConnection = async (provider: string): Promise => { + const testProviderConnection = useCallback(async (provider: string): Promise => { setProviderConnectionStatus(prev => ({ ...prev, [provider]: { ...prev[provider], checking: true } @@ -345,7 +493,7 @@ export const RAGSettings = ({ })); return false; } - }; + }, []); // Test provider connections when API credentials change useEffect(() => { @@ -371,7 +519,39 @@ export const RAGSettings = ({ const interval = setInterval(testConnections, 60000); return () => clearInterval(interval); - }, [apiCredentials]); // Test when credentials change + }, [apiCredentials, testProviderConnection]); // Test when credentials change + + useEffect(() => { + const handleCredentialUpdate = (event: Event) => { + const detail = (event as CustomEvent<{ keys?: string[] }>).detail; + const updatedKeys = (detail?.keys ?? []).map(key => key.toUpperCase()); + + if (updatedKeys.length === 0) { + void reloadApiCredentials(); + return; + } + + const touchedProviderKeys = updatedKeys.filter(key => key in CREDENTIAL_PROVIDER_MAP); + if (touchedProviderKeys.length === 0) { + return; + } + + void reloadApiCredentials(); + + touchedProviderKeys.forEach(key => { + const provider = CREDENTIAL_PROVIDER_MAP[key as ProviderCredentialKey]; + if (provider) { + void testProviderConnection(provider); + } + }); + }; + + window.addEventListener('archon:credentials-updated', handleCredentialUpdate); + + return () => { + window.removeEventListener('archon:credentials-updated', handleCredentialUpdate); + }; + }, [reloadApiCredentials, testProviderConnection]); // Ref to track if initial test has been run (will be used after function definitions) const hasRunInitialTestRef = useRef(false); @@ -444,7 +624,14 @@ export const RAGSettings = ({ }; // Manual test function with user feedback using backend proxy - const manualTestConnection = async (url: string, setStatus: React.Dispatch>, instanceName: string) => { +const manualTestConnection = async ( + url: string, + setStatus: React.Dispatch>, + instanceName: string, + context?: 'chat' | 'embedding', + options?: { suppressToast?: boolean } + ): Promise => { + const suppressToast = options?.suppressToast ?? false; setStatus(prev => ({ ...prev, checking: true })); const startTime = Date.now(); @@ -471,31 +658,58 @@ export const RAGSettings = ({ if (instanceStatus?.is_healthy) { const responseTime = Math.round(instanceStatus.response_time_ms || (Date.now() - startTime)); setStatus({ online: true, responseTime, checking: false }); - showToast(`${instanceName} connection successful: ${instanceStatus.models_available || 0} models available (${responseTime}ms)`, 'success'); - + + // Context-aware model count display + let modelCount = instanceStatus.models_available || 0; + let modelType = 'models'; + + if (context === 'chat') { + modelCount = ollamaMetrics.llmInstanceModels?.chat || 0; + modelType = 'chat models'; + } else if (context === 'embedding') { + modelCount = ollamaMetrics.embeddingInstanceModels?.embedding || 0; + modelType = 'embedding models'; + } + + if (!suppressToast) { + showToast(`${instanceName} connection successful: ${modelCount} ${modelType} available (${responseTime}ms)`, 'success'); + } + // Scenario 2: Manual "Test Connection" button - refresh Ollama metrics if Ollama provider is selected - if (ragSettings.LLM_PROVIDER === 'ollama') { + if (ragSettings.LLM_PROVIDER === 'ollama' || embeddingProvider === 'ollama' || context === 'embedding') { console.log('🔄 Fetching Ollama metrics - Test Connection button clicked'); fetchOllamaMetrics(); } + + return true; } else { setStatus({ online: false, responseTime: null, checking: false }); - showToast(`${instanceName} connection failed: ${instanceStatus?.error_message || 'Instance is not healthy'}`, 'error'); + if (!suppressToast) { + showToast(`${instanceName} connection failed: ${instanceStatus?.error_message || 'Instance is not healthy'}`, 'error'); + } + return false; } } else { setStatus({ online: false, responseTime: null, checking: false }); - showToast(`${instanceName} connection failed: Backend proxy error (HTTP ${response.status})`, 'error'); + if (!suppressToast) { + showToast(`${instanceName} connection failed: Backend proxy error (HTTP ${response.status})`, 'error'); + } + return false; } } catch (error: any) { setStatus({ online: false, responseTime: null, checking: false }); - - if (error.name === 'AbortError') { - showToast(`${instanceName} connection failed: Request timeout (>15s)`, 'error'); - } else { - showToast(`${instanceName} connection failed: ${error.message || 'Unknown error'}`, 'error'); + + if (!suppressToast) { + if (error.name === 'AbortError') { + showToast(`${instanceName} connection failed: Request timeout (>15s)`, 'error'); + } else { + showToast(`${instanceName} connection failed: ${error.message || 'Unknown error'}`, 'error'); + } } + + return false; } - };; + }; // Function to handle LLM instance deletion const handleDeleteLLMInstance = () => { @@ -546,11 +760,14 @@ export const RAGSettings = ({ try { setOllamaMetrics(prev => ({ ...prev, loading: true })); - // Prepare instance URLs for the API call - const instanceUrls = []; - if (llmInstanceConfig.url) instanceUrls.push(llmInstanceConfig.url); - if (embeddingInstanceConfig.url && embeddingInstanceConfig.url !== llmInstanceConfig.url) { - instanceUrls.push(embeddingInstanceConfig.url); + // Prepare normalized instance URLs for the API call + const instanceUrls: string[] = []; + const llmUrlBase = normalizeBaseUrl(llmInstanceConfig.url); + const embUrlBase = normalizeBaseUrl(embeddingInstanceConfig.url); + + if (llmUrlBase) instanceUrls.push(llmUrlBase); + if (embUrlBase && embUrlBase !== llmUrlBase) { + instanceUrls.push(embUrlBase); } if (instanceUrls.length === 0) { @@ -574,18 +791,18 @@ export const RAGSettings = ({ // Count models for LLM instance const llmChatModels = allChatModels.filter((model: any) => - model.instance_url === llmInstanceConfig.url + normalizeBaseUrl(model.instance_url) === llmUrlBase ); const llmEmbeddingModels = allEmbeddingModels.filter((model: any) => - model.instance_url === llmInstanceConfig.url + normalizeBaseUrl(model.instance_url) === llmUrlBase ); - + // Count models for Embedding instance const embChatModels = allChatModels.filter((model: any) => - model.instance_url === embeddingInstanceConfig.url + normalizeBaseUrl(model.instance_url) === embUrlBase ); const embEmbeddingModels = allEmbeddingModels.filter((model: any) => - model.instance_url === embeddingInstanceConfig.url + normalizeBaseUrl(model.instance_url) === embUrlBase ); // Calculate totals @@ -624,7 +841,7 @@ export const RAGSettings = ({ // Use refs to prevent infinite connection testing const lastTestedLLMConfigRef = useRef({ url: '', name: '', provider: '' }); const lastTestedEmbeddingConfigRef = useRef({ url: '', name: '', provider: '' }); - const lastMetricsFetchRef = useRef({ provider: '', llmUrl: '', embUrl: '', llmOnline: false, embOnline: false }); + const lastMetricsFetchRef = useRef({ provider: '', embProvider: '', llmUrl: '', embUrl: '', llmOnline: false, embOnline: false }); // Auto-testing disabled to prevent API calls on every keystroke per user request // Connection testing should only happen on manual "Test Connection" or "Save Changes" button clicks @@ -672,91 +889,106 @@ export const RAGSettings = ({ // } // }, [embeddingInstanceConfig.url, embeddingInstanceConfig.name, ragSettings.LLM_PROVIDER]); - // Fetch Ollama metrics only when Ollama provider is initially selected (not on URL changes during typing) React.useEffect(() => { - if (ragSettings.LLM_PROVIDER === 'ollama') { - const currentProvider = ragSettings.LLM_PROVIDER; - const lastProvider = lastMetricsFetchRef.current.provider; - - // Only fetch if provider changed to Ollama (scenario 1: user clicks on Ollama Provider) - if (currentProvider !== lastProvider) { - lastMetricsFetchRef.current = { - provider: currentProvider, - llmUrl: llmInstanceConfig.url, - embUrl: embeddingInstanceConfig.url, - llmOnline: llmStatus.online, - embOnline: embeddingStatus.online - }; - console.log('🔄 Fetching Ollama metrics - Provider selected'); - fetchOllamaMetrics(); - } + const current = { + provider: ragSettings.LLM_PROVIDER, + embProvider: embeddingProvider, + llmUrl: normalizeBaseUrl(llmInstanceConfig.url) ?? '', + embUrl: normalizeBaseUrl(embeddingInstanceConfig.url) ?? '', + llmOnline: llmStatus.online, + embOnline: embeddingStatus.online, + }; + const last = lastMetricsFetchRef.current; + + const meaningfulChange = + current.provider !== last.provider || + current.embProvider !== last.embProvider || + current.llmUrl !== last.llmUrl || + current.embUrl !== last.embUrl || + current.llmOnline !== last.llmOnline || + current.embOnline !== last.embOnline; + + if ((current.provider === 'ollama' || current.embProvider === 'ollama') && meaningfulChange) { + lastMetricsFetchRef.current = current; + console.log('🔄 Fetching Ollama metrics - state changed'); + fetchOllamaMetrics(); } - }, [ragSettings.LLM_PROVIDER]); // Only watch provider changes, not URL changes + }, [ragSettings.LLM_PROVIDER, embeddingProvider, llmStatus.online, embeddingStatus.online]); + + const hasApiCredential = (credentialKey: ProviderCredentialKey): boolean => { + if (credentialKey in apiCredentials) { + return Boolean(apiCredentials[credentialKey]); + } + + const fallbackKey = Object.keys(apiCredentials).find( + key => key.toUpperCase() === credentialKey, + ); + + return fallbackKey ? Boolean(apiCredentials[fallbackKey]) : false; + }; // Function to check if a provider is properly configured const getProviderStatus = (providerKey: string): 'configured' | 'missing' | 'partial' => { switch (providerKey) { case 'openai': - // Check if OpenAI API key is configured (case insensitive) - const openAIKey = Object.keys(apiCredentials).find(key => key.toUpperCase() === 'OPENAI_API_KEY'); - const keyValue = openAIKey ? apiCredentials[openAIKey] : undefined; - // Don't consider encrypted placeholders as valid API keys for connection testing - const hasOpenAIKey = openAIKey && keyValue && keyValue.trim().length > 0 && !keyValue.includes('[ENCRYPTED]'); - + const hasOpenAIKey = hasApiCredential('OPENAI_API_KEY'); + // Only show configured if we have both API key AND confirmed connection const openAIConnected = providerConnectionStatus['openai']?.connected || false; const isChecking = providerConnectionStatus['openai']?.checking || false; - - console.log('🔍 OpenAI status check:', { - openAIKey, - keyValue: keyValue ? `${keyValue.substring(0, 10)}...` : keyValue, - hasValue: !!keyValue, - hasOpenAIKey, - openAIConnected, - isChecking, - allCredentials: Object.keys(apiCredentials) - }); - + + // Intentionally avoid logging API key material. + if (!hasOpenAIKey) return 'missing'; if (isChecking) return 'partial'; return openAIConnected ? 'configured' : 'missing'; case 'google': - // Check if Google API key is configured (case insensitive) - const googleKey = Object.keys(apiCredentials).find(key => key.toUpperCase() === 'GOOGLE_API_KEY'); - const googleKeyValue = googleKey ? apiCredentials[googleKey] : undefined; - // Don't consider encrypted placeholders as valid API keys for connection testing - const hasGoogleKey = googleKey && googleKeyValue && googleKeyValue.trim().length > 0 && !googleKeyValue.includes('[ENCRYPTED]'); + const hasGoogleKey = hasApiCredential('GOOGLE_API_KEY'); // Only show configured if we have both API key AND confirmed connection const googleConnected = providerConnectionStatus['google']?.connected || false; const googleChecking = providerConnectionStatus['google']?.checking || false; - + if (!hasGoogleKey) return 'missing'; if (googleChecking) return 'partial'; return googleConnected ? 'configured' : 'missing'; case 'ollama': - // Check if both LLM and embedding instances are configured and online - if (llmStatus.online && embeddingStatus.online) return 'configured'; - if (llmStatus.online || embeddingStatus.online) return 'partial'; - return 'missing'; + { + if (ollamaManualConfirmed || llmStatus.online || embeddingStatus.online) { + return 'configured'; + } + + if (ollamaServerStatus === 'online') { + return 'partial'; + } + + if (ollamaServerStatus === 'offline') { + return 'missing'; + } + + return 'missing'; + } case 'anthropic': - // Use server-side connection status + const hasAnthropicKey = hasApiCredential('ANTHROPIC_API_KEY'); const anthropicConnected = providerConnectionStatus['anthropic']?.connected || false; const anthropicChecking = providerConnectionStatus['anthropic']?.checking || false; + if (!hasAnthropicKey) return 'missing'; if (anthropicChecking) return 'partial'; return anthropicConnected ? 'configured' : 'missing'; case 'grok': - // Use server-side connection status + const hasGrokKey = hasApiCredential('GROK_API_KEY'); const grokConnected = providerConnectionStatus['grok']?.connected || false; const grokChecking = providerConnectionStatus['grok']?.checking || false; + if (!hasGrokKey) return 'missing'; if (grokChecking) return 'partial'; return grokConnected ? 'configured' : 'missing'; case 'openrouter': - // Use server-side connection status + const hasOpenRouterKey = hasApiCredential('OPENROUTER_API_KEY'); const openRouterConnected = providerConnectionStatus['openrouter']?.connected || false; const openRouterChecking = providerConnectionStatus['openrouter']?.checking || false; + if (!hasOpenRouterKey) return 'missing'; if (openRouterChecking) return 'partial'; return openRouterConnected ? 'configured' : 'missing'; default: @@ -764,20 +996,147 @@ export const RAGSettings = ({ } }; - const selectedProviderKey = isProviderKey(ragSettings.LLM_PROVIDER) - ? (ragSettings.LLM_PROVIDER as ProviderKey) + const resolvedProviderForAlert = activeSelection === 'chat' ? chatProvider : embeddingProvider; + const activeProviderKey = isProviderKey(resolvedProviderForAlert) + ? (resolvedProviderForAlert as ProviderKey) : undefined; - const selectedProviderStatus = selectedProviderKey ? getProviderStatus(selectedProviderKey) : undefined; - const shouldShowProviderAlert = Boolean( - selectedProviderKey && selectedProviderStatus === 'missing' - ); - const providerAlertClassName = shouldShowProviderAlert && selectedProviderKey - ? providerAlertStyles[selectedProviderKey] - : ''; - const providerAlertMessage = shouldShowProviderAlert && selectedProviderKey - ? providerAlertMessages[selectedProviderKey] - : ''; + const selectedProviderStatus = activeProviderKey ? getProviderStatus(activeProviderKey) : undefined; + + let providerAlertMessage: string | null = null; + let providerAlertClassName = ''; + + if (activeProviderKey === 'ollama') { + if (ollamaServerStatus === 'offline') { + providerAlertMessage = 'Local Ollama service is not running. Start the Ollama server and ensure it is reachable at the configured URL.'; + providerAlertClassName = providerErrorAlertStyle; + } else if (selectedProviderStatus === 'partial' && ollamaServerStatus === 'online') { + providerAlertMessage = 'Local Ollama service detected. Click "Test Connection" to confirm model availability.'; + providerAlertClassName = providerWarningAlertStyle; + } + } else if (activeProviderKey && selectedProviderStatus === 'missing') { + const providerName = providerDisplayNames[activeProviderKey] ?? activeProviderKey; + providerAlertMessage = `${providerName} API key is not configured. Add it in Settings > API Keys.`; + providerAlertClassName = providerMissingAlertStyle; + } + + const shouldShowProviderAlert = Boolean(providerAlertMessage); + useEffect(() => { + if (chatProvider !== 'ollama') { + if (llmRetryTimeoutRef.current) { + clearTimeout(llmRetryTimeoutRef.current); + llmRetryTimeoutRef.current = null; + } + return; + } + + const baseUrl = ( + ragSettings.LLM_BASE_URL?.trim() || + llmInstanceConfig.url?.trim() || + DEFAULT_OLLAMA_URL + ); + + if (!baseUrl) { + return; + } + + const instanceName = llmInstanceConfig.name?.trim().length + ? llmInstanceConfig.name + : 'LLM Instance'; + + let cancelled = false; + + const runTest = async () => { + if (cancelled) return; + + const success = await manualTestConnection( + baseUrl, + setLLMStatus, + instanceName, + 'chat', + { suppressToast: true } + ); + + if (!success && chatProvider === 'ollama' && !cancelled) { + llmRetryTimeoutRef.current = window.setTimeout(runTest, 5000); + } + }; + + if (llmRetryTimeoutRef.current) { + clearTimeout(llmRetryTimeoutRef.current); + llmRetryTimeoutRef.current = null; + } + + setLLMStatus(prev => ({ ...prev, checking: true })); + runTest(); + + return () => { + cancelled = true; + if (llmRetryTimeoutRef.current) { + clearTimeout(llmRetryTimeoutRef.current); + llmRetryTimeoutRef.current = null; + } + }; + }, [chatProvider, ragSettings.LLM_BASE_URL, ragSettings.LLM_INSTANCE_NAME, llmInstanceConfig.url, llmInstanceConfig.name]); + + useEffect(() => { + if (embeddingProvider !== 'ollama') { + if (embeddingRetryTimeoutRef.current) { + clearTimeout(embeddingRetryTimeoutRef.current); + embeddingRetryTimeoutRef.current = null; + } + return; + } + + const baseUrl = ( + ragSettings.OLLAMA_EMBEDDING_URL?.trim() || + embeddingInstanceConfig.url?.trim() || + DEFAULT_OLLAMA_URL + ); + + if (!baseUrl) { + return; + } + + const instanceName = embeddingInstanceConfig.name?.trim().length + ? embeddingInstanceConfig.name + : 'Embedding Instance'; + + let cancelled = false; + + const runTest = async () => { + if (cancelled) return; + + const success = await manualTestConnection( + baseUrl, + setEmbeddingStatus, + instanceName, + 'embedding', + { suppressToast: true } + ); + + if (!success && embeddingProvider === 'ollama' && !cancelled) { + embeddingRetryTimeoutRef.current = window.setTimeout(runTest, 5000); + } + }; + + if (embeddingRetryTimeoutRef.current) { + clearTimeout(embeddingRetryTimeoutRef.current); + embeddingRetryTimeoutRef.current = null; + } + + setEmbeddingStatus(prev => ({ ...prev, checking: true })); + runTest(); + + return () => { + cancelled = true; + if (embeddingRetryTimeoutRef.current) { + clearTimeout(embeddingRetryTimeoutRef.current); + embeddingRetryTimeoutRef.current = null; + } + }; + }, [embeddingProvider, ragSettings.OLLAMA_EMBEDDING_URL, ragSettings.OLLAMA_EMBEDDING_INSTANCE_NAME, embeddingInstanceConfig.url, embeddingInstanceConfig.name]); + // Test Ollama connectivity when Settings page loads (scenario 4: page load) // This useEffect is placed after function definitions to ensure access to manualTestConnection useEffect(() => { @@ -792,35 +1151,74 @@ export const RAGSettings = ({ }); // Only run once when data is properly loaded and not run before - if (!hasRunInitialTestRef.current && - ragSettings.LLM_PROVIDER === 'ollama' && - Object.keys(ragSettings).length > 0 && - (llmInstanceConfig.url || embeddingInstanceConfig.url)) { + if ( + !hasRunInitialTestRef.current && + (ragSettings.LLM_PROVIDER === 'ollama' || embeddingProvider === 'ollama') && + Object.keys(ragSettings).length > 0 + ) { hasRunInitialTestRef.current = true; console.log('🔄 Settings page loaded with Ollama - Testing connectivity'); - - // Test LLM instance if configured (use URL presence as the key indicator) - // Only test if URL is explicitly set in ragSettings, not just using the default - if (llmInstanceConfig.url && ragSettings.LLM_BASE_URL) { + + // Test LLM instance if a URL is available (either saved or default) + if (llmInstanceConfig.url) { setTimeout(() => { const instanceName = llmInstanceConfig.name || 'LLM Instance'; console.log('🔍 Testing LLM instance on page load:', instanceName, llmInstanceConfig.url); - manualTestConnection(llmInstanceConfig.url, setLLMStatus, instanceName); + manualTestConnection( + llmInstanceConfig.url, + setLLMStatus, + instanceName, + 'chat', + { suppressToast: true } + ); }, 1000); // Increased delay to ensure component is fully ready } - + // If no saved URL, run tests against default endpoint + else { + setTimeout(() => { + const defaultInstanceName = 'Local Ollama (Default)'; + console.log('🔍 Testing default Ollama chat instance on page load:', DEFAULT_OLLAMA_URL); + manualTestConnection( + DEFAULT_OLLAMA_URL, + setLLMStatus, + defaultInstanceName, + 'chat', + { suppressToast: true } + ); + }, 1000); + } + // Test Embedding instance if configured and different from LLM instance - // Only test if URL is explicitly set in ragSettings, not just using the default - if (embeddingInstanceConfig.url && ragSettings.OLLAMA_EMBEDDING_URL && + if (embeddingInstanceConfig.url && embeddingInstanceConfig.url !== llmInstanceConfig.url) { setTimeout(() => { const instanceName = embeddingInstanceConfig.name || 'Embedding Instance'; console.log('🔍 Testing Embedding instance on page load:', instanceName, embeddingInstanceConfig.url); - manualTestConnection(embeddingInstanceConfig.url, setEmbeddingStatus, instanceName); + manualTestConnection( + embeddingInstanceConfig.url, + setEmbeddingStatus, + instanceName, + 'embedding', + { suppressToast: true } + ); }, 1500); // Stagger the tests } - + // If embedding provider is also Ollama but no specific URL is set, test default as fallback + else if (embeddingProvider === 'ollama' && !embeddingInstanceConfig.url) { + setTimeout(() => { + const defaultEmbeddingName = 'Local Ollama (Default)'; + console.log('🔍 Testing default Ollama embedding instance on page load:', DEFAULT_OLLAMA_URL); + manualTestConnection( + DEFAULT_OLLAMA_URL, + setEmbeddingStatus, + defaultEmbeddingName, + 'embedding', + { suppressToast: true } + ); + }, 1500); + } + // Fetch Ollama metrics after testing connections setTimeout(() => { console.log('📊 Fetching Ollama metrics on page load'); @@ -838,12 +1236,63 @@ export const RAGSettings = ({ knowledge retrieval.

- {/* Provider Selection - 6 Button Layout */} + {/* LLM Provider Settings Header */} +
+

+ LLM Provider Settings +

+
+ + {/* Provider Selection Buttons */} +
+ setActiveSelection('chat')} + variant="ghost" + className={`min-w-[180px] px-5 py-3 font-semibold text-white dark:text-white + border border-emerald-400/70 dark:border-emerald-400/40 + bg-black/40 backdrop-blur-md + shadow-[inset_0_0_16px_rgba(15,118,110,0.38)] + hover:bg-emerald-500/12 dark:hover:bg-emerald-500/20 + hover:border-emerald-300/80 hover:shadow-[0_0_22px_rgba(16,185,129,0.5)] + ${(activeSelection === 'chat') + ? 'shadow-[0_0_25px_rgba(16,185,129,0.5)] ring-2 ring-emerald-400/50' + : 'shadow-[0_0_15px_rgba(16,185,129,0.25)]'} + `} + > + + + + setActiveSelection('embedding')} + variant="ghost" + className={`min-w-[180px] px-5 py-3 font-semibold text-white dark:text-white + border border-purple-400/70 dark:border-purple-400/40 + bg-black/40 backdrop-blur-md + shadow-[inset_0_0_16px_rgba(109,40,217,0.38)] + hover:bg-purple-500/12 dark:hover:bg-purple-500/20 + hover:border-purple-300/80 hover:shadow-[0_0_24px_rgba(168,85,247,0.52)] + ${(activeSelection === 'embedding') + ? 'shadow-[0_0_26px_rgba(168,85,247,0.55)] ring-2 ring-purple-400/60' + : 'shadow-[0_0_15px_rgba(168,85,247,0.25)]'} + `} + > + + + +
+ + {/* Context-Aware Provider Grid */}
-
+
{[ { key: 'openai', name: 'OpenAI', logo: '/img/OpenAI.png', color: 'green' }, { key: 'google', name: 'Google', logo: '/img/google-logo.svg', color: 'blue' }, @@ -851,39 +1300,50 @@ export const RAGSettings = ({ { key: 'ollama', name: 'Ollama', logo: '/img/Ollama.png', color: 'purple' }, { key: 'anthropic', name: 'Anthropic', logo: '/img/claude-logo.svg', color: 'orange' }, { key: 'grok', name: 'Grok', logo: '/img/Grok.png', color: 'yellow' } - ].map(provider => ( + ] + .filter(provider => + activeSelection === 'chat' || EMBEDDING_CAPABLE_PROVIDERS.includes(provider.key as ProviderKey) + ) + .map(provider => (
-{(() => { + {(() => { const status = getProviderStatus(provider.key); - const isSelected = ragSettings.LLM_PROVIDER === provider.key; - + if (status === 'configured') { return (
@@ -919,394 +1378,93 @@ export const RAGSettings = ({ ))}
- - {/* Provider-specific configuration */} - {ragSettings.LLM_PROVIDER === 'ollama' && ( -
-
-
-

Ollama Configuration

-

Configure separate Ollama instances for LLM and embedding models

-
-
- {(llmStatus.online && embeddingStatus.online) ? "2 / 2 Online" : - (llmStatus.online || embeddingStatus.online) ? "1 / 2 Online" : "0 / 2 Online"} -
-
- - {/* LLM Instance Card */} -
-
-
-

LLM Instance

-

For chat completions and text generation

-
-
- {llmStatus.checking ? ( - Checking... - ) : llmStatus.online ? ( - Online ({llmStatus.responseTime}ms) - ) : ( - Offline - )} - {llmInstanceConfig.name && llmInstanceConfig.url && ( - - )} -
-
- -
-
- {llmInstanceConfig.name && llmInstanceConfig.url ? ( - <> -
-
{llmInstanceConfig.name}
-
{llmInstanceConfig.url}
-
- -
-
Model:
-
{getDisplayedChatModel(ragSettings)}
-
- -
- {llmStatus.checking ? ( - - ) : null} - {ollamaMetrics.loading ? 'Loading...' : `${ollamaMetrics.llmInstanceModels.total} models available`} -
- - ) : ( -
-
No LLM instance configured
-
Configure an instance to use LLM features
- - {/* Quick setup for single host users */} - {!embeddingInstanceConfig.url && ( -
- -
Sets up both LLM and Embedding for one host
-
- )} - - -
- )} -
- - {llmInstanceConfig.name && llmInstanceConfig.url && ( -
- - - -
- )} -
-
- - {/* Embedding Instance Card */} -
-
-
-

Embedding Instance

-

For generating text embeddings and vector search

-
-
- {embeddingStatus.checking ? ( - Checking... - ) : embeddingStatus.online ? ( - Online ({embeddingStatus.responseTime}ms) - ) : ( - Offline - )} - {embeddingInstanceConfig.name && embeddingInstanceConfig.url && ( - - )} -
-
- -
-
- {embeddingInstanceConfig.name && embeddingInstanceConfig.url ? ( - <> -
-
{embeddingInstanceConfig.name}
-
{embeddingInstanceConfig.url}
-
- -
-
Model:
-
{getDisplayedEmbeddingModel(ragSettings)}
-
- -
- {embeddingStatus.checking ? ( - - ) : null} - {ollamaMetrics.loading ? 'Loading...' : `${ollamaMetrics.embeddingInstanceModels.total} models available`} -
- - ) : ( -
-
No Embedding instance configured
-
Configure an instance to use embedding features
- -
- )} -
- - {embeddingInstanceConfig.name && embeddingInstanceConfig.url && ( -
- - - -
- )} -
-
- - {/* Single Host Indicator */} - {llmInstanceConfig.url && embeddingInstanceConfig.url && - llmInstanceConfig.url === embeddingInstanceConfig.url && ( -
-
- - - - Single Host Setup -
-

- Both LLM and Embedding instances are using the same Ollama host ({llmInstanceConfig.name}) -

-
- )} - - {/* Configuration Summary */} -
-

Configuration Summary

- - {/* Instance Comparison Table */} -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ConfigurationLLM InstanceEmbedding Instance
Instance Name - {llmInstanceConfig.name || Not configured} - - {embeddingInstanceConfig.name || Not configured} -
Status - - {llmStatus.checking ? "Checking..." : llmStatus.online ? `Online (${llmStatus.responseTime}ms)` : "Offline"} - - - - {embeddingStatus.checking ? "Checking..." : embeddingStatus.online ? `Online (${embeddingStatus.responseTime}ms)` : "Offline"} - -
Selected Model - {getDisplayedChatModel(ragSettings) || No model selected} - - {getDisplayedEmbeddingModel(ragSettings) || No model selected} -
Available Models - {ollamaMetrics.loading ? ( - - ) : ( -
-
{ollamaMetrics.llmInstanceModels.total} Total Models
- {ollamaMetrics.llmInstanceModels.total > 0 && ( -
- - {ollamaMetrics.llmInstanceModels.chat} Chat - - - {ollamaMetrics.llmInstanceModels.embedding} Embedding - -
- )} -
- )} -
- {ollamaMetrics.loading ? ( - - ) : ( -
-
{ollamaMetrics.embeddingInstanceModels.total} Total Models
- {ollamaMetrics.embeddingInstanceModels.total > 0 && ( -
- - {ollamaMetrics.embeddingInstanceModels.chat} Chat - - - {ollamaMetrics.embeddingInstanceModels.embedding} Embedding - -
- )} -
- )} -
- - {/* System Readiness Summary */} -
-
- System Readiness: - - {(llmStatus.online && embeddingStatus.online) ? "✓ Ready (Both Instances Online)" : - (llmStatus.online || embeddingStatus.online) ? "⚠ Partial (1 of 2 Online)" : "✗ Not Ready (No Instances Online)"} - -
- - {/* Overall Model Metrics */} -
-
- - - - Overall Available: - - {ollamaMetrics.loading ? ( - - ) : ( - `${ollamaMetrics.totalModels} total (${ollamaMetrics.chatModels} chat, ${ollamaMetrics.embeddingModels} embedding)` - )} - -
-
-
-
-
-
- )} - {shouldShowProviderAlert && (

{providerAlertMessage}

)} -
- + )} + + {/* Save Settings Button */} +
+ + {/* Expandable Ollama Configuration Container */} + {showOllamaConfig && ((activeSelection === 'chat' && chatProvider === 'ollama') || + (activeSelection === 'embedding' && embeddingProvider === 'ollama')) && ( +
+
+
+

+ {activeSelection === 'chat' ? 'LLM Chat Configuration' : 'Embedding Configuration'} +

+

+ {activeSelection === 'chat' + ? 'Configure Ollama instance for chat completions' + : 'Configure Ollama instance for text embeddings'} +

+
+
+ {(activeSelection === 'chat' ? llmStatus.online : embeddingStatus.online) + ? "Online" : "Offline"} +
+
+ + {/* Configuration Content */} +
+ {activeSelection === 'chat' ? ( + // Chat Model Configuration +
+ {llmInstanceConfig.name && llmInstanceConfig.url ? ( + <> +
+
{llmInstanceConfig.name}
+
{llmInstanceConfig.url}
+
+ +
+
Model:
+
{getDisplayedChatModel(ragSettings)}
+
+ +
+ {llmStatus.checking ? ( + + ) : null} + {ollamaMetrics.loading ? 'Loading...' : `${ollamaMetrics.llmInstanceModels?.chat || 0} chat models available`} +
+ +
+ + + +
+ + ) : ( +
+
No LLM instance configured
+
Configure an instance to use LLM chat features
+ +
+ )} +
+ ) : ( + // Embedding Model Configuration +
+ {embeddingInstanceConfig.name && embeddingInstanceConfig.url ? ( + <> +
+
{embeddingInstanceConfig.name}
+
{embeddingInstanceConfig.url}
+
+ +
+
Model:
+
{getDisplayedEmbeddingModel(ragSettings)}
+
+ +
+ {embeddingStatus.checking ? ( + + ) : null} + {ollamaMetrics.loading ? 'Loading...' : `${ollamaMetrics.embeddingInstanceModels?.embedding || 0} embedding models available`} +
+ +
+ + + +
+ + ) : ( +
+
No Embedding instance configured
+
Configure an instance to use embedding features
+ +
+ )} +
+ )} +
+ + {/* Context-Aware Configuration Summary */} +
+

+ {activeSelection === 'chat' ? 'LLM Instance Summary' : 'Embedding Instance Summary'} +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Configuration + {activeSelection === 'chat' ? 'LLM Instance' : 'Embedding Instance'} +
Instance Name + {activeSelection === 'chat' + ? (llmInstanceConfig.name || Not configured) + : (embeddingInstanceConfig.name || Not configured) + } +
Instance URL + {activeSelection === 'chat' + ? (llmInstanceConfig.url || Not configured) + : (embeddingInstanceConfig.url || Not configured) + } +
Status + {activeSelection === 'chat' ? ( + + {llmStatus.checking ? "Checking..." : llmStatus.online ? `Online (${llmStatus.responseTime}ms)` : "Offline"} + + ) : ( + + {embeddingStatus.checking ? "Checking..." : embeddingStatus.online ? `Online (${embeddingStatus.responseTime}ms)` : "Offline"} + + )} +
Selected Model + {activeSelection === 'chat' + ? (getDisplayedChatModel(ragSettings) || No model selected) + : (getDisplayedEmbeddingModel(ragSettings) || No model selected) + } +
Available Models + {ollamaMetrics.loading ? ( + + ) : activeSelection === 'chat' ? ( +
+ {ollamaMetrics.llmInstanceModels?.chat || 0} + chat models +
+ ) : ( +
+ {ollamaMetrics.embeddingInstanceModels?.embedding || 0} + embedding models +
+ )} +
+ + {/* Instance-Specific Readiness */} +
+
+ + {activeSelection === 'chat' ? 'LLM Instance Status:' : 'Embedding Instance Status:'} + + + {activeSelection === 'chat' + ? (llmStatus.online ? "✓ Ready" : "✗ Not Ready") + : (embeddingStatus.online ? "✓ Ready" : "✗ Not Ready") + } + +
+ + {/* Instance-Specific Model Metrics */} +
+
+ + + + Available on this instance: + + {ollamaMetrics.loading ? ( + + ) : activeSelection === 'chat' ? ( + `${ollamaMetrics.llmInstanceModels?.chat || 0} chat models` + ) : ( + `${ollamaMetrics.embeddingInstanceModels?.embedding || 0} embedding models` + )} + +
+
+
+
+
+
+ )}
- {/* Model Settings Row - Only show for non-Ollama providers */} - {ragSettings.LLM_PROVIDER !== 'ollama' && ( -
-
- setRagSettings({ - ...ragSettings, - MODEL_CHOICE: e.target.value - })} - placeholder={getModelPlaceholder(ragSettings.LLM_PROVIDER || 'openai')} - accentColor="green" - /> -
-
- setRagSettings({ - ...ragSettings, - EMBEDDING_MODEL: e.target.value - })} - placeholder={getEmbeddingPlaceholder(ragSettings.LLM_PROVIDER || 'openai')} - accentColor="green" - /> -
-
- )} - + {/* Second row: Contextual Embeddings, Max Workers, and description */}
@@ -1778,7 +2210,16 @@ export const RAGSettings = ({ showToast('LLM instance updated successfully', 'success'); // Wait 1 second then automatically test connection and refresh models setTimeout(() => { - manualTestConnection(llmInstanceConfig.url, setLLMStatus, llmInstanceConfig.name); + manualTestConnection( + llmInstanceConfig.url, + setLLMStatus, + llmInstanceConfig.name, + 'chat', + { suppressToast: true } + ).then((success) => { + setOllamaManualConfirmed(success); + setOllamaServerStatus(success ? 'online' : 'offline'); + }); fetchOllamaMetrics(); // Refresh model metrics after saving }, 1000); }} @@ -1829,7 +2270,16 @@ export const RAGSettings = ({ showToast('Embedding instance updated successfully', 'success'); // Wait 1 second then automatically test connection and refresh models setTimeout(() => { - manualTestConnection(embeddingInstanceConfig.url, setEmbeddingStatus, embeddingInstanceConfig.name); + manualTestConnection( + embeddingInstanceConfig.url, + setEmbeddingStatus, + embeddingInstanceConfig.name, + 'embedding', + { suppressToast: true } + ).then((success) => { + setOllamaManualConfirmed(success); + setOllamaServerStatus(success ? 'online' : 'offline'); + }); fetchOllamaMetrics(); // Refresh model metrics after saving }, 1000); }} @@ -1854,7 +2304,7 @@ export const RAGSettings = ({ ]} currentModel={ragSettings.MODEL_CHOICE} modelType="chat" - selectedInstanceUrl={llmInstanceConfig.url.replace('/v1', '')} + selectedInstanceUrl={normalizeBaseUrl(llmInstanceConfig.url) ?? ''} onSelectModel={(modelName: string) => { setRagSettings({ ...ragSettings, MODEL_CHOICE: modelName }); showToast(`Selected LLM model: ${modelName}`, 'success'); @@ -1873,7 +2323,7 @@ export const RAGSettings = ({ ]} currentModel={ragSettings.EMBEDDING_MODEL} modelType="embedding" - selectedInstanceUrl={embeddingInstanceConfig.url.replace('/v1', '')} + selectedInstanceUrl={normalizeBaseUrl(embeddingInstanceConfig.url) ?? ''} onSelectModel={(modelName: string) => { setRagSettings({ ...ragSettings, EMBEDDING_MODEL: modelName }); showToast(`Selected embedding model: ${modelName}`, 'success'); @@ -1907,7 +2357,7 @@ export const RAGSettings = ({ }; // Helper functions to get provider-specific model display -function getDisplayedChatModel(ragSettings: any): string { +function getDisplayedChatModel(ragSettings: RAGSettingsProps["ragSettings"]): string { const provider = ragSettings.LLM_PROVIDER || 'openai'; const modelChoice = ragSettings.MODEL_CHOICE; @@ -1935,8 +2385,8 @@ function getDisplayedChatModel(ragSettings: any): string { } } -function getDisplayedEmbeddingModel(ragSettings: any): string { - const provider = ragSettings.LLM_PROVIDER || 'openai'; +function getDisplayedEmbeddingModel(ragSettings: RAGSettingsProps["ragSettings"]): string { + const provider = ragSettings.EMBEDDING_PROVIDER || ragSettings.LLM_PROVIDER || 'openai'; const embeddingModel = ragSettings.EMBEDDING_MODEL; // Always prioritize user input to allow editing @@ -1964,7 +2414,7 @@ function getDisplayedEmbeddingModel(ragSettings: any): string { } // Helper functions for model placeholders -function getModelPlaceholder(provider: string): string { +function getModelPlaceholder(provider: ProviderKey): string { switch (provider) { case 'openai': return 'e.g., gpt-4o-mini'; @@ -1983,7 +2433,7 @@ function getModelPlaceholder(provider: string): string { } } -function getEmbeddingPlaceholder(provider: string): string { +function getEmbeddingPlaceholder(provider: ProviderKey): string { switch (provider) { case 'openai': return 'Default: text-embedding-3-small'; diff --git a/archon-ui-main/src/contexts/SettingsContext.tsx b/archon-ui-main/src/contexts/SettingsContext.tsx index fa44a438..ff8f2264 100644 --- a/archon-ui-main/src/contexts/SettingsContext.tsx +++ b/archon-ui-main/src/contexts/SettingsContext.tsx @@ -3,7 +3,9 @@ import { credentialsService } from '../services/credentialsService'; interface SettingsContextType { projectsEnabled: boolean; - setProjectsEnabled: (enabled: boolean) => void; + setProjectsEnabled: (enabled: boolean) => Promise; + styleGuideEnabled: boolean; + setStyleGuideEnabled: (enabled: boolean) => Promise; loading: boolean; refreshSettings: () => Promise; } @@ -24,24 +26,35 @@ interface SettingsProviderProps { export const SettingsProvider: React.FC = ({ children }) => { const [projectsEnabled, setProjectsEnabledState] = useState(true); + const [styleGuideEnabled, setStyleGuideEnabledState] = useState(false); const [loading, setLoading] = useState(true); const loadSettings = async () => { try { setLoading(true); - - // Load Projects setting - const projectsResponse = await credentialsService.getCredential('PROJECTS_ENABLED').catch(() => ({ value: undefined })); - + + // Load Projects and Style Guide settings + const [projectsResponse, styleGuideResponse] = await Promise.all([ + credentialsService.getCredential('PROJECTS_ENABLED').catch(() => ({ value: undefined })), + credentialsService.getCredential('STYLE_GUIDE_ENABLED').catch(() => ({ value: undefined })) + ]); + if (projectsResponse.value !== undefined) { setProjectsEnabledState(projectsResponse.value === 'true'); } else { setProjectsEnabledState(true); // Default to true } - + + if (styleGuideResponse.value !== undefined) { + setStyleGuideEnabledState(styleGuideResponse.value === 'true'); + } else { + setStyleGuideEnabledState(false); // Default to false + } + } catch (error) { console.error('Failed to load settings:', error); setProjectsEnabledState(true); + setStyleGuideEnabledState(false); } finally { setLoading(false); } @@ -72,6 +85,27 @@ export const SettingsProvider: React.FC = ({ children }) } }; + const setStyleGuideEnabled = async (enabled: boolean) => { + try { + // Update local state immediately + setStyleGuideEnabledState(enabled); + + // Save to backend + await credentialsService.createCredential({ + key: 'STYLE_GUIDE_ENABLED', + value: enabled.toString(), + is_encrypted: false, + category: 'features', + description: 'Show UI style guide and components in navigation' + }); + } catch (error) { + console.error('Failed to update style guide setting:', error); + // Revert on error + setStyleGuideEnabledState(!enabled); + throw error; + } + }; + const refreshSettings = async () => { await loadSettings(); }; @@ -79,6 +113,8 @@ export const SettingsProvider: React.FC = ({ children }) const value: SettingsContextType = { projectsEnabled, setProjectsEnabled, + styleGuideEnabled, + setStyleGuideEnabled, loading, refreshSettings }; diff --git a/archon-ui-main/src/contexts/ThemeContext.tsx b/archon-ui-main/src/contexts/ThemeContext.tsx index 726d8d96..fc92eca1 100644 --- a/archon-ui-main/src/contexts/ThemeContext.tsx +++ b/archon-ui-main/src/contexts/ThemeContext.tsx @@ -8,25 +8,22 @@ const ThemeContext = createContext(undefined); export const ThemeProvider: React.FC<{ children: React.ReactNode; }> = ({ children }) => { - const [theme, setTheme] = useState('dark'); - useEffect(() => { - // Check if theme is stored in localStorage + // Read from localStorage immediately to avoid flash + const [theme, setTheme] = useState(() => { const savedTheme = localStorage.getItem('theme') as Theme | null; - if (savedTheme) { - setTheme(savedTheme); - } else { - // Default to dark mode - setTheme('dark'); - localStorage.setItem('theme', 'dark'); - } - }, []); + return savedTheme || 'dark'; + }); useEffect(() => { // Apply theme class to document element const root = window.document.documentElement; - // Remove both classes first - root.classList.remove('dark', 'light'); - // Add the current theme class - root.classList.add(theme); + + // Tailwind v4: Only toggle .dark class, don't add .light + if (theme === 'dark') { + root.classList.add('dark'); + } else { + root.classList.remove('dark'); + } + // Save to localStorage localStorage.setItem('theme', theme); }, [theme]); 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 05c882de..0769e708 100644 --- a/archon-ui-main/src/features/knowledge/components/KnowledgeCard.tsx +++ b/archon-ui-main/src/features/knowledge/components/KnowledgeCard.tsx @@ -8,10 +8,11 @@ import { format } from "date-fns"; import { motion } from "framer-motion"; import { Clock, Code, ExternalLink, File, FileText, Globe } from "lucide-react"; import { useState } from "react"; +import { isOptimistic } from "@/features/shared/utils/optimistic"; import { KnowledgeCardProgress } from "../../progress/components/KnowledgeCardProgress"; import type { ActiveOperation } from "../../progress/types"; -import { isOptimistic } from "@/features/shared/utils/optimistic"; 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/projects/components/ProjectCard.tsx b/archon-ui-main/src/features/projects/components/ProjectCard.tsx index a6b62349..b89fdce8 100644 --- a/archon-ui-main/src/features/projects/components/ProjectCard.tsx +++ b/archon-ui-main/src/features/projects/components/ProjectCard.tsx @@ -1,8 +1,8 @@ -import { motion } from "framer-motion"; import { Activity, CheckCircle2, ListTodo } from "lucide-react"; import type React from "react"; import { isOptimistic } from "@/features/shared/utils/optimistic"; import { OptimisticIndicator } from "../../ui/primitives/OptimisticIndicator"; +import { SelectableCard } from "../../ui/primitives/selectable-card"; import { cn } from "../../ui/primitives/styles"; import type { Project } from "../types"; import { ProjectCardActions } from "./ProjectCardActions"; @@ -33,46 +33,24 @@ export const ProjectCard: React.FC = ({ const optimistic = isOptimistic(project); return ( - { - if (e.key === "Enter" || e.key === " ") { - e.preventDefault(); - onSelect(project); - } - }} - onClick={() => onSelect(project)} + onSelect(project)} + blur="xl" + transparency="light" + size="none" className={cn( - "relative rounded-xl backdrop-blur-md w-72 min-h-[180px] cursor-pointer overflow-visible group flex flex-col", - "transition-all duration-300", - "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-purple-500 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-zinc-900", + "w-72 min-h-[180px] flex flex-col shrink-0", project.pinned ? "bg-gradient-to-b from-purple-100/80 via-purple-50/30 to-purple-100/50 dark:from-purple-900/30 dark:via-purple-900/20 dark:to-purple-900/10" : isSelected ? "bg-gradient-to-b from-white/70 via-purple-50/20 to-white/50 dark:from-white/5 dark:via-purple-900/5 dark:to-black/20" : "bg-gradient-to-b from-white/80 to-white/60 dark:from-white/10 dark:to-black/30", - "border", - project.pinned - ? "border-purple-500/80 dark:border-purple-500/80 shadow-[0_0_15px_rgba(168,85,247,0.3)]" - : isSelected - ? "border-purple-400/60 dark:border-purple-500/60" - : "border-gray-200 dark:border-zinc-800/50", - isSelected - ? "shadow-[0_0_15px_rgba(168,85,247,0.4),0_0_10px_rgba(147,51,234,0.3)] dark:shadow-[0_0_20px_rgba(168,85,247,0.5),0_0_15px_rgba(147,51,234,0.4)]" - : "shadow-[0_10px_30px_-15px_rgba(0,0,0,0.1)] dark:shadow-[0_10px_30px_-15px_rgba(0,0,0,0.7)]", - "hover:shadow-[0_15px_40px_-15px_rgba(0,0,0,0.2)] dark:hover:shadow-[0_15px_40px_-15px_rgba(0,0,0,0.9)]", - isSelected ? "scale-[1.02]" : "hover:scale-[1.01]", // Use scale instead of translate to avoid clipping optimistic && "opacity-80 ring-1 ring-cyan-400/30", )} > - {/* Subtle aurora glow effect for selected card */} - {isSelected && ( -
    -
    -
    - )} {/* Main content area with padding */}
    @@ -94,7 +72,7 @@ export const ProjectCard: React.FC = ({
    {/* Task count pills */} -
    +
    {/* Todo pill */}
    = ({
    {/* Pinned indicator badge */} {project.pinned ? ( -
    +
    DEFAULT
    ) : ( @@ -275,6 +253,6 @@ export const ProjectCard: React.FC = ({ }} />
    - + ); }; diff --git a/archon-ui-main/src/features/projects/components/ProjectHeader.tsx b/archon-ui-main/src/features/projects/components/ProjectHeader.tsx index 995792e0..563035d7 100644 --- a/archon-ui-main/src/features/projects/components/ProjectHeader.tsx +++ b/archon-ui-main/src/features/projects/components/ProjectHeader.tsx @@ -1,10 +1,18 @@ import { motion } from "framer-motion"; -import { Plus } from "lucide-react"; +import { LayoutGrid, List, Plus, Search, X } from "lucide-react"; import type React from "react"; +import { ReactNode } from "react"; import { Button } from "../../ui/primitives/button"; +import { Input } from "../../ui/primitives/input"; +import { cn } from "../../ui/primitives/styles"; interface ProjectHeaderProps { onNewProject: () => void; + layoutMode?: "horizontal" | "sidebar"; + onLayoutModeChange?: (mode: "horizontal" | "sidebar") => void; + rightContent?: ReactNode; + searchQuery?: string; + onSearchChange?: (query: string) => void; } const titleVariants = { @@ -25,7 +33,14 @@ const itemVariants = { }, }; -export const ProjectHeader: React.FC = ({ onNewProject }) => { +export const ProjectHeader: React.FC = ({ + onNewProject, + layoutMode, + onLayoutModeChange, + rightContent, + searchQuery, + onSearchChange, +}) => { return ( = ({ onNewProject }) => /> Projects - +
    + {/* Search input */} + {searchQuery !== undefined && onSearchChange && ( +
    + + onSearchChange(e.target.value)} + className="pl-9 pr-8" + aria-label="Search projects" + /> + {searchQuery && ( + + )} +
    + )} + {/* Layout toggle - show if mode and change handler provided */} + {layoutMode && onLayoutModeChange && ( +
    + + +
    + )} + {rightContent} + +
    ); }; diff --git a/archon-ui-main/src/features/projects/components/ProjectList.tsx b/archon-ui-main/src/features/projects/components/ProjectList.tsx index 7b4b769e..aa4d0c58 100644 --- a/archon-ui-main/src/features/projects/components/ProjectList.tsx +++ b/archon-ui-main/src/features/projects/components/ProjectList.tsx @@ -97,7 +97,7 @@ export const ProjectList: React.FC = ({ } return ( - +