Files
DemonGatanjieu-Anomalous_Mo…/web/modules/update_guide_data.js
T
DemonGatanjieuandClaude Opus 5.5 8c1ad49e25 docs(copy): README, help and the "!" guide describe the rail layout
The README, the help page and the image-side "!" guide still described the
old top tabs and bottom bar and an earlier release's features. They now
cover the rail, Home, model type chips, the activity log, character voices
(with the optional Anomalous_TTS node pack) and the lighter memory use.

- New update guide id 2026-10-rail-and-voices with five steps; the old
  guide's locale keys (and the never-shown navigation/transfer/library
  ones) are removed.
- Help: the Civitai API key goes in with the key button in the scan wizard,
  not in Settings.
- The audio guide says the tag copy button only exists for characters
  with emotions.
- update-guide.md loses its section on the removed bottom bar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 17:58:10 +08:00

58 lines
4.2 KiB
JavaScript

// Content only. Maintenance checklist: docs/architecture/update-guide.md.
// Change the ID only when users should see a new guide, never for typo fixes.
export const CURRENT_UPDATE_GUIDE = Object.freeze({
id: '2026-10-rail-and-voices',
steps: Object.freeze([
{ id: 'layout', icon: '🧭', titleKey: 'updateGuideLayoutTitle', bodyKey: 'updateGuideLayoutBody' },
{ id: 'model-types', icon: '📦', titleKey: 'updateGuideModelTypesTitle', bodyKey: 'updateGuideModelTypesBody' },
{ id: 'activity', icon: '🕘', titleKey: 'updateGuideActivityTitle', bodyKey: 'updateGuideActivityBody' },
{ id: 'voices', icon: '🎙️', titleKey: 'updateGuideVoicesTitle', bodyKey: 'updateGuideVoicesBody' },
{ id: 'memory', icon: '🪶', titleKey: 'updateGuideMemoryTitle', bodyKey: 'updateGuideMemoryBody' },
]),
});
// Spotlight tour of the audio studio. Steps whose target is not on screen (no characters
// yet, the audio gallery open, sidebar collapsed) are skipped.
export const AUDIO_TOUR_STEPS = Object.freeze([
{ targetSelector: '[data-tour="audio-import"]', icon: '📥', titleKey: 'audioTourImportTitle', bodyKey: 'audioTourImportBody', position: 'bottom' },
{ targetSelector: '.anomalous-character-voice-header.is-draggable', icon: '🧲', titleKey: 'audioTourDragTitle', bodyKey: 'audioTourDragBody', position: 'bottom' },
{ targetSelector: '.anomalous-character-voice-actions', icon: '🎭', titleKey: 'audioTourEditTitle', bodyKey: 'audioTourEditBody', position: 'bottom' },
{ targetSelector: '.anomalous-voice-copy-btn', icon: '🏷️', titleKey: 'audioTourTagTitle', bodyKey: 'audioTourTagBody', position: 'bottom' },
{ targetSelector: '[data-tour="audio-script"]', icon: '📜', titleKey: 'audioTourScriptTitle', bodyKey: 'audioTourScriptBody', position: 'bottom' },
{ targetSelector: '#anomalous-audio-gallery-btn', icon: '🎧', titleKey: 'audioTourGalleryTitle', bodyKey: 'audioTourGalleryBody', position: 'right' },
{ targetSelector: '.anomalous-audio-sidebar-settings', icon: '⚙️', titleKey: 'audioTourSettingsTitle', bodyKey: 'audioTourSettingsBody', position: 'top' },
]);
// How-to for the audio studio; the "!" button opens it while the audio domain is active.
// Optional fields: titleKey (dialog title), tour: false (no spotlight tour banner) or the
// guide's own spotlight steps (default: the browser's tour).
export const AUDIO_USAGE_GUIDE = Object.freeze({
id: 'audio-studio-usage',
titleKey: 'audioGuideTitle',
tour: AUDIO_TOUR_STEPS,
steps: Object.freeze([
{ id: 'files', icon: '🎙️', titleKey: 'audioGuideFilesTitle', bodyKey: 'audioGuideFilesBody' },
{ id: 'drag', icon: '🧲', titleKey: 'audioGuideDragTitle', bodyKey: 'audioGuideDragBody' },
{ id: 'tags', icon: '🏷️', titleKey: 'audioGuideTagsTitle', bodyKey: 'audioGuideTagsBody' },
{ id: 'script', icon: '📜', titleKey: 'audioGuideScriptTitle', bodyKey: 'audioGuideScriptBody' },
{ id: 'generate', icon: '🎧', titleKey: 'audioGuideGenerateTitle', bodyKey: 'audioGuideGenerateBody' },
]),
});
export function validateUpdateGuide(guide, locales) {
if (!guide || typeof guide.id !== 'string' || !/^[a-z0-9][a-z0-9-]{0,79}$/.test(guide.id)) return false;
if (!Array.isArray(guide.steps) || guide.steps.length < 1 || guide.steps.length > 5) return false;
if (guide.titleKey !== undefined && !['zh', 'en'].every(locale => typeof locales[locale]?.[guide.titleKey] === 'string')) return false;
if (Array.isArray(guide.tour) && !guide.tour.every(step => typeof step?.targetSelector === 'string'
&& ['titleKey', 'bodyKey'].every(field => ['zh', 'en'].every(locale => typeof locales[locale]?.[step[field]] === 'string')))) return false;
const ids = new Set();
return guide.steps.every(step => {
if (!step || typeof step.id !== 'string' || !step.id || ids.has(step.id)) return false;
ids.add(step.id);
if (typeof step.icon !== 'string' || !step.icon || step.icon.length > 8) return false;
return ['titleKey', 'bodyKey'].every(field => typeof step[field] === 'string'
&& ['zh', 'en'].every(locale => typeof locales[locale]?.[step[field]] === 'string'
&& locales[locale][step[field]].trim().length > 0));
});
}