diff --git a/FRONTEND_UI_UPDATE_PROMPT.md b/FRONTEND_UI_UPDATE_PROMPT.md new file mode 100644 index 000000000..dc8067ac2 --- /dev/null +++ b/FRONTEND_UI_UPDATE_PROMPT.md @@ -0,0 +1,505 @@ +# Frontend UI Update Prompt for Claude 4.5 Sonnet + +## Objective +Update the current vTeam Next.js/React frontend UI (located at `components/frontend`) to match the design and user experience of the static HTML prototype (located at `static-prototype`). + +--- + +## Context + +### Current Frontend +- **Location**: `/Users/abraren/acorn/local/code/vTeam/components/frontend` +- **Tech Stack**: Next.js 15, React, TypeScript, TailwindCSS, Shadcn/ui, React Query +- **Architecture**: App Router with Server/Client Components + +### Static Prototype Reference +- **Location**: `/Users/abraren/acorn/local/code/vTeam/static-prototype` +- **Files**: HTML pages with inline CSS and vanilla JavaScript +- **Purpose**: Design reference and UX flow demonstration + +### Design Guidelines +- **MUST READ**: `components/frontend/DESIGN_GUIDELINES.md` +- **MUST READ**: `components/frontend/COMPONENT_PATTERNS.md` + +--- + +## Key Design Changes to Implement + +### 1. **Branding & Header** +**Prototype Reference**: `static-prototype/index.html` (lines 10-54) + +**Changes Needed**: +- Change branding from "vTeam" to **"Ambient Code Platform"** +- Update header logo/title styling to match prototype +- Implement user dropdown menu in header with: + - User email display + - User initials avatar bubble + - Dropdown arrow animation + - "Integrations" link + - "Logout" button +- Use prototype's header styling: white background, subtle border-bottom + +**Current Location**: `components/frontend/src/components/navigation.tsx` + +--- + +### 2. **Terminology Changes** +**Prototype shows different terminology throughout:** + +| Current Term | New Term (Prototype) | +|--------------|---------------------| +| "Projects" | "Workspaces" | +| "RFE Workflows" page | Integrated into workspace tabs | +| Page title on projects list | "Workspaces" | +| Create button | "New Workspace" | + +**Affected Files**: +- `src/app/projects/page.tsx` +- `src/components/navigation.tsx` +- `src/components/page-header.tsx` +- All project-related page titles and descriptions + +--- + +### 3. **Projects/Workspaces List Page** +**Prototype Reference**: `static-prototype/index.html` + +**Changes Needed**: + +#### Header Section +- Title: "Workspaces" +- Description: "Select a workspace or create a new one to get started" +- Actions: "Refresh" button + "New Workspace" button + +#### Table Design +- **Columns**: Name, Description, Sessions, Created, Actions +- **Special column**: "Sessions" shows: + - Total session count + - Badge with running session count (if any) with spinner icon + - Example: `4` + `1 Running` +- **Actions column**: Single delete button (trash icon) +- **Name column**: Link styled in blue (`#1e40af`) +- Clean white card with subtle borders + +#### Modal for Creation +- **Modal Title**: "Create New Workspace" +- **Fields**: + - Workspace Name (with validation message) + - Display Name + - Description (textarea) +- Pre-filled with auto-incrementing defaults (e.g., `demo-user-workspace-1`) +- Modal styling matches prototype's clean design + +**Current Location**: `src/app/projects/page.tsx` + +--- + +### 4. **Workspace Detail Page (Project Detail)** +**Prototype Reference**: `static-prototype/projects/sample-workspace/page.html` + +**Major Redesign Needed**: + +#### Layout +- **Left Sidebar Navigation** (vertical menu with icons): + - Sessions (star icon) - Active by default + - Sharing (users icon) - Previously "Permissions" + - Workspace Settings (settings icon) + +#### Breadcrumbs +- Format: `Workspaces / {workspace-name}` +- Located above page title + +#### Page Header +- Show workspace name as title +- Show workspace description below title + +#### Main Content Area - Tabbed Interface +The prototype shows different content sections that switch via sidebar navigation: + +**A. Sessions Tab (Default)** +- Title: "Agentic Sessions" +- Actions: Refresh + "New Session" button +- Table columns: + - Session Name (with description underneath) + - Status (badge with icon: Running/Completed) + - Mode (interactive/headless) + - Model (e.g., claude-3.5-sonnet) + - Created (relative time) +- Clickable rows that navigate to session detail +- Status badges with SVG icons (spinning loader for "Running", checkmark for "Completed") + +**B. Sharing Tab** (Previously "Permissions") +- Title: "Sharing" +- Description: "Users and groups with access to this workspace and their roles" +- Table columns: + - Users/Groups (name + subtitle) + - Type (User/Group badge) + - Role (Admin/View badge) + - Actions (delete button) +- Action: "Grant Permission" button + +**C. Workspace Settings Tab** +Multiple cards with different settings sections: + +1. **General Settings Card** + - Display Name (editable) + - Workspace Name (read-only with help text) + - Description (textarea) + - "Save Changes" button + +2. **Runner Secrets Card** + - Runner Secret dropdown selector + - Anthropic API Key (required field with asterisk) + - Additional Secrets section: + - Dynamic key/value pairs + - "Add Secret" button + - Grid layout: Key | Value | Delete button + - "Save Secrets" button + +3. **Resource Limits Card** + - Max Concurrent Sessions + - Max RFE Workspaces + - Storage Limit (GB) + - "Update Limits" button + +4. **API Keys Card** (embedded in settings) + - Table of API keys + - Columns: Name, Created, Last Used, Role, Actions + - Actions: Refresh + "Create Key" button + +**Current Locations**: +- `src/app/projects/[name]/page.tsx` +- `src/app/projects/[name]/layout.tsx` +- `src/app/projects/[name]/permissions/page.tsx` +- `src/app/projects/[name]/settings/page.tsx` + +--- + +### 5. **Session Creation Modal** +**Prototype Reference**: `static-prototype/projects/sample-workspace/page.html` (lines 698-776) + +**Features to Implement**: + +#### Basic Configuration +- **Model Dropdown**: + - Claude Sonnet 3.7 (default) + - Claude Opus 4.1 + - Claude Opus 4 + - Claude Sonnet 4 + - Claude Haiku 3.5 + +#### Headless-Specific Field +- **Agentic Prompt** textarea (only shown for headless mode) +- Help text: "Provide detailed instructions for the AI to execute without user interaction" + +#### Advanced Settings (Collapsible Accordion) +- **Button**: "Change Default Model Settings" with chevron +- **Fields when expanded**: + - Temperature (0.0 - 2.0, step 0.1) + - Timeout (60-1800 seconds) + - Max Output Tokens (100-8000) + - **Bring Your Own Key section**: + - Anthropic API Key input (password field) + - "Save key for future sessions" checkbox + - Help text: "Optional: Use your own Anthropic API key for this session" + +#### Modal Actions +- Cancel button +- "Create Session" button + +**Implementation**: +- Create new component: `src/components/session-config-dialog.tsx` +- Use Shadcn Dialog component +- Use Accordion component for advanced settings +- Integrate with session creation flow + +--- + +### 6. **Sessions List Page** +**Prototype Reference**: `static-prototype/projects/sample-workspace/sessions/page.html` + +**Design**: +- Breadcrumbs: `Workspaces / {workspace-name} / Sessions` +- Title: "Agentic Sessions" +- Description: "AI-powered coding sessions for your project" +- Left sidebar with workspace navigation +- Table with sessions (same as on workspace detail page) + +**Current Location**: `src/app/projects/[name]/sessions/page.tsx` + +--- + +### 7. **Visual Design System** + +#### Colors (from `static-prototype/styles.css`) +```css +Primary Blue: #1e40af (links, primary buttons) +Hover Blue: #1d4ed8 +Background: #f8fafc (page background) +Card Background: white +Borders: #e2e8f0 +Text Primary: #333 +Text Secondary: #64748b +Text Muted: #374151 +Success Green: #166534 (background: #dcfce7) +Warning Yellow: #92400e (background: #fef3c7) +Error Red: #991b1b (background: #fee2e2) +Info Blue: #1e40af (background: #dbeafe) +``` + +#### Typography +- Font Family: `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif` +- Page Title: `2rem`, `font-weight: bold` +- Card Title: `1.25rem`, `font-weight: 600` +- Body: `1rem`, `line-height: 1.6` + +#### Spacing & Layout +- Container: `max-width: 1200px`, centered +- Card Border Radius: `0.5rem` +- Button Border Radius: `0.375rem` +- Standard Padding: Card header/content `1.5rem` + +#### Buttons +- **Primary**: Blue background (`#1e40af`), white text +- **Secondary**: White background, gray border (`#d1d5db`), gray text +- **Icons**: Inline SVG, 14-16px size +- **Hover States**: Subtle background color changes + +#### Tables +- Header Background: `#f8fafc` +- Border: `#e2e8f0` +- Row Hover: `#f8fafc` +- Cell Padding: `0.75rem` + +#### Badges +- Border Radius: `9999px` (pill shape) +- Padding: `0.25rem 0.75rem` +- Font Size: `0.75rem`, `font-weight: 500` +- With inline SVG icons (12px) + +#### Modals +- Backdrop: `rgba(0, 0, 0, 0.5)` +- Content: White, rounded corners, shadow +- Max Width: `500-600px` +- Close button: `×` character + +--- + +## Implementation Guidelines + +### Architecture Requirements + +1. **Follow Existing Patterns**: + - Use `type` over `interface` (per COMPONENT_PATTERNS.md) + - No `any` types allowed + - Use Shadcn/ui components as foundation + - Maintain Server/Client Component separation + - Use React Query for all data fetching + +2. **File Organization**: + - Keep single-use components colocated with pages + - Reusable components go in `src/components/` + - Follow existing directory structure + +3. **Component Standards**: + - Loading states: Use `loading.tsx` with Skeleton components + - Error states: Use `error.tsx` with Error boundary + - Empty states: Use `EmptyState` component + - Forms: Use Shadcn Form components with validation + +4. **Data Fetching**: + - Use existing React Query hooks from `src/services/queries/` + - Create new query hooks if needed following same pattern + - Use API service layer from `src/services/api/` + +--- + +## Step-by-Step Implementation Plan + +### Phase 1: Global Changes +1. Update branding from "vTeam" to "Ambient Code Platform" +2. Update navigation component with user dropdown +3. Change "Projects" terminology to "Workspaces" everywhere +4. Update color scheme to match prototype + +### Phase 2: Workspaces List Page +1. Update page title and description +2. Modify table columns to match prototype +3. Add "Sessions" column with running count +4. Update create workspace modal +5. Style improvements to match prototype + +### Phase 3: Workspace Detail Page +1. Restructure layout with sidebar navigation +2. Implement tabbed interface (Sessions/Sharing/Settings) +3. Update breadcrumbs format +4. Move content to appropriate tabs + +### Phase 4: Sessions Management +1. Create session configuration modal component +2. Add model selection dropdown +3. Implement advanced settings accordion +4. Add BYOK (Bring Your Own Key) section +5. Update session creation flow + +### Phase 5: Settings & Permissions +1. Rename "Permissions" to "Sharing" +2. Restructure settings page with multiple cards +3. Add Runner Secrets configuration +4. Add Resource Limits section +5. Move API Keys to settings page + +### Phase 6: Polish & Testing +1. Fine-tune spacing and colors +2. Verify all interactions work +3. Test responsive behavior +4. Ensure accessibility standards +5. Validate with design guidelines + +--- + +## Critical Requirements + +### DO: +✅ Use Shadcn/ui components exclusively +✅ Follow DESIGN_GUIDELINES.md strictly +✅ Maintain TypeScript strict typing (no `any`) +✅ Use React Query for data fetching +✅ Implement proper loading/error states +✅ Use Tailwind CSS classes +✅ Keep existing API integration working +✅ Preserve functionality while updating UI +✅ Use semantic HTML +✅ Ensure accessibility (ARIA labels, keyboard navigation) + +### DON'T: +❌ Create custom UI components from scratch +❌ Use `any` types +❌ Break existing API integrations +❌ Remove error handling +❌ Ignore loading states +❌ Skip empty states +❌ Use inline styles (use Tailwind classes) +❌ Create new API endpoints (use existing backend) + +--- + +## Testing Checklist + +After implementation, verify: + +- [ ] All pages render without errors +- [ ] Branding updated throughout +- [ ] Terminology consistent ("Workspaces" not "Projects") +- [ ] User dropdown works in header +- [ ] Workspace list shows correct columns +- [ ] Session count displays properly +- [ ] Create workspace modal functions +- [ ] Workspace detail page has sidebar navigation +- [ ] Settings page has all sections +- [ ] Sharing page displays correctly +- [ ] Session creation modal works +- [ ] All forms validate properly +- [ ] Delete confirmations work +- [ ] Loading states display +- [ ] Error states display +- [ ] Empty states display +- [ ] Responsive design works +- [ ] Colors match prototype +- [ ] Typography matches prototype +- [ ] Icons display correctly +- [ ] Badges styled correctly +- [ ] Tables styled correctly +- [ ] Buttons work and are styled correctly + +--- + +## Reference Files + +### Must Read Before Starting: +1. `/Users/abraren/acorn/local/code/vTeam/components/frontend/DESIGN_GUIDELINES.md` +2. `/Users/abraren/acorn/local/code/vTeam/components/frontend/COMPONENT_PATTERNS.md` +3. `/Users/abraren/acorn/local/code/vTeam/static-prototype/README.md` + +### Key Prototype Files: +- `/Users/abraren/acorn/local/code/vTeam/static-prototype/index.html` - Workspaces list +- `/Users/abraren/acorn/local/code/vTeam/static-prototype/styles.css` - Design system +- `/Users/abraren/acorn/local/code/vTeam/static-prototype/projects/sample-workspace/page.html` - Workspace detail +- `/Users/abraren/acorn/local/code/vTeam/static-prototype/projects/sample-workspace/sessions/page.html` - Sessions list + +### Current Frontend Structure: +``` +components/frontend/src/ +├── app/ +│ ├── projects/ +│ │ ├── page.tsx # Main workspaces list +│ │ ├── new/page.tsx # Create workspace +│ │ └── [name]/ +│ │ ├── page.tsx # Workspace detail +│ │ ├── layout.tsx # Workspace layout +│ │ ├── sessions/page.tsx # Sessions list +│ │ ├── permissions/page.tsx # Permissions (→ Sharing) +│ │ └── settings/page.tsx # Settings +├── components/ +│ ├── ui/ # Shadcn components +│ ├── navigation.tsx # Header navigation +│ ├── page-header.tsx # Page headers +│ ├── breadcrumbs.tsx # Breadcrumbs +│ └── ... # Other components +├── services/ +│ ├── api/ # API client layer +│ └── queries/ # React Query hooks +└── types/ # TypeScript types +``` + +--- + +## Success Criteria + +The implementation is complete when: + +1. **Visual Consistency**: Frontend UI matches static prototype design +2. **Terminology**: All "Projects" references changed to "Workspaces" +3. **Branding**: "Ambient Code Platform" used throughout +4. **Functionality**: All existing features work with new UI +5. **Code Quality**: Follows all guidelines in DESIGN_GUIDELINES.md +6. **Type Safety**: No TypeScript errors, no `any` types +7. **User Experience**: Smooth interactions, proper loading/error states +8. **Responsive**: Works on desktop and mobile +9. **Accessible**: Keyboard navigation, ARIA labels, semantic HTML + +--- + +## Questions to Consider + +Before starting, review: +- What existing API endpoints are available? (Check `src/services/api/`) +- What data types are defined? (Check `src/types/`) +- What React Query hooks exist? (Check `src/services/queries/`) +- What Shadcn components are installed? (Check `src/components/ui/`) +- What's the current routing structure? (Check `src/app/`) + +--- + +## Getting Started + +1. **Read the design guidelines**: Start with DESIGN_GUIDELINES.md and COMPONENT_PATTERNS.md +2. **Explore the prototype**: Open static-prototype/index.html in a browser +3. **Review current frontend**: Familiarize yourself with the existing structure +4. **Plan your approach**: Break down work into small, testable changes +5. **Test incrementally**: Verify each change before moving to the next +6. **Commit frequently**: Make atomic commits for easy rollback + +--- + +## Additional Resources + +- **Shadcn/ui Docs**: https://ui.shadcn.com/ +- **Next.js App Router**: https://nextjs.org/docs/app +- **React Query**: https://tanstack.com/query/latest +- **Tailwind CSS**: https://tailwindcss.com/docs + +--- + +Good luck! Remember: **Preserve functionality while updating the UI** - this is a visual refresh, not a rewrite. + diff --git a/PHASE_4_IMPLEMENTATION_SUMMARY.md b/PHASE_4_IMPLEMENTATION_SUMMARY.md new file mode 100644 index 000000000..94c8aa0d6 --- /dev/null +++ b/PHASE_4_IMPLEMENTATION_SUMMARY.md @@ -0,0 +1,186 @@ +# Phase 4 Implementation Summary + +## Overview +Successfully implemented Phase 4: Advanced Session Configuration with collapsible settings and BYOK (Bring Your Own Key) functionality. + +## ✅ Completed Features + +### 1. **Enhanced Model Selection** (Phase 4.2) +- **Location**: `components/frontend/src/app/projects/[name]/sessions/new/model-configuration.tsx` +- **Changes**: + - Reordered model dropdown to match prototype specification + - Default model: **Claude Sonnet 3.7** + - Available models in order: + 1. Claude Sonnet 3.7 (default) + 2. Claude Opus 4.1 + 3. Claude Opus 4 + 4. Claude Sonnet 4 + 5. Claude Haiku 3.5 + +### 2. **Advanced Settings Accordion** (Phase 4.3) +- **Location**: `components/frontend/src/app/projects/[name]/sessions/new/model-configuration.tsx` +- **Implementation**: + - Created collapsible accordion with "Change Default Model Settings" button + - Moved temperature, timeout, and max tokens inside accordion + - Accordion expands/collapses smoothly using Radix UI primitives + +#### Advanced Settings Fields: +- **Temperature**: 0.0 - 2.0 (step 0.1) + - Description: "Controls randomness (0.0 - 2.0)" + - Default: 0.7 + +- **Timeout**: 60 - 1800 seconds + - Description: "Session timeout (60-1800 seconds)" + - Default: 300 seconds + +- **Max Output Tokens**: 100 - 8000 + - Description: "Maximum response length (100-8000)" + - Default: 4000 + +### 3. **Bring Your Own Key (BYOK) Section** (Phase 4.3) +- **Location**: Inside Advanced Settings Accordion +- **Features**: + - **Anthropic API Key Input**: + - Type: Password field for security + - Placeholder: `sk-ant-api03-...` + - Optional field (not required) + - Help text: "Optional: Use your own Anthropic API key for this session" + + - **Save Key Checkbox**: + - Label: "Save key for future sessions (encrypted)" + - Allows users to persist their API key for reuse + - Default: unchecked + +### 4. **Form Schema Updates** +- **Location**: `components/frontend/src/app/projects/[name]/sessions/new/page.tsx` +- **New Fields Added**: + ```typescript + anthropicApiKey: z.string().optional().default(""), + saveApiKeyForFuture: z.boolean().default(false), + ``` +- **Default Values**: + ```typescript + anthropicApiKey: "", + saveApiKeyForFuture: false, + ``` + +### 5. **New Shadcn Component Created** +- **File**: `components/frontend/src/components/ui/accordion.tsx` +- **Dependencies**: + - Installed `@radix-ui/react-accordion` (v1.x) + - Uses Radix UI primitives for accessible accordion behavior +- **Features**: + - Smooth expand/collapse animations + - Keyboard navigation support + - Chevron icon that rotates when expanded + - Fully accessible (ARIA compliant) + +## 📁 Files Modified + +1. **model-configuration.tsx** - Complete redesign with accordion +2. **page.tsx** (sessions/new) - Added BYOK fields to schema +3. **accordion.tsx** - New Shadcn UI component (created) + +## 🎨 UI/UX Improvements + +### Before: +- All model configuration fields always visible +- Cluttered interface with many inputs +- No clear separation between basic and advanced settings + +### After: +- Clean, focused interface with just Model selection visible by default +- Advanced settings hidden in collapsible accordion +- Clear "Change Default Model Settings" button with chevron indicator +- BYOK section properly separated with visual divider +- Progressive disclosure - users only see what they need + +## 🔧 Technical Implementation Details + +### Accordion Structure: +```tsx + + + Change Default Model Settings + + {/* Temperature & Timeout in grid */} + {/* Max Tokens */} + {/* BYOK Section with divider */} + + + +``` + +### Form Integration: +- All fields properly integrated with React Hook Form +- Validation maintained for all inputs +- Type-safe with Zod schema +- Values persist across accordion open/close + +## 🎯 Matches Prototype Specification + +### Prototype Requirements Met: +- ✅ Model dropdown with exact models in specified order +- ✅ "Change Default Model Settings" collapsible button +- ✅ Temperature field (0.0 - 2.0, step 0.1) +- ✅ Timeout field (60-1800 seconds) +- ✅ Max Output Tokens (100-8000) +- ✅ BYOK section with password field +- ✅ "Save key for future sessions" checkbox +- ✅ Proper help text and descriptions +- ✅ Clean visual separation with border-top divider + +## 📊 Testing Checklist + +To test the implementation: + +1. **Navigate to session creation**: + ``` + /projects/[name]/sessions/new + ``` + +2. **Verify Model Selection**: + - [ ] Model dropdown appears at top + - [ ] Claude Sonnet 3.7 is default + - [ ] All 5 models are available in correct order + +3. **Test Accordion Behavior**: + - [ ] "Change Default Model Settings" button is visible + - [ ] Click expands the accordion smoothly + - [ ] Chevron icon rotates when expanded + - [ ] Click again collapses the accordion + +4. **Verify Advanced Settings Fields**: + - [ ] Temperature input works (0.0 - 2.0, step 0.1) + - [ ] Timeout input works (60 - 1800 seconds) + - [ ] Max Tokens input works (100 - 8000) + +5. **Test BYOK Section**: + - [ ] API key input is type="password" (masked) + - [ ] Placeholder shows "sk-ant-api03-..." + - [ ] Checkbox for "Save key for future sessions" works + - [ ] Help text displays correctly + +6. **Form Submission**: + - [ ] Form submits with all values + - [ ] BYOK fields are optional (can be empty) + - [ ] Validation works as expected + +## 🚀 Next Steps (Optional Enhancements) + +Potential future improvements: +- Backend integration for storing encrypted API keys +- Validation for Anthropic API key format +- Key testing functionality +- Session history showing which used BYOK +- API key rotation/expiration warnings + +## 📝 Notes + +- The accordion component uses Radix UI primitives for best-in-class accessibility +- All styling uses Tailwind CSS classes for consistency +- Form validation maintained with Zod schemas +- Follows existing codebase patterns and conventions +- No breaking changes to existing functionality +- Fully backwards compatible + diff --git a/components/backend/Dockerfile.dev b/components/backend/Dockerfile.dev index 731cbfee9..79a601c7b 100644 --- a/components/backend/Dockerfile.dev +++ b/components/backend/Dockerfile.dev @@ -3,17 +3,25 @@ FROM golang:1.24-alpine WORKDIR /app -# Install git and build dependencies -RUN apk add --no-cache git build-base +# Install git, build dependencies, and tools for oc rsync +RUN apk add --no-cache git build-base tar rsync # Set environment variables ENV AGENTS_DIR=/app/agents ENV CGO_ENABLED=0 ENV GOOS=linux +# Copy go mod and sum files +COPY go.mod go.sum ./ + +# Download dependencies +RUN go mod download + +# Copy the source code +COPY . . + # Expose port EXPOSE 8080 # Simple development mode - just run the Go app directly -# Note: Source code will be mounted as volume at runtime -CMD ["sh", "-c", "while [ ! -f main.go ]; do echo 'Waiting for source sync...'; sleep 2; done && go run ."] +CMD ["go", "run", "."] diff --git a/components/frontend/.gitignore b/components/frontend/.gitignore index 3eb22dfc0..c9d206bd2 100644 --- a/components/frontend/.gitignore +++ b/components/frontend/.gitignore @@ -60,4 +60,7 @@ jspm_packages/ # TypeScript *.tsbuildinfo -next-env.d.ts \ No newline at end of file +next-env.d.ts + +# Previous frontend +previous-frontend/ \ No newline at end of file diff --git a/components/frontend/Dockerfile.dev b/components/frontend/Dockerfile.dev index 3c90fcb04..d1ffe9f2a 100644 --- a/components/frontend/Dockerfile.dev +++ b/components/frontend/Dockerfile.dev @@ -3,16 +3,24 @@ FROM node:20-alpine WORKDIR /app -# Install dependencies for building native modules -RUN apk add --no-cache libc6-compat python3 make g++ +# Install dependencies for building native modules and tools for oc rsync +RUN apk add --no-cache libc6-compat python3 make g++ tar rsync # Set NODE_ENV to development ENV NODE_ENV=development ENV NEXT_TELEMETRY_DISABLED=1 +# Copy package files +COPY package.json package-lock.json* ./ + +# Install dependencies +RUN npm ci + +# Copy source code +COPY . . + # Expose port EXPOSE 3000 -# Install dependencies when container starts (source mounted as volume) # Run Next.js in development mode -CMD ["sh", "-c", "npm ci && npm run dev"] +CMD ["npm", "run", "dev"] diff --git a/components/frontend/README.md b/components/frontend/README.md index c92748388..a5988e42e 100644 --- a/components/frontend/README.md +++ b/components/frontend/README.md @@ -92,11 +92,15 @@ In production, put an OAuth/ingress proxy in front of the app to set these heade ### Environment variables - `BACKEND_URL` (default: `http://localhost:8080/api`) - Used by server-side API routes to reach the backend. +- `FEEDBACK_URL` (optional) + - URL for the feedback link in the masthead. If not set, the link will not appear. - Optional dev helpers: `OC_USER`, `OC_EMAIL`, `OC_TOKEN`, `ENABLE_OC_WHOAMI=1` You can also put these in a `.env.local` file in this folder: ``` BACKEND_URL=http://localhost:8080/api +# Optional: URL for feedback link in masthead +# FEEDBACK_URL=https://forms.example.com/feedback # Optional dev helpers # OC_USER=your.name # OC_EMAIL=your.name@example.com diff --git a/components/frontend/package-lock.json b/components/frontend/package-lock.json index 8ae804d01..5b38164b9 100644 --- a/components/frontend/package-lock.json +++ b/components/frontend/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@hookform/resolvers": "^5.2.1", + "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dropdown-menu": "^2.1.16", @@ -1046,6 +1047,37 @@ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", "license": "MIT" }, + "node_modules/@radix-ui/react-accordion": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz", + "integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collapsible": "1.1.12", + "@radix-ui/react-collection": "1.1.7", + "@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-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "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-arrow": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", @@ -1126,6 +1158,36 @@ } } }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", + "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", + "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-id": "1.1.1", + "@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-layout-effect": "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", @@ -2170,6 +2232,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.2.tgz", "integrity": "sha512-CLABiR+h5PYfOWr/z+vWFt5VsOA2ekQeRQBFSKlcoW6Ndx/f8rfyVmq4LbgOM4GG2qtxAxjLYLOpCNTYm4uKzw==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/query-core": "5.90.2" }, @@ -2286,6 +2349,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz", "integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -2296,6 +2360,7 @@ "integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.0.0" } @@ -2352,6 +2417,7 @@ "integrity": "sha512-VGMpFQGUQWYT9LfnPcX8ouFojyrZ/2w3K5BucvxL/spdNehccKhB4jUyB1yBCXpr2XFm0jkECxgrpXBW2ipoAw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.44.0", "@typescript-eslint/types": "8.44.0", @@ -2875,6 +2941,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3877,6 +3944,7 @@ "integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4051,6 +4119,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -7276,6 +7345,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -7285,6 +7355,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -7297,6 +7368,7 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.62.0.tgz", "integrity": "sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==", "license": "MIT", + "peer": true, "engines": { "node": ">=18.0.0" }, @@ -8236,6 +8308,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -8415,6 +8488,7 @@ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/components/frontend/package.json b/components/frontend/package.json index df5f50ae9..68ff0792e 100644 --- a/components/frontend/package.json +++ b/components/frontend/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@hookform/resolvers": "^5.2.1", + "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dropdown-menu": "^2.1.16", diff --git a/components/frontend/src/app/api/projects/[name]/rfe-workflows/[id]/sessions/link/route.ts b/components/frontend/src/app/api/projects/[name]/rfe-workflows/[id]/sessions/link/route.ts new file mode 100644 index 000000000..5de001521 --- /dev/null +++ b/components/frontend/src/app/api/projects/[name]/rfe-workflows/[id]/sessions/link/route.ts @@ -0,0 +1,19 @@ +import { BACKEND_URL } from '@/lib/config' +import { buildForwardHeadersAsync } from '@/lib/auth' + +export async function POST( + request: Request, + { params }: { params: Promise<{ name: string; id: string }> }, +) { + const { name, id } = await params + const headers = await buildForwardHeadersAsync(request) + const body = await request.text() + const resp = await fetch(`${BACKEND_URL}/projects/${encodeURIComponent(name)}/rfe-workflows/${encodeURIComponent(id)}/sessions/link`, { + method: 'POST', + headers, + body, + }) + const data = await resp.text() + return new Response(data, { status: resp.status, headers: { 'Content-Type': 'application/json' } }) +} + diff --git a/components/frontend/src/app/globals.css b/components/frontend/src/app/globals.css index b77c4d606..2080a0957 100644 --- a/components/frontend/src/app/globals.css +++ b/components/frontend/src/app/globals.css @@ -52,7 +52,7 @@ --card-foreground: oklch(0.145 0 0); --popover: oklch(1 0 0); --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.205 0 0); + --primary: oklch(0.5 0.22 264); --primary-foreground: oklch(0.985 0 0); --secondary: oklch(0.97 0 0); --secondary-foreground: oklch(0.205 0 0); @@ -71,7 +71,7 @@ --chart-5: oklch(0.769 0.188 70.08); --sidebar: oklch(0.985 0 0); --sidebar-foreground: oklch(0.145 0 0); - --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary: oklch(0.5 0.22 264); --sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-accent: oklch(0.97 0 0); --sidebar-accent-foreground: oklch(0.205 0 0); @@ -86,8 +86,8 @@ --card-foreground: oklch(0.985 0 0); --popover: oklch(0.205 0 0); --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.922 0 0); - --primary-foreground: oklch(0.205 0 0); + --primary: oklch(0.6 0.2 264); + --primary-foreground: oklch(0.985 0 0); --secondary: oklch(0.269 0 0); --secondary-foreground: oklch(0.985 0 0); --muted: oklch(0.269 0 0); diff --git a/components/frontend/src/app/integrations/IntegrationsClient.tsx b/components/frontend/src/app/integrations/IntegrationsClient.tsx index 657447dcd..4d81fea81 100644 --- a/components/frontend/src/app/integrations/IntegrationsClient.tsx +++ b/components/frontend/src/app/integrations/IntegrationsClient.tsx @@ -1,73 +1,32 @@ 'use client' import React from 'react' -import { Button } from '@/components/ui/button' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { useGitHubStatus, useDisconnectGitHub } from '@/services/queries' -import { successToast, errorToast } from '@/hooks/use-toast' +import { GitHubConnectionCard } from '@/components/github-connection-card' +import { PageHeader } from '@/components/page-header' type Props = { appSlug?: string } export default function IntegrationsClient({ appSlug }: Props) { - const { data: status, isLoading, refetch } = useGitHubStatus() - const disconnectMutation = useDisconnectGitHub() - - const handleConnect = () => { - if (!appSlug) return - const setupUrl = new URL('/integrations/github/setup', window.location.origin) - const redirectUri = encodeURIComponent(setupUrl.toString()) - const url = `https://github.com/apps/${appSlug}/installations/new?redirect_uri=${redirectUri}` - window.location.href = url - } - - const handleDisconnect = async () => { - disconnectMutation.mutate(undefined, { - onSuccess: () => { - successToast('GitHub disconnected successfully') - refetch() - }, - onError: (error) => { - errorToast(error instanceof Error ? error.message : 'Failed to disconnect GitHub') - }, - }) - } - - const handleManage = () => { - window.open('https://github.com/settings/installations', '_blank') - } - return ( -
-

Integrations

- - - - GitHub - Connect GitHub to enable forks, PRs, and repo browsing - - -
- {status?.installed ? ( -
- Connected{status.githubUserId ? ` as ${status.githubUserId}` : ''} - {status.updatedAt ? ( - · updated {new Date(status.updatedAt).toLocaleString()} - ) : null} -
- ) : ( -
Not connected
- )} -
-
- - {status?.installed ? ( - - ) : ( - - )} +
+ {/* Sticky header */} +
+
+ +
+
+ +
+ {/* Content */} +
+
+
- - +
+
) } diff --git a/components/frontend/src/app/layout.tsx b/components/frontend/src/app/layout.tsx index 7da9cd6ae..d8ff51f21 100644 --- a/components/frontend/src/app/layout.tsx +++ b/components/frontend/src/app/layout.tsx @@ -21,14 +21,15 @@ export default function RootLayout({ children: React.ReactNode; }) { const wsBase = env.BACKEND_URL.replace(/^http:/, 'ws:').replace(/^https:/, 'wss:') + const feedbackUrl = env.FEEDBACK_URL return ( - + - + - +
{children}
diff --git a/components/frontend/src/app/page.tsx b/components/frontend/src/app/page.tsx index 8e1efe49c..ff947ecfc 100644 --- a/components/frontend/src/app/page.tsx +++ b/components/frontend/src/app/page.tsx @@ -16,7 +16,7 @@ export default function HomeRedirect() {
-

Redirecting to RFE Wokspaces...

+

Redirecting to Workspaces...

diff --git a/components/frontend/src/app/projects/[name]/layout.tsx b/components/frontend/src/app/projects/[name]/layout.tsx index aebc81ab7..3b7d26697 100644 --- a/components/frontend/src/app/projects/[name]/layout.tsx +++ b/components/frontend/src/app/projects/[name]/layout.tsx @@ -1,57 +1,5 @@ -"use client"; - -import Link from "next/link"; -import { usePathname } from "next/navigation"; -import { cn } from "@/lib/utils"; -import { Button } from "@/components/ui/button"; -import { Home, KeyRound, Settings, Users, Sparkles, ArrowLeft, GitBranch } from "lucide-react"; - export default function ProjectSectionLayout({ children }: { children: React.ReactNode; params: Promise<{ name: string }> }) { - const pathname = usePathname(); - - const base = pathname?.split("/").slice(0, 3).join("/") || "/projects"; - // base is /projects/[name] - - const items = [ - { href: base, label: "Overview", icon: Home }, - { href: `${base}/rfe`, label: "RFE Workspaces", icon: GitBranch }, - { href: `${base}/sessions`, label: "Sessions", icon: Sparkles }, - { href: `${base}/keys`, label: "Keys", icon: KeyRound }, - { href: `${base}/permissions`, label: "Permissions", icon: Users }, - { href: `${base}/settings`, label: "Settings", icon: Settings }, - ]; - - return ( -
-
- -
{children}
-
-
- ); + return <>{children}; } diff --git a/components/frontend/src/app/projects/[name]/page.tsx b/components/frontend/src/app/projects/[name]/page.tsx index 10ad504c7..7a1d327d3 100644 --- a/components/frontend/src/app/projects/[name]/page.tsx +++ b/components/frontend/src/app/projects/[name]/page.tsx @@ -1,116 +1,114 @@ 'use client'; -import { useCallback } from 'react'; -import { useParams, useRouter } from 'next/navigation'; -import { formatDistanceToNow } from 'date-fns'; -import { RefreshCw } from 'lucide-react'; +import { useState, useEffect } from 'react'; +import { useParams, useSearchParams } from 'next/navigation'; +import { Star, Settings, Users, RefreshCw } from 'lucide-react'; +import { cn } from '@/lib/utils'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Label } from '@/components/ui/label'; -import { ProjectSubpageHeader } from '@/components/project-subpage-header'; -import { ErrorMessage } from '@/components/error-message'; +import { PageHeader } from '@/components/page-header'; import { Breadcrumbs } from '@/components/breadcrumbs'; -import { useProject } from '@/services/queries'; +import { SessionsSection } from '@/components/workspace-sections/sessions-section'; +import { SharingSection } from '@/components/workspace-sections/sharing-section'; +import { SettingsSection } from '@/components/workspace-sections/settings-section'; +import { useProject } from '@/services/queries/use-projects'; + +type Section = 'sessions' | 'sharing' | 'settings'; export default function ProjectDetailsPage() { const params = useParams(); - const router = useRouter(); + const searchParams = useSearchParams(); const projectName = params?.name as string; + + // Fetch project data for display name and description + const { data: project, isLoading: projectLoading } = useProject(projectName); + + // Initialize active section from query parameter or default to 'sessions' + const initialSection = (searchParams.get('section') as Section) || 'sessions'; + const [activeSection, setActiveSection] = useState
(initialSection); - // React Query hook replaces all manual state management - const { data: project, isLoading, error, refetch } = useProject(projectName); + // Update active section when query parameter changes + useEffect(() => { + const sectionParam = searchParams.get('section') as Section; + if (sectionParam && ['sessions', 'sharing', 'settings'].includes(sectionParam)) { + setActiveSection(sectionParam); + } + }, [searchParams]); - const handleRefresh = useCallback(() => { - refetch(); - }, [refetch]); + const navItems = [ + { id: 'sessions' as Section, label: 'Sessions', icon: Star }, + { id: 'sharing' as Section, label: 'Sharing', icon: Users }, + { id: 'settings' as Section, label: 'Workspace Settings', icon: Settings }, + ]; // Loading state - if (!projectName || (isLoading && !project)) { + if (!projectName || projectLoading) { return (
- Loading project... + Loading workspace...
); } - // Error state (no project loaded) - if (error && !project) { - return ( -
- - -

{error instanceof Error ? error.message : 'Failed to load project'}

-
- - -
-
-
-
- ); - } - - if (!project) return null; - return ( -
- - {project.displayName || project.name}} - description={<>{projectName}} - actions={ - - } - /> - - {/* Error state (with project loaded) */} - {error && project && ( -
- +
+ {/* Sticky header */} +
+
+ +
- )} +
+ +
+ {/* Content */} +
+ {/* Sidebar Navigation */} + -
-
- {/* Project Info */} - - - Project Information - - -
- -

- {project.description || 'No description provided'} -

-
-
- -

- {project.creationTimestamp && - formatDistanceToNow(new Date(project.creationTimestamp), { - addSuffix: true, - })} -

-
-
-
+ {/* Main Content */} + {activeSection === 'sessions' && } + {activeSection === 'sharing' && } + {activeSection === 'settings' && }
diff --git a/components/frontend/src/app/projects/[name]/permissions/error.tsx b/components/frontend/src/app/projects/[name]/permissions/error.tsx deleted file mode 100644 index 9ad3cc608..000000000 --- a/components/frontend/src/app/projects/[name]/permissions/error.tsx +++ /dev/null @@ -1,37 +0,0 @@ -'use client'; - -import { useEffect } from 'react'; -import { Button } from '@/components/ui/button'; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; -import { AlertCircle } from 'lucide-react'; - -export default function PermissionsError({ - error, - reset, -}: { - error: Error & { digest?: string }; - reset: () => void; -}) { - useEffect(() => { - console.error('Permissions page error:', error); - }, [error]); - - return ( -
- - -
- - Failed to load permissions -
- - {error.message || 'An unexpected error occurred while loading permissions.'} - -
- - - -
-
- ); -} diff --git a/components/frontend/src/app/projects/[name]/permissions/loading.tsx b/components/frontend/src/app/projects/[name]/permissions/loading.tsx deleted file mode 100644 index 843fdcf6a..000000000 --- a/components/frontend/src/app/projects/[name]/permissions/loading.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { TableSkeleton } from '@/components/skeletons'; - -export default function PermissionsLoading() { - return ; -} diff --git a/components/frontend/src/app/projects/[name]/permissions/page.tsx b/components/frontend/src/app/projects/[name]/permissions/page.tsx index 08bc483c3..a07590d5f 100644 --- a/components/frontend/src/app/projects/[name]/permissions/page.tsx +++ b/components/frontend/src/app/projects/[name]/permissions/page.tsx @@ -1,404 +1,19 @@ 'use client'; -import { useCallback, useMemo, useState } from 'react'; -import { useParams } from 'next/navigation'; -import { Eye, Edit, Shield, Users, User as UserIcon, Plus, RefreshCw, Loader2, Trash2, Info } from 'lucide-react'; - -import { Button } from '@/components/ui/button'; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; -import { Badge } from '@/components/ui/badge'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; -import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; -import { ProjectSubpageHeader } from '@/components/project-subpage-header'; -import { ErrorMessage } from '@/components/error-message'; -import { DestructiveConfirmationDialog } from '@/components/confirmation-dialog'; -import { Breadcrumbs } from '@/components/breadcrumbs'; - -import { useProject, useProjectPermissions, useAddProjectPermission, useRemoveProjectPermission } from '@/services/queries'; -import { successToast, errorToast } from '@/hooks/use-toast'; -import type { PermissionRole, SubjectType } from '@/types/project'; - -const ROLE_DEFINITIONS = { - view: { - label: 'View', - description: 'Can see sessions and duplicate to their own project', - permissions: ['sessions:read', 'sessions:duplicate'] as const, - color: 'bg-blue-100 text-blue-800', - icon: Eye, - }, - edit: { - label: 'Edit', - description: 'Can create sessions in the project', - permissions: ['sessions:read', 'sessions:create', 'sessions:duplicate'] as const, - color: 'bg-green-100 text-green-800', - icon: Edit, - }, - admin: { - label: 'Admin', - description: 'Full project management access', - permissions: ['*'] as const, - color: 'bg-purple-100 text-purple-800', - icon: Shield, - }, -} as const; - -type GrantPermissionForm = { - subjectType: SubjectType; - subjectName: string; - role: PermissionRole; -}; +import { useEffect } from 'react'; +import { useParams, useRouter } from 'next/navigation'; export default function PermissionsPage() { const params = useParams(); + const router = useRouter(); const projectName = params?.name as string; - // React Query hooks replace all manual state management - const { data: project } = useProject(projectName); - const { data: permissions = [], isLoading, error, refetch } = useProjectPermissions(projectName); - const addPermissionMutation = useAddProjectPermission(); - const removePermissionMutation = useRemoveProjectPermission(); - - // Local UI state - const [showGrantDialog, setShowGrantDialog] = useState(false); - const [grantForm, setGrantForm] = useState({ - subjectType: 'group', - subjectName: '', - role: 'view', - }); - const [grantError, setGrantError] = useState(null); - const userRole: PermissionRole | undefined = undefined; // TODO: Fetch from /projects/:name/access - - const [showRevokeDialog, setShowRevokeDialog] = useState(false); - const [toRevoke, setToRevoke] = useState<{ subjectType: SubjectType; subjectName: string; role: PermissionRole } | null>(null); - - // Check if user is admin - for now assume admin, or fetch from a separate endpoint - // TODO: Implement proper user role fetching from /projects/:name/access - const isAdmin = userRole === 'admin' || userRole === undefined; // Default to admin for now - - const handleGrant = useCallback(() => { - if (!grantForm.subjectName.trim()) { - setGrantError(`${grantForm.subjectType === 'group' ? 'Group' : 'User'} name is required`); - return; - } - - const key = `${grantForm.subjectType}:${grantForm.subjectName}`.toLowerCase(); - if (permissions.some((i) => `${i.subjectType}:${i.subjectName}`.toLowerCase() === key)) { - setGrantError('This subject already has access to the project'); - return; + // Redirect to main workspace page + useEffect(() => { + if (projectName) { + router.replace(`/projects/${projectName}?section=sharing`); } + }, [projectName, router]); - setGrantError(null); - addPermissionMutation.mutate( - { - projectName, - permission: { - subjectType: grantForm.subjectType, - subjectName: grantForm.subjectName, - role: grantForm.role, - }, - }, - { - onSuccess: () => { - successToast(`Permission granted to ${grantForm.subjectName} successfully`); - setShowGrantDialog(false); - setGrantForm({ subjectType: 'group', subjectName: '', role: 'view' }); - }, - onError: (error) => { - const message = error instanceof Error ? error.message : 'Failed to grant permission'; - setGrantError(message); - errorToast(message); - }, - } - ); - }, [grantForm, permissions, projectName, addPermissionMutation]); - - const handleRevoke = useCallback(() => { - if (!toRevoke) return; - - removePermissionMutation.mutate( - { - projectName, - subjectType: toRevoke.subjectType, - subjectName: toRevoke.subjectName, - }, - { - onSuccess: () => { - successToast(`Permission revoked from ${toRevoke.subjectName} successfully`); - setShowRevokeDialog(false); - setToRevoke(null); - }, - onError: (error) => { - errorToast(error instanceof Error ? error.message : 'Failed to revoke permission'); - }, - } - ); - }, [toRevoke, projectName, removePermissionMutation]); - - const emptyState = useMemo( - () => ( -
- -

No users or groups have access yet

- {isAdmin && ( - - )} -
- ), - [isAdmin] - ); - - if (!projectName || (isLoading && permissions.length === 0)) { - return ( -
-
- - Loading permissions... -
-
- ); - } - - return ( -
- - Permissions} - description={<>Manage user and group access for {project?.displayName || projectName}} - actions={ - <> - - {isAdmin && ( - - )} - - } - /> - - {/* Error state */} - {error && refetch()} />} - - {/* Mutation errors */} - {addPermissionMutation.isError && ( -
- -
- )} - {removePermissionMutation.isError && ( -
- -
- )} - - {!isAdmin && ( - - - -

- You have {userRole || 'view'} access. Only admins can grant or revoke permissions. -

-
-
- )} - - - - - - Permissions - - Users and groups with access to this project and their roles - - - {permissions.length > 0 ? ( - - - - Subject - Type - Role - {isAdmin && Actions} - - - - {permissions.map((p) => { - const roleConfig = ROLE_DEFINITIONS[p.role]; - const RoleIcon = roleConfig.icon; - const isRevokingThis = - removePermissionMutation.isPending && - removePermissionMutation.variables?.subjectName === p.subjectName && - removePermissionMutation.variables?.subjectType === p.subjectType; - - return ( - - {p.subjectName} - -
- {p.subjectType === 'group' ? ( - - ) : ( - - )} - {p.subjectType === 'group' ? 'Group' : 'User'} -
-
- - - - {roleConfig.label} - - - - {isAdmin && ( - - - - )} -
- ); - })} -
-
- ) : ( - emptyState - )} -
-
- - {/* Grant Permission Dialog */} - - - - Grant Permission - Add a user or group to this project with a role - -
-
- - { - if (addPermissionMutation.isPending) return; - setGrantForm((prev) => ({ ...prev, subjectType: value as SubjectType })); - }} - > - - Group - User - - -
-
- - setGrantForm((prev) => ({ ...prev, subjectName: e.target.value }))} - disabled={addPermissionMutation.isPending} - /> -
-
- -
- {Object.entries(ROLE_DEFINITIONS).map(([roleKey, roleConfig]) => { - const RoleIcon = roleConfig.icon; - const id = `role-${roleKey}`; - return ( -
- setGrantForm((prev) => ({ ...prev, role: roleKey as PermissionRole }))} - disabled={addPermissionMutation.isPending} - /> - -
- ); - })} -
-
- {grantError &&
{grantError}
} -
- - - - -
-
- - {/* Revoke Permission Dialog */} - -
- ); + return null; } diff --git a/components/frontend/src/app/projects/[name]/rfe/[id]/page.tsx b/components/frontend/src/app/projects/[name]/rfe/[id]/page.tsx index ea97d2107..2fc068bf3 100644 --- a/components/frontend/src/app/projects/[name]/rfe/[id]/page.tsx +++ b/components/frontend/src/app/projects/[name]/rfe/[id]/page.tsx @@ -6,8 +6,11 @@ import { useParams, useRouter } from "next/navigation"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"; import { WorkflowPhase } from "@/types/agentic-session"; -import { ArrowLeft, Loader2 } from "lucide-react"; +import { ArrowLeft, Loader2, Bot } from "lucide-react"; +import { Badge } from "@/components/ui/badge"; +import { Checkbox } from "@/components/ui/checkbox"; import RepoBrowser from "@/components/RepoBrowser"; import type { GitHubFork } from "@/types"; import { Breadcrumbs } from "@/components/breadcrumbs"; @@ -16,7 +19,8 @@ import { RfePhaseCards } from "./rfe-phase-cards"; import { RfeWorkspaceCard } from "./rfe-workspace-card"; import { RfeHeader } from "./rfe-header"; import { RfeAgentsCard } from "./rfe-agents-card"; -import { useRfeWorkflow, useRfeWorkflowSessions, useDeleteRfeWorkflow, useRfeWorkflowSeeding, useSeedRfeWorkflow, useUpdateRfeWorkflow, useRepoBlob, useRepoTree, useOpenJiraIssue } from "@/services/queries"; +import { AVAILABLE_AGENTS } from "@/lib/agents"; +import { useRfeWorkflow, useRfeWorkflowSessions, useDeleteRfeWorkflow, useRfeWorkflowSeeding, useSeedRfeWorkflow, useUpdateRfeWorkflow, useRepoBlob, useRepoTree, useOpenJiraIssue, useRfeWorkflowAgents } from "@/services/queries"; export default function ProjectRFEDetailPage() { const params = useParams(); @@ -32,6 +36,7 @@ export default function ProjectRFEDetailPage() { const seedWorkflowMutation = useSeedRfeWorkflow(); const updateWorkflowMutation = useUpdateRfeWorkflow(); const { openJiraForPath } = useOpenJiraIssue(project, id); + const { data: repoAgents = AVAILABLE_AGENTS, isLoading: loadingAgents } = useRfeWorkflowAgents(project, id); // Extract repo info from workflow const repo = workflow?.umbrellaRepo?.url.replace(/^https?:\/\/(?:www\.)?github.com\//i, '').replace(/\.git$/i, '') || ''; @@ -200,7 +205,7 @@ export default function ProjectRFEDetailPage() { } ); }); - }, [project, id, updateWorkflowMutation, load, refetchSeeding]); + }, [project, id, updateWorkflowMutation, load, refetchSeeding, seedWorkflowMutation]); if (loading) return ( @@ -242,7 +247,7 @@ export default function ProjectRFEDetailPage() {
- - - + {/* Two Column Layout */} +
+ {/* Left Column - Tabs */} +
+ + + Overview + Sessions + {upstreamRepo ? Repository : null} + - - - Overview - Sessions - {upstreamRepo ? Repository : null} - + + { await load(); }} + onLoadSessions={async () => { await loadSessions(); }} + onError={setError} + onOpenJira={openJiraForPath} + /> + - - { await load(); }} - onLoadSessions={async () => { await loadSessions(); }} - onError={setError} - onOpenJira={openJiraForPath} - /> - + + + - - - + + + + +
- - - - - + {/* Right Column - Agents Accordion */} +
+ + + + + +
+ + Agents +
+
+ + {loadingAgents ? ( +
+ +
+ ) : repoAgents.length === 0 ? ( +
+ +

No agents found in repository .claude/agents directory

+

Seed the repository to add agent definitions

+
+ ) : ( + <> +
+ {repoAgents.map((agent) => { + const isSelected = selectedAgents.includes(agent.persona); + return ( +
+ +
+ ); + })} +
+ {selectedAgents.length > 0 && ( +
+
Selected Agents ({selectedAgents.length})
+
+ {selectedAgents.map(persona => { + const agent = repoAgents.find(a => a.persona === persona); + return agent ? ( + + {agent.name} + + ) : null; + })} +
+
+ )} + + )} +
+
+
+
+
+
+
diff --git a/components/frontend/src/app/projects/[name]/rfe/new/page.tsx b/components/frontend/src/app/projects/[name]/rfe/new/page.tsx index 9b6ba76b1..8842618f6 100644 --- a/components/frontend/src/app/projects/[name]/rfe/new/page.tsx +++ b/components/frontend/src/app/projects/[name]/rfe/new/page.tsx @@ -157,7 +157,7 @@ export default function ProjectNewRFEWorkflowPage() {
(""); const [sessionName, setSessionName] = useState(""); - const [activeTab, setActiveTab] = useState("overview"); const [promptExpanded, setPromptExpanded] = useState(false); const [chatInput, setChatInput] = useState(""); const [backHref, setBackHref] = useState(null); @@ -63,6 +81,16 @@ export default function ProjectSessionDetailPage({ const [contentPodSpawning, setContentPodSpawning] = useState(false); const [contentPodReady, setContentPodReady] = useState(false); const [contentPodError, setContentPodError] = useState(null); + const [selectedAgents, setSelectedAgents] = useState([]); + const [editRepoDialogOpen, setEditRepoDialogOpen] = useState(false); + const [selectedWorkflow, setSelectedWorkflow] = useState("none"); + const [githubModalOpen, setGithubModalOpen] = useState(false); + const [specRepoUrl, setSpecRepoUrl] = useState("https://github.com/org/repo.git"); + const [baseBranch, setBaseBranch] = useState("main"); + const [openAccordionItems, setOpenAccordionItems] = useState(["workflows"]); + const [contextModalOpen, setContextModalOpen] = useState(false); + const [contextUrl, setContextUrl] = useState(""); + const [contextBranch, setContextBranch] = useState("main"); // Extract params useEffect(() => { @@ -77,10 +105,24 @@ export default function ProjectSessionDetailPage({ }); }, [params]); + // Open spec-repository accordion when plan-feature or develop-feature is selected + useEffect(() => { + if (selectedWorkflow === "plan-feature" || selectedWorkflow === "develop-feature") { + setOpenAccordionItems(prev => { + if (!prev.includes("spec-repository")) { + return [...prev, "spec-repository"]; + } + return prev; + }); + } + }, [selectedWorkflow]); + // React Query hooks const { data: session, isLoading, error, refetch: refetchSession } = useSession(projectName, sessionName); const { data: messages = [] } = useSessionMessages(projectName, sessionName, session?.status?.phase); const { data: k8sResources } = useSessionK8sResources(projectName, sessionName); + const { data: githubStatus } = useGitHubStatus(); + const { data: secretsValues } = useSecretsValues(projectName); const stopMutation = useStopSession(); const deleteMutation = useDeleteSession(); const continueMutation = useContinueSession(); @@ -89,6 +131,27 @@ export default function ProjectSessionDetailPage({ const pushToGitHubMutation = usePushSessionToGitHub(); const abandonChangesMutation = useAbandonSessionChanges(); const writeWorkspaceFileMutation = useWriteWorkspaceFile(); + + // Get RFE workflow ID from session if this is an RFE session + const rfeWorkflowId = session?.metadata?.labels?.['rfe-workflow']; + const { data: rfeWorkflow, refetch: refetchWorkflow } = useRfeWorkflow(projectName, rfeWorkflowId || ''); + const { data: repoAgents = [], isLoading: loadingAgents } = useRfeWorkflowAgents( + projectName, + rfeWorkflowId || '' + ); + const { data: seedingData, isLoading: checkingSeeding, error: seedingQueryError, refetch: refetchSeeding } = useRfeWorkflowSeeding( + projectName, + rfeWorkflowId || '' + ); + const seedWorkflowMutation = useSeedRfeWorkflow(); + const updateWorkflowMutation = useUpdateRfeWorkflow(); + const createWorkflowMutation = useCreateRfeWorkflow(); + + // Fetch artifacts for the spec repository + const { data: workflowArtifacts = [], isLoading: artifactsLoading, refetch: refetchArtifacts } = useWorkflowArtifacts( + projectName, + rfeWorkflowId || '' + ); // Workspace state const [wsSelectedPath, setWsSelectedPath] = useState(); @@ -111,7 +174,7 @@ export default function ProjectSessionDetailPage({ projectName, sessionName, undefined, - { enabled: activeTab === 'workspace' } + { enabled: true } ); // Update tree when workspace items change @@ -141,6 +204,16 @@ export default function ProjectSessionDetailPage({ }); }, [queryClient]); + // Handler to refresh spec repository artifacts + const handleRefreshArtifacts = useCallback(async () => { + if (!rfeWorkflowId) return; + // Invalidate artifacts query to force fresh fetch + await queryClient.invalidateQueries({ + queryKey: rfeKeys.artifacts(projectName, rfeWorkflowId), + }); + await refetchArtifacts(); + }, [queryClient, projectName, rfeWorkflowId, refetchArtifacts]); + // GitHub diff state const [busyRepo, setBusyRepo] = useState>({}); @@ -450,7 +523,6 @@ export default function ProjectSessionDetailPage({ { onSuccess: () => { setChatInput(""); - setActiveTab('messages'); }, onError: (err) => errorToast(err instanceof Error ? err.message : "Failed to send message"), } @@ -477,6 +549,67 @@ export default function ProjectSessionDetailPage({ ); }; + const handleSeedWorkflow = useCallback(async () => { + if (!rfeWorkflowId) return; + return new Promise((resolve, reject) => { + seedWorkflowMutation.mutate( + { projectName, workflowId: rfeWorkflowId }, + { + onSuccess: () => { + successToast("Repository seeded successfully"); + refetchSeeding(); + resolve(); + }, + onError: (err) => { + errorToast(err instanceof Error ? err.message : "Failed to seed repository"); + reject(err); + }, + } + ); + }); + }, [projectName, rfeWorkflowId, seedWorkflowMutation, refetchSeeding]); + + const handleUpdateRepositories = useCallback(async (data: { umbrellaRepo: { url: string; branch?: string }; supportingRepos: { url: string; branch?: string }[] }) => { + if (!rfeWorkflowId) return; + return new Promise((resolve, reject) => { + updateWorkflowMutation.mutate( + { + projectName, + workflowId: rfeWorkflowId, + data: { + umbrellaRepo: data.umbrellaRepo, + supportingRepos: data.supportingRepos, + }, + }, + { + onSuccess: () => { + successToast("Repositories updated successfully"); + refetchWorkflow(); + refetchSeeding(); + seedWorkflowMutation.reset(); + resolve(); + }, + onError: (err) => { + errorToast(err instanceof Error ? err.message : "Failed to update repositories"); + reject(err); + }, + } + ); + }); + }, [projectName, rfeWorkflowId, updateWorkflowMutation, refetchWorkflow, refetchSeeding, seedWorkflowMutation]); + + // Seeding status from React Query + const isSeeded = seedingData?.isSeeded || false; + const seedingError = seedWorkflowMutation.error?.message || seedingQueryError?.message; + const hasCheckedSeeding = seedingData !== undefined || !!seedingQueryError; + const seedingStatus = { + checking: checkingSeeding, + isSeeded, + error: seedingError, + hasChecked: hasCheckedSeeding, + }; + const workflowWorkspace = rfeWorkflow?.workspacePath || (rfeWorkflowId ? `/rfe-workflows/${rfeWorkflowId}/workspace` : ''); + // Check if session is completed const sessionCompleted = ( session?.status?.phase === 'Completed' || @@ -484,14 +617,14 @@ export default function ProjectSessionDetailPage({ session?.status?.phase === 'Stopped' ); - // Auto-spawn content pod when workspace tab clicked on completed session + // Auto-spawn content pod on completed session // Don't auto-retry if we already encountered an error - user must explicitly retry useEffect(() => { - if (activeTab === 'workspace' && sessionCompleted && !contentPodReady && !contentPodSpawning && !contentPodError) { + if (sessionCompleted && !contentPodReady && !contentPodSpawning && !contentPodError) { spawnContentPodAsync(); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [activeTab, sessionCompleted, contentPodReady, contentPodSpawning, contentPodError]); + }, [sessionCompleted, contentPodReady, contentPodSpawning, contentPodError]); const spawnContentPodAsync = async () => { if (!projectName || !sessionName) return; @@ -691,10 +824,12 @@ export default function ProjectSessionDetailPage({ // Loading state - also check if params are loaded if (isLoading || !projectName || !sessionName) { return ( -
-
-
- Loading session... +
+
+
+
+ Loading session... +
); @@ -703,258 +838,834 @@ export default function ProjectSessionDetailPage({ // Error state if (error || !session) { return ( -
-
- - - +
+
+
+ + +
+
+
+
+ + +

Error: {error instanceof Error ? error.message : "Session not found"}

+
+
+
- - -

Error: {error instanceof Error ? error.message : "Session not found"}

-
-
); } return ( -
- - -
- {/* Header */} -
+ <> + {rfeWorkflow && ( + { + await handleUpdateRepositories(data); + setEditRepoDialogOpen(false); + }} + isSaving={updateWorkflowMutation.isPending} + /> + )} +
+ {/* Sticky header */} +
+
+ + + {session.spec.displayName || session.metadata.name} + + {session.status?.phase || "Pending"} + +
+ } + description={ +
+ {session.spec.displayName && ( +
{session.metadata.name}
+ )} +
+ Created {formatDistanceToNow(new Date(session.metadata.creationTimestamp), { addSuffix: true })} +
+
+ } + actions={ + <> + {/* Continue button for completed sessions */} + {(session.status?.phase === "Completed" || session.status?.phase === "Failed" || session.status?.phase === "Stopped") && ( + + )} + + {/* Stop button for active sessions */} + {(session.status?.phase === "Pending" || session.status?.phase === "Creating" || session.status?.phase === "Running") && ( + + )} + + {/* Actions dropdown menu */} + + + + + + refetchSession()} + trigger={ + e.preventDefault()}> + + Clone + + } + /> + + + + {deleteMutation.isPending ? "Deleting..." : "Delete"} + + + + + } + /> +
+
+ +
+
+ {/* Two Column Layout */} +
+ {/* Left Column - Accordions */}
-

- {session.spec.displayName || session.metadata.name} - - {session.status?.phase || "Pending"} - -

- {session.spec.displayName && ( -
{session.metadata.name}
- )} -
- Created {formatDistanceToNow(new Date(session.metadata.creationTimestamp), { addSuffix: true })} -
-
-
- {/* Continue button for completed sessions (converts headless to interactive) */} - {(session.status?.phase === "Completed" || session.status?.phase === "Failed" || session.status?.phase === "Stopped") && ( - - )} + + + + Workflows + + +
+
+ + +
+

+ Workflows provide Ambient agents with structured steps to follow toward more complex goals. +

+
+
+
- {/* Stop button for active sessions */} - {(session.status?.phase === "Pending" || session.status?.phase === "Creating" || session.status?.phase === "Running") && ( - - )} + {/* Only show Spec Repository for plan-feature and develop-feature workflows */} + {(selectedWorkflow === "plan-feature" || selectedWorkflow === "develop-feature") && ( + + + Spec Repository + + + {!rfeWorkflowId ? ( +
+ +

+ A spec repository is required to store agent config and workflow artifacts. +

+ +
+ ) : ( +
+
Workspace: {workflowWorkspace}
- {/* Actions dropdown menu */} - - - - - - + + Feature Branch + + All modifications will occur on feature branch{' '} + + {rfeWorkflow.branchName} + + {' '}for all supplied repositories. + + + )} + + {(rfeWorkflow?.umbrellaRepo || (rfeWorkflow?.supportingRepos || []).length > 0) && ( +
+ {rfeWorkflow.umbrellaRepo && ( +
+
+ Spec Repo: {rfeWorkflow.umbrellaRepo.url} +
+ {rfeWorkflow.umbrellaRepo.branch && ( +
+ Base branch: {rfeWorkflow.umbrellaRepo.branch} + {rfeWorkflow.branchName && ( + → Feature branch {rfeWorkflow.branchName} {isSeeded ? 'set up' : 'will be set up'} + )} +
+ )} +
+ )} + {(rfeWorkflow.supportingRepos || []).map( + (r: { url: string; branch?: string }, i: number) => ( +
+
+ Supporting: {r.url} +
+ {r.branch && ( +
+ Base branch: {r.branch} + {rfeWorkflow.branchName && ( + → Feature branch {rfeWorkflow.branchName} {isSeeded ? 'set up' : 'will be set up'} + )} +
+ )} +
+ ) + )} +
+ )} + + {!isSeeded && !seedingStatus.checking && seedingStatus.hasChecked && rfeWorkflow?.umbrellaRepo && ( + + + Spec Repository Not Seeded + +

+ Before you can start working on phases, the spec repository needs to be seeded. + This will: +

+
    +
  • Set up the feature branch{rfeWorkflow.branchName && ` (${rfeWorkflow.branchName})`} from the base branch
  • +
  • Add Spec-Kit template files for spec-driven development
  • +
  • Add agent definition files in the .claude directory
  • +
+ {seedingError && ( +
+ Seeding Failed: {seedingError} +
+ )} +
+ + +
+
+
+ )} + + {seedingStatus.checking && rfeWorkflow?.umbrellaRepo && ( +
+ + Checking repository seeding status... +
+ )} + + {isSeeded && ( +
+
+ + Repository seeded and ready +
+ +
+ )} +
+ )} + + {/* Spec Repository Files - Only show after spec repository is seeded */} + {rfeWorkflowId && isSeeded && ( +
+
+
+
+ + Files + {!artifactsLoading && ( + + ({workflowArtifacts.length} {workflowArtifacts.length === 1 ? 'file' : 'files'}) + + )} +
+ +
+
+ {artifactsLoading ? ( +
+ Loading files... +
+ ) : workflowArtifacts.length === 0 ? ( +
+ +

No files yet

+

Files will appear here as agents create artifacts

+
+ ) : ( +
+ {workflowArtifacts.map((artifact) => { + const isDirectory = artifact.type === 'tree'; + const Icon = isDirectory ? Folder : FileText; + const iconColor = isDirectory ? 'text-yellow-600' : 'text-blue-500'; + + return ( +
+ +
+
{artifact.name}
+ {artifact.path !== artifact.name && ( +
{artifact.path}
+ )} +
+ {!isDirectory && artifact.size > 0 && ( +
+ {(artifact.size / 1024).toFixed(1)} KB +
+ )} +
+ ); + })} +
+ )} +
+
+
+ )} +
+
+ )} + + + + Agents + + + {loadingAgents ? ( +
+ +
+ ) : !rfeWorkflowId || repoAgents.length === 0 ? ( +
+ +

No agents found in repository .claude/agents directory

+

Seed the repository to add agent definitions

+
+ ) : ( + <> +
+ {repoAgents.map((agent) => { + const isSelected = selectedAgents.includes(agent.persona); + return ( +
+ +
+ ); + })} +
+ {selectedAgents.length > 0 && ( +
+
Selected Agents ({selectedAgents.length})
+
+ {selectedAgents.map(persona => { + const agent = repoAgents.find(a => a.persona === persona); + return agent ? ( + + {agent.name} + + ) : null; + })} +
+
+ )} + + )} +
+
+ + + + Context + + + {!rfeWorkflowId || !rfeWorkflow?.supportingRepos || rfeWorkflow.supportingRepos.length === 0 ? ( +
+
+ +
+

No associated repositories configured

+

Add context from external sources

+ + {!rfeWorkflowId && ( +

Configure a spec repository first

+ )} +
+ ) : ( +
+
+ {rfeWorkflow.supportingRepos.map((repo, index) => ( +
+ +
+
{repo.url}
+ {repo.branch && ( +
+ Branch: {repo.branch} +
+ )} +
+
+ ))} +
+ +
+ )} +
+
+ + + + Artifacts + + + + + + + + + Session Details + + +
+
+
+ Status: + + {session.status?.phase || "Pending"} + +
+
+ Model: + {session.spec.llmSettings.model} +
+
+ Temperature: + {session.spec.llmSettings.temperature} +
+
+ Mode: + {session.spec?.interactive ? "Interactive" : "Headless"} +
+ {session.status?.startTime && ( +
+ Started: + {format(new Date(session.status.startTime), "PPp")} +
+ )} +
+ Duration: + {typeof durationMs === "number" ? `${durationMs}ms` : "-"} +
+ {k8sResources?.pvcName && ( +
+ PVC: + {k8sResources.pvcName} +
+ )} + {k8sResources?.pvcSize && ( +
+ PVC Size: + {k8sResources.pvcSize} +
+ )} + {session.status?.jobName && ( +
+ K8s Job: + {session.status.jobName} +
+ )} +
+ Messages: + {messages.length} +
+
+ Session prompt: + +
+ {promptExpanded && session.spec.prompt && ( +
+

{session.spec.prompt}

+
+ )} +
+
+
+
+
+
+ + {/* Right Column - Messages (Always Visible) */} +
+ + + refetchSession()} - trigger={ - e.preventDefault()}> - - Clone - - } + streamMessages={streamMessages} + chatInput={chatInput} + setChatInput={setChatInput} + onSendChat={() => Promise.resolve(sendChat())} + onInterrupt={() => Promise.resolve(handleInterrupt())} + onEndSession={() => Promise.resolve(handleEndSession())} + onGoToResults={() => {}} + onContinue={handleContinue} /> - - - - {deleteMutation.isPending ? "Deleting..." : "Delete"} - - - + +
+
+
+
+ + {/* Add Spec Repository Modal */} + + + + Add spec repository + + Set the spec repo and optional supporting repos. Base branch is the branch from which the feature branch will be set up. All modifications will be made to the feature branch. + + + + {!githubStatus?.installed && !secretsValues?.some(secret => secret.key === 'GIT_TOKEN' && secret.value) && ( +
+ +
+ )} - {/* Stats */} -
- - -
Duration
-
{typeof durationMs === "number" ? `${durationMs} ms` : "-"}
-
-
- - -
Messages
-
{messages.length}
-
-
- - -
Agents
-
{subagentStats.uniqueCount > 0 ? subagentStats.uniqueCount : "-"}
-
-
+
+
+ + setSpecRepoUrl(e.target.value)} + /> +

+ The spec repository contains your feature specifications, planning documents, and agent configurations for this RFE workspace +

+
+ +
+ + setBaseBranch(e.target.value)} + /> +
+ +
+ + +
- {/* Tabs */} - - - Overview - Messages - Workspace - Results - - - - { - const repo = session.spec.repos?.[idx]; - if (!repo) return; - - setBusyRepo((b) => ({ ...b, [idx]: 'push' })); - const folder = deriveRepoFolderFromUrl(repo.input.url); - const repoPath = `/sessions/${sessionName}/workspace/${folder}`; - - pushToGitHubMutation.mutate( - { projectName, sessionName, repoIndex: idx, repoPath }, - { - onSuccess: () => { - refetchDiffs(); - successToast('Changes pushed to GitHub'); + + + + + +
+ + {/* Add Context Modal */} + + + + Add Context + + Add external context sources to enhance agent understanding + + + +
+
+ + setContextUrl(e.target.value)} /> - - - - Promise.resolve(sendChat())} - onInterrupt={() => Promise.resolve(handleInterrupt())} - onEndSession={() => Promise.resolve(handleEndSession())} - onGoToResults={() => setActiveTab('results')} - onContinue={handleContinue} +

+ Currently supports GitHub repositories for code context +

+
+ +
+ + setContextBranch(e.target.value)} /> - +

+ Leave empty to use the default branch +

+
- - {sessionCompleted && !contentPodReady ? ( - -
- {contentPodSpawning ? ( - <> -
-
-
-

Starting workspace viewer...

-

This may take up to 30 seconds

- - ) : ( - <> -

- Session has completed. To view and edit your workspace files, please start a workspace viewer. -

- - - )} -
- + + + + Google Drive and Jira support coming soon + + +
+ + + +
-
+ + + + + ); } diff --git a/components/frontend/src/app/projects/[name]/sessions/error.tsx b/components/frontend/src/app/projects/[name]/sessions/error.tsx deleted file mode 100644 index 29ce50d08..000000000 --- a/components/frontend/src/app/projects/[name]/sessions/error.tsx +++ /dev/null @@ -1,37 +0,0 @@ -'use client'; - -import { useEffect } from 'react'; -import { Button } from '@/components/ui/button'; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; -import { AlertCircle } from 'lucide-react'; - -export default function SessionsError({ - error, - reset, -}: { - error: Error & { digest?: string }; - reset: () => void; -}) { - useEffect(() => { - console.error('Sessions page error:', error); - }, [error]); - - return ( -
- - -
- - Failed to load sessions -
- - {error.message || 'An unexpected error occurred while loading sessions.'} - -
- - - -
-
- ); -} diff --git a/components/frontend/src/app/projects/[name]/sessions/loading.tsx b/components/frontend/src/app/projects/[name]/sessions/loading.tsx deleted file mode 100644 index edec2cda6..000000000 --- a/components/frontend/src/app/projects/[name]/sessions/loading.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { TableSkeleton } from '@/components/skeletons'; - -export default function SessionsLoading() { - return ; -} diff --git a/components/frontend/src/app/projects/[name]/sessions/new/model-configuration.tsx b/components/frontend/src/app/projects/[name]/sessions/new/model-configuration.tsx index b5b7fe945..640693aa9 100644 --- a/components/frontend/src/app/projects/[name]/sessions/new/model-configuration.tsx +++ b/components/frontend/src/app/projects/[name]/sessions/new/model-configuration.tsx @@ -4,10 +4,12 @@ import { Control } from "react-hook-form"; import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"; +import { Checkbox } from "@/components/ui/checkbox"; const models = [ - { value: "claude-opus-4-1", label: "Claude Opus 4.1" }, { value: "claude-sonnet-4-5", label: "Claude Sonnet 4.5" }, + { value: "claude-opus-4-1", label: "Claude Opus 4.1" }, { value: "claude-haiku-4-5", label: "Claude Haiku 4.5" }, ]; @@ -19,100 +21,157 @@ type ModelConfigurationProps = { export function ModelConfiguration({ control }: ModelConfigurationProps) { return (
-
- ( - - Model - - - - )} - /> - - ( - - Temperature + {/* Model Selection */} + ( + + Model + field.onChange(parseFloat(e.target.value))} - /> + + + - Controls randomness (0.0 - 2.0) - - - )} - /> -
+ + {models.map((m) => ( + + {m.label} + + ))} + + + + + )} + /> -
- ( - - Max Output Tokens - - field.onChange(parseInt(e.target.value))} + {/* Advanced Settings Accordion */} + + + + Change Default Model Settings + + +
+ {/* Temperature and Timeout */} +
+ ( + + Temperature + + field.onChange(parseFloat(e.target.value))} + /> + + Controls randomness (0.0 - 2.0) + + + )} /> - - Maximum response length (100-8000) - - - )} - /> - ( - - Timeout (seconds) - - field.onChange(parseInt(e.target.value))} + ( + + Timeout (seconds) + + field.onChange(parseInt(e.target.value))} + /> + + Session timeout (60-1800 seconds) + + + )} /> - - Session timeout (60-1800 seconds) - - - )} - /> -
+
+ + {/* Max Output Tokens */} + ( + + Max Output Tokens + + field.onChange(parseInt(e.target.value))} + /> + + Maximum response length (100-8000) + + + )} + /> + + {/* Bring Your Own Key Section */} +
+ ( + + Bring Your Own Key + + + + + Optional: Use your own Anthropic API key for this session + + + + )} + /> + + ( + + + + +
+ + Save key for future sessions (encrypted) + +
+
+ )} + /> +
+
+ + +
); } diff --git a/components/frontend/src/app/projects/[name]/sessions/new/page.tsx b/components/frontend/src/app/projects/[name]/sessions/new/page.tsx index d84d5c4da..5ff0ea215 100644 --- a/components/frontend/src/app/projects/[name]/sessions/new/page.tsx +++ b/components/frontend/src/app/projects/[name]/sessions/new/page.tsx @@ -43,6 +43,9 @@ const formSchema = z autoPushOnComplete: z.boolean().default(false), // storage paths are not user-configurable anymore agentPersona: z.string().optional(), + // BYOK fields + anthropicApiKey: z.string().optional().default(""), + saveApiKeyForFuture: z.boolean().default(false), }) .superRefine((data, ctx) => { const isInteractive = Boolean(data.interactive); @@ -87,7 +90,7 @@ export default function NewProjectSessionPage({ params }: { params: Promise<{ na resolver: zodResolver(formSchema), defaultValues: { prompt: "", - model: "claude-3-7-sonnet-latest", + model: "claude-sonnet-4-5", temperature: 0.7, maxTokens: 4000, timeout: 300, @@ -96,6 +99,8 @@ export default function NewProjectSessionPage({ params }: { params: Promise<{ na agentPersona: "", repos: [], mainRepoIndex: 0, + anthropicApiKey: "", + saveApiKeyForFuture: false, }, }); @@ -193,7 +198,7 @@ export default function NewProjectSessionPage({ params }: { params: Promise<{ na
{ - stopSessionMutation.mutate( - { projectName, sessionName }, - { - onSuccess: () => { - successToast(`Session "${sessionName}" stopped successfully`); - }, - onError: (error) => { - errorToast(error instanceof Error ? error.message : 'Failed to stop session'); - }, - } - ); - }; - - - const handleDelete = async (sessionName: string) => { - if (!confirm(`Delete agentic session "${sessionName}"? This action cannot be undone.`)) return; - deleteSessionMutation.mutate( - { projectName, sessionName }, - { - onSuccess: () => { - successToast(`Session "${sessionName}" deleted successfully`); - }, - onError: (error) => { - errorToast(error instanceof Error ? error.message : 'Failed to delete session'); - }, - } - ); - }; - - const handleContinue = async (sessionName: string) => { - continueSessionMutation.mutate( - { projectName, parentSessionName: sessionName }, - { - onSuccess: () => { - successToast(`Session "${sessionName}" restarted successfully`); - }, - onError: (error) => { - errorToast(error instanceof Error ? error.message : 'Failed to restart session'); - }, - } - ); - }; - - // Loading state - if (!projectName || (isLoading && sessions.length === 0)) { - return ( -
-
- - Loading sessions... -
-
- ); - } - - // Sort sessions by creation time (newest first) - const sortedSessions = [...sessions].sort((a, b) => { - const aTime = a?.metadata?.creationTimestamp ? new Date(a.metadata.creationTimestamp).getTime() : 0; - const bTime = b?.metadata?.creationTimestamp ? new Date(b.metadata.creationTimestamp).getTime() : 0; - return bTime - aTime; - }); - - return ( -
- - Agentic Sessions} - description={<>Sessions scoped to this project} - actions={ - <> - - - - - - } - /> - - {/* Error state */} - {error && refetch()} />} - - - - Agentic Sessions ({sessions?.length || 0}) - Sessions scoped to this project - - - {sessions.length === 0 ? ( - (window.location.href = `/projects/${encodeURIComponent(projectName)}/sessions/new`), - }} - /> - ) : ( -
- - - - Name - Status - Mode - Model - Created - Cost - Actions - - - - {sortedSessions.map((session) => { - const sessionName = session.metadata.name; - const phase = session.status?.phase || 'Pending'; - const isActionPending = - (stopSessionMutation.isPending && stopSessionMutation.variables?.sessionName === sessionName) || - (deleteSessionMutation.isPending && deleteSessionMutation.variables?.sessionName === sessionName); - - return ( - - - -
-
{session.spec.displayName || session.metadata.name}
- {session.spec.displayName && ( -
{session.metadata.name}
- )} -
- -
- - - - - - {session.spec?.interactive ? 'Interactive' : 'Headless'} - - - - - {session.spec.llmSettings.model} - - - - {session.metadata?.creationTimestamp && - formatDistanceToNow(new Date(session.metadata.creationTimestamp), { addSuffix: true })} - - - {session.status?.total_cost_usd ? ( - ${session.status.total_cost_usd.toFixed(4)} - ) : ( - - )} - - - {isActionPending ? ( - - ) : ( - - )} - -
- ); - })} -
-
-
- )} -
-
-
- ); -} - -// Session actions component extracted for clarity -type SessionActionsProps = { - sessionName: string; - phase: string; - onStop: (sessionName: string) => void; - onContinue: (sessionName: string) => void; - onDelete: (sessionName: string) => void; -}; - -function SessionActions({ sessionName, phase, onStop, onContinue, onDelete }: SessionActionsProps) { - type RowAction = { - key: string; - label: string; - onClick: () => void; - icon: React.ReactNode; - className?: string; - }; - - const actions: RowAction[] = []; - - if (phase === 'Pending' || phase === 'Creating' || phase === 'Running') { - actions.push({ - key: 'stop', - label: 'Stop', - onClick: () => onStop(sessionName), - icon: , - className: 'text-orange-600', - }); - } - - // Allow continue for all completed sessions (converts headless to interactive) - if (phase === 'Completed' || phase === 'Failed' || phase === 'Stopped' || phase === 'Error') { - actions.push({ - key: 'continue', - label: 'Continue', - onClick: () => onContinue(sessionName), - icon: , - className: 'text-green-600', - }); - } - - // Delete is always available except when Creating - if (phase !== 'Creating') { - actions.push({ - key: 'delete', - label: 'Delete', - onClick: () => onDelete(sessionName), - icon: , - className: 'text-red-600', - }); - } - - // Single action: show as button - if (actions.length === 1) { - const action = actions[0]; - return ( - - ); - } - - // No actions: show disabled button - if (actions.length === 0) { - return ( - - ); - } + // Redirect to main workspace page (sessions is the default view) + useEffect(() => { + if (projectName) { + router.replace(`/projects/${projectName}`); + } + }, [projectName, router]); - // Multiple actions: show dropdown - return ( - - - - - - {actions.map((action) => ( - - {action.label} - - ))} - - - ); + return null; } diff --git a/components/frontend/src/app/projects/[name]/settings/error.tsx b/components/frontend/src/app/projects/[name]/settings/error.tsx deleted file mode 100644 index 84ba3e418..000000000 --- a/components/frontend/src/app/projects/[name]/settings/error.tsx +++ /dev/null @@ -1,37 +0,0 @@ -'use client'; - -import { useEffect } from 'react'; -import { Button } from '@/components/ui/button'; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; -import { AlertCircle } from 'lucide-react'; - -export default function SettingsError({ - error, - reset, -}: { - error: Error & { digest?: string }; - reset: () => void; -}) { - useEffect(() => { - console.error('Settings page error:', error); - }, [error]); - - return ( -
- - -
- - Failed to load settings -
- - {error.message || 'An unexpected error occurred while loading settings.'} - -
- - - -
-
- ); -} diff --git a/components/frontend/src/app/projects/[name]/settings/loading.tsx b/components/frontend/src/app/projects/[name]/settings/loading.tsx deleted file mode 100644 index 775621468..000000000 --- a/components/frontend/src/app/projects/[name]/settings/loading.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { FormSkeleton } from '@/components/skeletons'; - -export default function SettingsLoading() { - return ; -} diff --git a/components/frontend/src/app/projects/[name]/settings/page.tsx b/components/frontend/src/app/projects/[name]/settings/page.tsx index bd29a87b5..39584ddfd 100644 --- a/components/frontend/src/app/projects/[name]/settings/page.tsx +++ b/components/frontend/src/app/projects/[name]/settings/page.tsx @@ -1,479 +1,19 @@ -"use client"; +'use client'; -import { useEffect, useState } from "react"; -import { ProjectSubpageHeader } from "@/components/project-subpage-header"; -import { Breadcrumbs } from "@/components/breadcrumbs"; -import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; -import { Label } from "@/components/ui/label"; -import { Input } from "@/components/ui/input"; -import { Textarea } from "@/components/ui/textarea"; -import { Button } from "@/components/ui/button"; -import { RefreshCw, Save, Loader2, Info } from "lucide-react"; -import { Plus, Trash2, Eye, EyeOff } from "lucide-react"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Alert, AlertDescription } from "@/components/ui/alert"; -import { successToast, errorToast } from "@/hooks/use-toast"; -import { useProject, useUpdateProject } from "@/services/queries/use-projects"; -import { useSecretsList, useSecretsConfig, useSecretsValues, useUpdateSecretsConfig, useUpdateSecrets } from "@/services/queries/use-secrets"; -import { useMemo } from "react"; +import { useEffect } from 'react'; +import { useParams, useRouter } from 'next/navigation'; -export default function ProjectSettingsPage({ params }: { params: Promise<{ name: string }> }) { - const [projectName, setProjectName] = useState(""); - const [formData, setFormData] = useState({ displayName: "", description: "" }); - const [secretName, setSecretName] = useState(""); - const [secrets, setSecrets] = useState>([]); - const [mode, setMode] = useState<"existing" | "new">("existing"); - const [showValues, setShowValues] = useState>({}); - const [anthropicApiKey, setAnthropicApiKey] = useState(""); - const [showAnthropicKey, setShowAnthropicKey] = useState(false); - const [gitUserName, setGitUserName] = useState(""); - const [gitUserEmail, setGitUserEmail] = useState(""); - const [gitToken, setGitToken] = useState(""); - const [showGitToken, setShowGitToken] = useState(false); - const [jiraUrl, setJiraUrl] = useState(""); - const [jiraProject, setJiraProject] = useState(""); - const [jiraEmail, setJiraEmail] = useState(""); - const [jiraToken, setJiraToken] = useState(""); - const [showJiraToken, setShowJiraToken] = useState(false); - const FIXED_KEYS = useMemo(() => ["ANTHROPIC_API_KEY","GIT_USER_NAME","GIT_USER_EMAIL","GIT_TOKEN","JIRA_URL","JIRA_PROJECT","JIRA_EMAIL","JIRA_API_TOKEN"] as const, []); +export default function ProjectSettingsPage() { + const params = useParams(); + const router = useRouter(); + const projectName = params?.name as string; - // React Query hooks - const { data: project, isLoading: projectLoading, refetch: refetchProject } = useProject(projectName); - const { data: secretsList } = useSecretsList(projectName); - const { data: secretsConfig } = useSecretsConfig(projectName); - const { data: secretsValues } = useSecretsValues(projectName); - const updateProjectMutation = useUpdateProject(); - const updateSecretsConfigMutation = useUpdateSecretsConfig(); - const updateSecretsMutation = useUpdateSecrets(); - - // Extract projectName from params - useEffect(() => { - params.then(({ name }) => setProjectName(name)); - }, [params]); - - // Sync project data to form - useEffect(() => { - if (project) { - setFormData({ displayName: project.displayName || "", description: project.description || "" }); - } - }, [project]); - - // Sync secrets config to state + // Redirect to main workspace page useEffect(() => { - if (secretsConfig) { - if (secretsConfig.secretName) { - setSecretName(secretsConfig.secretName); - setMode("existing"); - } else { - setSecretName("ambient-runner-secrets"); - setMode("new"); - } + if (projectName) { + router.replace(`/projects/${projectName}?section=settings`); } - }, [secretsConfig]); - - // Sync secrets values to state - useEffect(() => { - if (secretsValues) { - const byKey: Record = Object.fromEntries(secretsValues.map(s => [s.key, s.value])); - setAnthropicApiKey(byKey["ANTHROPIC_API_KEY"] || ""); - setGitUserName(byKey["GIT_USER_NAME"] || ""); - setGitUserEmail(byKey["GIT_USER_EMAIL"] || ""); - setGitToken(byKey["GIT_TOKEN"] || ""); - setJiraUrl(byKey["JIRA_URL"] || ""); - setJiraProject(byKey["JIRA_PROJECT"] || ""); - setJiraEmail(byKey["JIRA_EMAIL"] || ""); - setJiraToken(byKey["JIRA_API_TOKEN"] || ""); - setSecrets(secretsValues.filter(s => !FIXED_KEYS.includes(s.key as typeof FIXED_KEYS[number]))); - } - }, [secretsValues, FIXED_KEYS]); - - const handleRefresh = () => { - void refetchProject(); - }; - - const handleSave = () => { - if (!project) return; - updateProjectMutation.mutate( - { - name: projectName, - data: { - displayName: formData.displayName.trim(), - description: formData.description.trim() || undefined, - annotations: project.annotations || {}, - }, - }, - { - onSuccess: () => { - successToast("Project settings updated successfully!"); - }, - onError: (error) => { - const message = error instanceof Error ? error.message : "Failed to update project"; - errorToast(message); - }, - } - ); - }; - - const handleSaveSecrets = () => { - if (!projectName) return; - - const name = secretName.trim() || "ambient-runner-secrets"; - - // First update config - updateSecretsConfigMutation.mutate( - { projectName, secretName: name }, - { - onSuccess: () => { - // Then update secrets values - const data: Record = {}; - if (anthropicApiKey) data["ANTHROPIC_API_KEY"] = anthropicApiKey; - if (gitUserName) data["GIT_USER_NAME"] = gitUserName; - if (gitUserEmail) data["GIT_USER_EMAIL"] = gitUserEmail; - if (gitToken) data["GIT_TOKEN"] = gitToken; - if (jiraUrl) data["JIRA_URL"] = jiraUrl; - if (jiraProject) data["JIRA_PROJECT"] = jiraProject; - if (jiraEmail) data["JIRA_EMAIL"] = jiraEmail; - if (jiraToken) data["JIRA_API_TOKEN"] = jiraToken; - for (const { key, value } of secrets) { - if (!key) continue; - if (FIXED_KEYS.includes(key as typeof FIXED_KEYS[number])) continue; - data[key] = value ?? ""; - } - - updateSecretsMutation.mutate( - { - projectName, - secrets: Object.entries(data).map(([key, value]) => ({ key, value })), - }, - { - onSuccess: () => { - successToast("Secrets saved successfully!"); - }, - onError: (error) => { - const message = error instanceof Error ? error.message : "Failed to save secrets"; - errorToast(message); - }, - } - ); - }, - onError: (error) => { - const message = error instanceof Error ? error.message : "Failed to save secret config"; - errorToast(message); - }, - } - ); - }; - - const addSecretRow = () => { - setSecrets((prev) => [...prev, { key: "", value: "" }]); - }; - - const removeSecretRow = (idx: number) => { - setSecrets((prev) => prev.filter((_, i) => i !== idx)); - }; - - return ( -
- - Project Settings} - description={<>{projectName}} - actions={ - - } - /> - - {/* Only show project metadata editor on OpenShift */} - {project?.isOpenShift ? ( - - - Edit Project - Rename display name or update description - - -
- - setFormData((prev) => ({ ...prev, displayName: e.target.value }))} - placeholder="My Awesome Project" - maxLength={100} - /> -
-
- - +
+ +
+ + +
+ +
+
+
+ + + + + + + diff --git a/components/frontend/static-prototype/integrations/page.html b/components/frontend/static-prototype/integrations/page.html new file mode 100644 index 000000000..b7ab3a411 --- /dev/null +++ b/components/frontend/static-prototype/integrations/page.html @@ -0,0 +1,130 @@ + + + + + + Integrations - Ambient Code Platform + + + +
+
+
+ +
+
+ + +
+
+
+
+
+ + + +
+
+
+
+
+
+ + + +
+
+

GitHub

+

Connect to GitHub repositories

+
+
+
+
+
+
+
+ Not Connected +
+
+ Connect to GitHub to manage repositories and create pull requests +
+
+ +
+
+
+
+ + + + diff --git a/components/frontend/static-prototype/projects/new/page.html b/components/frontend/static-prototype/projects/new/page.html new file mode 100644 index 000000000..df43d831c --- /dev/null +++ b/components/frontend/static-prototype/projects/new/page.html @@ -0,0 +1,136 @@ + + + + + + Create New Workspace - Ambient Code Platform + + + +
+
+
+ +
+
+ + +
+
+
+
+
+ + + +
+
+
+

Workspace Details

+

Provide basic information about your workspace

+
+
+
+
+ + + Must be lowercase, alphanumeric with hyphens +
+ +
+ + +
+ +
+ + +
+ +
+ + Cancel +
+
+
+
+
+ + + + diff --git a/components/frontend/static-prototype/projects/sample-workspace/create-api-tests/page.html b/components/frontend/static-prototype/projects/sample-workspace/create-api-tests/page.html new file mode 100644 index 000000000..973d0d583 --- /dev/null +++ b/components/frontend/static-prototype/projects/sample-workspace/create-api-tests/page.html @@ -0,0 +1,274 @@ + + + + + + create-api-tests - sample-workspace - Ambient Code Platform + + + + +
+
+
+ +
+ +
+ + +
+
+
+
+
+ + + +
+ +
+
+
+
DU
+
+
You
+
Write comprehensive API tests for the authentication endpoints we created
+
+
+ +
+
AI
+
+
Claude
+
I'll create comprehensive API tests for your authentication endpoints. Let me start with testing the registration, login, and token validation endpoints...
+
+
+ +
+
DU
+
+
You
+
Make sure to test edge cases like invalid emails and weak passwords
+
+
+ +
+
AI
+
+
Claude
+
Excellent point! I'll include comprehensive edge case testing including invalid email formats, weak passwords, duplicate registrations, and malformed requests. Here are the test suites...
+
+
+ +
+
DU
+
+
You
+
Perfect! Also add tests for JWT token expiration and refresh
+
+
+ +
+
AI
+
+
Claude
+
Done! I've added comprehensive JWT testing including token expiration, refresh token flow, and invalid token handling. All tests are now complete and passing. ✅
+
+
+
+ +
+ + +
+
+
+ + + + diff --git a/components/frontend/static-prototype/projects/sample-workspace/implement-login-flow/page.html b/components/frontend/static-prototype/projects/sample-workspace/implement-login-flow/page.html new file mode 100644 index 000000000..03c6369da --- /dev/null +++ b/components/frontend/static-prototype/projects/sample-workspace/implement-login-flow/page.html @@ -0,0 +1,310 @@ + + + + + + implement-login-flow - sample-workspace - Ambient Code Platform + + + + +
+
+
+ +
+ +
+ + +
+
+
+
+
+ + + +
+ +
+
+
+
DU
+
+
You
+
Create user login and registration endpoints with proper validation and security
+
+
+ +
+
AI
+
+
Claude
+
I'll help you create secure login and registration endpoints. Let me start by setting up the user model with proper password hashing and validation...
+
+
+ +
+
DU
+
+
You
+
Make sure to include JWT token generation and email validation
+
+
+ +
+
AI
+
+
Claude
+
Absolutely! I'll implement JWT token generation for authentication and comprehensive email validation. Here's the updated user model with bcrypt password hashing and JWT integration...
+
+
+
+ +
+ + +
+
+
+ + + + diff --git a/components/frontend/static-prototype/projects/sample-workspace/info/page.html b/components/frontend/static-prototype/projects/sample-workspace/info/page.html new file mode 100644 index 000000000..5e339fcfd --- /dev/null +++ b/components/frontend/static-prototype/projects/sample-workspace/info/page.html @@ -0,0 +1,228 @@ + + + + + + Project Information - sample-workspace - Ambient Code Platform + + + +
+
+
+ +
+ +
+ + +
+
+
+
+
+ + + +
+
+ + +
+
+
+
+

Project Information

+
+
+
+ Created: 2 days ago +
+
+ Status: + Active +
+
+
+ +
+
+

Quick Stats

+
+
+
+ RFE Workspaces: 3 +
+
+ Active Sessions: 2 +
+
+ API Keys: 1 +
+
+ Team Members: 5 +
+
+
+
+
+
+
+ + + + diff --git a/components/frontend/static-prototype/projects/sample-workspace/keys/page.html b/components/frontend/static-prototype/projects/sample-workspace/keys/page.html new file mode 100644 index 000000000..4d7f33097 --- /dev/null +++ b/components/frontend/static-prototype/projects/sample-workspace/keys/page.html @@ -0,0 +1,295 @@ + + + + + + API Keys - sample-workspace - Ambient Code Platform + + + +
+
+
+ +
+ +
+ + +
+
+
+
+
+ + + +
+
+ + +
+
+
+
+
+

API Keys

+

API keys provide secure access to external services and integrations

+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameCreatedLast UsedRoleActions
+
Production API Key
+
For production deployments and CI/CD
+
1 week ago2 hours agoAdmin + +
+
Development Key
+
For local development and testing
+
2 weeks ago1 day agoAdmin + +
+
Monitoring Key
+
Read-only access for monitoring tools
+
3 days agoNeverAdmin + +
+
+
+
+
+
+ + + + diff --git a/components/frontend/static-prototype/projects/sample-workspace/optimize-queries/headless.html b/components/frontend/static-prototype/projects/sample-workspace/optimize-queries/headless.html new file mode 100644 index 000000000..f1b9dbd34 --- /dev/null +++ b/components/frontend/static-prototype/projects/sample-workspace/optimize-queries/headless.html @@ -0,0 +1,303 @@ + + + + + + optimize-queries - sample-workspace - Ambient Code Platform + + + + +
+
+
+ +
+ +
+ + +
+
+
+
+
+ + + +
+ +
+
+
+

Session Configuration

+
+
+
+ Task: Optimize database queries for better performance +
+
+ Model: claude-3.5-sonnet +
+
+ Mode: Headless +
+
+ Duration: 18 minutes +
+
+ Status: + + + + + Completed + +
+
+
+ +
+
+

Performance Results

+
+
+
+
+ Query Optimization + 100% +
+
+
+
+
+
+
✓ Analyzed slow queries
+
✓ Added database indexes
+
✓ Optimized JOIN operations
+
✓ Reduced query time by 75%
+
✓ Updated documentation
+
+
+
+
+ +
+
+
+

Execution Log

+ +
+
+
+
+
+ 14:15:30 + [INFO] Session started: Optimize database queries for better performance +
+
+ 14:15:32 + [INFO] Analyzing database query performance +
+
+ 14:16:15 + [WARNING] Found 5 slow queries without proper indexing +
+
+ 14:16:45 + [INFO] Creating optimized database indexes +
+
+ 14:18:22 + [SUCCESS] Database indexes created successfully +
+
+ 14:19:10 + [INFO] Optimizing JOIN operations and subqueries +
+
+ 14:21:35 + [SUCCESS] Query performance improved by 75% +
+
+ 14:22:15 + [INFO] Updating query documentation +
+
+ 14:23:48 + [SUCCESS] Database optimization completed successfully +
+
+
+
+
+ + + + diff --git a/components/frontend/static-prototype/projects/sample-workspace/page.html b/components/frontend/static-prototype/projects/sample-workspace/page.html new file mode 100644 index 000000000..af7c4813f --- /dev/null +++ b/components/frontend/static-prototype/projects/sample-workspace/page.html @@ -0,0 +1,1059 @@ + + + + + + sample-workspace - Ambient Code Platform + + + +
+
+
+ +
+
+ + +
+
+
+
+
+ + + +
+
+ + +
+ + +
+
+
+
+

Agentic Sessions

+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Session NameStatusModeModelCreated
+
+
implement-login-flow
+
Create user login and registration endpoints
+
+
Runninginteractiveclaude-3.5-sonnet4 hours ago
+
+
setup-database
+
Initialize PostgreSQL database with user tables
+
+
Completedheadlessclaude-3.5-sonnet1 day ago
+
+
create-api-tests
+
Write comprehensive API tests for authentication endpoints
+
+
Completedinteractiveclaude-3.5-sonnet30 minutes ago
+
+
optimize-queries
+
Optimize database queries for better performance
+
+
Completedheadlessclaude-3.5-sonnet2 hours ago
+
+
+
+ + + + + + + + + + + + +
+
+
+ + + + + + + diff --git a/components/frontend/static-prototype/projects/sample-workspace/permissions/page.html b/components/frontend/static-prototype/projects/sample-workspace/permissions/page.html new file mode 100644 index 000000000..b6c91c929 --- /dev/null +++ b/components/frontend/static-prototype/projects/sample-workspace/permissions/page.html @@ -0,0 +1,318 @@ + + + + + + Permissions - sample-workspace - Ambient Code Platform + + + +
+
+
+ +
+ +
+ + +
+
+
+
+
+ + + +
+
+ + +
+
+
+
+
+

Sharing

+

Users and groups with access to this project and their roles

+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Users/GroupsTypeRoleActions
+
+
john.doe@company.com
+
Project Owner
+
+
UserAdmin + +
+
+
development-team
+
5 members
+
+
GroupView + +
+
+
sarah.admin@company.com
+
Senior Developer
+
+
UserAdmin + +
+
+
qa-team
+
3 members
+
+
GroupView + +
+
+
+
+
+
+ + + + diff --git a/components/frontend/static-prototype/projects/sample-workspace/rfe/page.html b/components/frontend/static-prototype/projects/sample-workspace/rfe/page.html new file mode 100644 index 000000000..81987f1d4 --- /dev/null +++ b/components/frontend/static-prototype/projects/sample-workspace/rfe/page.html @@ -0,0 +1,262 @@ + + + + + + RFE Workspaces - sample-workspace - Ambient Code Platform + + + +
+
+
+ +
+ +
+ + +
+
+
+
+
+ + + +
+
+ + +
+
+
+
+

User Authentication System

+ Planning +
+
+

Implement secure user authentication with JWT tokens and role-based access control.

+
+ Repository: github.com/company/auth-service +
+
+ Branch: feature/user-auth +
+
+ Created: 2 hours ago +
+
+ + +
+
+
+ +
+
+

API Gateway

+ In Progress +
+
+

Central API gateway for microservices with rate limiting and request routing.

+
+ Repository: github.com/company/api-gateway +
+
+ Branch: feature/gateway-v2 +
+
+ Created: 1 day ago +
+
+ + +
+
+
+ +
+
+

Database Migration Tool

+ Completed +
+
+

Automated database migration and versioning system for production deployments.

+
+ Repository: github.com/company/db-migrator +
+
+ Branch: main +
+
+ Created: 3 days ago +
+
+ + +
+
+
+
+
+
+
+ + + + diff --git a/components/frontend/static-prototype/projects/sample-workspace/session-1/headless.html b/components/frontend/static-prototype/projects/sample-workspace/session-1/headless.html new file mode 100644 index 000000000..9edf4e635 --- /dev/null +++ b/components/frontend/static-prototype/projects/sample-workspace/session-1/headless.html @@ -0,0 +1,385 @@ + + + + + + session-1 (Headless) - sample-workspace - Ambient Code Platform + + + + +
+
+
+ +
+ +
+ + +
+
+
+
+
+ + + +
+ +
+
+
+

Session Configuration

+
+
+
+ Task: Create user authentication system +
+
+ Model: claude-3.5-sonnet +
+
+ Mode: Headless +
+
+ Timeout: 30 minutes +
+
+ Status: + + + + + Running + +
+
+
+ +
+
+

Progress

+
+
+
+
+ Overall Progress + 65% +
+
+
+
+
+
+
✓ Repository cloned
+
✓ Dependencies installed
+
⏳ Creating authentication models
+
⏸️ Writing tests
+
⏸️ Documentation update
+
+
+
+
+ +
+
+
+

Execution Log

+ +
+
+
+
+
+ 13:45:23 + [INFO] Session started with task: Create user authentication system +
+
+ 13:45:24 + [INFO] Cloning repository from https://github.com/company/auth-service.git +
+
+ 13:45:28 + [SUCCESS] Repository cloned successfully +
+
+ 13:45:29 + [INFO] Installing dependencies... +
+
+ 13:46:15 + [SUCCESS] Dependencies installed successfully +
+
+ 13:46:16 + [INFO] Analyzing existing codebase structure +
+
+ 13:46:22 + [INFO] Creating User model with authentication fields +
+
+ 13:46:35 + [WORKING] Implementing password hashing and validation... +
+
+ 13:46:42 + [INFO] Adding JWT token generation methods +
+
+ 13:47:01 + [INFO] Creating authentication middleware +
+
+
+
+ +
+
+

Session Actions

+
+
+
+ + + +
+
+
+
+ + + + diff --git a/components/frontend/static-prototype/projects/sample-workspace/session-1/page.html b/components/frontend/static-prototype/projects/sample-workspace/session-1/page.html new file mode 100644 index 000000000..53100742f --- /dev/null +++ b/components/frontend/static-prototype/projects/sample-workspace/session-1/page.html @@ -0,0 +1,2692 @@ + + + + + + session-1 - sample-workspace - Ambient Code Platform + + + + +
+
+
+ +
+ +
+ + +
+
+
+
+
+ + + +
+
+ +
+ +
+ +
+
+
+ + +
+ + +
+
Workflows provide a structured processes for Ambient Code Platform agents to follow and achieve complex goals.
+
+ + + + + + +
+
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+ + +
+
+
+
ACP
+
+
ACP
+
+
+
+
+ +
+ + + + + + + + +
+
+
+
+ + + + + + + diff --git a/components/frontend/static-prototype/projects/sample-workspace/sessions/page.html b/components/frontend/static-prototype/projects/sample-workspace/sessions/page.html new file mode 100644 index 000000000..cf2dc0034 --- /dev/null +++ b/components/frontend/static-prototype/projects/sample-workspace/sessions/page.html @@ -0,0 +1,273 @@ + + + + + + Sessions - sample-workspace - Ambient Code Platform + + + +
+
+
+ +
+ +
+ + +
+
+
+
+
+ + + +
+
+ + +
+
+
+

Agentic Sessions

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Session NameStatusModelStartedDurationActions
+
+
implement-login-flow
+
Create user login and registration endpoints
+
+
Completedclaude-3.5-sonnet4 hours ago23 minutes + +
+
+
setup-database
+
Initialize PostgreSQL database with user tables
+
+
Failedclaude-3.5-sonnet1 day ago12 minutes + +
+
+
create-api-tests
+
Write comprehensive API tests for authentication endpoints
+
+
Runningclaude-3.5-sonnet30 minutes ago30 minutes + +
+
+
optimize-queries
+
Optimize database queries for better performance
+
+
Pendingclaude-3.5-sonnet-- + +
+
+
+
+
+
+ + + + diff --git a/components/frontend/static-prototype/projects/sample-workspace/settings/page.html b/components/frontend/static-prototype/projects/sample-workspace/settings/page.html new file mode 100644 index 000000000..46fad4db0 --- /dev/null +++ b/components/frontend/static-prototype/projects/sample-workspace/settings/page.html @@ -0,0 +1,340 @@ + + + + + + Settings - sample-workspace - Ambient Code Platform + + + +
+
+
+ +
+ +
+ + +
+
+
+
+
+ + + +
+
+ + +
+
+
+

General Settings

+

Basic project configuration

+
+
+
+
+ + + Project name cannot be changed after creation +
+
+ + +
+
+ + +
+ +
+
+
+ +
+
+

Runner Secrets

+

Configure the Secret and manage key/value pairs used by workspace runners.

+
+
+
+
+ + +
+ +
+ + + Required for running agentic sessions +
+ +
+
+

Additional Secrets

+ +
+ +
+
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+ +
+
+
+ + +
+
+
+ +
+
+

Resource Limits

+

Configure resource limits for sessions and workspaces

+
+
+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+ +
+
+

Danger Zone

+

Irreversible and destructive actions

+
+
+
+
+
Archive Project
+
Archive this project to make it read-only
+
+ +
+
+
+
Delete Project
+
Permanently delete this project and all its data
+
+ +
+
+
+
+
+
+ + + + diff --git a/components/frontend/static-prototype/projects/sample-workspace/setup-database/headless.html b/components/frontend/static-prototype/projects/sample-workspace/setup-database/headless.html new file mode 100644 index 000000000..25f775a06 --- /dev/null +++ b/components/frontend/static-prototype/projects/sample-workspace/setup-database/headless.html @@ -0,0 +1,307 @@ + + + + + + setup-database - sample-workspace - Ambient Code Platform + + + + +
+
+
+ +
+ +
+ + +
+
+
+
+
+ + + +
+ +
+
+
+

Session Configuration

+
+
+
+ Task: Initialize PostgreSQL database with user tables +
+
+ Model: claude-3.5-sonnet +
+
+ Mode: Headless +
+
+ Duration: 12 minutes +
+
+ Status: + + + + + Completed + +
+
+
+ +
+
+

Results Summary

+
+
+
+
+ Overall Progress + 100% +
+
+
+
+
+
+
✓ Database schema created
+
✓ User table with constraints
+
✓ Password hashing implemented
+
✓ Migration scripts generated
+
✓ Initial data seeded
+
+
+
+
+ +
+
+
+

Execution Log

+ +
+
+
+
+
+ 12:30:15 + [INFO] Session started: Initialize PostgreSQL database with user tables +
+
+ 12:30:16 + [INFO] Analyzing existing database structure +
+
+ 12:30:22 + [INFO] Creating users table schema +
+
+ 12:31:05 + [SUCCESS] Users table created with proper constraints +
+
+ 12:31:08 + [INFO] Adding password hashing utilities +
+
+ 12:31:45 + [SUCCESS] Password hashing implemented with bcrypt +
+
+ 12:32:12 + [INFO] Creating database migration scripts +
+
+ 12:33:01 + [SUCCESS] Migration scripts generated +
+
+ 12:33:15 + [INFO] Seeding initial user data +
+
+ 12:33:28 + [SUCCESS] Database initialization completed successfully +
+
+
+
+
+ + + + diff --git a/components/frontend/static-prototype/rfe.md b/components/frontend/static-prototype/rfe.md new file mode 100644 index 000000000..387d4720f --- /dev/null +++ b/components/frontend/static-prototype/rfe.md @@ -0,0 +1,1092 @@ +# RFE: Visual Redesign of Red Hat OpenShift AI (RHOAI) 3.0 Dashboard + +## Executive Summary + +This Request for Enhancement (RFE) proposes three distinct visual redesign directions for the Red Hat OpenShift AI (RHOAI) 3.0 dashboard to address the core challenge faced by AI Platform Engineers like Paula: **efficiently finding and evaluating production-ready AI models among thousands of options**. + +The current dashboard, while functional, presents a traditional enterprise interface that doesn't leverage modern AI-centric design patterns or optimize for the unique workflows of AI practitioners. This redesign focuses on transforming the user experience from a data-heavy administrative interface to an intelligent, task-oriented platform that accelerates model discovery and deployment decisions. + +## Current State Analysis + +### Existing Architecture +- **Framework**: React with TypeScript, PatternFly React components +- **Navigation**: Traditional sidebar navigation with hierarchical structure +- **Layout**: Standard enterprise dashboard with card-based model catalog +- **Feature Management**: Comprehensive feature flag system supporting MVP mode vs full feature set +- **Components**: Heavy use of PatternFly components (Cards, Tables, Forms, Modals, Dropdowns) + +### Current User Journey Pain Points +1. **Cognitive Overload**: Thousands of models presented in basic card/table format +2. **Inefficient Filtering**: Multiple separate filter interfaces without visual feedback +3. **Limited Comparison**: No side-by-side model comparison capabilities +4. **Static Information**: Performance metrics buried in text rather than visual indicators +5. **Context Switching**: Frequent navigation between catalog, registry, and deployment sections + +### Technical Foundation +- **PatternFly Integration**: Extensive use of existing components provides solid accessibility foundation +- **Feature Flags**: Robust system for MVP/full feature mode switching +- **State Management**: Context API for global state, component-level state for UI interactions +- **Routing**: React Router with dynamic route generation based on feature flags + +## User Persona: Paula - AI Platform Engineer + +**Primary Goal**: Find production-ready AI models that balance performance, cost, and specific use case requirements + +**Key Workflows**: +1. **Model Discovery**: Search through thousands of models using multiple criteria +2. **Performance Evaluation**: Compare latency, throughput, accuracy, and resource requirements +3. **Compatibility Assessment**: Verify model compatibility with existing infrastructure +4. **Deployment Planning**: Understand deployment requirements and costs +5. **Monitoring Setup**: Configure monitoring and alerting for deployed models + +**Success Metrics**: +- Time to find relevant models reduced by 60% +- Improved task completion rates for model selection workflows +- Reduced cognitive load when comparing multiple models +- Increased user satisfaction with filtering and search capabilities + +--- + +# Design Direction 1: "AI-First Visual Intelligence" + +## Philosophy +Treat AI models as visual, interactive objects rather than data rows. Transform the dashboard into an intelligent visual workspace where data visualization, interactive filtering, and AI-powered recommendations create an intuitive model discovery experience. + +## User Journey: Paula's Model Discovery Workflow + +### 1. Landing Experience +Paula arrives at a **Visual Model Universe** - a dynamic, interactive visualization showing all available models as nodes in a network graph, clustered by use case, provider, and performance characteristics. + +### 2. Intelligent Filtering +She uses **Visual Filter Sliders** to narrow down options: +- Latency requirement: Drag slider to <100ms +- Cost threshold: Visual budget indicator shows real-time cost implications +- Hardware compatibility: Interactive hardware requirement visualization + +### 3. AI-Powered Recommendations +The **Recommendation Engine** surfaces relevant models based on her query: "Customer service chatbot, production-ready, <100ms latency" with confidence scores and reasoning. + +### 4. Visual Comparison +Paula selects 3-4 models for **Side-by-Side Visual Comparison** with interactive performance charts, compatibility matrices, and deployment requirement visualizations. + +### 5. Workflow Integration +She connects her selected model to MCP servers and agents using the **Visual Workflow Builder** - a drag-and-drop interface showing data flow and dependencies. + +## Key UI Components + +### 1. Visual Model Universe +``` +┌─────────────────────────────────────────────────────────┐ +│ ○ Interactive Network Graph │ +│ ├── Nodes: Models (size = popularity, color = type) │ +│ ├── Clusters: Auto-grouped by ML similarity │ +│ ├── Zoom/Pan: Smooth navigation with mini-map │ +│ └── Search Overlay: Highlights matching nodes │ +└─────────────────────────────────────────────────────────┘ +``` + +### 2. Smart Filter Panel +``` +┌─────────────────────────────────────────────────────────┐ +│ 🎛️ Visual Performance Sliders │ +│ ├── Latency: [====●----] <100ms (23 models) │ +│ ├── Accuracy: [======●--] >95% (45 models) │ +│ ├── Cost/Hour: [$●--------] <$2.50 (67 models) │ +│ └── Hardware: [GPU Memory Visualization] │ +│ │ +│ 🎯 Use Case Tags (Visual Bubbles) │ +│ ├── [NLP] [Computer Vision] [Code Generation] │ +│ └── [Multimodal] [Reasoning] [Translation] │ +│ │ +│ 🤖 AI Recommendations │ +│ ├── "Based on your criteria, try granite-7b-code" │ +│ └── Confidence: ████████░░ 85% │ +└─────────────────────────────────────────────────────────┘ +``` + +### 3. Enhanced Model Cards +``` +┌─────────────────────────────────────────────────────────┐ +│ 📊 granite-7b-code:1.1 [⭐ 4.8/5] │ +│ ├── Performance Radar Chart │ +│ │ ├── Speed: ████████░░ │ +│ │ ├── Accuracy: ██████░░░░ │ +│ │ └── Efficiency: ████████░░ │ +│ ├── Compatibility Badges │ +│ │ ├── ✅ CUDA 12.0 ✅ 16GB RAM ⚠️ Requires A100 │ +│ ├── Live Deployment Status │ +│ │ └── 🟢 23 active deployments, avg 45ms latency │ +│ └── Quick Actions: [Compare] [Deploy] [Details] │ +└─────────────────────────────────────────────────────────┘ +``` + +### 4. Multi-Model Comparison View +``` +┌─────────────────────────────────────────────────────────┐ +│ 📈 Performance Comparison (3 models selected) │ +│ ├── Overlay Chart: Latency vs Accuracy │ +│ │ ├── Model A: ● (45ms, 94%) │ +│ │ ├── Model B: ● (78ms, 97%) │ +│ │ └── Model C: ● (23ms, 89%) │ +│ ├── Specification Matrix │ +│ │ ├──────────────┬─────────┬─────────┬─────────┐ │ +│ │ │ Metric │ Model A │ Model B │ Model C │ │ +│ │ ├──────────────┼─────────┼─────────┼─────────┤ │ +│ │ │ Parameters │ 7B │ 13B │ 3B │ │ +│ │ │ Memory │ 16GB │ 32GB │ 8GB │ │ +│ │ │ Cost/Hour │ $2.40 │ $4.80 │ $1.20 │ │ +│ │ └──────────────┴─────────┴─────────┴─────────┘ │ +│ └── Recommendation: Model C best for your use case │ +└─────────────────────────────────────────────────────────┘ +``` + +### 5. Visual Workflow Builder +``` +┌─────────────────────────────────────────────────────────┐ +│ 🔄 AI Workflow Designer │ +│ ├── [Model] ──→ [MCP Server] ──→ [Agent] ──→ [Output] │ +│ │ │ │ │ │ │ +│ │ granite-7b GitHub MCP Customer Response │ +│ │ Service │ +│ ├── Drag & Drop Components │ +│ ├── Real-time Validation │ +│ └── Performance Prediction: ~67ms end-to-end │ +└─────────────────────────────────────────────────────────┘ +``` + +## Information Architecture + +``` +RHOAI Dashboard (AI-First Visual Intelligence) +├── Visual Model Universe (landing page) +│ ├── Interactive Network Graph (main visualization) +│ ├── Smart Filter Panel (left sidebar) +│ │ ├── Visual Performance Sliders +│ │ ├── Use Case Tag Cloud +│ │ └── AI Recommendation Engine +│ ├── Model Detail Overlay (contextual) +│ └── Quick Action Toolbar (bottom) +├── Comparison Workspace +│ ├── Multi-Model Performance Charts +│ ├── Specification Matrix +│ └── Deployment Cost Calculator +├── Workflow Builder +│ ├── Visual Pipeline Designer +│ ├── Component Library +│ └── Performance Simulator +└── Deployment Dashboard + ├── Live Status Visualization + ├── Performance Monitoring Charts + └── Alert Management +``` + +## Visual Design Language + +### Color Palette +- **Primary**: Deep Blue (#0066CC) - Trust, intelligence +- **Secondary**: Vibrant Teal (#17A2B8) - Innovation, technology +- **Accent**: Warm Orange (#FF6B35) - Energy, action +- **Success**: Green (#28A745) - Deployed, healthy +- **Warning**: Amber (#FFC107) - Attention needed +- **Error**: Red (#DC3545) - Critical issues +- **Neutral**: Grays (#F8F9FA to #343A40) - Background, text + +### Typography +- **Headers**: Red Hat Display (Bold, 24-32px) +- **Body**: Red Hat Text (Regular, 14-16px) +- **Code/Metrics**: Red Hat Mono (Regular, 12-14px) +- **Emphasis**: Red Hat Text (Medium, 16-18px) + +### Spacing & Layout +- **Grid**: 8px base unit, 24px component spacing +- **Cards**: 16px padding, 8px border radius, subtle shadows +- **Interactive Elements**: 44px minimum touch target +- **Whitespace**: Generous spacing for visual breathing room + +### Animation & Interaction +- **Micro-interactions**: 200ms ease-in-out transitions +- **Loading States**: Skeleton screens with shimmer effects +- **Hover States**: Subtle elevation and color changes +- **Focus States**: High-contrast outlines for accessibility + +## Technical Considerations + +### PatternFly Integration (80% Reuse Target) +- **Reuse**: Card, Button, Form, Select, Modal, Tooltip, Progress, Label +- **Extend**: Custom chart components using Recharts with PatternFly theming +- **New Components**: + - `ModelUniverseGraph` (D3.js-based network visualization) + - `VisualFilterPanel` (Custom sliders with real-time feedback) + - `ModelComparisonMatrix` (Interactive specification table) + - `WorkflowBuilder` (Drag-and-drop pipeline designer) + - `PerformanceRadarChart` (Model capability visualization) + +### React Architecture +``` +src/ +├── components/ +│ ├── ai-hub/ +│ │ ├── ModelUniverse/ +│ │ │ ├── NetworkGraph.tsx +│ │ │ ├── FilterPanel.tsx +│ │ │ └── ModelCard.tsx +│ │ ├── Comparison/ +│ │ │ ├── ComparisonView.tsx +│ │ │ └── PerformanceChart.tsx +│ │ └── Workflow/ +│ │ ├── WorkflowBuilder.tsx +│ │ └── ComponentLibrary.tsx +│ └── shared/ +│ ├── Charts/ +│ └── Visualizations/ +├── hooks/ +│ ├── useModelRecommendations.ts +│ ├── useVisualFilters.ts +│ └── useWorkflowValidation.ts +└── utils/ + ├── chartHelpers.ts + └── performanceCalculations.ts +``` + +### Performance Optimizations +- **Virtual Scrolling**: React-window for large model lists (5,000+ items) +- **Lazy Loading**: Code splitting for heavy visualization components +- **Memoization**: React.memo for expensive chart re-renders +- **Debouncing**: 300ms debounce for filter inputs +- **Caching**: React Query with 5-minute cache for model data + +### Data Management +- **GraphQL API**: Flexible queries for model metadata and performance metrics +- **Real-time Updates**: WebSocket connections for live deployment status +- **Optimistic Updates**: Immediate UI feedback for user actions +- **Progressive Loading**: Initial 50 models, infinite scroll for more + +## Accessibility Features + +### Keyboard Navigation +- **Tab Order**: Filter panel → Model cards → Action buttons → Comparison view +- **Shortcuts**: + - `/` to focus search + - `Cmd+K` for command palette + - `Escape` to close modals/overlays + - Arrow keys for graph navigation + +### Screen Reader Support +- **ARIA Labels**: Comprehensive labeling for all interactive elements +- **Live Regions**: Announce filter results and recommendations +- **Alternative Text**: Detailed descriptions for all charts and visualizations +- **Data Tables**: Accessible alternatives for all visual comparisons + +### Visual Accessibility +- **High Contrast**: WCAG AA compliant color ratios (4.5:1 minimum) +- **Focus Indicators**: 2px high-contrast outlines +- **Text Scaling**: Support up to 200% zoom without horizontal scrolling +- **Motion Reduction**: Respect `prefers-reduced-motion` settings + +## Mobile/Responsive Design + +### Breakpoints +- **Mobile**: 320px - 767px (Stacked layout, touch-optimized) +- **Tablet**: 768px - 1023px (Hybrid layout, collapsible panels) +- **Desktop**: 1024px+ (Full layout, multi-panel views) + +### Mobile Adaptations +- **Navigation**: Collapsible hamburger menu +- **Filters**: Bottom sheet modal for filter panel +- **Cards**: Full-width stacked layout +- **Comparison**: Swipeable carousel for model comparison +- **Touch Targets**: Minimum 44px for all interactive elements + +## Performance Impact Assessment + +### Rendering Optimizations +- **Canvas Rendering**: Use HTML5 Canvas for network graphs with >1000 nodes +- **WebGL**: Hardware acceleration for complex visualizations +- **Virtual DOM**: Minimize re-renders with React.memo and useMemo +- **Intersection Observer**: Lazy load off-screen model cards + +### Bundle Size Impact +- **Estimated Addition**: +150KB gzipped for visualization libraries +- **Code Splitting**: Lazy load heavy components (WorkflowBuilder, NetworkGraph) +- **Tree Shaking**: Import only used chart components +- **CDN Assets**: Serve large datasets from CDN with compression + +### Memory Management +- **Cleanup**: Proper cleanup of D3.js event listeners and timers +- **Garbage Collection**: Avoid memory leaks in long-running visualizations +- **Data Pagination**: Limit in-memory model data to 500 items max + +--- + +# Design Direction 2: "Enterprise Command Center" + +## Philosophy +Transform the dashboard into a mission-critical control center optimized for power users who need dense information display, advanced filtering capabilities, and efficient bulk operations. Emphasize data density, customization, and keyboard-driven workflows. + +## User Journey: Paula's Power User Workflow + +### 1. Customizable Dashboard Landing +Paula arrives at her **Personalized Command Center** with customizable widgets showing her most relevant data: recent deployments, model performance alerts, and saved filter sets. + +### 2. Advanced Search & Filtering +She uses the **Command Palette** (Cmd+K) to quickly execute complex queries: "Show GPU models under $3/hour with >95% accuracy deployed in last 30 days" + +### 3. Bulk Operations +Paula selects multiple models using **Batch Selection** and performs bulk actions: compare specifications, export data, or queue for deployment testing. + +### 4. Real-time Monitoring +The **Live Monitoring Dashboard** shows real-time metrics for all deployed models with customizable alerts and drill-down capabilities. + +### 5. Efficient Navigation +She navigates using **Keyboard Shortcuts** and **Breadcrumb Navigation** without touching the mouse, maintaining focus on critical tasks. + +## Key UI Components + +### 1. Customizable Dashboard Widgets +``` +┌─────────────────────────────────────────────────────────┐ +│ 📊 Command Center Dashboard (Drag & Drop Layout) │ +│ ├─────────────────┬─────────────────┬─────────────────┤ │ +│ │ 🎯 Quick Filters │ 📈 Performance │ 🚨 Alerts │ │ +│ │ ├─ Production │ │ ┌─ Latency ──┐ │ │ ⚠️ Model A │ │ +│ │ ├─ <100ms │ │ │ ████████░░ │ │ │ High CPU │ │ +│ │ ├─ GPU Ready │ │ └─ Accuracy ─┘ │ │ 🔴 Model B │ │ +│ │ └─ [23 models] │ │ │ │ Offline │ │ +│ ├─────────────────┼─────────────────┼─────────────────┤ │ +│ │ 📋 Recent Models│ 💰 Cost Monitor │ 🔧 Quick Actions│ │ +│ │ ├─ granite-7b │ │ This Month: │ │ ├─ Deploy │ │ +│ │ ├─ llama-3.1 │ │ $2,847 / $5K │ │ ├─ Compare │ │ +│ │ └─ mistral-7b │ │ ████████░░░░░ │ │ └─ Export │ │ +│ └─────────────────┴─────────────────┴─────────────────┘ │ +└─────────────────────────────────────────────────────────┘ +``` + +### 2. Advanced Command Palette +``` +┌─────────────────────────────────────────────────────────┐ +│ 🔍 Command Palette (Cmd+K) │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ > deploy granite-7b to production │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ 📋 Suggestions: │ +│ ├── 🚀 Deploy model to production Cmd+D │ +│ ├── 📊 Compare selected models Cmd+C │ +│ ├── 📁 Export model specifications Cmd+E │ +│ ├── 🔍 Filter by latency <100ms /lat<100 │ +│ ├── 📈 Show performance dashboard Cmd+P │ +│ └── ⚙️ Open model settings Cmd+, │ +│ │ +│ 🕐 Recent Actions: │ +│ ├── Deployed llama-3.1-8b (2 min ago) │ +│ └── Compared 3 models (5 min ago) │ +└─────────────────────────────────────────────────────────┘ +``` + +### 3. Dense Information Table +``` +┌─────────────────────────────────────────────────────────┐ +│ 📋 Model Registry (Advanced Table View) │ +│ ├── 🔍 [Search] 🎛️ [Filters] 📊 [Columns] 💾 [Save] │ +│ ├─────┬──────────────┬─────────┬─────────┬─────────────┤ +│ │ ☐ │ Model Name │ Latency │ Accuracy│ Status │ +│ ├─────┼──────────────┼─────────┼─────────┼─────────────┤ +│ │ ☑ │ granite-7b │ 45ms ⚡ │ 94% ✅ │ 🟢 Active │ +│ │ ☑ │ llama-3.1-8b │ 67ms │ 96% ✅ │ 🟡 Warning │ +│ │ ☐ │ mistral-7b │ 23ms ⚡ │ 89% │ 🟢 Active │ +│ │ ☐ │ gpt-oss-120b │ 156ms │ 97% ✅ │ 🔴 Error │ +│ ├─────┴──────────────┴─────────┴─────────┴─────────────┤ +│ │ 📊 Bulk Actions: [Compare] [Deploy] [Export] [Delete]│ +│ │ 📈 Selected: 2 models | Total: 1,247 models │ +│ └─────────────────────────────────────────────────────┘ +└─────────────────────────────────────────────────────────┘ +``` + +### 4. Multi-Panel Comparison View +``` +┌─────────────────────────────────────────────────────────┐ +│ 📊 Split-Screen Comparison (2/3/4 panel layout) │ +│ ├─────────────────────┬─────────────────────────────────┤ +│ │ 🏷️ granite-7b-code │ 🏷️ llama-3.1-8b-instruct │ +│ │ ├─ Latency: 45ms │ ├─ Latency: 67ms │ +│ │ ├─ Accuracy: 94% │ ├─ Accuracy: 96% │ +│ │ ├─ Memory: 16GB │ ├─ Memory: 24GB │ +│ │ ├─ Cost: $2.40/hr │ ├─ Cost: $3.60/hr │ +│ │ └─ GPU: A100 │ └─ GPU: A100/H100 │ +│ ├─────────────────────┼─────────────────────────────────┤ +│ │ 📈 Performance Chart│ 📈 Performance Chart │ +│ │ ┌─ Latency Trend ─┐ │ ┌─ Latency Trend ─────────────┐│ +│ │ │ ████████████░░░ │ │ │ ████████░░░░░░░░░░░░░░░░░░░ ││ +│ │ └─ Last 24h ──────┘ │ └─ Last 24h ──────────────────┘│ +│ └─────────────────────┴─────────────────────────────────┤ +│ 🔄 Sync Scroll: ☑ | Export: [PDF] [CSV] | Add Panel: +│ +└─────────────────────────────────────────────────────────┘ +``` + +### 5. Real-Time Monitoring Dashboard +``` +┌─────────────────────────────────────────────────────────┐ +│ 🖥️ Live Deployment Monitor │ +│ ├─────────────────────┬─────────────────────────────────┤ +│ │ 🎯 Status Overview │ 📊 Performance Metrics │ +│ │ ├─ 🟢 Healthy: 23 │ ├─ Avg Latency: 67ms │ +│ │ ├─ 🟡 Warning: 3 │ ├─ Throughput: 1.2K req/s │ +│ │ ├─ 🔴 Critical: 1 │ ├─ Error Rate: 0.02% │ +│ │ └─ 🔵 Total: 27 │ └─ SLA Compliance: 99.8% │ +│ ├─────────────────────┴─────────────────────────────────┤ +│ │ 🚨 Active Alerts │ +│ │ ├─ ⚠️ granite-7b: High CPU usage (85%) │ +│ │ ├─ 🔴 llama-3.1: Connection timeout (3 failures) │ +│ │ └─ 🟡 mistral-7b: Memory usage above threshold │ +│ ├─────────────────────────────────────────────────────┤ +│ │ 📈 Historical Performance (Zoomable Timeline) │ +│ │ ┌─ Response Time ─────────────────────────────────────┐│ +│ │ │ ╭─╮ ││ +│ │ │ ╭───╯ ╰─╮ ╭─╮ ││ +│ │ │ ╯ ╰─────╯ ╰─╮ ││ +│ │ │ ╰──────────────────────────────││ +│ │ └─ 1h 6h 12h 24h 7d ──────────────────────┘│ +└─────────────────────────────────────────────────────────┘ +``` + +## Information Architecture + +``` +RHOAI Dashboard (Enterprise Command Center) +├── Customizable Dashboard (landing page) +│ ├── Widget Grid (main content - drag-and-drop) +│ │ ├── Model List Widget (configured queries) +│ │ ├── Performance Charts Widget (selected models) +│ │ ├── Deployment Status Widget (live monitoring) +│ │ ├── Cost Monitor Widget (budget tracking) +│ │ └── Quick Filters Widget (saved filter sets) +│ ├── Top Toolbar +│ │ ├── Command Palette (Cmd+K) +│ │ ├── Search Bar (/ to focus) +│ │ ├── Layout Selector (saved layouts dropdown) +│ │ └── Settings (dashboard configuration) +│ └── Status Bar (bottom) +│ ├── System Status +│ ├── Active Filters Count +│ └── Keyboard Shortcuts Help +├── Advanced Model Catalog +│ ├── Dense Table View (default) +│ │ ├── Sortable/Filterable Columns +│ │ ├── Bulk Selection & Actions +│ │ └── Inline Quick Actions +│ ├── Saved Queries Sidebar +│ │ ├── Predefined Filters +│ │ ├── Custom Query Builder +│ │ └── Recent Searches +│ └── Export & Reporting +│ ├── CSV/Excel Export +│ ├── PDF Reports +│ └── Scheduled Reports +├── Multi-Panel Comparison +│ ├── Split-Screen Layout (2/3/4 panels) +│ ├── Synchronized Navigation +│ ├── Diff Highlighting +│ └── Export Comparison Reports +└── Live Monitoring Center + ├── Real-time Metrics Dashboard + ├── Alert Management System + ├── Historical Performance Analytics + └── SLA Monitoring & Reporting +``` + +## Visual Design Language + +### Color Palette (Professional/High-Contrast) +- **Primary**: Navy Blue (#1F2937) - Authority, reliability +- **Secondary**: Steel Blue (#374151) - Professional, technical +- **Accent**: Electric Blue (#3B82F6) - Action, focus +- **Success**: Forest Green (#059669) - Healthy, operational +- **Warning**: Amber (#D97706) - Attention, caution +- **Error**: Crimson (#DC2626) - Critical, urgent +- **Neutral**: Cool Grays (#F9FAFB to #111827) - Background hierarchy + +### Typography (Information Dense) +- **Headers**: Red Hat Display (Bold, 18-24px) - Compact hierarchy +- **Body**: Red Hat Text (Regular, 13-14px) - Dense readability +- **Code/Data**: Red Hat Mono (Regular, 11-12px) - Technical precision +- **Labels**: Red Hat Text (Medium, 12-13px) - Clear identification + +### Layout Principles +- **Information Density**: Maximize data per screen real estate +- **Scannable Hierarchy**: Clear visual hierarchy for rapid scanning +- **Consistent Spacing**: 4px/8px grid for tight, organized layout +- **Functional Grouping**: Related data clustered with subtle borders + +### Interaction Patterns +- **Keyboard-First**: All actions accessible via keyboard shortcuts +- **Hover Details**: Rich tooltips with additional context +- **Contextual Menus**: Right-click menus for power user actions +- **Bulk Operations**: Multi-select with batch action capabilities + +## Technical Considerations + +### PatternFly Integration (85% Reuse Target) +- **Heavy Reuse**: Table, Toolbar, Dropdown, Modal, Form, Button, Card +- **Enhanced Components**: + - `AdvancedTable` (sortable, filterable, bulk selection) + - `CommandPalette` (fuzzy search, keyboard navigation) + - `DashboardWidget` (drag-and-drop, resizable) + - `MultiPanelLayout` (split-screen comparison) + - `MonitoringChart` (real-time data visualization) + +### React Architecture +``` +src/ +├── components/ +│ ├── command-center/ +│ │ ├── Dashboard/ +│ │ │ ├── DashboardGrid.tsx +│ │ │ ├── Widget.tsx +│ │ │ └── WidgetLibrary.tsx +│ │ ├── CommandPalette/ +│ │ │ ├── CommandPalette.tsx +│ │ │ └── CommandRegistry.ts +│ │ ├── AdvancedTable/ +│ │ │ ├── DataTable.tsx +│ │ │ ├── BulkActions.tsx +│ │ │ └── ColumnManager.tsx +│ │ └── Monitoring/ +│ │ ├── MetricsDashboard.tsx +│ │ ├── AlertManager.tsx +│ │ └── PerformanceChart.tsx +│ └── shared/ +│ ├── KeyboardShortcuts/ +│ └── ExportManager/ +├── hooks/ +│ ├── useKeyboardShortcuts.ts +│ ├── useBulkOperations.ts +│ ├── useRealTimeMetrics.ts +│ └── useDashboardLayout.ts +└── utils/ + ├── commandRegistry.ts + ├── exportHelpers.ts + └── keyboardNavigation.ts +``` + +### Performance Optimizations +- **Virtual Scrolling**: Handle tables with 10,000+ rows efficiently +- **Memoized Calculations**: Cache expensive sorting/filtering operations +- **Debounced Updates**: 150ms debounce for real-time search +- **Lazy Widget Loading**: Load dashboard widgets on demand +- **Efficient Re-renders**: Minimize table re-renders with React.memo + +### Data Management +- **Real-time WebSockets**: Live metrics and alert updates +- **Optimistic UI**: Immediate feedback for bulk operations +- **Background Sync**: Periodic data refresh without UI interruption +- **Offline Capability**: Cache critical data for offline viewing + +## Accessibility Features + +### Keyboard Navigation Excellence +- **Tab Order**: Logical flow through dense interface elements +- **Shortcuts**: Comprehensive keyboard shortcuts for all actions + - `Cmd+K`: Command palette + - `Cmd+F`: Advanced search + - `Cmd+A`: Select all in current view + - `Space`: Toggle selection + - `Enter`: Execute primary action + - `Escape`: Cancel/close current operation + +### Screen Reader Optimization +- **Table Navigation**: Proper table headers and navigation +- **Live Regions**: Announce real-time updates and alerts +- **Descriptive Labels**: Detailed ARIA labels for complex widgets +- **Status Announcements**: Clear feedback for bulk operations + +### Visual Accessibility +- **High Contrast Mode**: Enhanced contrast ratios (7:1 for text) +- **Focus Management**: Clear focus indicators throughout interface +- **Text Scaling**: Support 200% zoom with horizontal scrolling +- **Color Independence**: Information conveyed beyond color alone + +## Mobile/Responsive Design + +### Responsive Strategy +- **Desktop-First**: Optimized for desktop power users +- **Tablet Adaptation**: Collapsible panels, touch-friendly controls +- **Mobile Fallback**: Essential functions only, simplified interface + +### Mobile Adaptations +- **Navigation**: Collapsible command center with essential widgets +- **Tables**: Horizontal scroll with sticky columns +- **Comparison**: Stacked layout with swipe navigation +- **Monitoring**: Simplified metric cards with drill-down + +## Performance Impact Assessment + +### Rendering Performance +- **Table Virtualization**: Handle large datasets without performance degradation +- **Chart Optimization**: Canvas rendering for real-time metrics +- **Memory Management**: Efficient cleanup of real-time subscriptions +- **Bundle Splitting**: Lazy load monitoring and comparison components + +### Data Processing +- **Client-side Filtering**: Fast filtering for large datasets +- **Incremental Updates**: Efficient real-time data updates +- **Background Processing**: Web Workers for heavy calculations +- **Caching Strategy**: Intelligent caching for frequently accessed data + +--- + +# Design Direction 3: "Conversational AI Assistant" + +## Philosophy +Transform the primary interface into a natural language conversation where an AI assistant helps users discover, evaluate, and deploy models through intelligent dialogue. Minimize traditional UI elements in favor of contextual, conversation-driven interactions. + +## User Journey: Paula's Conversational Workflow + +### 1. Natural Language Query +Paula starts with a conversational query: "I need a production-ready model for customer service chatbots that responds in under 100ms and costs less than $3 per hour" + +### 2. Intelligent Clarification +The AI assistant asks clarifying questions: "What type of customer inquiries will this handle? Do you need multilingual support? Any specific compliance requirements?" + +### 3. Smart Recommendations +Based on the conversation, the assistant presents 3-4 tailored recommendations with explanations: "Based on your requirements, I recommend granite-7b-code because it excels at structured responses with 45ms average latency..." + +### 4. Guided Comparison +Paula asks: "How does granite-7b compare to llama-3.1 for my use case?" The assistant provides a contextual comparison with visual aids. + +### 5. Deployment Assistance +The assistant guides through deployment: "I can help you deploy granite-7b. Would you like me to configure it for your customer service environment?" + +## Key UI Components + +### 1. Conversational Interface +``` +┌─────────────────────────────────────────────────────────┐ +│ 🤖 RHOAI Assistant 🎤 🔊 ⚙️ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ 👤 I need a production model for customer service │ │ +│ │ chatbots under 100ms latency │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ 🤖 I can help you find the perfect model! Let me │ │ +│ │ ask a few questions to narrow down the options: │ │ +│ │ │ │ +│ │ • What type of customer inquiries? (FAQ, tech │ │ +│ │ support, sales, etc.) │ │ +│ │ • Do you need multilingual support? │ │ +│ │ • Any specific compliance requirements? │ │ +│ │ │ │ +│ │ 💡 Quick suggestions: │ │ +│ │ [FAQ Support] [Tech Support] [Sales Inquiries] │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ Type your message... 🎤 │ │ +│ └─────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────┘ +``` + +### 2. Contextual Model Cards +``` +┌─────────────────────────────────────────────────────────┐ +│ 🤖 Based on your needs, here are my top 3 recommendations:│ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ 🥇 granite-7b-code:1.1 [Deploy] │ │ +│ │ ├── ⚡ 45ms avg latency (✅ meets your <100ms req) │ │ +│ │ ├── 💰 $2.40/hour (✅ under your $3 budget) │ │ +│ │ ├── 🎯 94% accuracy on customer service tasks │ │ +│ │ ├── 🏆 Why I recommend this: Excellent balance of │ │ +│ │ │ speed and accuracy, proven in production │ │ +│ │ └── [Tell me more] [Compare with others] [Deploy] │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ 🥈 llama-3.1-8b-instruct [Deploy] │ │ +│ │ ├── ⚡ 67ms avg latency (✅ meets requirement) │ │ +│ │ ├── 💰 $3.60/hour (⚠️ slightly over budget) │ │ +│ │ ├── 🎯 96% accuracy (higher than granite-7b) │ │ +│ │ └── 🏆 Why consider: Better accuracy, multilingual │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ 💬 Would you like me to explain why I ranked these │ +│ models this way, or shall we dive deeper into one? │ +└─────────────────────────────────────────────────────────┘ +``` + +### 3. Smart Comparison View +``` +┌─────────────────────────────────────────────────────────┐ +│ 👤 "How does granite-7b compare to llama-3.1?" │ +│ │ +│ 🤖 Great question! Here's how they stack up for your │ +│ customer service use case: │ +│ │ +│ ┌─────────────────────┬─────────────────────────────────┤ +│ │ 🏷️ granite-7b-code │ 🏷️ llama-3.1-8b-instruct │ +│ ├─────────────────────┼─────────────────────────────────┤ +│ │ ⚡ Speed: 45ms │ ⚡ Speed: 67ms │ +│ │ 🎯 Accuracy: 94% │ 🎯 Accuracy: 96% │ +│ │ 💰 Cost: $2.40/hr │ 💰 Cost: $3.60/hr │ +│ │ 🌍 Languages: EN │ 🌍 Languages: 50+ │ +│ │ 📊 Production: ✅ │ 📊 Production: ✅ │ +│ └─────────────────────┴─────────────────────────────────┘ +│ │ +│ 💡 My recommendation: If you only need English and want │ +│ to stay under budget, go with granite-7b. If you │ +│ might expand internationally or need the highest │ +│ accuracy, llama-3.1 is worth the extra cost. │ +│ │ +│ 🎯 Quick actions: │ +│ [Deploy granite-7b] [Deploy llama-3.1] [See more options]│ +└─────────────────────────────────────────────────────────┘ +``` + +### 4. Guided Deployment Wizard +``` +┌─────────────────────────────────────────────────────────┐ +│ 👤 "Let's deploy granite-7b for my customer service" │ +│ │ +│ 🤖 Perfect choice! I'll guide you through the setup. │ +│ I've pre-configured everything based on our │ +│ conversation: │ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ 🚀 Deployment Configuration │ │ +│ │ ├── Model: granite-7b-code:1.1 │ │ +│ │ ├── Environment: Production │ │ +│ │ ├── Instance: A100 GPU (recommended for <100ms) │ │ +│ │ ├── Scaling: Auto-scale 1-5 instances │ │ +│ │ ├── Monitoring: Customer service metrics enabled │ │ +│ │ └── Budget Alert: $3/hour threshold │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ 💬 Everything look good? I can also: │ +│ • Set up A/B testing with llama-3.1 │ +│ • Configure custom monitoring for your KPIs │ +│ • Create alerts for latency spikes │ +│ │ +│ [Deploy Now] [Customize Settings] [Schedule Deployment] │ +└─────────────────────────────────────────────────────────┘ +``` + +### 5. Voice Interface Integration +``` +┌─────────────────────────────────────────────────────────┐ +│ 🎤 Voice Assistant Active 🔊 Speaking │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ 🌊 ████████████████████████████████████████████████ │ │ +│ │ "Show me models with latency under fifty │ │ +│ │ milliseconds for text generation" │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ 🤖 I found 12 models matching your criteria. The top │ +│ performers are granite-7b at 45ms, mistral-7b at │ +│ 23ms, and claude-3-haiku at 38ms. Would you like │ +│ me to show you the details or compare them? │ +│ │ +│ 🎯 Voice Commands: │ +│ • "Show details" - Display model specifications │ +│ • "Compare all three" - Side-by-side comparison │ +│ • "Deploy granite" - Start deployment process │ +│ • "Filter by cost" - Add cost filtering │ +│ │ +│ [🎤 Hold to Speak] [⌨️ Switch to Text] [🔇 Mute Voice] │ +└─────────────────────────────────────────────────────────┘ +``` + +### 6. Contextual Help & Learning +``` +┌─────────────────────────────────────────────────────────┐ +│ 🤖 I notice you often look for models under 100ms │ +│ latency. Here are some tips to help you find them │ +│ faster: │ +│ │ +│ 💡 Pro Tips: │ +│ • Use "fast models" as a shortcut for <100ms filter │ +│ • Try "production ready + fast" for pre-vetted options│ +│ • Ask "what's the fastest model for [use case]" │ +│ │ +│ 📚 Would you like me to: │ +│ • Create a saved search for your common criteria? │ +│ • Set up alerts for new fast models? │ +│ • Show you advanced filtering techniques? │ +│ │ +│ 🎯 Quick Actions: │ +│ [Create Saved Search] [Set Up Alerts] [Learn More] │ +│ │ +│ 💬 You can also say things like: │ +│ • "Find me something like granite-7b but faster" │ +│ • "What's new in the model catalog this week?" │ +│ • "Help me optimize costs for my current deployments" │ +└─────────────────────────────────────────────────────────┘ +``` + +## Information Architecture + +``` +RHOAI Dashboard (Conversational AI Assistant) +├── Main Conversation Interface +│ ├── Chat History (persistent, searchable) +│ ├── Message Input (text + voice) +│ ├── Quick Action Buttons (contextual) +│ └── Voice Controls (always available) +├── Contextual Overlays (triggered by conversation) +│ ├── Model Detail Cards +│ ├── Comparison Views +│ ├── Deployment Wizards +│ └── Performance Charts +├── Smart Suggestions Panel (adaptive) +│ ├── Conversation Starters +│ ├── Follow-up Questions +│ ├── Related Actions +│ └── Learning Resources +├── Traditional Fallback Navigation (collapsible) +│ ├── Model Catalog (simplified) +│ ├── Deployments (status only) +│ └── Settings (voice preferences) +└── Assistant Personality & Learning + ├── User Preference Learning + ├── Context Memory (session + long-term) + ├── Expertise Areas (model types, use cases) + └── Conversation History Analysis +``` + +## Visual Design Language + +### Color Palette (Conversational/Friendly) +- **Primary**: Warm Blue (#2563EB) - Trustworthy, intelligent +- **Secondary**: Soft Purple (#7C3AED) - Creative, innovative +- **Accent**: Vibrant Green (#10B981) - Positive, helpful +- **Assistant**: Cool Gray (#6B7280) - Neutral, professional +- **User**: Warm Gray (#374151) - Personal, human +- **Success**: Emerald (#059669) - Achievement, completion +- **Warning**: Amber (#F59E0B) - Caution, attention +- **Error**: Rose (#F43F5E) - Issues, problems + +### Typography (Conversational) +- **Headers**: Red Hat Display (Medium, 20-28px) - Friendly authority +- **Body/Chat**: Red Hat Text (Regular, 15-16px) - Readable conversation +- **Assistant**: Red Hat Text (Regular, 15px) - Consistent, clear +- **User**: Red Hat Text (Medium, 15px) - Slightly emphasized +- **Code/Data**: Red Hat Mono (Regular, 13-14px) - Technical precision + +### Layout Principles +- **Conversation Flow**: Chronological, chat-like interface +- **Contextual Density**: Information appears when relevant +- **Breathing Room**: Generous spacing for comfortable reading +- **Focus Management**: Single conversation thread with contextual overlays + +### Interaction Patterns +- **Natural Language**: Primary interaction through conversation +- **Voice Integration**: Seamless voice input and output +- **Contextual Actions**: Buttons appear based on conversation context +- **Progressive Disclosure**: Information revealed as needed + +## Technical Considerations + +### PatternFly Integration (60% Reuse Target) +- **Selective Reuse**: Card, Modal, Button, Form components for overlays +- **Custom Components**: + - `ConversationInterface` (chat-like message flow) + - `VoiceInput` (speech recognition integration) + - `ContextualOverlay` (smart information display) + - `SmartSuggestions` (AI-powered recommendations) + - `ConversationMemory` (context persistence) + +### React Architecture +``` +src/ +├── components/ +│ ├── conversation/ +│ │ ├── ChatInterface/ +│ │ │ ├── MessageThread.tsx +│ │ │ ├── MessageInput.tsx +│ │ │ └── VoiceControls.tsx +│ │ ├── Assistant/ +│ │ │ ├── AIResponse.tsx +│ │ │ ├── SmartSuggestions.tsx +│ │ │ └── ContextualCards.tsx +│ │ ├── Voice/ +│ │ │ ├── SpeechRecognition.tsx +│ │ │ ├── TextToSpeech.tsx +│ │ │ └── VoiceCommands.tsx +│ │ └── Overlays/ +│ │ ├── ModelDetails.tsx +│ │ ├── ComparisonView.tsx +│ │ └── DeploymentWizard.tsx +│ └── shared/ +│ ├── NaturalLanguage/ +│ └── ContextManager/ +├── hooks/ +│ ├── useConversation.ts +│ ├── useVoiceInterface.ts +│ ├── useAIAssistant.ts +│ └── useContextMemory.ts +├── services/ +│ ├── nlpService.ts +│ ├── voiceService.ts +│ ├── aiAssistant.ts +│ └── conversationMemory.ts +└── utils/ + ├── speechProcessing.ts + ├── contextAnalysis.ts + └── intentRecognition.ts +``` + +### AI/ML Integration +- **Natural Language Processing**: Intent recognition and entity extraction +- **Conversation Management**: Context tracking and memory +- **Voice Processing**: Speech-to-text and text-to-speech +- **Recommendation Engine**: ML-powered model suggestions +- **Learning System**: User preference adaptation + +### Performance Optimizations +- **Streaming Responses**: Real-time AI response generation +- **Voice Processing**: Local speech recognition when possible +- **Context Caching**: Efficient conversation memory management +- **Lazy Loading**: Load overlays and detailed views on demand +- **Offline Capability**: Basic conversation when network limited + +## Accessibility Features + +### Voice Interface Accessibility +- **Multiple Input Methods**: Voice, text, and traditional navigation +- **Voice Feedback**: Audio confirmation of actions +- **Speech Rate Control**: Adjustable speaking speed +- **Voice Commands**: Comprehensive voice control vocabulary + +### Screen Reader Excellence +- **Conversation Flow**: Proper reading order for chat interface +- **Live Regions**: Announce new messages and responses +- **Rich Descriptions**: Detailed descriptions of visual elements +- **Alternative Navigation**: Traditional navigation always available + +### Motor Accessibility +- **Voice Primary**: Reduce need for precise mouse/touch input +- **Large Touch Targets**: 44px minimum for all interactive elements +- **Keyboard Alternatives**: Full keyboard navigation support +- **Dwell Clicking**: Support for eye-tracking and dwell interfaces + +## Mobile/Responsive Design + +### Mobile-First Approach +- **Touch Optimized**: Large touch targets, swipe gestures +- **Voice Primary**: Emphasize voice input on mobile devices +- **Simplified UI**: Minimal chrome, focus on conversation +- **Offline Capability**: Basic functionality without network + +### Responsive Adaptations +- **Mobile**: Full-screen conversation interface +- **Tablet**: Split view with conversation + contextual panels +- **Desktop**: Multi-panel layout with traditional fallback options + +### Cross-Platform Voice +- **Native Integration**: Use platform voice APIs when available +- **Consistent Experience**: Same conversation across all devices +- **Sync Capability**: Conversation history syncs across devices + +## Performance Impact Assessment + +### AI/ML Processing +- **Edge Computing**: Local processing for basic NLP when possible +- **Cloud Integration**: Advanced AI features through API calls +- **Caching Strategy**: Cache common responses and user preferences +- **Progressive Enhancement**: Graceful degradation when AI unavailable + +### Voice Processing +- **Browser APIs**: Use native Web Speech API when supported +- **Fallback Options**: Text input always available +- **Bandwidth Optimization**: Compress voice data for transmission +- **Local Processing**: Client-side voice recognition when possible + +### Memory Management +- **Conversation Pruning**: Limit conversation history length +- **Context Compression**: Efficient storage of conversation context +- **Cleanup**: Proper cleanup of voice processing resources +- **Background Processing**: Handle AI responses without blocking UI + +--- + +# Implementation Recommendations + +## Phased Rollout Strategy + +### Phase 1: Foundation (Months 1-3) +- **Direction 2 (Command Center)**: Implement as primary interface + - Lowest risk, builds on existing patterns + - Immediate productivity gains for power users + - Establishes advanced filtering and bulk operations + +### Phase 2: Visual Enhancement (Months 4-6) +- **Direction 1 (Visual Intelligence)**: Add visual components + - Implement model visualization and comparison tools + - Add performance charts and recommendation engine + - Enhance with interactive filtering + +### Phase 3: AI Integration (Months 7-9) +- **Direction 3 (Conversational)**: Introduce AI assistant + - Start with basic natural language queries + - Add voice interface capabilities + - Implement learning and personalization + +## Technical Implementation Priority + +### High Priority (Must Have) +1. **Advanced Filtering System** (All Directions) +2. **Model Comparison Interface** (Directions 1 & 2) +3. **Real-time Performance Monitoring** (Direction 2) +4. **Responsive Design Foundation** (All Directions) + +### Medium Priority (Should Have) +1. **Visual Model Cards** (Direction 1) +2. **Command Palette** (Direction 2) +3. **Basic AI Recommendations** (Direction 3) +4. **Customizable Dashboards** (Direction 2) + +### Low Priority (Nice to Have) +1. **Network Graph Visualization** (Direction 1) +2. **Voice Interface** (Direction 3) +3. **Workflow Builder** (Direction 1) +4. **Advanced AI Learning** (Direction 3) + +## Success Metrics & KPIs + +### User Experience Metrics +- **Task Completion Time**: 60% reduction in model discovery time +- **User Satisfaction**: >4.5/5 rating for new interface +- **Feature Adoption**: >80% usage of new filtering capabilities +- **Error Reduction**: 50% fewer user errors in model selection + +### Technical Performance Metrics +- **Page Load Time**: <2 seconds for initial dashboard load +- **Filter Response Time**: <300ms for filter operations +- **Accessibility Score**: WCAG 2.1 AA compliance (100%) +- **Mobile Performance**: <3 seconds load time on 3G networks + +### Business Impact Metrics +- **Model Deployment Efficiency**: 40% faster deployment workflows +- **User Retention**: Increased daily active users +- **Support Ticket Reduction**: 30% fewer UI-related support requests +- **Training Time**: 50% reduction in new user onboarding time + +## Risk Mitigation + +### Technical Risks +- **Performance Impact**: Implement progressive loading and virtualization +- **Browser Compatibility**: Provide fallbacks for advanced features +- **Accessibility Regression**: Comprehensive testing throughout development +- **Data Security**: Ensure all new features maintain security standards + +### User Adoption Risks +- **Change Management**: Provide optional traditional interface during transition +- **Training Requirements**: Create comprehensive documentation and tutorials +- **Feature Complexity**: Implement progressive disclosure of advanced features +- **Feedback Integration**: Establish user feedback loops for continuous improvement + +## Conclusion + +These three design directions offer distinct approaches to solving Paula's core challenge of efficiently finding and evaluating AI models. Each direction can be implemented independently or combined to create a comprehensive solution that serves different user preferences and workflows. + +The **Enterprise Command Center** approach provides immediate value with minimal risk, while the **AI-First Visual Intelligence** direction offers innovative visualization capabilities. The **Conversational AI Assistant** represents the future of human-computer interaction for complex technical tasks. + +By implementing these designs with a focus on accessibility, performance, and user experience, RHOAI 3.0 can transform from a traditional enterprise dashboard into a modern, intelligent platform that accelerates AI adoption and deployment across organizations. \ No newline at end of file diff --git a/components/frontend/static-prototype/styles.css b/components/frontend/static-prototype/styles.css new file mode 100644 index 000000000..d73bfb2ad --- /dev/null +++ b/components/frontend/static-prototype/styles.css @@ -0,0 +1,527 @@ +/* vTeam Static Prototype Styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + line-height: 1.6; + color: #333; + background-color: #f8fafc; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 20px; +} + +/* Header */ +.header { + background: white; + border-bottom: 1px solid #e2e8f0; + padding: 1rem 0; + position: sticky; + top: 0; + z-index: 100; +} + +.header-content { + display: flex; + justify-content: space-between; + align-items: center; +} + +.logo { + font-size: 1.5rem; + font-weight: bold; + color: #000000; + text-decoration: none; +} + +.nav { + display: flex; + gap: 2rem; +} + +.nav a { + text-decoration: none; + color: #64748b; + font-weight: 500; + transition: color 0.2s; +} + +.nav a:hover, .nav a.active { + color: #1e40af; +} + +/* Page Header */ +.page-header { + background: white; + border-bottom: 1px solid #e2e8f0; + padding: 2rem 0; +} + +.page-header-content { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 2rem; +} + +.page-header-left { + flex: 1; +} + +.page-title { + font-size: 2rem; + font-weight: bold; + margin-bottom: 0.5rem; +} + +.page-description { + color: #64748b; + font-size: 1rem; +} + +.page-actions { + display: flex; + gap: 1rem; + align-items: center; + flex-shrink: 0; +} + +.page-actions .btn { + min-width: 140px; + height: 40px; + justify-content: center; + align-items: center; + padding: 0 1rem; +} + +/* Buttons */ +.btn { + padding: 0.5rem 1rem; + border: none; + border-radius: 0.375rem; + font-weight: 500; + cursor: pointer; + text-decoration: none; + display: inline-flex; + align-items: center; + gap: 0.5rem; + transition: all 0.2s; +} + +.btn-primary { + background: #1e40af; + color: white; +} + +.btn-primary:hover { + background: #1d4ed8; +} + +.btn-secondary { + background: white; + color: #374151; + border: 1px solid #d1d5db; +} + +.btn-secondary:hover { + background: #f9fafb; +} + +/* Cards */ +.card { + background: white; + border-radius: 0.5rem; + border: 1px solid #e2e8f0; + overflow: hidden; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; + margin-bottom: 0.5rem; +} + +.card-description { + color: #64748b; +} + +.card-content { + padding: 1.5rem; +} + +/* Tables */ +.table { + width: 100%; + border-collapse: collapse; +} + +.table th, +.table td { + text-align: left; + padding: 0.75rem; + border-bottom: 1px solid #e2e8f0; +} + +.table th { + font-weight: 600; + color: #374151; + background: #f8fafc; +} + +.table tr:hover { + background: #f8fafc; +} + +/* Status badges */ +.badge { + padding: 0.25rem 0.75rem; + border-radius: 9999px; + font-size: 0.75rem; + font-weight: 500; +} + +.badge-success { + background: #dcfce7; + color: #166534; +} + +.badge-warning { + background: #fef3c7; + color: #92400e; +} + +.badge-error { + background: #fee2e2; + color: #991b1b; +} + +.badge-info { + background: #dbeafe; + color: #1e40af; +} + +/* Tabs */ +.tabs { + margin-top: 2rem; +} + +.tab-list { + display: flex; + border-bottom: 1px solid #e2e8f0; + margin-bottom: 1.5rem; +} + +.tab-button { + padding: 0.75rem 1rem; + border: none; + background: none; + cursor: pointer; + color: #64748b; + font-weight: 500; + border-bottom: 2px solid transparent; +} + +.tab-button.active { + color: #1e40af; + border-bottom-color: #1e40af; +} + +.tab-content { + display: none; +} + +.tab-content.active { + display: block; +} + +/* Grid layouts */ +.grid { + display: grid; + gap: 1.5rem; +} + +.grid-2 { + grid-template-columns: repeat(2, 1fr); +} + +.grid-3 { + grid-template-columns: repeat(3, 1fr); +} + +@media (max-width: 768px) { + .grid-2, + .grid-3 { + grid-template-columns: 1fr; + } + + .page-header-content { + flex-direction: column; + align-items: stretch; + gap: 1rem; + } + + .page-actions { + justify-content: flex-start; + } + + .nav { + display: none; + } +} + +/* Breadcrumbs */ +.breadcrumbs { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 1rem; + color: #64748b; + font-size: 0.875rem; +} + +.breadcrumbs a { + color: #1e40af; + text-decoration: none; +} + +.breadcrumbs a:hover { + text-decoration: underline; +} + +/* Project sidebar */ +.project-layout { + display: grid; + grid-template-columns: 250px 1fr; + gap: 2rem; + margin-top: 2rem; +} + +.project-sidebar { + background: white; + border-radius: 0.5rem; + border: 1px solid #e2e8f0; + padding: 1rem; + height: fit-content; +} + +.project-sidebar ul { + list-style: none; +} + +.project-sidebar li { + margin-bottom: 0.5rem; +} + +.project-sidebar a { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.5rem; + color: #64748b; + text-decoration: none; + border-radius: 0.375rem; + transition: all 0.2s; +} + +.project-sidebar a:hover, +.project-sidebar a.active { + background: #f1f5f9; + color: #1e40af; +} + +@media (max-width: 768px) { + .project-layout { + grid-template-columns: 1fr; + } + + .project-sidebar { + order: 2; + } +} + +/* Empty states */ +.empty-state { + text-align: center; + padding: 3rem 1rem; + color: #64748b; +} + +.empty-state h3 { + margin-bottom: 0.5rem; + color: #374151; +} + +/* Animations */ +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +/* Form styles */ +.form-group { + margin-bottom: 1rem; +} + +.form-label { + display: block; + margin-bottom: 0.5rem; + font-weight: 500; + color: #374151; +} + +.form-input { + width: 100%; + padding: 0.5rem; + border: 1px solid #d1d5db; + border-radius: 0.375rem; + font-size: 1rem; +} + +select.form-input, +.form-input select { + padding-right: 2.5rem !important; +} + +select { + padding-right: 2.5rem; +} + +.form-input:focus { + outline: none; + border-color: #1e40af; + box-shadow: 0 0 0 3px rgba(30, 64, 175, 0.1); +} + +.form-textarea { + resize: vertical; + min-height: 100px; +} + +/* Modal styles */ +.modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; +} + +.modal-content { + background: white; + border-radius: 0.5rem; + width: 90%; + max-width: 500px; + max-height: 90vh; + overflow-y: auto; + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); +} + +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; +} + +.modal-header h2 { + margin: 0; + font-size: 1.25rem; + font-weight: 600; +} + +.modal-close { + background: none; + border: none; + font-size: 1.5rem; + cursor: pointer; + color: #64748b; + padding: 0; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; +} + +.modal-close:hover { + color: #374151; +} + +.modal-body { + padding: 1.5rem; +} + +/* User dropdown styles */ +.user-dropdown-trigger:hover { + background-color: #f1f5f9 !important; +} + +.user-dropdown-menu button:hover { + background-color: #f1f5f9 !important; +} + +/* Session dropdown styles */ +#sessionDropdownMenu button:hover { + background-color: #f1f5f9 !important; +} + +/* Accordion styles */ +.accordion-item { + background: white; + border: 1px solid #e2e8f0; + border-radius: 0.375rem; + overflow: hidden; +} + +.accordion-header { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem 1rem; + background: white; + border: none; + cursor: pointer; + font-weight: 500; + font-size: 0.875rem; + color: #374151; + transition: background-color 0.2s; + text-align: left; +} + +.accordion-header:hover { + background: #f8fafc; +} + +.accordion-header svg { + transition: transform 0.2s; + flex-shrink: 0; +} + +.accordion-content { + background: #f8fafc; + border-top: 1px solid #e2e8f0; + overflow: hidden; +} diff --git a/components/manifests/overlays/local-dev/build-configs.yaml b/components/manifests/overlays/local-dev/build-configs.yaml index 8d5999d59..de4f4ec40 100644 --- a/components/manifests/overlays/local-dev/build-configs.yaml +++ b/components/manifests/overlays/local-dev/build-configs.yaml @@ -25,7 +25,7 @@ spec: strategy: type: Docker dockerStrategy: - dockerfilePath: Dockerfile + dockerfilePath: Dockerfile.dev output: to: kind: ImageStreamTag @@ -43,7 +43,7 @@ spec: strategy: type: Docker dockerStrategy: - dockerfilePath: Dockerfile + dockerfilePath: Dockerfile.dev output: to: kind: ImageStreamTag diff --git a/components/scripts/local-dev/crc-start.sh b/components/scripts/local-dev/crc-start.sh index f1a805210..52754ac8d 100755 --- a/components/scripts/local-dev/crc-start.sh +++ b/components/scripts/local-dev/crc-start.sh @@ -275,9 +275,12 @@ apply_rbac() { log "Creating frontend authentication..." oc apply -f "${MANIFESTS_DIR}/frontend-auth.yaml" -n "$PROJECT_NAME" - # Wait for token secret to be populated + # Wait for token secret to be populated (service account tokens are auto-created) log "Waiting for frontend auth token to be created..." - oc wait --for=condition=complete secret/frontend-auth-token --timeout=60s -n "$PROJECT_NAME" || true + until oc get secret/frontend-auth-token -n "$PROJECT_NAME" -o jsonpath='{.data.token}' 2>/dev/null | grep -q .; do + sleep 1 + done + log "Frontend auth token created successfully" } apply_operator_rbac() {