Phase 3: Skills & Context — skill system, personal context, context layering

Backend:
- Skill model + migration (with FK on chats.skill_id)
- Personal + general skill CRUD services with access isolation
- Admin skill CRUD endpoints (POST/GET/PATCH/DELETE /admin/skills)
- User skill CRUD endpoints (POST/GET/PATCH/DELETE /skills/)
- Personal context GET/PUT at /users/me/context
- Extended context assembly: primary + personal context + skill prompt
- Chat creation/update now accepts skill_id with validation

Frontend:
- Skill selector dropdown in chat header (grouped: general + personal)
- Reusable skill editor form component
- Admin skills management page (/admin/skills)
- Personal skills page (/skills)
- Personal context editor page (/profile/context)
- Updated sidebar: Skills, My Context nav items + admin skills link
- English + Russian translations for all skill/context UI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-19 12:55:02 +03:00
parent 70469beef8
commit 03afb7a075
33 changed files with 1387 additions and 62 deletions

View File

@@ -46,7 +46,7 @@ export async function getChat(chatId: string): Promise<Chat> {
export async function updateChat(
chatId: string,
updates: { title?: string; is_archived?: boolean }
updates: { title?: string; is_archived?: boolean; skill_id?: string }
): Promise<Chat> {
const { data } = await api.patch<Chat>(`/chats/${chatId}`, updates);
return data;

View File

@@ -0,0 +1,93 @@
import api from "./client";
export interface Skill {
id: string;
user_id: string | null;
name: string;
description: string | null;
system_prompt: string;
icon: string | null;
is_active: boolean;
sort_order: number;
created_at: string;
}
export interface SkillListResponse {
skills: Skill[];
}
export async function getSkills(includeGeneral = true): Promise<Skill[]> {
const { data } = await api.get<SkillListResponse>("/skills/", {
params: { include_general: includeGeneral },
});
return data.skills;
}
export async function getSkill(skillId: string): Promise<Skill> {
const { data } = await api.get<Skill>(`/skills/${skillId}`);
return data;
}
export async function createSkill(skill: {
name: string;
description?: string;
system_prompt: string;
icon?: string;
}): Promise<Skill> {
const { data } = await api.post<Skill>("/skills/", skill);
return data;
}
export async function updateSkill(
skillId: string,
updates: Partial<{
name: string;
description: string;
system_prompt: string;
icon: string;
is_active: boolean;
sort_order: number;
}>
): Promise<Skill> {
const { data } = await api.patch<Skill>(`/skills/${skillId}`, updates);
return data;
}
export async function deleteSkill(skillId: string): Promise<void> {
await api.delete(`/skills/${skillId}`);
}
// Admin skill functions
export async function getGeneralSkills(): Promise<Skill[]> {
const { data } = await api.get<SkillListResponse>("/admin/skills");
return data.skills;
}
export async function createGeneralSkill(skill: {
name: string;
description?: string;
system_prompt: string;
icon?: string;
}): Promise<Skill> {
const { data } = await api.post<Skill>("/admin/skills", skill);
return data;
}
export async function updateGeneralSkill(
skillId: string,
updates: Partial<{
name: string;
description: string;
system_prompt: string;
icon: string;
is_active: boolean;
sort_order: number;
}>
): Promise<Skill> {
const { data } = await api.patch<Skill>(`/admin/skills/${skillId}`, updates);
return data;
}
export async function deleteGeneralSkill(skillId: string): Promise<void> {
await api.delete(`/admin/skills/${skillId}`);
}

View File

@@ -0,0 +1,14 @@
import api from "./client";
import type { ContextFile } from "./admin";
export async function getPersonalContext(): Promise<ContextFile | null> {
const { data } = await api.get<ContextFile | null>("/users/me/context");
return data;
}
export async function updatePersonalContext(
content: string
): Promise<ContextFile> {
const { data } = await api.put<ContextFile>("/users/me/context", { content });
return data;
}