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,74 @@
# Code Review Report
**Date**: 2026-06-14
**Scope**: Working tree for `specs/200-fullstacks/237-unified-prompt-management-ux-ui` plus related modified files. No staged changes found.
**Overall**: REQUEST CHANGES
## Summary
| Severity | Count |
| --- | ---: |
| Critical | 1 |
| High | 2 |
| Medium | 2 |
| Low | 0 |
| Suggestions | 0 |
## Findings
### HIGH: Backend build is currently broken
`pnpm --filter backend build` fails with 10 TypeScript errors in `backend/src/modules/rfa/rfa.service.ts`. This blocks merge even if Feature 237 code compiles.
Key examples:
- `backend/src/modules/rfa/rfa.service.ts:457`: `RfaService.WORKFLOW_CODE` is referenced but not defined.
- `backend/src/modules/rfa/rfa.service.ts:700`: `templateRepo` is still used after being removed from constructor injection.
- `backend/src/modules/rfa/rfa.service.ts:748`: `CorrespondenceRouting` is still used after its import was removed.
- `backend/src/modules/rfa/rfa.service.ts:753`: the code appears corrupted: `firstStep.toOrganizatioTransaction();`.
**Fix**: Either complete the ADR-001/021 RFA migration in this file, or isolate/revert this unrelated partial change before reviewing Feature 237 for merge.
### CRITICAL: Context filtering can leak cross-project master data
`backend/src/modules/ai/prompts/ai-prompts.service.ts:59` converts `contextConfig.filter.projectId` with `Number(...)`, while the frontend sends `publicId` UUID strings from `frontend/components/admin/ai/ContextConfigEditor.tsx:120`. A UUID becomes `NaN`; without an override, the later `if (targetProjectId)` checks do not apply project filtering, so AI context can include all projects/orgs/tags.
This violates ADR-019 and the AI multi-tenancy boundary.
**Fix**: Treat stored filters as `projectPublicId` / `contractPublicId` strings, validate with `@IsUUID()`, resolve them to internal IDs inside the service, and apply filters only after successful resolution. Add regression tests for "stored UUID filter without override restricts context".
### HIGH: New mutations do not enforce `Idempotency-Key`
Several new or affected mutating endpoints do not read or require `Idempotency-Key`, despite AGENTS/ADR-016 requiring it for critical `POST`/`PUT`/`PATCH`.
Examples:
- `backend/src/modules/ai/prompts/ai-prompts.controller.ts:68`: create prompt version.
- `backend/src/modules/ai/prompts/ai-prompts.controller.ts:104`: activate prompt.
- `backend/src/modules/ai/prompts/ai-prompts.controller.ts:159`: update context config.
- `backend/src/modules/ai/ai.controller.ts:677`: sandbox RAG prep queues work but generates a new request ID every retry.
**Fix**: Require `@Headers('idempotency-key')`, reject missing keys with `ValidationException`, and use the header as the job/cache key where the operation is queueing or changing state. Frontend calls in `frontend/lib/services/admin-ai.service.ts` also need to send the header.
### MEDIUM: Prompt placeholder contract is inconsistent
The validator requires `rag_query_prompt` to include `{{query}}` and `{{context}}`, and `rag_prep_prompt` to include `{{text}}` in `backend/src/modules/ai/prompts/ai-prompts.service.ts:353`. But the new seed file inserts:
- `rag_query_prompt` with `{{context}}` and `{{ocr_text}}` at `specs/03-Data-and-Storage/deltas/2026-06-14-seed-additional-prompt-types.sql:21`.
- `rag_prep_prompt` with `{{ocr_text}}` at `specs/03-Data-and-Storage/deltas/2026-06-14-seed-additional-prompt-types.sql:33`.
- `classification_prompt` with `{{ocr_text}}` at `specs/03-Data-and-Storage/deltas/2026-06-14-seed-additional-prompt-types.sql:45`.
**Fix**: Choose one placeholder contract per prompt type and align seed, validation, and replacement code. Right now an admin cannot save a modified version of the seeded RAG prep/classification prompt under the service's own rules.
### MEDIUM: DTO validation is too weak for public identifiers and sandbox input
`backend/src/modules/ai/dto/context-config.dto.ts` uses plain `@IsString()` for project/contract identifiers and `@IsObject()` for nested filter, so nested validation does not run and UUID format is not enforced. `backend/src/modules/ai/dto/sandbox-rag-prep.dto.ts` accepts unbounded text.
**Fix**: Use `@ValidateNested()`, `@Type(() => ContextFilterDto)`, `@IsUUID()` for public IDs, whitelist language values, add max page size, and cap sandbox text length.
## Verification
- `pnpm --filter backend build` failed with 10 TypeScript errors.
- `pnpm --filter lcbp3-frontend exec tsc --noEmit` passed.
Merge should stay blocked until the backend build and the context-filter/idempotency issues are fixed.
@@ -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 },
};
@@ -0,0 +1,230 @@
# Data Model: Unified Prompt Management UX/UI
**Feature**: 237-unified-prompt-management-ux-ui
**Date**: 2026-06-14
**Purpose**: Define data entities, relationships, and validation rules
## Entities
### AiPrompt
Represents a prompt version with template, context config, and activation status.
**Table**: `ai_prompts` (extends existing ADR-029 schema)
| Column | Type | Constraints | Description |
|--------|------|-------------|-------------|
| `id` | INT AUTO_INCREMENT | PRIMARY KEY | Internal ID (not exposed) |
| `public_id` | UUID | UNIQUE, NOT NULL | Public identifier (ADR-019) |
| `prompt_type` | VARCHAR(50) | NOT NULL, INDEX | Prompt type: ocr_extraction, rag_query_prompt, rag_prep_prompt, classification_prompt |
| `version_number` | INT | NOT NULL, INDEX | Version number (per prompt_type) |
| `template` | TEXT | NOT NULL | Prompt template with placeholders |
| `context_config` | JSON | NULL | Context configuration (filter, pageSize, language) |
| `is_active` | TINYINT(1) | NOT NULL, DEFAULT 0 | Active flag (1 = active) |
| `manual_note` | VARCHAR(500) | NULL | Manual annotation |
| `created_by` | INT | NOT NULL, FK → users.id | Creator user ID |
| `created_at` | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | Creation timestamp |
| `updated_at` | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP ON UPDATE | Update timestamp |
**Unique Constraint**: `(prompt_type, version_number)` - ensures version numbers are unique per type
**Validation Rules**:
- `prompt_type` must be one of: ocr_extraction, rag_query_prompt, rag_prep_prompt, classification_prompt
- `template` must contain required placeholders based on `prompt_type`:
- ocr_extraction: {{ocr_text}}, {{master_data_context}}
- rag_query_prompt: {{query}}, {{context}}
- rag_prep_prompt: {{text}}
- classification_prompt: {{document_text}}
- `context_config` JSON structure:
```json
{
"filter": {
"projectId": "uuid|null",
"contractId": "uuid|null"
},
"pageSize": 3,
"language": "th",
"outputLanguage": "th"
}
```
- `is_active` can only be true for one version per `prompt_type` at a time
**Relationships**:
- `created_by` → `users.id` (many-to-one)
---
### AiExecutionProfile
Represents runtime parameters for AI model behavior (global per profile, not per prompt version).
**Table**: `ai_execution_profiles` (new table per ADR-036)
| Column | Type | Constraints | Description |
|--------|------|-------------|-------------|
| `id` | INT AUTO_INCREMENT | PRIMARY KEY | Internal ID (not exposed) |
| `public_id` | UUID | UNIQUE, NOT NULL | Public identifier (ADR-019) |
| `profile_name` | VARCHAR(100) | NOT NULL, UNIQUE | Profile name (e.g., "default", "fast", "accurate") |
| `temperature` | DECIMAL(3,2) | NOT NULL, DEFAULT 0.7 | Temperature (0.0 - 1.0) |
| `top_p` | DECIMAL(3,2) | NOT NULL, DEFAULT 0.9 | Top-P (0.0 - 1.0) |
| `repeat_penalty` | DECIMAL(3,2) | NOT NULL, DEFAULT 1.0 | Repeat penalty (1.0 - 2.0) |
| `max_tokens` | INT | NOT NULL, DEFAULT 2048 | Max tokens (1 - 8192) |
| `ctx_size` | INT | NOT NULL, DEFAULT 4096 | Context size (1 - 16384) |
| `keep_alive` | INT | NOT NULL, DEFAULT 300 | Keep-alive seconds (0 - 3600) |
| `is_default` | TINYINT(1) | NOT NULL, DEFAULT 0 | Default profile flag |
| `created_by` | INT | NOT NULL, FK → users.id | Creator user ID |
| `created_at` | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | Creation timestamp |
| `updated_at` | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP ON UPDATE | Update timestamp |
**Validation Rules**:
- `temperature` must be between 0.0 and 1.0
- `top_p` must be between 0.0 and 1.0
- `repeat_penalty` must be between 1.0 and 2.0
- `max_tokens` must be between 1 and 8192
- `ctx_size` must be between 1 and 16384
- `keep_alive` must be between 0 and 3600
- `is_default` can only be true for one profile at a time
**Relationships**:
- `created_by` → `users.id` (many-to-one)
---
### SandboxJob
Represents a sandbox test execution (transient, stored in Redis, not in database).
**Redis Key**: `sandbox:job:{jobId}` (TTL: 1 hour)
**Structure**:
```json
{
"jobId": "uuid",
"jobType": "ocr|ai-extract|rag-prep",
"status": "pending|processing|completed|failed",
"result": {
"ocrText": "string|null",
"extractedMetadata": "object|null",
"ragChunks": "array|null",
"ragVectors": "array|null",
"error": "string|null"
},
"createdAt": "timestamp",
"completedAt": "timestamp|null"
}
```
**Validation Rules**:
- `jobType` must be one of: ocr, ai-extract, rag-prep
- `status` transitions: pending → processing → completed/failed
- `result` structure depends on `jobType`:
- ocr: { ocrText, error }
- ai-extract: { extractedMetadata, error }
- rag-prep: { ragChunks, ragVectors, error }
**Relationships**: None (transient data)
---
## Entity Relationships
```
users (1) ──────── (∞) ai_prompts
└── (1) ──────── (∞) ai_execution_profiles
ai_prompts (independent from ai_execution_profiles)
- Runtime parameters are global (ai_execution_profiles)
- Context config is per version (ai_prompts.context_config)
```
---
## Indexes
### ai_prompts
- `idx_prompt_type_version`: `(prompt_type, version_number)` - UNIQUE
- `idx_prompt_type_active`: `(prompt_type, is_active)` - for finding active version
- `idx_public_id`: `(public_id)` - UNIQUE
### ai_execution_profiles
- `idx_profile_name`: `(profile_name)` - UNIQUE
- `idx_is_default`: `(is_default)` - for finding default profile
- `idx_public_id`: `(public_id)` - UNIQUE
---
## Database Schema Changes (ADR-009)
### New Table: ai_execution_profiles
```sql
CREATE TABLE ai_execution_profiles (
id INT AUTO_INCREMENT PRIMARY KEY,
public_id UUID NOT NULL,
profile_name VARCHAR(100) NOT NULL UNIQUE,
temperature DECIMAL(3,2) NOT NULL DEFAULT 0.7,
top_p DECIMAL(3,2) NOT NULL DEFAULT 0.9,
repeat_penalty DECIMAL(3,2) NOT NULL DEFAULT 1.0,
max_tokens INT NOT NULL DEFAULT 2048,
ctx_size INT NOT NULL DEFAULT 4096,
keep_alive INT NOT NULL DEFAULT 300,
is_default TINYINT(1) NOT NULL DEFAULT 0,
created_by INT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE,
UNIQUE KEY uk_public_id (public_id),
KEY idx_is_default (is_default),
CONSTRAINT fk_execution_profile_user FOREIGN KEY (created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```
### Seed Data: ai_execution_profiles
```sql
INSERT INTO ai_execution_profiles (public_id, profile_name, temperature, top_p, repeat_penalty, max_tokens, ctx_size, keep_alive, is_default, created_by)
VALUES
(UUID(), 'default', 0.7, 0.9, 1.0, 2048, 4096, 300, 1, 1),
(UUID(), 'fast', 0.5, 0.8, 1.0, 1024, 2048, 0, 0, 1),
(UUID(), 'accurate', 0.8, 0.95, 1.1, 4096, 8192, 600, 0, 1);
```
### Seed Data: ai_prompts (additional types)
```sql
-- RAG Query Prompt
INSERT INTO ai_prompts (public_id, prompt_type, version_number, template, context_config, is_active, created_by)
VALUES (
UUID(),
'rag_query_prompt',
1,
'Answer the following question based on the provided context:\n\nQuestion: {{query}}\n\nContext: {{context}}\n\nAnswer:',
'{"filter": null, "language": "th"}',
1,
1
);
-- RAG Prep Prompt
INSERT INTO ai_prompts (public_id, prompt_type, version_number, template, context_config, is_active, created_by)
VALUES (
UUID(),
'rag_prep_prompt',
1,
'Split the following text into semantic chunks for RAG indexing:\n\nText: {{text}}\n\nOutput JSON array of chunks with "text" and "summary" fields.',
'{"filter": null, "language": "th"}',
1,
1
);
-- Classification Prompt
INSERT INTO ai_prompts (public_id, prompt_type, version_number, template, context_config, is_active, created_by)
VALUES (
UUID(),
'classification_prompt',
1,
'Classify the following document into one of these categories: Correspondence, RFA, Transmittal, Circulation, Shop Drawing, Contract Drawing\n\nDocument: {{document_text}}\n\nOutput JSON with "category" and "confidence" fields.',
'{"filter": null, "language": "th"}',
1,
1
);
```
@@ -0,0 +1,133 @@
# Implementation Plan: Unified Prompt Management UX/UI
**Branch**: `237-unified-prompt-management-ux-ui` | **Date**: 2026-06-14 | **Spec**: [spec.md](./spec.md)
**Input**: Feature specification from `/specs/200-fullstacks/237-unified-prompt-management-ux-ui/spec.md`
## Summary
This feature extends ADR-029 Dynamic Prompt Management to support multiple prompt types (OCR extraction, RAG query, RAG preparation, document classification) with a unified single-page UI. The implementation adds context configuration management, a 3-step sandbox workflow (OCR → AI Extract → RAG Prep), and clear separation between Runtime Parameters (AI model behavior) and Context Config (data context). The backend will extend existing AiPromptsService with new endpoints for context config CRUD and RAG Prep sandbox testing, while the frontend will create a unified Prompt Management page with PromptTypeDropdown, VersionHistory, PromptEditor, ContextConfigEditor, and SandboxTabs components.
## Technical Context
**Language/Version**: TypeScript 5.6 (Backend: NestJS 11, Frontend: Next.js 16)
**Primary Dependencies**:
- Backend: @nestjs/common, @nestjs/typeorm, @nestjs/bull, class-validator, class-transformer, redis, ioredis
- Frontend: next, react, @tanstack/react-query, react-hook-form, zod, shadcn/ui, lucide-react
**Storage**: MariaDB 11.8 (ai_prompts, ai_execution_profiles tables), Redis (prompt cache, BullMQ queues)
**Testing**: Jest (backend unit/integration/e2e), Vitest (frontend unit), Playwright (frontend e2e)
**Target Platform**: Linux server (QNAP NAS) for backend, Web browser for frontend
**Project Type**: fullstack (backend + frontend)
**Performance Goals**:
- Sandbox OCR results within 30s
- Sandbox AI Extract within 60s
- Version history load within 1s
- Context config activation within 5s
**Constraints**:
- ADR-019: No parseInt on UUID, use publicId only
- ADR-009: No TypeORM migrations, edit SQL directly
- ADR-016: CASL guards on all mutations, ThrottlerGuard on auth
- ADR-023/023A: AI boundary enforcement, BullMQ queues (ai-realtime, ai-batch)
- ADR-029: Prompt templates in DB, Redis cache TTL 60s
- ADR-007: Layered error handling, user-friendly messages
**Scale/Scope**:
- 4 prompt types with versioning
- Single page UI with 3-panel layout
- 3-step sandbox workflow
- ~10 backend endpoints, ~15 frontend components
## Constitution Check
_GATE: Must pass before Phase 0 research. Re-check after Phase 1 design._
| Principle | Status | Notes |
|-----------|--------|-------|
| ADR-019 UUID | ✅ PASS | Will use publicId only, no parseInt on UUID |
| ADR-009 Schema | ✅ PASS | Will add SQL delta for new columns, no migrations |
| ADR-016 Security | ✅ PASS | Will add CASL guards on all mutation endpoints |
| ADR-002 Numbering | N/A | Not applicable (no document numbering) |
| ADR-008 Notifications | ✅ PASS | Sandbox jobs use BullMQ, no inline processing |
| ADR-023/023A AI Boundary | ✅ PASS | Sandbox uses existing BullMQ queues, no direct AI access |
| ADR-029 Dynamic Prompts | ✅ PASS | Extends existing ai_prompts table, follows Redis cache pattern |
| ADR-007 Error Handling | ✅ PASS | Will use BusinessException hierarchy |
| TypeScript Strict | ✅ PASS | Zero any, zero console.log |
| i18n | ✅ PASS | Will use i18n keys, no hardcoded text |
| File Upload | ✅ PASS | Sandbox uses existing two-phase upload |
**Result**: All applicable principles pass. No violations.
## Project Structure
### Documentation (this feature)
```text
specs/200-fullstacks/237-unified-prompt-management-ux-ui/
├── plan.md # This file
├── research.md # Phase 0 output
├── data-model.md # Phase 1 output
├── quickstart.md # Phase 1 output
├── contracts/ # Phase 1 output
│ ├── backend-api.yaml
│ └── frontend-types.ts
└── tasks.md # Phase 2 output (from /speckit-tasks)
```
### Source Code (repository root)
```text
backend/
├── src/
│ ├── modules/
│ │ └── ai/
│ │ ├── controllers/
│ │ │ └── ai-prompts.controller.ts (extend with context config endpoints)
│ │ ├── services/
│ │ │ ├── ai-prompts.service.ts (extend with context config CRUD)
│ │ │ └── ocr.service.ts (extend with RAG Prep endpoint)
│ │ ├── processors/
│ │ │ └── ai-batch.processor.ts (extend with sandbox-rag-prep job)
│ │ └── dto/
│ │ ├── context-config.dto.ts (new)
│ │ └── sandbox-rag-prep.dto.ts (new)
│ └── common/
│ └── decorators/
│ └── casl-abilities.decorator.ts (existing)
└── test/
└── integration/
└── ai/
└── ai-prompts.service.spec.ts (extend tests)
frontend/
├── app/
│ └── (admin)/
│ └── admin/
│ └── ai/
│ └── prompt-management/
│ └── page.tsx (new - unified prompt management page)
├── components/
│ └── admin/
│ └── ai/
│ ├── PromptTypeDropdown.tsx (new)
│ ├── VersionHistory.tsx (extend with type filtering)
│ ├── PromptEditor.tsx (new)
│ ├── ContextConfigEditor.tsx (new)
│ ├── RuntimeParametersPanel.tsx (new)
│ └── SandboxTabs.tsx (new - 3-step workflow)
├── lib/
│ ├── services/
│ │ └── admin-ai.service.ts (extend with context config methods)
│ └── types/
│ └── ai-prompts.ts (extend with context config types)
└── __tests__/
└── components/
└── admin/
└── ai/
└── prompt-management.test.tsx (new)
```
**Structure Decision**: Fullstack web application (backend + frontend) following existing LCBP3-DMS patterns. Backend extends existing ai module, frontend adds new page under (admin)/admin/ai/ consistent with ADR-027 single page layout.
## Complexity Tracking
> **Fill ONLY if Constitution Check has violations that must be justified**
No violations - this section is not applicable.
@@ -0,0 +1,222 @@
# Quickstart: Unified Prompt Management UX/UI
**Feature**: 237-unified-prompt-management-ux-ui
**Date**: 2026-06-14
**Purpose**: Quick start guide for testing and validating the implementation
## Prerequisites
- Backend server running on port 3001
- Frontend server running on port 3000
- Database (MariaDB) with ai_prompts table (from ADR-029)
- Redis running (for BullMQ and caching)
- OCR sidecar running on Desk-5439 (typhoon-np-dms-ocr)
- Ollama running with typhoon2.5-np-dms model
## Database Setup
### 1. Create ai_execution_profiles table
```bash
mysql -u root -p lcbp3 < specs/03-Data-and-Storage/deltas/2026-06-14-create-ai-execution-profiles.sql
```
### 2. Seed execution profiles
```bash
mysql -u root -p lcbp3 < specs/03-Data-and-Storage/deltas/2026-06-14-seed-execution-profiles.sql
```
### 3. Seed additional prompt types
```bash
mysql -u root -p lcbp3 < specs/03-Data-and-Storage/deltas/2026-06-14-seed-additional-prompt-types.sql
```
## Backend Testing
### 1. Test context config endpoints
```bash
# Get context config for OCR extraction v1
curl -H "Authorization: Bearer <token>" \
http//lloaalhist:3001i/prompts/ocr_extraction/1/context-config
# Update context config
curl -X PUT -H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"filter": {"projectId": null, "contractId": null}, "pageSize": 5, "language": "th", "outputLanguage": "th"}' \
http//lloaalhist:3001i/prompts/ocr_extraction/1/context-config
```
### 2. Test execution profile endpoints
```bash
# Get all execution profiles
curl -H "Authorization: Bearer <token>" \
https://backend.np-dms.work/api/ai/execution-profiles
# Create new profile
curl -X POST -H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"profileName": "test", "temperature": 0.6, "topP": 0.85, "repeatPenalty": 1.0, "maxTokens": 1024, "ctxSize": 2048, "keepAlive": 0}' \
https://backend.np-dms.work/api/ai/execution-profiles
```
### 3. Test sandbox RAG Prep endpoint
```bash
# Start RAG Prep job
curl -X POST -H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"text": "Sample text for chunking", "profileId": null}' \
https://backend.np-dms.work/api/ai/admin/sandbox/rag-prep
# Poll job status (replace <jobId>)
curl -H "Authorization: Bearer <token>" \
https://backend.np-dms.work/api/ai/admin/sandbox/job/<jobId>
```
## Frontend Testing
### 1. Navigate to Prompt Management page
```
http://localhost:3000/admin/ai/prompt-management
```
### 2. Test Multi-Type Prompt Management
1. Select "OCR Extraction" from Prompt Type dropdown
2. Verify version history shows only OCR extraction versions
3. Click on a version to view template and context config
4. Edit template and context config
5. Click "Save New Version"
6. Verify new version appears in history with incremented version number
7. Click "Activate" on the new version
8. Verify active badge (✅) moves to the new version
### 3. Test Context Configuration
1. Select a prompt version
2. Modify Project Filter field (select a project or leave null)
3. Modify Contract Filter field (select a contract or leave null)
4. Modify Page Size (e.g., change from 3 to 5)
5. Modify Language (e.g., change from "th" to "en")
6. Click "Save New Version"
7. Verify context config is saved with the new version
8. Activate the version
9. Verify context config is applied (check Redis cache)
### 4. Test Three-Step Sandbox Workflow
1. Upload a PDF in the Sandbox tab
2. Click "Run OCR"
3. Wait for OCR results (should show raw OCR text)
4. Select a prompt version from dropdown
5. Click "Run AI Extract"
6. Wait for extracted metadata (should show structured JSON)
7. Click "Test RAG Prep" (optional)
8. Wait for RAG chunks and vectors
9. Review all results
10. Click "Activate This Version" if satisfied
### 5. Test Runtime Parameters vs Context Config Separation
1. Go to Sandbox tab
2. View Runtime Parameters panel
3. Adjust Temperature slider (e.g., from 0.7 to 0.8)
4. Adjust Top-P slider (e.g., from 0.9 to 0.95)
5. Click "Apply to Production"
6. Verify parameters are saved to ai_execution_profiles
7. Go to Prompt Editor panel
8. View Context Config Editor
9. Modify Project Filter (different from Runtime Parameters)
10. Click "Save New Version"
11. Verify context config is saved to ai_prompts (per version)
12. Confirm that Runtime Parameters and Context Config are separate
## Validation Checklist
### Backend
- [ ] ai_execution_profiles table created successfully
- [ ] Execution profiles seeded (default, fast, accurate)
- [ ] Additional prompt types seeded (rag_query_prompt, rag_prep_prompt, classification_prompt)
- [ ] GET /api/ai/prompts/:type/:version/context-config returns context config
- [ ] PUT /api/ai/prompts/:type/:version/context-config updates context config
- [ ] GET /api/ai/execution-profiles returns all profiles
- [ ] POST /api/ai/execution-profiles creates new profile
- [ ] PUT /api/ai/execution-profiles/:id updates profile
- [ ] DELETE /api/ai/execution-profiles/:id deletes non-default profile
- [ ] POST /api/ai/admin/sandbox/rag-prep creates sandbox job
- [ ] GET /api/ai/admin/sandbox/job/:jobId returns job status and results
- [ ] Placeholder validation works (rejects templates without required placeholders)
- [ ] Context config validation works (rejects invalid project/contract IDs)
- [ ] Redis cache invalidated on version activation
- [ ] CASL guards applied to all mutation endpoints
### Frontend
- [ ] PromptTypeDropdown switches between prompt types
- [ ] VersionHistory filters by selected prompt type
- [ ] Active badge (✅) displays correctly
- [ ] PromptEditor validates placeholders
- [ ] ContextConfigEditor saves and displays context config
- [ ] RuntimeParametersPanel displays sliders
- [ ] RuntimeParametersPanel applies changes to ai_execution_profiles
- [ ] SandboxTabs show 3 tabs (OCR, Extract, RAG Prep)
- [ ] Sandbox OCR step returns raw OCR text
- [ ] Sandbox AI Extract step returns structured metadata
- [ ] Sandbox RAG Prep step returns chunks and vectors
- [ ] "Activate This Version" button works from sandbox
- [ ] Single page layout consistent with ADR-027
- [ ] i18n keys used (no hardcoded text)
- [ ] TypeScript strict mode passes (no any, no console.log)
### Integration
- [ ] Full 3-step sandbox workflow completes successfully
- [ ] Sandbox results match production behavior
- [ ] Context config applied to production jobs within 5 seconds
- [ ] Runtime parameters applied to sandbox tests immediately
- [ ] Version history loads within 1 second
- [ ] Sandbox OCR results within 30 seconds
- [ ] Sandbox AI Extract results within 60 seconds
## Troubleshooting
### Context config not saving
- Check that ai_prompts table has context_config column (JSON type)
- Verify Redis cache is running
- Check backend logs for validation errors
### Sandbox RAG Prep failing
- Verify Ollama is running with typhoon2.5-np-dms model
- Check embedding service (BGE-M3) is available
- Verify BullMQ ai-realtime queue is processing jobs
- Check Redis for job status
### Runtime parameters not applying
- Verify ai_execution_profiles table exists
- Check that profile is not marked as default (can't modify default)
- Verify CASL permissions (system.manage_all)
### Placeholder validation failing
- Check that template contains required placeholders for the prompt type
- Verify placeholder format: {{placeholder_name}}
- Check backend logs for specific validation error
## Performance Benchmarks
After implementation, verify these targets:
- Version history load: < 1 second
- Context config activation: < 5 seconds
- Sandbox OCR: < 30 seconds
- Sandbox AI Extract: < 60 seconds
- Runtime parameter application: immediate (no page refresh)
@@ -0,0 +1,207 @@
# Research: Unified Prompt Management UX/UI
**Feature**: 237-unified-prompt-management-ux-ui
**Date**: 2026-06-14
**Purpose**: Resolve technical unknowns and document decisions for implementation
## Research Topics
### 1. Context Config Schema Structure
**Question**: What JSON structure should context_config use in ai_prompts table?
**Decision**: Use flat JSON object with these fields:
```json
{
"filter": {
"projectId": "uuid|null",
"contractId": "uuid|null"
},
"pageSize": 3,
"language": "th",
"outputLanguage": "th"
}
```
**Rationale**:
- Follows ADR-030 context-aware prompt template structure
- Matches existing OCR extraction prompt context_config from ADR-029
- Flat structure simplifies validation and UI binding
- Filter object allows null values for "all projects/contracts"
**Alternatives Considered**:
- Nested structure with separate sections (rejected: too complex for simple use case)
- Array-based filter (rejected: single project/contract filter is sufficient)
---
### 2. Runtime Parameters Storage
**Question**: Should runtime parameters be stored in a new ai_execution_profiles table or as part of ai_prompts?
**Decision**: Store in new ai_execution_profiles table (global per profile, not per prompt version)
**Rationale**:
- Runtime parameters control AI model behavior (temperature, topP) which applies globally across all prompt types
- Separates concerns: Runtime Parameters = AI behavior, Context Config = data context
- Allows admins to test different parameter sets in sandbox without affecting prompts
- Follows ADR-036 unified AI model architecture
**Alternatives Considered**:
- Store in ai_prompts per version (rejected: would duplicate same parameters across versions)
- Store in Redis only (rejected: no persistence, lost on restart)
---
### 3. Sandbox RAG Prep Implementation
**Question**: How should RAG Prep sandbox endpoint integrate with existing infrastructure?
**Decision**:
- Create new BullMQ job type: "sandbox-rag-prep" in ai-realtime queue
- Reuse existing OllamaService for semantic chunking (typhoon2.5-np-dms)
- Reuse existing embedding service (BGE-M3 via sidecar)
- Return chunks + vectors in sandbox result for display
**Rationale**:
- Consistent with existing sandbox OCR and AI Extract patterns
- Leverages existing ADR-023A infrastructure (2-model stack, BullMQ queues)
- Allows testing of full production pipeline before deployment
- Follows ADR-035 AI pipeline flow architecture
**Alternatives Considered**:
- Inline processing without BullMQ (rejected: blocks request thread, no retry)
- Separate queue for RAG Prep (rejected: ai-realtime queue already handles sandbox jobs)
---
### 4. Context Config Validation
**Question**: How should invalid context config references (e.g., non-existent project ID) be handled?
**Decision**:
- Validate project/contract IDs against database on save
- Allow null values (meaning "all projects/contracts")
- Return validation error with user-friendly message if ID doesn't exist
- Do not block activation if context config is valid at save time
**Rationale**:
- Prevents orphaned references that would cause production failures
- User-friendly error messages align with ADR-007 error handling
- Null values are valid for "unfiltered" context
- Validation at save time is sufficient (no need to re-validate on activation)
**Alternatives Considered**:
- Allow invalid references and handle at runtime (rejected: production failures)
- Re-validate on activation (rejected: unnecessary if validated at save)
---
### 5. Placeholder Validation Strategy
**Question**: How should required placeholders (e.g., {{ocr_text}}) be validated?
**Decision**:
- Define required placeholders per prompt type:
- ocr_extraction: {{ocr_text}}, {{master_data_context}}
- rag_query_prompt: {{query}}, {{context}}
- rag_prep_prompt: {{text}}
- classification_prompt: {{document_text}}
- Validate on save: template must contain all required placeholders
- Return validation error listing missing placeholders
- Allow additional optional placeholders
**Rationale**:
- Prevents production failures from missing placeholders
- Clear error messages help admins understand requirements
- Flexible enough for future placeholder additions
- Aligns with ADR-029 dynamic prompt management
**Alternatives Considered**:
- Validate at runtime (rejected: production failures)
- No validation (rejected: too error-prone)
---
### 6. Version Numbering Strategy
**Question**: How should version numbers be incremented across prompt types?
**Decision**:
- Version numbers are per prompt_type (independent counters)
- Each prompt_type has its own sequence: ocr_extraction v1, v2, v3; rag_query_prompt v1, v2, etc.
- Auto-increment on save: MAX(version_number) + 1 for that prompt_type
- Display version number in format: "v{number} ({prompt_type})"
**Rationale**:
- Clear separation between prompt types
- No confusion about which version belongs to which type
- Auto-increment prevents manual errors
- Consistent with ADR-029 versioning approach
**Alternatives Considered**:
- Global version counter across all types (rejected: confusing which version is for which type)
- Manual version entry (rejected: error-prone)
---
### 7. Sandbox State Management
**Question**: How should sandbox state (OCR text, extracted metadata) be passed between steps?
**Decision**:
- Store sandbox job results in Redis with TTL 1 hour
- Use job ID as key: `sandbox:job:{jobId}`
- Each step (OCR, AI Extract, RAG Prep) writes its result to the same key
- Frontend polls job status using job ID
- Results cleared after TTL or manual "Clear Sandbox" action
**Rationale**:
- Stateless API design (no session state in backend)
- Redis is already available for BullMQ
- TTL prevents memory leaks
- Allows multi-step workflow without passing large payloads in requests
- Consistent with existing sandbox patterns
**Alternatives Considered**:
- Pass results in request/response (rejected: large payloads, complexity)
- Store in database (rejected: unnecessary persistence, cleanup overhead)
---
### 8. Frontend Component Architecture
**Question**: How should the unified prompt management page be structured?
**Decision**:
- Single page at `/admin/ai/prompt-management`
- 3-panel layout: Left (Version History), Center (Prompt Editor + Context Config), Right (Sandbox)
- PromptTypeDropdown at top of page (global state)
- Use React Hook Form for Context Config Editor (validation, type safety)
- Use TanStack Query for data fetching (version history, active version)
- Use shadcn/ui components (consistent with existing admin pages)
**Rationale**:
- Consistent with ADR-027 single page layout
- 3-panel layout maximizes screen real estate
- RHF + Zod for form validation (best practice)
- TanStack Query for caching and optimistic updates
- shadcn/ui for consistent styling
**Alternatives Considered**:
- Multi-page design (rejected: violates ADR-027 single page constraint)
- 2-panel layout (rejected: insufficient space for sandbox)
---
## Summary
All technical unknowns resolved. Key decisions:
1. Context config uses flat JSON with filter object
2. Runtime parameters in new ai_execution_profiles table
3. Sandbox RAG Prep uses existing BullMQ infrastructure
4. Context config validated on save against database
5. Placeholder validation per prompt type
6. Version numbers per prompt_type (independent counters)
7. Sandbox state in Redis with TTL 1 hour
8. Frontend: 3-panel single page with RHF + TanStack Query + shadcn/ui
@@ -0,0 +1,137 @@
# Feature Specification: Unified Prompt Management UX/UI
**Feature Branch**: `237-unified-prompt-management-ux-ui`
**Created**: 2026-06-14
**Status**: Draft
**Input**: ADR-037: Unified Prompt Management UX/UI
## User Scenarios & Testing _(mandatory)_
### User Story 1 - Multi-Type Prompt Management (Priority: P1)
Admin users need to manage prompt templates for multiple AI workflow types (OCR extraction, RAG query, RAG preparation, and document classification) through a single unified interface that separates version history, template editing, and context configuration.
**Why this priority**: This is the foundation of the feature - without multi-type support, the system cannot manage the full AI pipeline defined in ADR-035. Admins currently struggle with confusing version history that mixes different prompt types, making it difficult to track which version is active for which workflow.
**Independent Test**: Can be fully tested by creating and activating versions for different prompt types, verifying that version history is correctly separated by type, and confirming that the active version badge displays correctly for each type.
**Acceptance Scenarios**:
1. **Given** admin is on the Prompt Management page, **When** they select "OCR Extraction" from the Prompt Type dropdown, **Then** only OCR extraction prompt versions are displayed in the Version History panel
2. **Given** admin has selected a prompt type, **When** they click on a version in the Version History, **Then** the Prompt Editor displays that version's template and context config
3. **Given** admin is viewing version history, **When** a version is marked as active, **Then** an active badge (✅) is displayed next to that version
4. **Given** admin has edited a prompt template, **When** they click "Save New Version", **Then** a new version is created with incremented version number and the version appears in the history list
---
### User Story 2 - Context Configuration Management (Priority: P1)
Admin users need to view, edit, save, and apply context configuration (project filter, contract filter, page size, language) for each prompt version to control what data context the AI sees during processing.
**Why this priority**: Context configuration is critical for AI accuracy - without it, admins cannot control which master data (projects, contracts) the AI uses for extraction. This directly impacts the quality of AI-generated metadata.
**Independent Test**: Can be fully tested by editing context config fields, saving a new version, and verifying that the context config is correctly persisted and applied when the version is activated.
**Acceptance Scenarios**:
1. **Given** admin is editing a prompt version, **When** they modify the Project Filter field, **Then** the change is reflected in the Context Config Editor preview
2. **Given** admin has modified context config, **When** they click "Save New Version", **Then** the context config is saved as part of the new version
3. **Given** admin activates a version with specific context config, **Then** production AI jobs use that context config for processing
4. **Given** admin views an existing version, **When** the version has context config, **Then** the Context Config Editor displays the current values
---
### User Story 3 - Three-Step Sandbox Testing (Priority: P1)
Admin users need to test the full AI pipeline (OCR → AI Extract → RAG Prep) in the sandbox to validate prompt versions before activating them to production, ensuring sandbox results match production behavior.
**Why this priority**: Currently sandbox uses a 2-step flow (OCR → Extract) while production uses 3-step (OCR → Extract → RAG Prep), causing testing gaps. Admins cannot fully validate prompts before deployment, leading to production issues.
**Independent Test**: Can be fully tested by uploading a PDF, running all three sandbox steps sequentially, and verifying that each step produces expected outputs (OCR text, extracted metadata, RAG chunks).
**Acceptance Scenarios**:
1. **Given** admin has uploaded a PDF in the sandbox, **When** they click "Run OCR", **Then** the system returns raw OCR text from the OCR sidecar
2. **Given** admin has OCR results, **When** they select a prompt version and click "Run AI Extract", **Then** the system returns structured metadata (JSON) using the selected prompt
3. **Given** admin has extracted metadata, **When** they click "Test RAG Prep" (optional), **Then** the system returns semantic chunks and embedding vectors
4. **Given** admin is satisfied with sandbox results, **When** they click "Activate This Version", **Then** the version is activated for production use
---
### User Story 4 - Runtime Parameters vs Context Config Separation (Priority: P2)
Admin users need clear separation between Runtime Parameters (AI model behavior controls like temperature, topP) and Context Config (data context controls like project filter) to avoid confusion about which settings affect what aspect of AI processing.
**Why this priority**: Admins currently confuse these two config types, leading to incorrect settings and unpredictable AI behavior. Clear separation reduces operational errors and improves system reliability.
**Independent Test**: Can be fully tested by verifying that Runtime Parameters are in the Sandbox tab and apply globally to AI execution profiles, while Context Config is in the Prompt Editor panel and applies per prompt version.
**Acceptance Scenarios**:
1. **Given** admin is in the Sandbox tab, **When** they view the Runtime Parameters panel, **Then** they see sliders for Temperature, Top-P, Repeat Penalty, Max Tokens, Ctx Size, Keep-Alive
2. **Given** admin adjusts Runtime Parameters in sandbox, **When** they apply changes to production, **Then** the parameters are saved to ai_execution_profiles (global per profile)
3. **Given** admin is in the Prompt Editor panel, **When** they view the Context Config Editor, **Then** they see fields for Project Filter, Contract Filter, Page Size, Language
4. **Given** admin saves a new prompt version with context config, **When** they activate that version, **Then** the context config is saved to ai_prompts (per version)
---
### Edge Cases
- What happens when admin tries to activate a version without required placeholders (e.g., {{ocr_text}} missing from OCR extraction template)?
- How does system handle concurrent edits when multiple admins are editing the same prompt version?
- What happens when sandbox OCR sidecar is unavailable or returns an error?
- How does system handle activation of a version when another version is already active?
- What happens when context config contains invalid references (e.g., project ID that doesn't exist)?
- How does system handle very large prompt templates (e.g., >10,000 characters)?
- What happens when admin tries to delete the currently active version?
- How does system handle rollback to a previous version if the new version causes issues in production?
## Requirements _(mandatory)_
### Functional Requirements
- **FR-001**: System MUST support 4 prompt types: ocr_extraction, rag_query_prompt, rag_prep_prompt, classification_prompt
- **FR-002**: System MUST separate version history by prompt_type so admins can view versions for each type independently
- **FR-003**: System MUST display an active badge (✅) next to the currently active version for each prompt type
- **FR-004**: System MUST provide a Prompt Type dropdown to switch between different prompt types
- **FR-005**: System MUST provide a Prompt Editor textarea for editing prompt templates with placeholder validation
- **FR-006**: System MUST provide a Context Config Editor form with fields: Project Filter, Contract Filter, Page Size, Language
- **FR-007**: System MUST allow admins to save new versions of prompts with both template and context config
- **FR-008**: System MUST allow admins to activate a specific version for a prompt type
- **FR-009**: System MUST invalidate Redis cache when a version is activated
- **FR-010**: System MUST provide a 3-step sandbox workflow: OCR → AI Extract → RAG Prep
- **FR-011**: System MUST allow admins to upload PDFs for sandbox testing
- **FR-012**: System MUST display sandbox results for each step (OCR text, extracted metadata, RAG chunks)
- **FR-013**: System MUST allow admins to activate a version directly from sandbox results
- **FR-014**: System MUST separate Runtime Parameters (in Sandbox tab) from Context Config (in Prompt Editor panel)
- **FR-015**: System MUST provide Runtime Parameters sliders: Temperature, Top-P, Repeat Penalty, Max Tokens, Ctx Size, Keep-Alive
- **FR-016**: System MUST save Runtime Parameters to ai_execution_profiles (global per profile)
- **FR-017**: System MUST save Context Config to ai_prompts (per prompt version)
- **FR-018**: System MUST validate that OCR extraction templates contain {{ocr_text}} placeholder
- **FR-019**: System MUST provide manual_note field for version annotations
- **FR-020**: System MUST allow admins to delete non-active versions
- **FR-021**: System MUST use single page layout consistent with ADR-027 AI Admin Console
### Key Entities
- **AiPrompt**: Represents a prompt version with fields: prompt_type, version_number, template, context_config (JSON), is_active, manual_note, created_by, created_at
- **AiExecutionProfile**: Represents runtime parameters with fields: profile_name, temperature, top_p, repeat_penalty, max_tokens, ctx_size, keep_alive
- **SandboxJob**: Represents a sandbox test execution with fields: job_type (ocr, ai-extract, rag-prep), status, result_data, created_at
## Clarifications
### Session 2026-06-14
- Q: Are there critical ambiguities requiring clarification? → A: No - spec is clear and complete. Edge case scenarios will be addressed during planning phase.
## Success Criteria _(mandatory)_
### Measurable Outcomes
- **SC-001**: Admins can create and activate a new prompt version in under 2 minutes
- **SC-002**: Sandbox test results are returned within 30 seconds for OCR step and 60 seconds for AI Extract step
- **SC-003**: 95% of admins successfully complete the full 3-step sandbox workflow on first attempt
- **SC-004**: Context config changes are applied to production jobs within 5 seconds of activation
- **SC-005**: Version history loads in under 1 second regardless of number of versions
- **SC-006**: Runtime parameter changes are applied to sandbox tests immediately without page refresh
- **SC-007**: Support tickets related to prompt management confusion are reduced by 70%
@@ -0,0 +1,273 @@
# Tasks: Unified Prompt Management UX/UI
**Input**: Design documents from `/specs/200-fullstacks/237-unified-prompt-management-ux-ui/`
**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/
**Tests**: Tests are included for backend services to ensure quality and coverage targets (Backend 70%+, Business Logic 80%+).
**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
## Format: `[ID] [P?] [Story] Description`
- **[P]**: Can run in parallel (different files, no dependencies)
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
- Include exact file paths in descriptions
## Path Conventions
- **Backend**: `backend/src/`, `backend/test/`
- **Frontend**: `frontend/src/`, `frontend/__tests__/`
## Phase 1: Setup (Shared Infrastructure)
**Purpose**: Database schema and seed data setup
- [x] T001 Create SQL delta for ai_execution_profiles table in specs/03-Data-and-Storage/deltas/2026-06-14-create-ai-execution-profiles.sql
- [x] T002 Create SQL delta for execution profiles seed data in specs/03-Data-and-Storage/deltas/2026-06-14-seed-execution-profiles.sql
- [x] T003 Create SQL delta for additional prompt types seed data in specs/03-Data-and-Storage/deltas/2026-06-14-seed-additional-prompt-types.sql
- [x] T004 [P] Run SQL deltas to create ai_execution_profiles table and seed data
---
## Phase 2: Foundational (Blocking Prerequisites)
**Purpose**: Core backend entities and DTOs that all user stories depend on
**⚠️ CRITICAL**: No user story work can begin until this phase is complete
- [x] T005 Create AiExecutionProfile entity in backend/src/modules/ai/entities/ai-execution-profile.entity.ts
- [x] T006 [P] Create ContextConfigDto in backend/src/modules/ai/dto/context-config.dto.ts
- [x] T007 [P] Create SandboxRagPrepDto in backend/src/modules/ai/dto/sandbox-rag-prep.dto.ts
- [x] T008 [P] Create CreateExecutionProfileDto in backend/src/modules/ai/dto/create-execution-profile.dto.ts
- [x] T009 [P] Create UpdateExecutionProfileDto in backend/src/modules/ai/dto/update-execution-profile.dto.ts
- [x] T010 [P] Create frontend types in frontend/lib/types/ai-prompts.ts (extend with ContextConfig, ExecutionProfile, SandboxJob types)
- [x] T011 Register AiExecutionProfile entity in AiModule in backend/src/modules/ai/ai.module.ts
**Checkpoint**: Foundation ready - user story implementation can now begin in parallel
---
## Phase 3: User Story 1 - Multi-Type Prompt Management (Priority: P1) 🎯 MVP
**Goal**: Admin users can manage prompt templates for multiple AI workflow types through a single unified interface with separated version history.
**Independent Test**: Create and activate versions for different prompt types, verify version history is correctly separated by type, and confirm active version badge displays correctly.
### Tests for User Story 1
- [x] T012 [P] [US1] Unit test for placeholder validation in AiPromptsService in backend/test/unit/ai/ai-prompts.service.spec.ts
- [x] T013 [P] [US1] Integration test for version number increment per prompt type in backend/test/integration/ai/ai-prompts.service.spec.ts
### Implementation for User Story 1
- [x] T014 [US1] Extend AiPromptsService with placeholder validation logic in backend/src/modules/ai/services/ai-prompts.service.ts
- [x] T015 [US1] Extend AiPromptsService with version number increment per prompt_type in backend/src/modules/ai/services/ai-prompts.service.ts
- [x] T016 [US1] Create PromptTypeDropdown component in frontend/components/admin/ai/PromptTypeDropdown.tsx
- [x] T017 [US1] Extend VersionHistory component with prompt_type filtering in frontend/components/admin/ai/VersionHistory.tsx
- [x] T018 [US1] Create PromptEditor component with placeholder validation in frontend/components/admin/ai/PromptEditor.tsx
- [x] T019 [US1] Create unified prompt management page in frontend/app/(admin)/admin/ai/prompt-management/page.tsx
- [x] T020 [US1] Extend admin-ai.service.ts with prompt type filtering methods in frontend/lib/services/admin-ai.service.ts
- [x] T021 [US1] Add i18n keys for prompt management UI in frontend/public/locales/th/common.json and en/common.json
**Checkpoint**: At this point, User Story 1 should be fully functional and testable independently
---
## Phase 4: User Story 2 - Context Configuration Management (Priority: P1)
**Goal**: Admin users can view, edit, save, and apply context configuration for each prompt version to control what data context the AI sees.
**Independent Test**: Edit context config fields, save a new version, and verify that the context config is correctly persisted and applied when the version is activated.
### Tests for User Story 2
- [x] T022 [P] [US2] Unit test for context config CRUD in AiPromptsService in backend/test/unit/ai/ai-prompts.service.spec.ts
- [x] T023 [P] [US2] Integration test for context config validation against database in backend/test/integration/ai/ai-prompts.service.spec.ts
### Implementation for User Story 2
- [x] T024 [US2] Add GET /api/ai/prompts/:type/:version/context-config endpoint in backend/src/modules/ai/controllers/ai-prompts.controller.ts
- [x] T025 [US2] Add PUT /api/ai/prompts/:type/:version/context-config endpoint in backend/src/modules/ai/controllers/ai-prompts.controller.ts
- [x] T026 [US2] Extend AiPromptsService with context config CRUD methods in backend/src/modules/ai/services/ai-prompts.service.ts
- [x] T027 [US2] Add context config validation (project/contract ID validation) in backend/src/modules/ai/services/ai-prompts.service.ts
- [x] T028 [US2] Create ContextConfigEditor component in frontend/components/admin/ai/ContextConfigEditor.tsx
- [x] T029 [US2] Integrate ContextConfigEditor into prompt management page in frontend/app/(admin)/admin/ai/prompt-management/page.tsx
- [x] T030 [US2] Extend admin-ai.service.ts with context config API methods in frontend/lib/services/admin-ai.service.ts
**Checkpoint**: At this point, User Stories 1 AND 2 should both work independently
---
## Phase 5: User Story 3 - Three-Step Sandbox Testing (Priority: P1)
**Goal**: Admin users can test the full AI pipeline (OCR → AI Extract → RAG Prep) in sandbox to validate prompt versions before activation.
**Independent Test**: Upload a PDF, run all three sandbox steps sequentially, and verify that each step produces expected outputs (OCR text, extracted metadata, RAG chunks).
### Tests for User Story 3
- [x] T031 [P] [US3] Unit test for sandbox RAG Prep job processing in ai-batch.processor in backend/test/unit/ai/ai-batch.processor.spec.ts
- [x] T032 [P] [US3] Integration test for 3-step sandbox workflow in backend/test/integration/ai/sandbox-workflow.spec.ts
### Implementation for User Story 3
- [x] T033 [US3] Add POST /api/ai/admin/sandbox/rag-prep endpoint in backend/src/modules/ai/controllers/ai.controller.ts
- [x] T034 [US3] Add GET /api/ai/admin/sandbox/job/:jobId endpoint in backend/src/modules/ai/controllers/ai.controller.ts
- [x] T035 [US3] Extend ai-batch.processor with sandbox-rag-prep job handler in backend/src/modules/ai/processors/ai-batch.processor.ts
- [x] T036 [US3] Extend OcrService with RAG Prep integration (semantic chunking + embedding) in backend/src/modules/ai/services/ocr.service.ts
- [x] T037 [US3] Create SandboxTabs component with 3-step workflow in frontend/components/admin/ai/SandboxTabs.tsx
- [x] T038 [US3] Integrate SandboxTabs into prompt management page in frontend/app/(admin)/admin/ai/prompt-management/page.tsx
- [x] T039 [US3] Extend admin-ai.service.ts with sandbox RAG Prep API methods in frontend/lib/services/admin-ai.service.ts
- [x] T040 [US3] Add "Activate This Version" button in sandbox results in frontend/components/admin/ai/SandboxTabs.tsx
**Checkpoint**: All user stories should now be independently functional
---
## Phase 6: User Story 4 - Runtime Parameters vs Context Config Separation (Priority: P2)
**Goal**: Admin users have clear separation between Runtime Parameters (AI model behavior) and Context Config (data context) to avoid confusion.
**Independent Test**: Verify that Runtime Parameters are in the Sandbox tab and apply globally to AI execution profiles, while Context Config is in the Prompt Editor panel and applies per prompt version.
### Tests for User Story 4
- [x] T041 [P] [US4] Unit test for execution profile CRUD in AiExecutionProfilesService in backend/test/unit/ai/ai-execution-profiles.service.spec.ts
- [x] T042 [P] [US4] Integration test for runtime parameters application to sandbox in backend/test/integration/ai/execution-profiles.spec.ts
### Implementation for User Story 4
- [x] T043 [US4] Create AiExecutionProfilesService in backend/src/modules/ai/services/ai-execution-profiles.service.ts
- [x] T044 [US4] Add GET /api/ai/execution-profiles endpoint in backend/src/modules/ai/controllers/ai.controller.ts
- [x] T045 [US4] Add POST /api/ai/execution-profiles endpoint in backend/src/modules/ai/controllers/ai.controller.ts
- [x] T046 [US4] Add PUT /api/ai/execution-profiles/:id endpoint in backend/src/modules/ai/controllers/ai.controller.ts
- [x] T047 [US4] Add DELETE /api/ai/execution-profiles/:id endpoint in backend/src/modules/ai/controllers/ai.controller.ts
- [x] T048 [US4] Create RuntimeParametersPanel component in frontend/components/admin/ai/RuntimeParametersPanel.tsx
- [x] T049 [US4] Integrate RuntimeParametersPanel into SandboxTabs in frontend/components/admin/ai/SandboxTabs.tsx
- [x] T050 [US4] Extend admin-ai.service.ts with execution profile API methods in frontend/lib/services/admin-ai.service.ts
- [x] T051 [US4] Add "Apply to Production" button in RuntimeParametersPanel in frontend/components/admin/ai/RuntimeParametersPanel.tsx
**Checkpoint**: All user stories including US4 should now be independently functional
---
## Phase 7: Polish & Cross-Cutting Concerns
**Purpose**: Improvements that affect multiple user stories
- [x] T052 [P] Add error handling following ADR-007 (BusinessException hierarchy) in backend/src/modules/ai/services/ai-prompts.service.ts
- [x] T053 [P] Add error handling following ADR-007 in backend/src/modules/ai/services/ai-execution-profiles.service.ts
- [x] T054 [P] Add CASL guards to all new mutation endpoints in backend/src/modules/ai/controllers/ai-prompts.controller.ts
- [x] T055 [P] Add CASL guards to all new mutation endpoints in backend/src/modules/ai/controllers/ai.controller.ts
- [x] T056 [P] Add ThrottlerGuard to sandbox endpoints in backend/src/modules/ai/controllers/ai.controller.ts
- [x] T057 [P] Add Redis cache invalidation on version activation in backend/src/modules/ai/services/ai-prompts.service.ts
- [x] T058 [P] Add i18n keys for all new UI components in frontend/public/locales/th/common.json and en/common.json
- [x] T059 [P] Add TypeScript strict mode compliance checks (no any, no console.log) in backend/src/modules/ai/ and frontend/components/admin/ai/
- [x] T060 [P] Add E2E test for full prompt management workflow in frontend/e2e/prompt-management.spec.ts
- [x] T061 Run quickstart.md validation checklist
- [x] T062 Update ADR-037 with implementation status
---
## Dependencies & Execution Order
### Phase Dependencies
- **Setup (Phase 1)**: No dependencies - can start immediately
- **Foundational (Phase 2)**: Depends on Setup completion - BLOCKS all user stories
- **User Stories (Phase 3-6)**: All depend on Foundational phase completion
- User stories can then proceed in parallel (if staffed)
- Or sequentially in priority order (US1 → US2 → US3 → US4)
- **Polish (Phase 7)**: Depends on all desired user stories being complete
### User Story Dependencies
- **User Story 1 (P1)**: Can start after Foundational (Phase 2) - No dependencies on other stories
- **User Story 2 (P1)**: Can start after Foundational (Phase 2) - Integrates with US1 (uses same page) but independently testable
- **User Story 3 (P1)**: Can start after Foundational (Phase 2) - Integrates with US1 (uses same page) but independently testable
- **User Story 4 (P2)**: Can start after Foundational (Phase 2) - Integrates with US3 (SandboxTabs) but independently testable
### Within Each User Story
- Tests MUST be written and FAIL before implementation
- DTOs before services
- Services before controllers
- Backend before frontend (for API-dependent features)
- Core implementation before integration
- Story complete before moving to next priority
### Parallel Opportunities
- All Setup tasks marked [P] can run in parallel
- All Foundational tasks marked [P] can run in parallel (within Phase 2)
- Once Foundational phase completes, all user stories can start in parallel (if team capacity allows)
- All tests for a user story marked [P] can run in parallel
- DTOs within a story marked [P] can run in parallel
- Different user stories can be worked on in parallel by different team members
---
## Parallel Example: User Story 1
```bash
# Launch all tests for User Story 1 together:
Task: "Unit test for placeholder validation in AiPromptsService in backend/test/unit/ai/ai-prompts.service.spec.ts"
Task: "Integration test for version number increment per prompt type in backend/test/integration/ai/ai-prompts.service.spec.ts"
# Launch all DTOs for User Story 1 together (in Foundational phase):
Task: "Create ContextConfigDto in backend/src/modules/ai/dto/context-config.dto.ts"
Task: "Create SandboxRagPrepDto in backend/src/modules/ai/dto/sandbox-rag-prep.dto.ts"
```
---
## Implementation Strategy
### MVP First (User Story 1 Only)
1. Complete Phase 1: Setup
2. Complete Phase 2: Foundational (CRITICAL - blocks all stories)
3. Complete Phase 3: User Story 1
4. **STOP and VALIDATE**: Test User Story 1 independently
5. Deploy/demo if ready
### Incremental Delivery
1. Complete Setup + Foundational → Foundation ready
2. Add User Story 1 → Test independently → Deploy/Demo (MVP!)
3. Add User Story 2 → Test independently → Deploy/Demo
4. Add User Story 3 → Test independently → Deploy/Demo
5. Add User Story 4 → Test independently → Deploy/Demo
6. Each story adds value without breaking previous stories
### Parallel Team Strategy
With multiple developers:
1. Team completes Setup + Foundational together
2. Once Foundational is done:
- Developer A: User Story 1 (Multi-Type Prompt Management)
- Developer B: User Story 2 (Context Configuration Management)
- Developer C: User Story 3 (Three-Step Sandbox Testing)
3. After P1 stories complete:
- Developer A: User Story 4 (Runtime Parameters Separation)
- Developer B: Polish & Cross-Cutting Concerns
- Developer C: E2E testing
4. Stories complete and integrate independently
---
## Notes
- [P] tasks = different files, no dependencies
- [Story] label maps task to specific user story for traceability
- Each user story should be independently completable and testable
- Verify tests fail before implementing
- Commit after each task or logical group
- Stop at any checkpoint to validate story independently
- Avoid: vague tasks, same file conflicts, cross-story dependencies that break independence
- Follow ADR-019 UUID handling (no parseInt, use publicId only)
- Follow ADR-009 schema changes (edit SQL directly, no migrations)
- Follow ADR-016 security (CASL guards on all mutations)
- Follow ADR-007 error handling (layered classification)
- Follow ADR-023/023A AI boundary (BullMQ queues, no direct AI access)