Skip to content
Closed
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
45 changes: 45 additions & 0 deletions app/api/projects/[project_id]/skills/[name]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Single skill API
* GET /api/projects/[project_id]/skills/[name] - read a skill
* DELETE /api/projects/[project_id]/skills/[name] - delete a skill
*/

import { NextRequest } from 'next/server';
import { getSkill, deleteSkill, SkillError } from '@/lib/services/skills';
import { createSuccessResponse, createErrorResponse, handleApiError } from '@/lib/utils/api-response';

interface RouteContext {
params: Promise<{ project_id: string; name: string }>;
}

export async function GET(_request: NextRequest, { params }: RouteContext) {
try {
const { project_id, name } = await params;
const skill = await getSkill(project_id, name);
if (!skill) {
return createErrorResponse('Skill not found', undefined, 404);
}
return createSuccessResponse(skill);
} catch (error) {
if (error instanceof SkillError) {
return createErrorResponse(error.message, undefined, error.status);
}
return handleApiError(error, 'API', 'Failed to read skill');
}
}

export async function DELETE(_request: NextRequest, { params }: RouteContext) {
try {
const { project_id, name } = await params;
const ok = await deleteSkill(project_id, name);
return createSuccessResponse({ deleted: ok, name });
} catch (error) {
if (error instanceof SkillError) {
return createErrorResponse(error.message, undefined, error.status);
}
return handleApiError(error, 'API', 'Failed to delete skill');
}
}

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
51 changes: 51 additions & 0 deletions app/api/projects/[project_id]/skills/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Per-project Skills API
* GET /api/projects/[project_id]/skills - list skills
* POST /api/projects/[project_id]/skills - create/update a skill
*/

import { NextRequest } from 'next/server';
import { listAllSkills, saveSkill, SkillError } from '@/lib/services/skills';
import { createSuccessResponse, createErrorResponse, handleApiError } from '@/lib/utils/api-response';

interface RouteContext {
params: Promise<{ project_id: string }>;
}

export async function GET(_request: NextRequest, { params }: RouteContext) {
try {
const { project_id } = await params;
const skills = await listAllSkills(project_id);
return createSuccessResponse(skills);
} catch (error) {
if (error instanceof SkillError) {
return createErrorResponse(error.message, undefined, error.status);
}
return handleApiError(error, 'API', 'Failed to list skills');
}
}

export async function POST(request: NextRequest, { params }: RouteContext) {
try {
const { project_id } = await params;
const body = await request.json().catch(() => ({}));
if (!body || typeof body.name !== 'string' || body.name.trim().length === 0) {
return createErrorResponse('name is required', undefined, 400);
}
const skill = await saveSkill(project_id, {
name: body.name,
description: typeof body.description === 'string' ? body.description : '',
content: typeof body.content === 'string' ? body.content : '',
raw: typeof body.raw === 'string' ? body.raw : undefined,
});
return createSuccessResponse(skill, 201);
} catch (error) {
if (error instanceof SkillError) {
return createErrorResponse(error.message, undefined, error.status);
}
return handleApiError(error, 'API', 'Failed to save skill');
}
}

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
17 changes: 14 additions & 3 deletions components/settings/ProjectSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { GeneralSettings } from './GeneralSettings';
import { AIAssistantSettings } from './AIAssistantSettings';
import { EnvironmentSettings } from './EnvironmentSettings';
import { ServiceSettings } from './ServiceSettings';
import { SkillsSettings } from './SkillsSettings';
import GlobalSettings from './GlobalSettings';

interface ProjectSettingsProps {
Expand All @@ -21,7 +22,7 @@ interface ProjectSettingsProps {
onProjectUpdated?: (update: { name: string; description?: string | null }) => void;
}

type SettingsTab = 'general' | 'ai-assistant' | 'environment' | 'services';
type SettingsTab = 'general' | 'ai-assistant' | 'environment' | 'services' | 'skills';

export function ProjectSettings({
isOpen,
Expand Down Expand Up @@ -58,6 +59,12 @@ export function ProjectSettings({
label: 'Services',
icon: <span className="w-4 h-4 inline-flex"><FaPlug /></span>,
},
{
id: 'skills' as SettingsTab,
label: 'Skills',
icon: <span className="w-4 h-4 inline-flex items-center justify-center text-sm leading-none">✦</span>,
hidden: !isProjectScoped,
},
].filter(tab => !('hidden' in tab) || !tab.hidden),
[isProjectScoped]
);
Expand Down Expand Up @@ -140,15 +147,19 @@ export function ProjectSettings({
)}

{activeTab === 'services' && (
<ServiceSettings
projectId={projectId}
<ServiceSettings
projectId={projectId}
onOpenGlobalSettings={() => {
// Open Global Settings with services tab
setShowGlobalSettings(true);
onClose(); // Close current modal
}}
/>
)}

{activeTab === 'skills' && (
<SkillsSettings projectId={projectId} />
)}
</div>
</div>
</SettingsModal>
Expand Down
Loading