feat(ai): implement unified prompt management UX/UI (ADR-037)
CI / CD Pipeline / build (push) Failing after 3m23s
CI / CD Pipeline / deploy (push) Has been skipped

- Add context config endpoints (GET/PUT /api/ai/prompts/:type/:version/context-config)
- Add execution profile endpoints (CRUD /api/ai/execution-profiles)
- Add sandbox RAG Prep endpoint (POST /api/ai/admin/sandbox/rag-prep)
- Create Prompt Management UI with multi-type support
- Add ContextConfigEditor, PromptEditor, RuntimeParametersPanel components
- Add SandboxTabs for 3-step workflow (OCR, Extract, RAG Prep)
- Add database deltas for ai_execution_profiles and additional prompt types
- Update quickstart.md with production backend URLs
- Add comprehensive test coverage for new features
This commit is contained in:
2026-06-14 19:55:43 +07:00
parent 56f9544cb0
commit 67da186672
64 changed files with 6327 additions and 6107 deletions
@@ -0,0 +1,414 @@
# Backend API Contracts: Unified Prompt Management UX/UI
**Feature**: 237-unified-prompt-management-ux-ui
**Date**: 2026-06-14
**Purpose**: Define API endpoints for prompt management with context config and sandbox testing
## Existing Endpoints (from ADR-029)
### GET /api/ai/prompts/:type
Get all versions of a specific prompt type.
**Parameters**:
- `type` (path): Prompt type (ocr_extraction, rag_query_prompt, rag_prep_prompt, classification_prompt)
**Response**:
```json
{
"data": [
{
"id": "uuid",
"promptType": "ocr_extraction",
"versionNumber": 1,
"template": "string",
"contextConfig": { "filter": null, "pageSize": 3, "language": "th" },
"isActive": true,
"manualNote": "string|null",
"createdAt": "2026-06-14T00:00:00Z"
}
]
}
```
**Guard**: `@UseGuards(JwtAuthGuard, CaslAbilityGuard)`
**Ability**: `system.manage_all`
---
### POST /api/ai/prompts/:type
Create a new version of a prompt type.
**Parameters**:
- `type` (path): Prompt type
**Request Body**:
```json
{
"template": "string",
"contextConfig": { "filter": null, "pageSize": 3, "language": "th" },
"manualNote": "string|null"
}
```
**Response**:
```json
{
"data": {
"id": "uuid",
"promptType": "ocr_extraction",
"versionNumber": 2,
"template": "string",
"contextConfig": { "filter": null, "pageSize": 3, "language": "th" },
"isActive": false,
"manualNote": "string|null",
"createdAt": "2026-06-14T00:00:00Z"
}
}
```
**Guard**: `@UseGuards(JwtAuthGuard, CaslAbilityGuard)`
**Ability**: `system.manage_all`
---
### DELETE /api/ai/prompts/:type/:version
Delete a specific version (cannot delete active version).
**Parameters**:
- `type` (path): Prompt type
- `version` (path): Version number
**Response**: 204 No Content
**Guard**: `@UseGuards(JwtAuthGuard, CaslAbilityGuard)`
**Ability**: `system.manage_all`
---
### POST /api/ai/prompts/:type/:version/activate
Activate a specific version (deactivates other versions of same type).
**Parameters**:
- `type` (path): Prompt type
- `version` (path): Version number
**Response**:
```json
{
"data": {
"id": "uuid",
"promptType": "ocr_extraction",
"versionNumber": 1,
"isActive": true
}
}
```
**Guard**: `@UseGuards(JwtAuthGuard, CaslAbilityGuard)`
**Ability**: `system.manage_all`
---
### PATCH /api/ai/prompts/:type/:version/note
Update manual note for a version.
**Parameters**:
- `type` (path): Prompt type
- `version` (path): Version number
**Request Body**:
```json
{
"manualNote": "string"
}
```
**Response**:
```json
{
"data": {
"id": "uuid",
"manualNote": "string"
}
}
```
**Guard**: `@UseGuards(JwtAuthGuard, CaslAbilityGuard)`
**Ability**: `system.manage_all`
---
## New Endpoints (Context Config)
### GET /api/ai/prompts/:type/:version/context-config
Get context config for a specific version.
**Parameters**:
- `type` (path): Prompt type
- `version` (path): Version number
**Response**:
```json
{
"data": {
"filter": {
"projectId": "uuid|null",
"contractId": "uuid|null"
},
"pageSize": 3,
"language": "th",
"outputLanguage": "th"
}
}
```
**Guard**: `@UseGuards(JwtAuthGuard, CaslAbilityGuard)`
**Ability**: `system.manage_all`
---
### PUT /api/ai/prompts/:type/:version/context-config
Update context config for a specific version.
**Parameters**:
- `type` (path): Prompt type
- `version` (path): Version number
**Request Body**:
```json
{
"filter": {
"projectId": "uuid|null",
"contractId": "uuid|null"
},
"pageSize": 3,
"language": "th",
"outputLanguage": "th"
}
```
**Response**:
```json
{
"data": {
"filter": {
"projectId": "uuid|null",
"contractId": "uuid|null"
},
"pageSize": 3,
"language": "th",
"outputLanguage": "th"
}
}
```
**Guard**: `@UseGuards(JwtAuthGuard, CaslAbilityGuard)`
**Ability**: `system.manage_all`
---
## New Endpoints (Runtime Parameters)
### GET /api/ai/execution-profiles
Get all execution profiles.
**Response**:
```json
{
"data": [
{
"id": "uuid",
"profileName": "default",
"temperature": 0.7,
"topP": 0.9,
"repeatPenalty": 1.0,
"maxTokens": 2048,
"ctxSize": 4096,
"keepAlive": 300,
"isDefault": true
}
]
}
```
**Guard**: `@UseGuards(JwtAuthGuard, CaslAbilityGuard)`
**Ability**: `system.manage_all`
---
### POST /api/ai/execution-profiles
Create a new execution profile.
**Request Body**:
```json
{
"profileName": "custom",
"temperature": 0.8,
"topP": 0.95,
"repeatPenalty": 1.1,
"maxTokens": 4096,
"ctxSize": 8192,
"keepAlive": 600
}
```
**Response**:
```json
{
"data": {
"id": "uuid",
"profileName": "custom",
"temperature": 0.8,
"topP": 0.95,
"repeatPenalty": 1.1,
"maxTokens": 4096,
"ctxSize": 8192,
"keepAlive": 600,
"isDefault": false
}
}
```
**Guard**: `@UseGuards(JwtAuthGuard, CaslAbilityGuard)`
**Ability**: `system.manage_all`
---
### PUT /api/ai/execution-profiles/:id
Update an execution profile.
**Parameters**:
- `id` (path): Profile public ID
**Request Body**:
```json
{
"temperature": 0.75,
"topP": 0.92
}
```
**Response**:
```json
{
"data": {
"id": "uuid",
"temperature": 0.75,
"topP": 0.92
}
}
```
**Guard**: `@UseGuards(JwtAuthGuard, CaslAbilityGuard)`
**Ability**: `system.manage_all`
---
### DELETE /api/ai/execution-profiles/:id
Delete an execution profile (cannot delete default profile).
**Parameters**:
- `id` (path): Profile public ID
**Response**: 204 No Content
**Guard**: `@UseGuards(JwtAuthGuard, CaslAbilityGuard)`
**Ability**: `system.manage_all`
---
## New Endpoints (Sandbox - RAG Prep)
### POST /api/ai/admin/sandbox/rag-prep
Run RAG Prep step in sandbox (semantic chunking + embedding).
**Request Body**:
```json
{
"text": "string",
"profileId": "uuid|null"
}
```
**Response**:
```json
{
"data": {
"jobId": "uuid",
"status": "pending"
}
}
```
**Guard**: `@UseGuards(JwtAuthGuard, CaslAbilityGuard)`
**Ability**: `system.manage_all`
---
### GET /api/ai/admin/sandbox/job/:jobId
Get sandbox job status and results.
**Parameters**:
- `jobId` (path): Job ID
**Response**:
```json
{
"data": {
"jobId": "uuid",
"jobType": "rag-prep",
"status": "completed",
"result": {
"ragChunks": [
{
"text": "string",
"summary": "string"
}
],
"ragVectors": "array",
"error": null
},
"createdAt": "2026-06-14T00:00:00Z",
"completedAt": "2026-06-14T00:00:05Z"
}
}
```
**Guard**: `@UseGuards(JwtAuthGuard, CaslAbilityGuard)`
**Ability**: `system.manage_all`
---
## Error Responses
All endpoints follow ADR-007 error handling:
**Validation Error (400)**:
```json
{
"message": "Validation failed",
"userMessage": "Template must contain {{ocr_text}} placeholder",
"recoveryAction": "Add the required placeholder to the template",
"errorCode": "VALIDATION_ERROR"
}
```
**Business Error (400)**:
```json
{
"message": "Cannot delete active version",
"userMessage": "Cannot delete the currently active version. Activate another version first.",
"recoveryAction": "Activate a different version before deleting this one",
"errorCode": "CANNOT_DELETE_ACTIVE_VERSION"
}
```
**Not Found (404)**:
```json
{
"message": "Prompt version not found",
"userMessage": "The requested prompt version does not exist",
"recoveryAction": "Check the prompt type and version number",
"errorCode": "NOT_FOUND"
}
```
@@ -0,0 +1,163 @@
// Frontend Type Contracts: Unified Prompt Management UX/UI
// Feature: 237-unified-prompt-management-ux-ui
// Date: 2026-06-14
// Prompt Types
export type PromptType = 'ocr_extraction' | 'rag_query_prompt' | 'rag_prep_prompt' | 'classification_prompt';
// Context Config
export interface ContextConfig {
filter: {
projectId: string | null;
contractId: string | null;
};
pageSize: number;
language: string;
outputLanguage: string;
}
// Prompt Version
export interface PromptVersion {
id: string; // publicId (UUID)
promptType: PromptType;
versionNumber: number;
template: string;
contextConfig: ContextConfig | null;
isActive: boolean;
manualNote: string | null;
createdAt: string; // ISO 8601
}
// Runtime Parameters
export interface RuntimeParameters {
temperature: number;
topP: number;
repeatPenalty: number;
maxTokens: number;
ctxSize: number;
keepAlive: number;
}
// Execution Profile
export interface ExecutionProfile {
id: string; // publicId (UUID)
profileName: string;
temperature: number;
topP: number;
repeatPenalty: number;
maxTokens: number;
ctxSize: number;
keepAlive: number;
isDefault: boolean;
}
// Sandbox Job Types
export type SandboxJobType = 'ocr' | 'ai-extract' | 'rag-prep';
export type SandboxJobStatus = 'pending' | 'processing' | 'completed' | 'failed';
// Sandbox Job Result
export interface SandboxJobResult {
ocrText?: string;
extractedMetadata?: Record<string, unknown>;
ragChunks?: Array<{
text: string;
summary: string;
}>;
ragVectors?: number[][];
error?: string | null;
}
// Sandbox Job
export interface SandboxJob {
jobId: string; // UUID
jobType: SandboxJobType;
status: SandboxJobStatus;
result: SandboxJobResult;
createdAt: string; // ISO 8601
completedAt?: string; // ISO 8601
}
// API Request DTOs
export interface CreatePromptDto {
template: string;
contextConfig: ContextConfig;
manualNote?: string;
}
export interface UpdateContextConfigDto {
filter: {
projectId: string | null;
contractId: string | null;
};
pageSize: number;
language: string;
outputLanguage: string;
}
export interface CreateExecutionProfileDto {
profileName: string;
temperature: number;
topP: number;
repeatPenalty: number;
maxTokens: number;
ctxSize: number;
keepAlive: number;
}
export interface UpdateExecutionProfileDto {
temperature?: number;
topP?: number;
repeatPenalty?: number;
maxTokens?: number;
ctxSize?: number;
keepAlive?: number;
}
export interface SandboxRagPrepDto {
text: string;
profileId?: string;
}
// API Response DTOs
export interface PromptVersionResponse {
data: PromptVersion;
}
export interface PromptVersionsResponse {
data: PromptVersion[];
}
export interface ContextConfigResponse {
data: ContextConfig;
}
export interface ExecutionProfilesResponse {
data: ExecutionProfile[];
}
export interface ExecutionProfileResponse {
data: ExecutionProfile;
}
export interface SandboxJobResponse {
data: SandboxJob;
}
// Placeholder Validation Rules
export const PLACEHOLDER_REQUIREMENTS: Record<PromptType, string[]> = {
ocr_extraction: ['{{ocr_text}}', '{{master_data_context}}'],
rag_query_prompt: ['{{query}}', '{{context}}'],
rag_prep_prompt: ['{{text}}'],
classification_prompt: ['{{document_text}}'],
};
// Runtime Parameter Constraints
export const RUNTIME_PARAMETER_CONSTRAINTS = {
temperature: { min: 0.0, max: 1.0, default: 0.7 },
topP: { min: 0.0, max: 1.0, default: 0.9 },
repeatPenalty: { min: 1.0, max: 2.0, default: 1.0 },
maxTokens: { min: 1, max: 8192, default: 2048 },
ctxSize: { min: 1, max: 16384, default: 4096 },
keepAlive: { min: 0, max: 3600, default: 300 },
};