220 lines
6.6 KiB
TypeScript
220 lines
6.6 KiB
TypeScript
import type { APIRoute } from 'astro';
|
|
|
|
/**
|
|
* Spintax Pattern Validator
|
|
* Check syntax before running 10k generations
|
|
*/
|
|
|
|
function validateGodToken(request: Request): boolean {
|
|
const token = request.headers.get('X-God-Token') ||
|
|
request.headers.get('Authorization')?.replace('Bearer ', '') ||
|
|
new URL(request.url).searchParams.get('token');
|
|
|
|
const godToken = process.env.GOD_MODE_TOKEN || import.meta.env.GOD_MODE_TOKEN;
|
|
if (!godToken) return true;
|
|
return token === godToken;
|
|
}
|
|
|
|
interface ValidationError {
|
|
type: string;
|
|
position: number;
|
|
message: string;
|
|
}
|
|
|
|
/**
|
|
* Validate spintax pattern syntax
|
|
*/
|
|
function validateSpintax(pattern: string): { valid: boolean; errors: ValidationError[] } {
|
|
const errors: ValidationError[] = [];
|
|
let braceStack: number[] = [];
|
|
let inBraces = false;
|
|
let currentDepth = 0;
|
|
const maxDepth = 3;
|
|
|
|
for (let i = 0; i < pattern.length; i++) {
|
|
const char = pattern[i];
|
|
const prevChar = i > 0 ? pattern[i - 1] : '';
|
|
|
|
// Check for opening brace
|
|
if (char === '{' && prevChar !== '\\') {
|
|
braceStack.push(i);
|
|
currentDepth++;
|
|
inBraces = true;
|
|
|
|
if (currentDepth > maxDepth) {
|
|
errors.push({
|
|
type: 'max_depth',
|
|
position: i,
|
|
message: `Nested too deep (max ${maxDepth} levels)`
|
|
});
|
|
}
|
|
}
|
|
|
|
// Check for closing brace
|
|
if (char === '}' && prevChar !== '\\') {
|
|
if (braceStack.length === 0) {
|
|
errors.push({
|
|
type: 'unmatched_closing',
|
|
position: i,
|
|
message: 'Closing brace without opening brace'
|
|
});
|
|
} else {
|
|
const openPos = braceStack.pop();
|
|
const content = pattern.substring(openPos! + 1, i);
|
|
|
|
// Check for empty braces
|
|
if (content.trim() === '') {
|
|
errors.push({
|
|
type: 'empty_braces',
|
|
position: openPos!,
|
|
message: 'Empty option set {}'
|
|
});
|
|
}
|
|
|
|
// Check for missing pipes
|
|
if (!content.includes('|')) {
|
|
errors.push({
|
|
type: 'no_alternatives',
|
|
position: openPos!,
|
|
message: 'Option set must contain at least one pipe |'
|
|
});
|
|
}
|
|
|
|
// Check for empty options
|
|
const options = content.split('|');
|
|
for (let j = 0; j < options.length; j++) {
|
|
if (options[j].trim() === '') {
|
|
errors.push({
|
|
type: 'empty_option',
|
|
position: openPos! + content.indexOf('||'),
|
|
message: 'Empty option between pipes'
|
|
});
|
|
}
|
|
}
|
|
|
|
currentDepth--;
|
|
}
|
|
inBraces = braceStack.length > 0;
|
|
}
|
|
}
|
|
|
|
// Check for unclosed braces
|
|
if (braceStack.length > 0) {
|
|
for (const pos of braceStack) {
|
|
errors.push({
|
|
type: 'unclosed_brace',
|
|
position: pos,
|
|
message: 'Opening brace not closed'
|
|
});
|
|
}
|
|
}
|
|
|
|
return {
|
|
valid: errors.length === 0,
|
|
errors
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Generate a few sample variations
|
|
*/
|
|
function generateSamples(pattern: string, count: number = 3): string[] {
|
|
const samples: string[] = [];
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
let result = pattern;
|
|
const regex = /\{([^{}]+)\}/g;
|
|
|
|
result = result.replace(regex, (match, content) => {
|
|
const options = content.split('|').map((s: string) => s.trim());
|
|
return options[Math.floor(Math.random() * options.length)];
|
|
});
|
|
|
|
samples.push(result);
|
|
}
|
|
|
|
return samples;
|
|
}
|
|
|
|
export const POST: APIRoute = async ({ request }) => {
|
|
if (!validateGodToken(request)) {
|
|
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
|
|
status: 401,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
|
|
try {
|
|
const { pattern, max_depth = 3 } = await request.json();
|
|
|
|
if (!pattern) {
|
|
return new Response(JSON.stringify({
|
|
error: 'Missing pattern'
|
|
}), {
|
|
status: 400,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
|
|
const validation = validateSpintax(pattern);
|
|
const samples = validation.valid ? generateSamples(pattern, 5) : [];
|
|
|
|
return new Response(JSON.stringify({
|
|
pattern,
|
|
valid: validation.valid,
|
|
errors: validation.errors,
|
|
samples: validation.valid ? samples : null,
|
|
stats: {
|
|
length: pattern.length,
|
|
braces: (pattern.match(/\{/g) || []).length,
|
|
pipes: (pattern.match(/\|/g) || []).length
|
|
},
|
|
recommendation: validation.valid
|
|
? '✅ Pattern is valid - safe to use in batch generation'
|
|
: '❌ Fix errors before using in production',
|
|
timestamp: new Date().toISOString()
|
|
}), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
|
|
} catch (error: any) {
|
|
return new Response(JSON.stringify({
|
|
error: 'Validation failed',
|
|
details: error.message
|
|
}), {
|
|
status: 500,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
};
|
|
|
|
export const GET: APIRoute = async ({ request }) => {
|
|
if (!validateGodToken(request)) {
|
|
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
|
|
status: 401,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
}
|
|
|
|
return new Response(JSON.stringify({
|
|
endpoint: 'POST /api/intelligence/spintax/validate',
|
|
description: 'Validate spintax patterns before batch generation',
|
|
examples: {
|
|
valid: '{Hello|Hi|Hey} {world|there|friend}!',
|
|
invalid: '{Hello|Hi} {world',
|
|
nested: '{The {best|top} {solution|answer}}',
|
|
empty: '{|option} // Error: empty option'
|
|
},
|
|
errors_detected: [
|
|
'unmatched_braces',
|
|
'empty_option_sets',
|
|
'no_alternatives',
|
|
'max_depth_exceeded'
|
|
]
|
|
}, null, 2), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
};
|