Files
P4RS3LT0NGV3/js/core/spellingAlphabetTransform.js
T

273 lines
9.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Factory for ICAO-style spelling alphabet transforms (built-in or user-saved).
*/
(function(global) {
'use strict';
var LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
var SAMPLE_TEXT = 'hello world';
function emptyAlphabet() {
var alphabet = {};
LETTERS.forEach(function(letter) {
alphabet[letter] = '';
});
return alphabet;
}
function normalizeWord(word) {
return word ? String(word).toUpperCase().replace(/[^A-Z0-9]/g, '') : '';
}
function countFilled(alphabet) {
return LETTERS.filter(function(letter) {
return !!alphabet[letter];
}).length;
}
function mergeAlphabet(target, source) {
LETTERS.forEach(function(letter) {
var word = normalizeWord(source && source[letter]);
if (word && !target[letter]) {
target[letter] = word;
}
});
return target;
}
function tryParseJsonObject(text) {
var start = text.indexOf('{');
var end = text.lastIndexOf('}');
if (start === -1 || end <= start) {
return null;
}
var candidates = [
text.slice(start, end + 1),
text.slice(start, end + 1).replace(/,\s*([}\]])/g, '$1')
];
for (var i = 0; i < candidates.length; i++) {
try {
var parsed = JSON.parse(candidates[i]);
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
return parsed;
}
} catch (e) {
// try next candidate
}
}
return null;
}
function extractPairsFromText(text) {
var alphabet = emptyAlphabet();
var patterns = [
/"([A-Z])"\s*:\s*"([^"]+)"/g,
/'([A-Z])'\s*:\s*'([^']+)'/g,
/(?:^|[\n,{])\s*([A-Z])\s*[:=\-]\s*"?([A-Za-z0-9]+)"?/g,
/(?:^|\n)\s*([A-Z])\s+[—–-]\s+([A-Za-z0-9]+)/g
];
patterns.forEach(function(pattern) {
var match;
while ((match = pattern.exec(text)) !== null) {
var letter = match[1].toUpperCase();
var word = normalizeWord(match[2]);
if (LETTERS.indexOf(letter) !== -1 && word && !alphabet[letter]) {
alphabet[letter] = word;
}
}
});
return alphabet;
}
function parseAlphabetResponse(rawText) {
var text = String(rawText || '').trim();
if (!text) {
throw new Error('Empty response from model.');
}
var fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
if (fenced) {
text = fenced[1].trim();
}
var merged = emptyAlphabet();
var jsonObject = tryParseJsonObject(text);
if (jsonObject) {
mergeAlphabet(merged, normalizeAlphabet(jsonObject));
}
mergeAlphabet(merged, extractPairsFromText(text));
var filledCount = countFilled(merged);
if (filledCount === 0) {
throw new Error('Model did not return JSON. Try again or fill letters manually.');
}
return {
alphabet: merged,
filledCount: filledCount,
partial: filledCount < LETTERS.length
};
}
function extractMessageContent(message) {
if (!message) {
return '';
}
var content = message.content;
if (typeof content === 'string') {
return content;
}
if (Array.isArray(content)) {
return content.map(function(part) {
if (typeof part === 'string') {
return part;
}
if (part && typeof part.text === 'string') {
return part.text;
}
return '';
}).join('\n');
}
return content == null ? '' : String(content);
}
function buildAlphabetPrompts(category) {
var theme = String(category || '').trim() || 'general';
var system = [
'You create NATO/ICAO-style spelling alphabets.',
'',
'Task: given a theme, produce one codeword per letter AZ for spelling text aloud.',
'',
'Hard rules:',
'- Output ONLY valid JSON — one object, no markdown, no code fences, no explanation.',
'- Exactly 26 keys: "A" through "Z" (uppercase).',
'- Each value is ONE uppercase English word with no spaces.',
'- Every word MUST start with its key letter (A→ANCHOR, not A→HARBOR).',
'- All 26 words must be unique.',
'- Words must fit the theme and be easy to say aloud (prefer 13 syllables).',
'- Avoid obscure jargon unless the theme requires it.',
'',
'Letter tips:',
'- Q, X, and Z are hard: pick the best theme word that starts with that letter.',
'- For X, XRAY or a theme word starting with X is fine.',
'',
'Example theme "nautical" (format only — use different words for other themes):',
'{"A":"ANCHOR","B":"BUOY","C":"CORAL","D":"DOCK","E":"EDDY","F":"FOG","G":"GALLEY","H":"HARBOR","I":"ISLAND","J":"JIB","K":"KNOT","L":"LAGOON","M":"MAST","N":"NAVY","O":"OCEAN","P":"PORT","Q":"QUAY","R":"REEF","S":"SAIL","T":"TIDE","U":"UNDERTOW","V":"VOYAGE","W":"WHARF","X":"XRAY","Y":"YACHT","Z":"ZEPHYR"}'
].join('\n');
var user = [
'Theme: "' + theme + '"',
'',
'Return the complete AZ JSON object now.',
'Double-check: 26 keys, each word starts with its letter, all unique, theme-appropriate.'
].join('\n');
return { system: system, user: user };
}
function buildAlphabetPrompt(category) {
return buildAlphabetPrompts(category).user;
}
function normalizeAlphabet(alphabet) {
var normalized = emptyAlphabet();
if (!alphabet || typeof alphabet !== 'object') {
return normalized;
}
LETTERS.forEach(function(letter) {
var word = alphabet[letter] || alphabet[letter.toLowerCase()];
normalized[letter] = normalizeWord(word);
});
return normalized;
}
function createSpellingAlphabetTransform(config) {
var alphabet = normalizeAlphabet(config.alphabet);
var name = config.name || 'Spelling Alphabet';
var category = config.category || 'custom_spelling';
var priority = typeof config.priority === 'number' ? config.priority : 200;
var customId = config.id || null;
var transform = {
name: name,
priority: priority,
category: category,
alphabet: alphabet,
customSpellingId: customId,
func: function(text) {
var cleaned = text.toUpperCase().replace(/[^A-Z]/g, '');
if (cleaned.length === 0) {
return text;
}
var result = '';
for (var i = 0; i < cleaned.length; i++) {
var char = cleaned[i];
if (this.alphabet[char]) {
result += this.alphabet[char] + ' ';
} else {
result += char + ' ';
}
}
return result.trim();
},
reverse: function(text) {
var reverseMap = {};
for (var letter in this.alphabet) {
if (Object.prototype.hasOwnProperty.call(this.alphabet, letter)) {
reverseMap[this.alphabet[letter].toUpperCase()] = letter;
}
}
var words = text.toUpperCase().split(/\s+/);
var decoded = '';
for (var j = 0; j < words.length; j++) {
var word = words[j];
if (reverseMap[word]) {
decoded += reverseMap[word];
} else if (word.length === 1 && /[A-Z]/.test(word)) {
decoded += word;
}
}
return decoded;
},
preview: function(text) {
if (!text) {
return '[spelling]';
}
return this.func(text.slice(0, 3));
},
detector: function(text) {
var words = Object.values(this.alphabet).filter(Boolean);
if (words.length < 2) {
return false;
}
var upper = text.toUpperCase();
var matches = words.filter(function(word) {
return upper.includes(word);
});
return matches.length >= 2;
}
};
return transform;
}
global.SpellingAlphabetTransform = {
LETTERS: LETTERS,
SAMPLE_TEXT: SAMPLE_TEXT,
emptyAlphabet: emptyAlphabet,
normalizeAlphabet: normalizeAlphabet,
parseAlphabetResponse: parseAlphabetResponse,
extractMessageContent: extractMessageContent,
buildAlphabetPrompts: buildAlphabetPrompts,
buildAlphabetPrompt: buildAlphabetPrompt,
create: createSpellingAlphabetTransform
};
})(typeof window !== 'undefined' ? window : globalThis);