mirror of
https://github.com/elder-plinius/P4RS3LT0NGV3.git
synced 2026-07-12 07:26:33 +02:00
880 lines
35 KiB
JavaScript
880 lines
35 KiB
JavaScript
const baseData = {
|
|
activeTheme: 'dark',
|
|
themeOptions: (window.ThemeUtils && window.ThemeUtils.getThemes)
|
|
? window.ThemeUtils.getThemes()
|
|
: [{ id: 'dark', name: 'Dark' }, { id: 'light', name: 'Light' }],
|
|
activeTab: 'transforms',
|
|
registeredTools: [],
|
|
universalDecodeInput: '',
|
|
universalDecodeResult: null,
|
|
isPasteOperation: false,
|
|
lastCopyTime: 0,
|
|
ignoreKeyboardEvents: false,
|
|
isTransformCopy: false,
|
|
keyboardEventsTimeout: null,
|
|
showDecoder: true,
|
|
tbCarrierManual: '',
|
|
copyHistory: [],
|
|
maxHistoryItems: window.CONFIG.MAX_HISTORY_ITEMS,
|
|
showCopyHistory: false,
|
|
activeUtilityPanel: 'history',
|
|
mobileUtilityOpen: false,
|
|
utilityPanelInteractionGuard: false,
|
|
showUnicodePanel: false,
|
|
unicodeApplyBusy: false,
|
|
unicodeApplyFlash: false,
|
|
unicodePanelToggleLock: false,
|
|
unicodeApplyFlashTimeout: null,
|
|
showDangerModal: false,
|
|
dangerThresholdTokens: window.CONFIG.DANGER_THRESHOLD_TOKENS,
|
|
showGlitchTokenPanel: false,
|
|
showEndSequencePanel: false,
|
|
endSequenceCategories: (typeof window !== 'undefined' && window.END_SEQUENCE_CATEGORIES)
|
|
? window.END_SEQUENCE_CATEGORIES
|
|
: [],
|
|
glitchTokensLoaded: false,
|
|
glitchTokenBehavior: '',
|
|
glitchTokenSearch: '',
|
|
filteredGlitchTokens: [],
|
|
allGlitchTokens: [],
|
|
openrouterApiKey: localStorage.getItem('openrouter-api-key') || '',
|
|
showApiKey: false,
|
|
apiKeySaved: false,
|
|
openRouterModels: (window.OpenRouterModels && window.OpenRouterModels.getStaticFallback)
|
|
? window.OpenRouterModels.getStaticFallback()
|
|
: [],
|
|
openRouterModelsCatalog: (window.OpenRouterModels && window.OpenRouterModels.getStaticFallback)
|
|
? window.OpenRouterModels.getStaticFallback()
|
|
: [],
|
|
openRouterModelsDisabled: (window.OpenRouterModels && window.OpenRouterModels.loadDisabledIds)
|
|
? window.OpenRouterModels.loadDisabledIds()
|
|
: [],
|
|
openRouterModelsFilterQuery: '',
|
|
openRouterModelsLoading: false,
|
|
openRouterModelsError: '',
|
|
openRouterModelsKeyInfo: null
|
|
};
|
|
|
|
const toolData = (window.toolRegistry && typeof window.toolRegistry.mergeVueData === 'function')
|
|
? window.toolRegistry.mergeVueData()
|
|
: {};
|
|
const mergedData = Object.assign({}, baseData, toolData);
|
|
|
|
const toolMethods = (window.toolRegistry && typeof window.toolRegistry.mergeVueMethods === 'function')
|
|
? window.toolRegistry.mergeVueMethods()
|
|
: {};
|
|
|
|
Vue.component('openrouter-model-select', {
|
|
props: {
|
|
value: { type: String, default: '' },
|
|
label: { type: String, default: 'Model' },
|
|
showRefresh: { type: Boolean, default: true }
|
|
},
|
|
computed: {
|
|
models: function() {
|
|
return this.$root.openRouterModels || [];
|
|
},
|
|
loading: function() {
|
|
return this.$root.openRouterModelsLoading;
|
|
},
|
|
error: function() {
|
|
return this.$root.openRouterModelsError;
|
|
},
|
|
keyInfo: function() {
|
|
return this.$root.openRouterModelsKeyInfo;
|
|
},
|
|
hasApiKey: function() {
|
|
return this.$root.getOpenRouterApiKey ? !!this.$root.getOpenRouterApiKey() : false;
|
|
},
|
|
routerHint: function() {
|
|
if (!this.value || !window.OpenRouterModels || !window.OpenRouterModels.getRouterHint) {
|
|
return '';
|
|
}
|
|
return window.OpenRouterModels.getRouterHint(this.value);
|
|
}
|
|
},
|
|
methods: {
|
|
onChange: function(event) {
|
|
this.$emit('input', event.target.value);
|
|
},
|
|
refresh: function() {
|
|
if (this.$root.refreshOpenRouterModels) {
|
|
this.$root.refreshOpenRouterModels(true);
|
|
}
|
|
},
|
|
formatLabel: function(model) {
|
|
return this.$root.formatOpenRouterModelLabel
|
|
? this.$root.formatOpenRouterModelLabel(model)
|
|
: (model && model.name) || '';
|
|
}
|
|
},
|
|
template:
|
|
'<label class="openrouter-model-picker">' +
|
|
'<span class="openrouter-model-label">{{ label }}</span>' +
|
|
'<div class="openrouter-model-row">' +
|
|
'<select ' +
|
|
'class="openrouter-model-select" ' +
|
|
':value="value" ' +
|
|
'@change="onChange" ' +
|
|
':disabled="loading && !models.length"' +
|
|
'>' +
|
|
'<option v-if="loading && !models.length" disabled value="">Loading models…</option>' +
|
|
'<option v-for="m in models" :key="m.id" :value="m.id">{{ formatLabel(m) }}</option>' +
|
|
'</select>' +
|
|
'<button ' +
|
|
'v-if="showRefresh" ' +
|
|
'type="button" ' +
|
|
'class="openrouter-model-refresh" ' +
|
|
'@click="refresh" ' +
|
|
':disabled="loading" ' +
|
|
'title="Refresh model list from OpenRouter"' +
|
|
'aria-label="Refresh model list"' +
|
|
'>' +
|
|
'<i class="fas" :class="loading ? \'fa-spinner fa-spin\' : \'fa-sync-alt\'"></i>' +
|
|
'</button>' +
|
|
'</div>' +
|
|
'<small v-if="error" class="openrouter-model-hint openrouter-model-hint-error">{{ error }}</small>' +
|
|
'<small v-else-if="routerHint" class="openrouter-model-hint openrouter-model-hint-router">{{ routerHint }}</small>' +
|
|
'<small v-else-if="!hasApiKey" class="openrouter-model-hint">Add an OpenRouter key in Settings to load models for your account.</small>' +
|
|
'<small v-else-if="keyInfo && keyInfo.is_free_tier" class="openrouter-model-hint">Free tier account — models marked · free need no credits.</small>' +
|
|
'<small v-else class="openrouter-model-hint">Curate visible models in Settings → AI Models.</small>' +
|
|
'</label>'
|
|
});
|
|
|
|
window.app = new Vue({
|
|
el: '#app',
|
|
data: mergedData,
|
|
computed: {
|
|
filteredOpenRouterModelsCatalog: function() {
|
|
var query = (this.openRouterModelsFilterQuery || '').trim().toLowerCase();
|
|
var catalog = this.openRouterModelsCatalog || [];
|
|
if (!query) return catalog;
|
|
return catalog.filter(function(model) {
|
|
var haystack = [
|
|
model.id,
|
|
model.name,
|
|
model.provider,
|
|
model.summary
|
|
].filter(Boolean).join(' ').toLowerCase();
|
|
return haystack.indexOf(query) !== -1;
|
|
});
|
|
}
|
|
},
|
|
methods: Object.assign({}, toolMethods || {}, {
|
|
toggleUnicodePanel(event) {
|
|
if (this.unicodePanelToggleLock) return;
|
|
this.unicodePanelToggleLock = true;
|
|
this.switchUtilityPanel('settings');
|
|
setTimeout(() => {
|
|
this.unicodePanelToggleLock = false;
|
|
}, 300);
|
|
},
|
|
applyUnicodeOptions() {
|
|
if (this.unicodeApplyBusy) return;
|
|
|
|
if (this.unicodeApplyFlashTimeout) {
|
|
clearTimeout(this.unicodeApplyFlashTimeout);
|
|
this.unicodeApplyFlashTimeout = null;
|
|
}
|
|
|
|
this.unicodeApplyFlash = false;
|
|
this.unicodeApplyBusy = true;
|
|
|
|
try {
|
|
const initSel = document.querySelector('.steg-initial-presentation');
|
|
const vs0Sel = document.querySelector('.steg-vs-zero');
|
|
const vs1Sel = document.querySelector('.steg-vs-one');
|
|
const zwSel = document.querySelector('.steg-inter-zw');
|
|
const everyInput = document.querySelector('.steg-inter-every');
|
|
const orderSel = document.querySelector('.steg-bit-order');
|
|
const trailSel = document.querySelector('.steg-trailing-zw');
|
|
|
|
const parseEsc = (s) => window.EscapeParser.parseEscapeSequence(s);
|
|
|
|
if (window.steganography && window.steganography.setStegOptions) {
|
|
window.steganography.setStegOptions({
|
|
initialPresentation: (initSel && initSel.value) || 'none',
|
|
bitZeroVS: parseEsc(vs0Sel && vs0Sel.value) || '\ufe0e',
|
|
bitOneVS: parseEsc(vs1Sel && vs1Sel.value) || '\ufe0f',
|
|
interBitZW: parseEsc(zwSel && zwSel.value) || null,
|
|
interBitEvery: Math.max(1, Math.min(8, Number((everyInput && everyInput.value) || 1))),
|
|
bitOrder: (orderSel && orderSel.value) || 'msb',
|
|
trailingZW: parseEsc(trailSel && trailSel.value) || ''
|
|
});
|
|
this.unicodeApplyFlash = true;
|
|
this.showNotification('Advanced settings applied', 'success', 'fas fa-sliders-h');
|
|
this.unicodeApplyFlashTimeout = setTimeout(() => {
|
|
this.unicodeApplyFlash = false;
|
|
this.unicodeApplyFlashTimeout = null;
|
|
}, 1200);
|
|
} else {
|
|
this.showNotification('Engine missing setStegOptions()', 'warning', 'fas fa-exclamation-triangle');
|
|
}
|
|
} catch (e) {
|
|
console.error('Apply Unicode options error', e);
|
|
this.showNotification('Failed to apply settings', 'error', 'fas fa-exclamation-triangle');
|
|
this.unicodeApplyFlash = false;
|
|
} finally {
|
|
this.unicodeApplyBusy = false;
|
|
}
|
|
},
|
|
focusWithoutScroll(el) {
|
|
window.FocusUtils.focusWithoutScroll(el);
|
|
},
|
|
|
|
triggerRandomizerChaos() {
|
|
try {
|
|
const section = document.getElementById('category-randomizer');
|
|
const overlay = section && section.querySelector('.chaos-overlay');
|
|
if (!overlay) return;
|
|
const emojis = ['✨','🌀','💥','⚡','🔥','🌈','🎲','🔮','💫','🌪️'];
|
|
for (let i=0;i<10;i++) {
|
|
const el = document.createElement('div');
|
|
el.className = 'chaos-particle';
|
|
el.textContent = emojis[Math.floor(Math.random()*emojis.length)];
|
|
el.style.left = (10 + Math.random()*80) + '%';
|
|
el.style.fontSize = (14 + Math.random()*10) + 'px';
|
|
el.style.animationDelay = (Math.random()*0.2) + 's';
|
|
overlay.appendChild(el);
|
|
setTimeout(()=>{ if (el.parentNode) el.parentNode.removeChild(el); }, 1300);
|
|
}
|
|
section.classList.add('shake-once','randomizer-glow');
|
|
setTimeout(()=>section && section.classList.remove('shake-once','randomizer-glow'), 600);
|
|
} catch(_) {}
|
|
},
|
|
getValidToolIds() {
|
|
if (window.toolRegistry && typeof window.toolRegistry.getAll === 'function') {
|
|
return window.toolRegistry.getAll().map(function(tool) { return tool.id; });
|
|
}
|
|
return [];
|
|
},
|
|
|
|
getRouteSubState(tabName) {
|
|
if (tabName === 'codes' && this.codesMode === 'decode') {
|
|
return 'decode';
|
|
}
|
|
return '';
|
|
},
|
|
|
|
applyRouteSubState(route) {
|
|
if (!route || !route.sub) {
|
|
return;
|
|
}
|
|
|
|
if (route.tab === 'codes' && (route.sub === 'generate' || route.sub === 'decode')) {
|
|
this.codesMode = route.sub;
|
|
}
|
|
},
|
|
|
|
applyRouteFromHash() {
|
|
if (!window.TabRouting) {
|
|
return;
|
|
}
|
|
|
|
var route = window.TabRouting.parse();
|
|
var validIds = this.getValidToolIds();
|
|
var defaultTab = 'transforms';
|
|
|
|
if (!route || !route.tab) {
|
|
return;
|
|
}
|
|
|
|
if (!validIds.includes(route.tab)) {
|
|
this.switchToTab(defaultTab, { fromRoute: true, updateUrl: true, replaceUrl: true });
|
|
return;
|
|
}
|
|
|
|
if (route.tab !== this.activeTab) {
|
|
this.switchToTab(route.tab, { fromRoute: true, updateUrl: false, route: route });
|
|
return;
|
|
}
|
|
|
|
this.applyRouteSubState(route);
|
|
},
|
|
|
|
switchToTab(tabName, options) {
|
|
options = options || {};
|
|
|
|
if (this.activeTab && window.toolRegistry) {
|
|
window.toolRegistry.deactivateTool(this.activeTab, this);
|
|
}
|
|
|
|
this.activeTab = tabName;
|
|
this.universalDecodeInput = '';
|
|
this.universalDecodeResult = null;
|
|
|
|
if (window.toolRegistry) {
|
|
window.toolRegistry.activateTool(tabName, this);
|
|
}
|
|
|
|
if (options.fromRoute && options.route) {
|
|
this.applyRouteSubState(options.route);
|
|
}
|
|
|
|
if (options.updateUrl !== false && !options.fromRoute && window.TabRouting) {
|
|
window.TabRouting.setHash(tabName, this.getRouteSubState(tabName), !!options.replaceUrl);
|
|
}
|
|
},
|
|
|
|
setTheme(themeId) {
|
|
if (!window.ThemeUtils) return;
|
|
this.activeTheme = window.ThemeUtils.applyTheme(themeId);
|
|
},
|
|
|
|
cycleTheme() {
|
|
if (!window.ThemeUtils) return;
|
|
this.activeTheme = window.ThemeUtils.cycleTheme(this.activeTheme);
|
|
},
|
|
|
|
toggleCopyHistory() {
|
|
this.switchUtilityPanel('history');
|
|
},
|
|
|
|
toggleGlitchTokenPanel() {
|
|
this.switchUtilityPanel('glitch');
|
|
},
|
|
|
|
toggleEndSequencePanel() {
|
|
this.switchUtilityPanel('endsequences');
|
|
},
|
|
|
|
markUtilityPanelInteraction() {
|
|
this.utilityPanelInteractionGuard = true;
|
|
var self = this;
|
|
setTimeout(function() {
|
|
self.utilityPanelInteractionGuard = false;
|
|
}, 400);
|
|
},
|
|
|
|
switchUtilityPanel(panelId) {
|
|
var isMobile = window.matchMedia && window.matchMedia('(max-width: 768px)').matches;
|
|
|
|
this.activeUtilityPanel = panelId;
|
|
this.showCopyHistory = panelId === 'history';
|
|
this.showGlitchTokenPanel = panelId === 'glitch';
|
|
this.showEndSequencePanel = panelId === 'endsequences';
|
|
this.showUnicodePanel = panelId === 'settings';
|
|
|
|
if (isMobile) {
|
|
this.mobileUtilityOpen = true;
|
|
this.markUtilityPanelInteraction();
|
|
}
|
|
|
|
if (panelId === 'glitch' && !this.glitchTokensLoaded) {
|
|
this.loadGlitchTokens();
|
|
}
|
|
|
|
if (panelId === 'history' && this.copyHistory.length > 0) {
|
|
this.$nextTick(function() {
|
|
var firstCopyButton = document.querySelector('.copy-again-button');
|
|
if (firstCopyButton) {
|
|
firstCopyButton.focus();
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
closeMobileUtility() {
|
|
if (this.utilityPanelInteractionGuard) {
|
|
return;
|
|
}
|
|
this.mobileUtilityOpen = false;
|
|
},
|
|
|
|
openMobileUtility() {
|
|
this.mobileUtilityOpen = true;
|
|
this.markUtilityPanelInteraction();
|
|
},
|
|
|
|
copyEndSequence(value) {
|
|
if (!value) return;
|
|
this.copyToClipboard(value);
|
|
this.showNotification('Copied', 'success', 'fas fa-copy');
|
|
},
|
|
|
|
async loadGlitchTokens() {
|
|
if (this.glitchTokensLoaded) return;
|
|
|
|
try {
|
|
if (window.loadGlitchTokens) {
|
|
await window.loadGlitchTokens();
|
|
}
|
|
|
|
if (window.getAllGlitchTokens) {
|
|
this.allGlitchTokens = window.getAllGlitchTokens();
|
|
this.filteredGlitchTokens = this.allGlitchTokens;
|
|
this.glitchTokensLoaded = true;
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading glitch tokens:', error);
|
|
this.showNotification('Failed to load glitch tokens', 'error', 'fas fa-exclamation-triangle');
|
|
}
|
|
},
|
|
|
|
filterGlitchTokens() {
|
|
let filtered = this.allGlitchTokens;
|
|
|
|
// Filter by behavior
|
|
if (this.glitchTokenBehavior) {
|
|
filtered = filtered.filter(token => token.behavior === this.glitchTokenBehavior);
|
|
}
|
|
|
|
// Filter by search
|
|
if (this.glitchTokenSearch) {
|
|
const searchLower = this.glitchTokenSearch.toLowerCase();
|
|
filtered = filtered.filter(token => {
|
|
const tokenText = (token.token || '').toLowerCase();
|
|
const origin = (token.origin || '').toLowerCase();
|
|
const observedOutput = (token.observed_output || '').toLowerCase();
|
|
const tokenId = String(token.token_id || '');
|
|
|
|
return tokenText.includes(searchLower) ||
|
|
origin.includes(searchLower) ||
|
|
observedOutput.includes(searchLower) ||
|
|
tokenId.includes(searchLower);
|
|
});
|
|
}
|
|
|
|
this.filteredGlitchTokens = filtered;
|
|
},
|
|
|
|
copyGlitchToken(tokenText) {
|
|
if (!tokenText) return;
|
|
|
|
this.copyToClipboard(tokenText);
|
|
this.showNotification('Glitch token copied!', 'success', 'fas fa-copy');
|
|
},
|
|
|
|
addToCopyHistory(source, content) {
|
|
window.HistoryUtils.addToHistory(
|
|
this.copyHistory,
|
|
this.maxHistoryItems,
|
|
source,
|
|
content
|
|
);
|
|
},
|
|
|
|
clearCopyHistory() {
|
|
window.HistoryUtils.clearHistory(this.copyHistory);
|
|
this.showNotification('History cleared', 'success', 'fas fa-check');
|
|
},
|
|
|
|
removeFromCopyHistory(id) {
|
|
window.HistoryUtils.removeFromHistory(this.copyHistory, id);
|
|
this.showNotification('Removed from history', 'success', 'fas fa-check');
|
|
},
|
|
|
|
formatHistoryTime(timestamp) {
|
|
if (!timestamp) return '';
|
|
const date = new Date(timestamp);
|
|
return date.toLocaleString();
|
|
},
|
|
|
|
async copyToClipboard(text, skipHistory = false) {
|
|
if (!text || !window.ClipboardUtils) return;
|
|
|
|
// Check if content already exists in history
|
|
const alreadyInHistory = this.copyHistory.some(item => item.content === text);
|
|
|
|
const source = window.HistoryUtils.getHistorySource(this.activeTab, {
|
|
activeTransform: this.activeTransform,
|
|
activeSteg: this.activeSteg,
|
|
selectedEmoji: this.selectedEmoji
|
|
});
|
|
|
|
const success = await window.ClipboardUtils.copy(text, {
|
|
onSuccess: () => {
|
|
// Only add to history if not skipping and not already in history
|
|
if (!skipHistory && !alreadyInHistory) {
|
|
this.addToCopyHistory(source, text);
|
|
}
|
|
window.FocusUtils.clearFocusAndSelection();
|
|
}
|
|
});
|
|
|
|
return success;
|
|
},
|
|
|
|
forceCopyToClipboard(text) {
|
|
if (!text || !window.ClipboardUtils) return;
|
|
|
|
if (this.isPasteOperation) {
|
|
this.isPasteOperation = false;
|
|
return;
|
|
}
|
|
|
|
if (!this.isTransformCopy && this.ignoreKeyboardEvents) {
|
|
return;
|
|
}
|
|
|
|
const source = window.HistoryUtils.getHistorySource(this.activeTab, {
|
|
activeTransform: this.activeTransform,
|
|
activeSteg: this.activeSteg,
|
|
selectedEmoji: this.selectedEmoji
|
|
});
|
|
|
|
window.ClipboardUtils.copy(text, {
|
|
onSuccess: () => {
|
|
this.addToCopyHistory(source, text);
|
|
if (this.isTransformCopy) {
|
|
this.showCopiedPopup();
|
|
this.ignoreKeyboardEvents = true;
|
|
clearTimeout(this.keyboardEventsTimeout);
|
|
this.keyboardEventsTimeout = setTimeout(() => {
|
|
this.ignoreKeyboardEvents = false;
|
|
}, window.CONFIG.KEYBOARD_EVENTS_TIMEOUT_MS);
|
|
}
|
|
this.isTransformCopy = false;
|
|
const inputBox = document.querySelector('#transform-input');
|
|
if (inputBox) {
|
|
window.FocusUtils.focusWithoutScroll(inputBox);
|
|
const len = inputBox.value.length;
|
|
try { inputBox.setSelectionRange(len, len); } catch (_) {}
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
showNotification(message, type = 'success', iconClass = null) {
|
|
window.NotificationUtils.showNotification(message, type, iconClass);
|
|
},
|
|
|
|
showCopiedPopup() {
|
|
window.NotificationUtils.showCopiedPopup();
|
|
},
|
|
|
|
getOpenRouterApiKey() {
|
|
var key = (this.openrouterApiKey || '').trim();
|
|
if (key) return key;
|
|
if (window.OpenRouterModels && window.OpenRouterModels.getApiKey) {
|
|
return window.OpenRouterModels.getApiKey();
|
|
}
|
|
return '';
|
|
},
|
|
|
|
formatOpenRouterModelLabel(model) {
|
|
if (window.OpenRouterModels && window.OpenRouterModels.formatLabel) {
|
|
return window.OpenRouterModels.formatLabel(model);
|
|
}
|
|
return model && model.name ? model.name : '';
|
|
},
|
|
|
|
isOpenRouterModelEnabled(modelId) {
|
|
if (!window.OpenRouterModels) return true;
|
|
return window.OpenRouterModels.isModelEnabled(modelId, this.openRouterModelsDisabled);
|
|
},
|
|
|
|
rebuildOpenRouterDropdown() {
|
|
if (!window.OpenRouterModels) return;
|
|
var pinned = window.OpenRouterModels.getPinnedModelIds(this);
|
|
this.openRouterModels = window.OpenRouterModels.filterForDropdown(
|
|
this.openRouterModelsCatalog,
|
|
this.openRouterModelsDisabled,
|
|
pinned
|
|
);
|
|
},
|
|
|
|
toggleOpenRouterModelEnabled(modelId) {
|
|
if (!modelId) return;
|
|
var disabled = this.openRouterModelsDisabled.slice();
|
|
var index = disabled.indexOf(modelId);
|
|
if (index === -1) {
|
|
disabled.push(modelId);
|
|
} else {
|
|
disabled.splice(index, 1);
|
|
}
|
|
this.openRouterModelsDisabled = disabled;
|
|
if (window.OpenRouterModels) {
|
|
window.OpenRouterModels.saveDisabledIds(disabled);
|
|
}
|
|
this.rebuildOpenRouterDropdown();
|
|
this.syncOpenRouterModelSelections();
|
|
},
|
|
|
|
enableAllOpenRouterModels() {
|
|
this.openRouterModelsDisabled = [];
|
|
if (window.OpenRouterModels) {
|
|
window.OpenRouterModels.saveDisabledIds([]);
|
|
}
|
|
this.rebuildOpenRouterDropdown();
|
|
this.syncOpenRouterModelSelections();
|
|
},
|
|
|
|
showFreeOpenRouterModelsOnly() {
|
|
var disabled = (this.openRouterModelsCatalog || [])
|
|
.filter(function(model) {
|
|
return model && model.id && !model.free && !model.virtual;
|
|
})
|
|
.map(function(model) { return model.id; });
|
|
this.openRouterModelsDisabled = disabled;
|
|
if (window.OpenRouterModels) {
|
|
window.OpenRouterModels.saveDisabledIds(disabled);
|
|
}
|
|
this.rebuildOpenRouterDropdown();
|
|
this.syncOpenRouterModelSelections();
|
|
},
|
|
|
|
syncOpenRouterModelSelections() {
|
|
if (!window.OpenRouterModels || !this.openRouterModels.length) return;
|
|
var models = this.openRouterModels;
|
|
var ensure = window.OpenRouterModels.ensureValidSelection.bind(window.OpenRouterModels);
|
|
|
|
if (typeof this.pcModel !== 'undefined') {
|
|
this.pcModel = ensure(this.pcModel, models, localStorage.getItem('pc-model') || 'openrouter/auto');
|
|
}
|
|
if (typeof this.acModel !== 'undefined') {
|
|
this.acModel = ensure(this.acModel, models, localStorage.getItem('ac-model') || 'openrouter/auto');
|
|
}
|
|
if (typeof this.saModel !== 'undefined') {
|
|
this.saModel = ensure(this.saModel, models, localStorage.getItem('sa-model') || 'openrouter/free');
|
|
}
|
|
if (typeof this.translateModel !== 'undefined') {
|
|
this.translateModel = ensure(this.translateModel, models, localStorage.getItem('translate-model') || 'google/gemma-3-27b-it');
|
|
}
|
|
},
|
|
|
|
refreshOpenRouterModels: async function(force) {
|
|
if (!window.OpenRouterModels) return;
|
|
if (this.openRouterModelsLoading) return;
|
|
|
|
this.openRouterModelsLoading = true;
|
|
this.openRouterModelsError = '';
|
|
|
|
var apiKey = this.getOpenRouterApiKey();
|
|
|
|
try {
|
|
var models = await window.OpenRouterModels.fetch(apiKey, { force: !!force });
|
|
this.openRouterModelsCatalog = models;
|
|
this.rebuildOpenRouterDropdown();
|
|
this.syncOpenRouterModelSelections();
|
|
|
|
if (apiKey) {
|
|
this.openRouterModelsKeyInfo = await window.OpenRouterModels.fetchKeyInfo(apiKey);
|
|
} else {
|
|
this.openRouterModelsKeyInfo = null;
|
|
}
|
|
} catch (err) {
|
|
console.warn('OpenRouter model fetch failed:', err);
|
|
var fallback = window.OpenRouterModels.getStaticFallback();
|
|
this.openRouterModelsCatalog = window.OpenRouterModels.mergeWithVirtual(fallback);
|
|
this.rebuildOpenRouterDropdown();
|
|
this.syncOpenRouterModelSelections();
|
|
|
|
if (err && err.status === 401) {
|
|
this.openRouterModelsError = 'Invalid API key — check Settings.';
|
|
} else {
|
|
this.openRouterModelsError = (err && err.message) || 'Could not load models; using offline list.';
|
|
}
|
|
} finally {
|
|
this.openRouterModelsLoading = false;
|
|
}
|
|
},
|
|
|
|
saveApiKey() {
|
|
var trimmed = (this.openrouterApiKey || '').trim();
|
|
if (trimmed) {
|
|
this.openrouterApiKey = trimmed;
|
|
localStorage.setItem('openrouter-api-key', trimmed);
|
|
this.apiKeySaved = true;
|
|
this.showNotification('API key saved', 'success');
|
|
setTimeout(() => { this.apiKeySaved = false; }, 2000);
|
|
this.refreshOpenRouterModels(true);
|
|
}
|
|
},
|
|
|
|
clearApiKey() {
|
|
this.openrouterApiKey = '';
|
|
this.showApiKey = false;
|
|
localStorage.removeItem('openrouter-api-key');
|
|
localStorage.removeItem('openrouter_api_key');
|
|
localStorage.removeItem('plinyos-api-key');
|
|
this.showNotification('API key cleared', 'success');
|
|
this.openRouterModelsKeyInfo = null;
|
|
this.refreshOpenRouterModels(true);
|
|
},
|
|
|
|
setupPasteHandlers() {
|
|
const textareas = document.querySelectorAll('textarea');
|
|
textareas.forEach(textarea => {
|
|
textarea.addEventListener('paste', (e) => {
|
|
this.isPasteOperation = true;
|
|
setTimeout(() => {
|
|
this.isPasteOperation = false;
|
|
}, window.CONFIG.PASTE_FLAG_RESET_DELAY_MS);
|
|
});
|
|
});
|
|
}
|
|
}),
|
|
mounted() {
|
|
if (window.ThemeUtils) {
|
|
this.activeTheme = window.ThemeUtils.applyTheme(window.ThemeUtils.initializeTheme());
|
|
this._themeKeyHandler = (event) => {
|
|
if (event.defaultPrevented || event.repeat) return;
|
|
if (event.key !== 'd' && event.key !== 'D') return;
|
|
var tag = event.target && event.target.tagName;
|
|
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || (event.target && event.target.isContentEditable)) {
|
|
return;
|
|
}
|
|
event.preventDefault();
|
|
this.cycleTheme();
|
|
};
|
|
document.addEventListener('keydown', this._themeKeyHandler);
|
|
}
|
|
|
|
if (window.toolRegistry && typeof window.toolRegistry.mergeVueLifecycle === 'function') {
|
|
const lifecycleHooks = window.toolRegistry.mergeVueLifecycle();
|
|
if (lifecycleHooks && lifecycleHooks.mounted) {
|
|
lifecycleHooks.mounted.call(this);
|
|
}
|
|
}
|
|
|
|
if (window.toolRegistry && typeof window.toolRegistry.getAll === 'function') {
|
|
this.registeredTools = window.toolRegistry.getAll();
|
|
}
|
|
|
|
this.refreshOpenRouterModels(false);
|
|
|
|
var initialRoute = window.TabRouting && window.TabRouting.parse();
|
|
if (initialRoute && initialRoute.tab && this.getValidToolIds().includes(initialRoute.tab)) {
|
|
this.applyRouteFromHash();
|
|
} else if (window.toolRegistry) {
|
|
window.toolRegistry.activateTool(this.activeTab, this);
|
|
}
|
|
|
|
this._onHashChange = () => {
|
|
if (window.TabRouting && window.TabRouting.shouldIgnoreHashChange()) {
|
|
return;
|
|
}
|
|
this.applyRouteFromHash();
|
|
};
|
|
window.addEventListener('hashchange', this._onHashChange);
|
|
|
|
document.addEventListener('click', (e) => {
|
|
if (e.target.closest('.custom-tooltip')) {
|
|
return;
|
|
}
|
|
|
|
const tooltipIcon = e.target.closest('.tooltip-icon');
|
|
|
|
if (tooltipIcon) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
const tooltipText = tooltipIcon.getAttribute('data-tooltip');
|
|
if (!tooltipText) return;
|
|
|
|
const existingTooltip = document.querySelector('.custom-tooltip.active');
|
|
if (existingTooltip && existingTooltip.textContent === tooltipText) {
|
|
existingTooltip.classList.remove('active');
|
|
setTimeout(() => {
|
|
if (!existingTooltip.classList.contains('active')) {
|
|
existingTooltip.remove();
|
|
}
|
|
}, 200);
|
|
return;
|
|
}
|
|
|
|
document.querySelectorAll('.custom-tooltip.active').forEach(tooltip => {
|
|
tooltip.classList.remove('active');
|
|
setTimeout(() => {
|
|
if (!tooltip.classList.contains('active')) {
|
|
tooltip.remove();
|
|
}
|
|
}, 200);
|
|
});
|
|
|
|
setTimeout(() => {
|
|
const tooltip = document.createElement('div');
|
|
tooltip.className = 'custom-tooltip active';
|
|
tooltip.textContent = tooltipText;
|
|
|
|
document.body.appendChild(tooltip);
|
|
|
|
const rect = tooltipIcon.getBoundingClientRect();
|
|
tooltip.style.left = (rect.left + rect.width / 2) + 'px';
|
|
tooltip.style.top = (rect.top - tooltip.offsetHeight - 8) + 'px';
|
|
tooltip.style.transform = 'translateX(-50%)';
|
|
}, 10);
|
|
|
|
return;
|
|
}
|
|
|
|
if (e.target.closest('#unicode-options-panel')) {
|
|
return;
|
|
}
|
|
|
|
document.querySelectorAll('.custom-tooltip.active').forEach(tooltip => {
|
|
tooltip.classList.remove('active');
|
|
setTimeout(() => {
|
|
if (!tooltip.classList.contains('active')) {
|
|
tooltip.remove();
|
|
}
|
|
}, 200);
|
|
});
|
|
});
|
|
|
|
this.$nextTick(() => {
|
|
const initializeEmojiGrid = () => {
|
|
if (this.activeTab !== 'steganography') {
|
|
return;
|
|
}
|
|
|
|
const emojiGridContainer = document.getElementById('emoji-grid-container');
|
|
|
|
if (emojiGridContainer) {
|
|
emojiGridContainer.setAttribute('style', 'display: block !important; visibility: visible !important; min-height: 300px; padding: 10px;');
|
|
|
|
const emojiLibrary = document.querySelector('.emoji-library');
|
|
if (emojiLibrary) {
|
|
emojiLibrary.setAttribute('style', 'display: block !important; visibility: visible !important; margin-top: 20px; overflow: visible;');
|
|
}
|
|
|
|
this.renderEmojiGrid();
|
|
clearInterval(emojiGridInitializer);
|
|
}
|
|
};
|
|
|
|
const emojiGridInitializer = setInterval(initializeEmojiGrid, window.CONFIG.EMOJI_GRID_INIT_INTERVAL_MS);
|
|
this._emojiGridInitializer = emojiGridInitializer;
|
|
this.setupPasteHandlers();
|
|
});
|
|
},
|
|
|
|
created() {
|
|
if (window.toolRegistry && typeof window.toolRegistry.mergeVueLifecycle === 'function') {
|
|
const lifecycleHooks = window.toolRegistry.mergeVueLifecycle();
|
|
if (lifecycleHooks && lifecycleHooks.created) {
|
|
lifecycleHooks.created.call(this);
|
|
}
|
|
}
|
|
},
|
|
|
|
beforeDestroy() {
|
|
if (this._themeKeyHandler) {
|
|
document.removeEventListener('keydown', this._themeKeyHandler);
|
|
this._themeKeyHandler = null;
|
|
}
|
|
|
|
if (this._onHashChange) {
|
|
window.removeEventListener('hashchange', this._onHashChange);
|
|
this._onHashChange = null;
|
|
}
|
|
|
|
if (this._emojiGridInitializer) {
|
|
clearInterval(this._emojiGridInitializer);
|
|
this._emojiGridInitializer = null;
|
|
}
|
|
|
|
if (window.toolRegistry && typeof window.toolRegistry.mergeVueLifecycle === 'function') {
|
|
const lifecycleHooks = window.toolRegistry.mergeVueLifecycle();
|
|
if (lifecycleHooks && lifecycleHooks.beforeDestroy) {
|
|
lifecycleHooks.beforeDestroy.call(this);
|
|
}
|
|
}
|
|
},
|
|
|
|
watch: (window.toolRegistry && typeof window.toolRegistry.mergeVueWatchers === 'function')
|
|
? window.toolRegistry.mergeVueWatchers()
|
|
: {}
|
|
});
|