Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ import GuardrailsHistoryPage from './pages/guardrails/HistoryPage';
import DashboardPage from './pages/DashboardPage';
import OverviewPage from './pages/overview/OverviewPage';
import ModelTestPage from './pages/ModelTestPage';
import UserPage from './pages/prompt/UserPage';
import SkillPage from './pages/prompt/SkillPage';
import SkillScanPage from './pages/guardrails/SkillScanPage';
import CommandPage from './pages/prompt/CommandPage';
import RemoteCoderPage from './pages/remote-coder/RemoteCoderPage';
import RemoteCoderSessionsPage from './pages/remote-coder/RemoteCoderSessionsPage';
Expand Down Expand Up @@ -310,8 +309,7 @@ function AppContent() {
<Route path="/overview/:timeRange" element={<OverviewPage />} />
<Route path="/model-test/:providerUuid" element={<ModelTestPage />} />
{/* Prompt routes */}
<Route path="/prompt/user" element={<UserPage />} />
<Route path="/prompt/skill" element={<SkillPage />} />
<Route path="/prompt/skill" element={<Navigate to="/guardrails/skill-scan" replace />} />
<Route path="/prompt/command" element={<CommandPage />} />
{/* Remote Control routes */}
<Route path="/remote-coder" element={<Navigate to="/remote-coder/chat" replace />} />
Expand All @@ -332,6 +330,7 @@ function AppContent() {
<Route path="/remote-control/slack" element={<SlackPage />} />
{/* Guardrails */}
<Route path="/guardrails" element={<GuardrailsPage />} />
<Route path="/guardrails/skill-scan" element={<SkillScanPage />} />
<Route path="/guardrails/groups" element={<GuardrailsGroupsPage />} />
<Route path="/guardrails/rules" element={<GuardrailsRulesPage />} />
<Route path="/guardrails/credentials" element={<GuardrailsCredentialsPage />} />
Expand Down
78 changes: 1 addition & 77 deletions frontend/src/components/GlobalExperimentalFeatures.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,17 @@
import {useFeatureFlags} from '@/contexts/FeatureFlagsContext';
import { Cloud, Psychology, Security } from '@mui/icons-material';
import { Security } from '@mui/icons-material';
import {Alert, Box, Chip, Tooltip, Typography,} from '@mui/material';
import React, {useEffect, useState} from 'react';
import {api} from '../services/api';
import {isFullEdition} from "@/utils/edition.ts";

const SKILL_FEATURES = [
{
key: 'skill_ide',
label: 'IDE Skills',
description: 'Enable IDE Skills feature for managing code snippets and skills from IDEs'
},
] as const;

const GlobalExperimentalFeatures: React.FC = () => {
const [features, setFeatures] = useState<Record<string, boolean>>({});
const [guardrailsEnabled, setGuardrailsEnabled] = useState(false);
const [loading, setLoading] = useState(true);
const {refresh} = useFeatureFlags();

const loadFeatures = async () => {
try {
setLoading(true);
// Load skill features
const results = await Promise.all(
SKILL_FEATURES.map(f => api.getScenarioFlag('_global', f.key))
);
const newFeatures: Record<string, boolean> = {};
SKILL_FEATURES.forEach((f, i) => {
newFeatures[f.key] = results[i]?.data?.value || false;
});
setFeatures(newFeatures);

// Load Guardrails flag
const guardrailsResult = await api.getScenarioFlag('_global', 'guardrails');
setGuardrailsEnabled(guardrailsResult?.data?.value || false);

Expand All @@ -43,26 +22,6 @@ const GlobalExperimentalFeatures: React.FC = () => {
}
};

const toggleFeature = (featureKey: string) => {
const newValue = !features[featureKey];
console.log('toggleGlobalFeature called:', featureKey, newValue);
api.setScenarioFlag('_global', featureKey, newValue)
.then((result) => {
console.log('setScenarioFlag result:', result);
if (result.success) {
setFeatures(prev => ({...prev, [featureKey]: newValue}));
refresh()
} else {
console.error('Failed to set global feature:', result);
loadFeatures();
}
})
.catch((err) => {
console.error('Failed to set global feature:', err);
loadFeatures();
});
};

const toggleGuardrails = () => {
const newValue = !guardrailsEnabled;
api.setScenarioFlag('_global', 'guardrails', newValue)
Expand Down Expand Up @@ -102,41 +61,6 @@ const GlobalExperimentalFeatures: React.FC = () => {

return (
<Box sx={{display: 'flex', flexDirection: 'column', gap: 0}}>
{/* Skill Features - Only in full edition */}
{isFullEdition && (
<Box sx={{display: 'flex', alignItems: 'center', py: 2, gap: 3}}>
{/* Label */}
<Box sx={{display: 'flex', alignItems: 'center', gap: 1, minWidth: 180}}>
<Psychology sx={{fontSize: '1rem', color: 'text.secondary'}}/>
<Typography variant="subtitle2" sx={{color: 'text.secondary'}}>
Skills
</Typography>
<Tooltip title="Skill Features - Enable prompt and skill management features" arrow>
<Box/>
</Tooltip>
</Box>

{/* Skill feature toggles as clickable chips */}
<Box sx={{display: 'flex', alignItems: 'center', gap: 2, flex: 1}}>
{SKILL_FEATURES.map((feature) => {
const isEnabled = features[feature.key] || false;
return (
<Tooltip key={feature.key}
title={feature.description + (isEnabled ? ' (enabled)' : ' (disabled) - Click to enable')}
arrow>
<Chip
label={`${feature.label} · ${isEnabled ? 'On' : 'Off'}`}
onClick={() => toggleFeature(feature.key)}
size="small"
sx={chipStyle(isEnabled)}
/>
</Tooltip>
);
})}
</Box>
</Box>)
}

{/* Guardrails Section */}
<Box sx={{ display: 'flex', alignItems: 'center', py: 2, gap: 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, minWidth: 180 }}>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/prompt/skill/SkillDetailDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const SkillDetailDialog = ({ open, skill, location, onClose }: SkillDetailDialog
const result = await api.getSkillContent(
location.id,
skill.id,
skill.path
skill.entry_path || skill.path
);
if (result.success && result.data) {
setContent(result.data.content || '');
Expand Down Expand Up @@ -130,7 +130,7 @@ const SkillDetailDialog = ({ open, skill, location, onClose }: SkillDetailDialog
{skill.name}
</Typography>
<Typography variant="caption" color="text.secondary" noWrap>
{skill.filename}
{skill.path}
</Typography>
</Box>
</Box>
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/components/prompt/skill/SkillListDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ const SkillListDialog = ({ open, location, onClose, onSkillClick }: SkillListDia
const filteredSkills = skills.filter(
(skill) =>
skill.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
skill.filename.toLowerCase().includes(searchQuery.toLowerCase())
skill.filename.toLowerCase().includes(searchQuery.toLowerCase()) ||
skill.path.toLowerCase().includes(searchQuery.toLowerCase())
);

if (!location) return null;
Expand Down Expand Up @@ -235,7 +236,7 @@ const SkillListDialog = ({ open, location, onClose, onSkillClick }: SkillListDia
variant="caption"
color="text.secondary"
>
{skill.filename}
{skill.path}
</Typography>
<Typography
variant="caption"
Expand Down
14 changes: 2 additions & 12 deletions frontend/src/contexts/FeatureFlagsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import { api } from '@/services/api';
import { useAuth } from './AuthContext';

interface FeatureFlagsContextType {
skillUser: boolean;
skillIde: boolean;
enableGuardrails: boolean;
loading: boolean;
refresh: () => void;
Expand All @@ -26,20 +24,12 @@ interface FeatureFlagsProviderProps {

export const FeatureFlagsProvider: React.FC<FeatureFlagsProviderProps> = ({ children }) => {
const { isLoading: isAuthLoading } = useAuth();
const [skillUser, setSkillUser] = useState(false);
const [skillIde, setSkillIde] = useState(false);
const [enableGuardrails, setEnableGuardrails] = useState(false);
const [loading, setLoading] = useState(true);

const loadFlags = async () => {
try {
const [skillUserResult, skillIdeResult, guardrailsResult] = await Promise.all([
api.getScenarioFlag('_global', 'skill_user'),
api.getScenarioFlag('_global', 'skill_ide'),
api.getScenarioFlag('_global', 'guardrails'),
]);
setSkillUser(skillUserResult?.data?.value || false);
setSkillIde(skillIdeResult?.data?.value || false);
const guardrailsResult = await api.getScenarioFlag('_global', 'guardrails');
setEnableGuardrails(guardrailsResult?.data?.value || false);
} catch (error) {
// Silently fail - flags will default to false
Expand All @@ -62,7 +52,7 @@ export const FeatureFlagsProvider: React.FC<FeatureFlagsProviderProps> = ({ chil
};

return (
<FeatureFlagsContext.Provider value={{ skillUser, skillIde, enableGuardrails, loading, refresh }}>
<FeatureFlagsContext.Provider value={{ enableGuardrails, loading, refresh }}>
{children}
</FeatureFlagsContext.Provider>
);
Expand Down
38 changes: 7 additions & 31 deletions frontend/src/layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,10 @@ import {
ListAlt as LogsIcon,
Menu as MenuIcon,
NewReleases,
Psychology as PromptIcon,
Lan as RemoteIcon,
Bolt as SkillIcon,
Settings as SystemIcon,
Today as TodayIcon,
Send as UserPromptIcon,
Extension as VSCodeIcon,
Rule,
History as HistoryIcon,
Expand Down Expand Up @@ -109,7 +107,7 @@ const Layout = ({ children }: LayoutProps) => {
const navigate = useNavigate();
const { hasUpdate, currentVersion, showUpdateDialog } = useAppVersion();
const { isHealthy, showDisconnectDialog } = useHealth();
const { skillUser, skillIde, enableGuardrails} = useFeatureFlags();
const { enableGuardrails } = useFeatureFlags();
const [mobileOpen, setMobileOpen] = useState(false);
const [easterEggAnchorEl, setEasterEggAnchorEl] = useState<HTMLElement | null>(null);
const { profiles, refresh } = useProfileContext();
Expand Down Expand Up @@ -163,26 +161,6 @@ const Layout = ({ children }: LayoutProps) => {
return children?.some(item => isActive(item.path)) ?? false;
};

// Build prompt menu items based on feature flags
const promptMenuItems = useMemo(() => {
const items: NavItem[] = [];
if (skillUser) {
items.push({
path: '/prompt/user',
label: 'User Request',
icon: <UserPromptIcon sx={{ fontSize: 20 }} />,
});
}
if (skillIde) {
items.push({
path: '/prompt/skill',
label: 'Skills',
icon: <SkillIcon sx={{ fontSize: 20 }} />,
});
}
return items;
}, [skillUser, skillIde]);

// Activity bar items
const activityItems: ActivityItem[] = useMemo(() => {
// Build profile sidebar items dynamically
Expand Down Expand Up @@ -297,13 +275,6 @@ const Layout = ({ children }: LayoutProps) => {
},
],
},
// Only add Prompt menu if full edition
...(isFullEdition && promptMenuItems.length > 0 ? [{
key: 'prompt' as const,
icon: <PromptIcon sx={{ fontSize: 22 }} />,
label: 'Prompt',
children: promptMenuItems,
}] : []),
// Only add Remote menu if full edition
...(isFullEdition ? [{
key: 'remote-control' as const,
Expand Down Expand Up @@ -358,6 +329,11 @@ const Layout = ({ children }: LayoutProps) => {
label: 'Overview',
icon: <AccessControlIcon sx={{ fontSize: 20 }} />,
},
{
path: '/guardrails/skill-scan',
label: 'Skill Scan',
icon: <SkillIcon sx={{ fontSize: 20 }} />,
},
{
path: '/guardrails/groups',
label: 'Policy Groups',
Expand Down Expand Up @@ -416,7 +392,7 @@ const Layout = ({ children }: LayoutProps) => {
},
];
return items;
}, [t, promptMenuItems, enableGuardrails, profiles]);
}, [t, enableGuardrails, profiles]);

// Find current active activity
const activeActivity = useMemo(() => {
Expand Down
44 changes: 20 additions & 24 deletions frontend/src/pages/Guiding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { OpenAI, Anthropic, ClaudeCode } from '../components/BrandIcons';
import { Settings as SystemIcon, Code as CodeIcon, BarChart as BarChartIcon, Lock as LockIcon, AutoAwesome } from '@mui/icons-material';
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
import { useTranslation } from 'react-i18next';
import { useFeatureFlags } from '../contexts/FeatureFlagsContext';
import { Send as UserPromptIcon, Bolt as SkillIcon } from '@mui/icons-material';
import { Bolt as SkillIcon, Security as GuardrailsIcon } from '@mui/icons-material';

interface NavCard {
title: string;
Expand All @@ -23,28 +22,16 @@ interface CardGroup {
const Guiding = () => {
const navigate = useNavigate();
const { t } = useTranslation();
const { skillUser, skillIde } = useFeatureFlags();

// Build prompt cards based on feature flags
const promptCards: NavCard[] = [];
if (skillUser) {
promptCards.push({
title: 'User Request',
description: 'Manage user prompt templates',
path: '/prompt/user',
icon: <UserPromptIcon sx={{ fontSize: 40 }} />,
color: '#9333ea',
});
}
if (skillIde) {
promptCards.push({
title: 'Skills',
description: 'Configure AI skills and commands',
path: '/prompt/skill',
const guardrailsCards: NavCard[] = [
{
title: 'Skill Scan',
description: 'Scan local AI skills and review their contents',
path: '/guardrails/skill-scan',
icon: <SkillIcon sx={{ fontSize: 40 }} />,
color: '#e11d48',
});
}
},
];

const cardGroups: CardGroup[] = [
{
Expand Down Expand Up @@ -106,9 +93,18 @@ const Guiding = () => {
},
],
},
...(promptCards.length > 0 ? [{
categoryLabel: 'Prompt',
cards: promptCards,
...(guardrailsCards.length > 0 ? [{
categoryLabel: 'Guardrails',
cards: [
{
title: 'Guardrails',
description: 'Manage guardrail policies and review enforcement state',
path: '/guardrails',
icon: <GuardrailsIcon sx={{ fontSize: 40 }} />,
color: '#0f766e',
},
...guardrailsCards,
],
}] : []),
{
categoryLabel: 'Credentials',
Expand Down
Loading