@nac3/forge-cli 1.0.55 → 1.0.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chat/flow_bpmn.d.ts +54 -0
- package/dist/chat/flow_bpmn.d.ts.map +1 -0
- package/dist/chat/flow_bpmn.js +112 -0
- package/dist/chat/flow_bpmn.js.map +1 -0
- package/dist/chat/panel.d.ts.map +1 -1
- package/dist/chat/panel.js +524 -383
- package/dist/chat/panel.js.map +1 -1
- package/dist/chat/server.d.ts.map +1 -1
- package/dist/chat/server.js +16 -1
- package/dist/chat/server.js.map +1 -1
- package/dist/chat/tools/pizarron.d.ts.map +1 -1
- package/dist/chat/tools/pizarron.js +13 -2
- package/dist/chat/tools/pizarron.js.map +1 -1
- package/dist/docs/doctrine/pizarron.md +12 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/doctrine/pizarron.md +12 -0
- package/package.json +115 -115
- package/src/i18n/catalogs/ar.json +292 -1
- package/src/i18n/catalogs/de.json +292 -1
- package/src/i18n/catalogs/en.json +292 -1
- package/src/i18n/catalogs/es.json +292 -1
- package/src/i18n/catalogs/fr.json +292 -1
- package/src/i18n/catalogs/hi.json +292 -1
- package/src/i18n/catalogs/it.json +292 -1
- package/src/i18n/catalogs/ja.json +292 -1
- package/src/i18n/catalogs/pt.json +292 -1
- package/src/i18n/catalogs/zh.json +292 -1
package/dist/chat/panel.js
CHANGED
|
@@ -121,6 +121,176 @@ export function renderPanelHtml(cfg) {
|
|
|
121
121
|
wizardDoneKanji: tForLang(lang, 'wizard.done.kanji'),
|
|
122
122
|
wizardDoneTitle: tForLang(lang, 'wizard.done.title'),
|
|
123
123
|
wizardDoneMsg: tForLang(lang, 'wizard.done.msg'),
|
|
124
|
+
/* Batch 2 -- runtime status / error toasts + confirm/prompt
|
|
125
|
+
* dialogs + voice (STT/TTS) messages from the client JS. */
|
|
126
|
+
panelStatusProjectSwitchFail: tForLang(lang, 'panel.status.projectSwitchFail'),
|
|
127
|
+
panelStatusProjectRemoveFail: tForLang(lang, 'panel.status.projectRemoveFail'),
|
|
128
|
+
panelStatusProjectAddFail: tForLang(lang, 'panel.status.projectAddFail'),
|
|
129
|
+
panelStatusCancelFail: tForLang(lang, 'panel.status.cancelFail'),
|
|
130
|
+
panelStatusCancelNetError: tForLang(lang, 'panel.status.cancelNetError'),
|
|
131
|
+
panelStatusNotEditable: tForLang(lang, 'panel.status.notEditable'),
|
|
132
|
+
panelStatusSavedAsPicker: tForLang(lang, 'panel.status.savedAsPicker'),
|
|
133
|
+
panelStatusSaveAsFail: tForLang(lang, 'panel.status.saveAsFail'),
|
|
134
|
+
panelStatusSaveFail: tForLang(lang, 'panel.status.saveFail'),
|
|
135
|
+
panelStatusSavedAs: tForLang(lang, 'panel.status.savedAs'),
|
|
136
|
+
panelStatusNetError: tForLang(lang, 'panel.status.netError'),
|
|
137
|
+
panelStatusSavedTabOnly: tForLang(lang, 'panel.status.savedTabOnly'),
|
|
138
|
+
panelStatusSaved: tForLang(lang, 'panel.status.saved'),
|
|
139
|
+
panelStatusCopied: tForLang(lang, 'panel.status.copied'),
|
|
140
|
+
panelStatusCopyFail: tForLang(lang, 'panel.status.copyFail'),
|
|
141
|
+
panelStatusModeAvailable: tForLang(lang, 'panel.status.modeAvailable'),
|
|
142
|
+
panelStatusModeChangeFail: tForLang(lang, 'panel.status.modeChangeFail'),
|
|
143
|
+
panelStatusModeChanged: tForLang(lang, 'panel.status.modeChanged'),
|
|
144
|
+
panelStatusGenericError: tForLang(lang, 'panel.status.genericError'),
|
|
145
|
+
panelStatusBrainTierAuto: tForLang(lang, 'panel.status.brainTierAuto'),
|
|
146
|
+
panelStatusBrainTierForced: tForLang(lang, 'panel.status.brainTierForced'),
|
|
147
|
+
panelStatusReading: tForLang(lang, 'panel.status.reading'),
|
|
148
|
+
panelStatusIngestFailed: tForLang(lang, 'panel.status.ingest_failed'),
|
|
149
|
+
panelStatusReadFailed: tForLang(lang, 'panel.status.read_failed'),
|
|
150
|
+
panelStatusAudioPlayError: tForLang(lang, 'panel.status.audio_play_error'),
|
|
151
|
+
panelStatusImageTooLarge: tForLang(lang, 'panel.status.imageTooLarge'),
|
|
152
|
+
panelStatusImageReady: tForLang(lang, 'panel.status.imageReady'),
|
|
153
|
+
panelStatusImageReadFail: tForLang(lang, 'panel.status.imageReadFail'),
|
|
154
|
+
panelConfirmTabUnsaved: tForLang(lang, 'panel.confirm.tabUnsaved'),
|
|
155
|
+
panelConfirmTabsUnsaved: tForLang(lang, 'panel.confirm.tabsUnsaved'),
|
|
156
|
+
panelConfirmDiscardChanges: tForLang(lang, 'panel.confirm.discardChanges'),
|
|
157
|
+
panelConfirmRemoveSlot: tForLang(lang, 'panel.confirm.removeSlot'),
|
|
158
|
+
panelConfirmRemoveKey: tForLang(lang, 'panel.confirm.removeKey'),
|
|
159
|
+
panelPromptSaveAs: tForLang(lang, 'panel.prompt.saveAs'),
|
|
160
|
+
panelVoiceSttFallback: tForLang(lang, 'panel.voice.sttFallback'),
|
|
161
|
+
panelVoiceNoMicSupport: tForLang(lang, 'panel.voice.noMicSupport'),
|
|
162
|
+
panelVoiceRecordingCountdown: tForLang(lang, 'panel.voice.recordingCountdown'),
|
|
163
|
+
panelVoiceAutoStopped: tForLang(lang, 'panel.voice.autoStopped'),
|
|
164
|
+
panelVoiceNoAudio: tForLang(lang, 'panel.voice.noAudio'),
|
|
165
|
+
panelVoiceAudioTooLong: tForLang(lang, 'panel.voice.audioTooLong'),
|
|
166
|
+
panelVoiceDidNotCatch: tForLang(lang, 'panel.status.did_not_catch'),
|
|
167
|
+
panelVoiceMicDenied: tForLang(lang, 'panel.status.mic_denied'),
|
|
168
|
+
panelVoiceTtsUnavailable: tForLang(lang, 'panel.voice.ttsUnavailable'),
|
|
169
|
+
panelVoiceMicClosedHalfDuplex: tForLang(lang, 'panel.voice.micClosedHalfDuplex'),
|
|
170
|
+
panelVoiceMicBlocked: tForLang(lang, 'panel.voice.micBlocked'),
|
|
171
|
+
panelVoiceNoMicDevice: tForLang(lang, 'panel.voice.noMicDevice'),
|
|
172
|
+
panelVoiceMicOpenFail: tForLang(lang, 'panel.voice.micOpenFail'),
|
|
173
|
+
panelVoiceMicOpenListening: tForLang(lang, 'panel.voice.micOpenListening'),
|
|
174
|
+
panelVoiceListening: tForLang(lang, 'panel.voice.listening'),
|
|
175
|
+
panelVoiceListeningInterim: tForLang(lang, 'panel.voice.listeningInterim'),
|
|
176
|
+
panelVoiceMicBlockedBrowser: tForLang(lang, 'panel.voice.micBlockedBrowser'),
|
|
177
|
+
panelVoiceNoMicPhysical: tForLang(lang, 'panel.voice.noMicPhysical'),
|
|
178
|
+
panelVoiceNetworkRequired: tForLang(lang, 'panel.voice.networkRequired'),
|
|
179
|
+
panelVoiceLangNotSupported: tForLang(lang, 'panel.voice.langNotSupported'),
|
|
180
|
+
panelVoiceMicError: tForLang(lang, 'panel.voice.micError'),
|
|
181
|
+
panelVoiceNoAudioToRecognizer: tForLang(lang, 'panel.voice.noAudioToRecognizer'),
|
|
182
|
+
panelVoiceMicOpenNoVoice: tForLang(lang, 'panel.voice.micOpenNoVoice'),
|
|
183
|
+
panelVoiceVoiceNoText: tForLang(lang, 'panel.voice.voiceNoText'),
|
|
184
|
+
panelVoiceStartingDictation: tForLang(lang, 'panel.voice.startingDictation'),
|
|
185
|
+
panelVoiceCouldNotStartMic: tForLang(lang, 'panel.voice.couldNotStartMic'),
|
|
186
|
+
/* Batch 3 -- tooltips/hints/runtime literals from the client JS. */
|
|
187
|
+
tipRefreshDiscovery: tForLang(lang, 'panel.tip.refreshDiscovery'),
|
|
188
|
+
tipOpenInEditor: tForLang(lang, 'panel.tip.openInEditor'),
|
|
189
|
+
tipOpen: tForLang(lang, 'panel.tip.open'),
|
|
190
|
+
tipSaveAs: tForLang(lang, 'panel.tip.saveAs'),
|
|
191
|
+
tipCodeModeDetail: tForLang(lang, 'panel.tip.codeModeDetail'),
|
|
192
|
+
tipCodeModeZen: tForLang(lang, 'panel.tip.codeModeZen'),
|
|
193
|
+
tipCancelShell: tForLang(lang, 'panel.tip.cancelShell'),
|
|
194
|
+
tipCloseTab: tForLang(lang, 'panel.tip.closeTab'),
|
|
195
|
+
tipReopenNativeTabs: tForLang(lang, 'panel.tip.reopenNativeTabs'),
|
|
196
|
+
tipNotTestedYet: tForLang(lang, 'panel.tip.notTestedYet'),
|
|
197
|
+
ariaThinking: tForLang(lang, 'panel.aria.thinking'),
|
|
198
|
+
hintBoardEmpty: tForLang(lang, 'panel.hint.boardEmpty'),
|
|
199
|
+
hintBookmarksEmpty: tForLang(lang, 'panel.hint.bookmarksEmpty'),
|
|
200
|
+
panelVoiceReplyFailed: tForLang(lang, 'panel.voice.replyFailed'),
|
|
201
|
+
panelVoiceReplyError: tForLang(lang, 'panel.voice.replyError'),
|
|
202
|
+
panelStatusLanguageSwitchFailed: tForLang(lang, 'panel.status.languageSwitchFailed'),
|
|
203
|
+
panelStatusLanguageListFailed: tForLang(lang, 'panel.status.languageListFailed'),
|
|
204
|
+
legalLoading: tForLang(lang, 'panel.legal.loading'),
|
|
205
|
+
legalLoadError: tForLang(lang, 'panel.legal.loadError'),
|
|
206
|
+
legalNetworkError: tForLang(lang, 'panel.legal.networkError'),
|
|
207
|
+
legalDefaultTitle: tForLang(lang, 'panel.legal.defaultTitle'),
|
|
208
|
+
fbCopied: tForLang(lang, 'panel.feedback.copied'),
|
|
209
|
+
fbCopyFailed: tForLang(lang, 'panel.feedback.copyFailed'),
|
|
210
|
+
fbClipboardUnavailable: tForLang(lang, 'panel.feedback.clipboardUnavailable'),
|
|
211
|
+
fbSaving: tForLang(lang, 'panel.feedback.saving'),
|
|
212
|
+
fbSaved: tForLang(lang, 'panel.feedback.saved'),
|
|
213
|
+
fbSaveError: tForLang(lang, 'panel.feedback.saveError'),
|
|
214
|
+
fbNetError: tForLang(lang, 'panel.feedback.netError'),
|
|
215
|
+
fbNetErrorSaving: tForLang(lang, 'panel.feedback.netErrorSaving'),
|
|
216
|
+
fbErrorPrefix: tForLang(lang, 'panel.feedback.errorPrefix'),
|
|
217
|
+
pairGenerating: tForLang(lang, 'panel.pair.generating'),
|
|
218
|
+
pairGenerateQr: tForLang(lang, 'panel.pair.generateQr'),
|
|
219
|
+
pairCopy: tForLang(lang, 'panel.pair.copy'),
|
|
220
|
+
pairExpiry: tForLang(lang, 'panel.pair.expiry'),
|
|
221
|
+
pairQrNetError: tForLang(lang, 'panel.pair.qrNetError'),
|
|
222
|
+
auditCopyJson: tForLang(lang, 'panel.audit.copyJson'),
|
|
223
|
+
limitsResetApplied: tForLang(lang, 'panel.limits.resetApplied'),
|
|
224
|
+
nac3Effects: tForLang(lang, 'panel.nac3.effects'),
|
|
225
|
+
nac3EffectsUnknown: tForLang(lang, 'panel.nac3.effectsUnknown'),
|
|
226
|
+
nac3Cancel: tForLang(lang, 'panel.nac3.cancel'),
|
|
227
|
+
nac3Reject: tForLang(lang, 'panel.nac3.reject'),
|
|
228
|
+
nac3Approve: tForLang(lang, 'panel.nac3.approve'),
|
|
229
|
+
nac3ApproveDestructive: tForLang(lang, 'panel.nac3.approveDestructive'),
|
|
230
|
+
nac3Executing: tForLang(lang, 'panel.nac3.executing'),
|
|
231
|
+
ttsProviderBrowser: tForLang(lang, 'panel.tts.providerBrowser'),
|
|
232
|
+
ttsProviderGoogle: tForLang(lang, 'panel.tts.providerGoogle'),
|
|
233
|
+
ttsProviderTitle: tForLang(lang, 'panel.tts.providerTitle'),
|
|
234
|
+
sttProviderBrowser: tForLang(lang, 'panel.stt.providerBrowser'),
|
|
235
|
+
sttProviderGoogle: tForLang(lang, 'panel.stt.providerGoogle'),
|
|
236
|
+
sttProviderWhisper: tForLang(lang, 'panel.stt.providerWhisper'),
|
|
237
|
+
sttProviderTitle: tForLang(lang, 'panel.stt.providerTitle'),
|
|
238
|
+
ttsVoiceTitle: tForLang(lang, 'panel.tts.voiceTitle'),
|
|
239
|
+
licLendNeedsLicense: tForLang(lang, 'panel.license.lendNeedsLicense'),
|
|
240
|
+
licEmailInvalid: tForLang(lang, 'panel.license.emailInvalid'),
|
|
241
|
+
licConnecting: tForLang(lang, 'panel.license.connecting'),
|
|
242
|
+
licConnect: tForLang(lang, 'settings.license.connect'),
|
|
243
|
+
licSavingEmail: tForLang(lang, 'panel.license.savingEmail'),
|
|
244
|
+
licSyncFailedRetry: tForLang(lang, 'panel.license.syncFailedRetry'),
|
|
245
|
+
licPlanOk: tForLang(lang, 'panel.license.planOk'),
|
|
246
|
+
licNetError: tForLang(lang, 'panel.license.netError'),
|
|
247
|
+
licSyncing: tForLang(lang, 'panel.license.syncing'),
|
|
248
|
+
licQuerying: tForLang(lang, 'panel.license.querying'),
|
|
249
|
+
licSyncError: tForLang(lang, 'panel.license.syncError'),
|
|
250
|
+
licPlanUnchanged: tForLang(lang, 'panel.license.planUnchanged'),
|
|
251
|
+
licPlanUpdated: tForLang(lang, 'panel.license.planUpdated'),
|
|
252
|
+
licSyncWithPolar: tForLang(lang, 'panel.license.syncWithPolar'),
|
|
253
|
+
licErrorWithCode: tForLang(lang, 'panel.license.errorWithCode'),
|
|
254
|
+
keyEditorTooShort: tForLang(lang, 'panel.keyEditor.keyTooShort'),
|
|
255
|
+
bridgeCoordUnavailable: tForLang(lang, 'panel.bridge.coordUnavailable'),
|
|
256
|
+
bridgeStatusUnavailable: tForLang(lang, 'panel.bridge.statusUnavailable'),
|
|
257
|
+
mcpLoadingBridges: tForLang(lang, 'panel.mcp.loadingBridges'),
|
|
258
|
+
toolsLoading: tForLang(lang, 'panel.tools.loading'),
|
|
259
|
+
agentsChatBackPairs: tForLang(lang, 'panel.agentsChat.backPairs'),
|
|
260
|
+
agentsChatNoMessages: tForLang(lang, 'panel.agentsChat.noMessages'),
|
|
261
|
+
agentsChatNoAgents: tForLang(lang, 'panel.agentsChat.noAgents'),
|
|
262
|
+
agentsChatPickAgent: tForLang(lang, 'panel.agentsChat.pickAgent'),
|
|
263
|
+
agentsChatSending: tForLang(lang, 'panel.agentsChat.sending'),
|
|
264
|
+
agentsChatErrorGeneric: tForLang(lang, 'panel.agentsChat.errorGeneric'),
|
|
265
|
+
alertNoEditor: tForLang(lang, 'panel.alert.noEditor'),
|
|
266
|
+
alertPathRelative: tForLang(lang, 'panel.alert.pathRelative'),
|
|
267
|
+
saveCopy: tForLang(lang, 'pizarron.button.copy'),
|
|
268
|
+
filesLoadingGeneric: tForLang(lang, 'panel.files.loading'),
|
|
269
|
+
tipSaveTo: tForLang(lang, 'panel.tip.saveTo'),
|
|
270
|
+
tipSaveToMemory: tForLang(lang, 'panel.tip.saveToMemory'),
|
|
271
|
+
btnSave: tForLang(lang, 'vault.add.button_save'),
|
|
272
|
+
btnSaveAs: tForLang(lang, 'panel.button.saveAs'),
|
|
273
|
+
fbSavedShort: tForLang(lang, 'panel.feedback.savedShort'),
|
|
274
|
+
pairQrFailed: tForLang(lang, 'panel.pair.qrFailed'),
|
|
275
|
+
brainModePlan: tForLang(lang, 'panel.brain.modePlan'),
|
|
276
|
+
brainModeConsole: tForLang(lang, 'panel.brain.modeConsole'),
|
|
277
|
+
brainApplying: tForLang(lang, 'panel.brain.applying'),
|
|
278
|
+
brainApplied: tForLang(lang, 'panel.brain.applied'),
|
|
279
|
+
brainReopenHint: tForLang(lang, 'panel.brain.reopenHint'),
|
|
280
|
+
brainActive: tForLang(lang, 'panel.brain.active'),
|
|
281
|
+
bridgeRoleHost: tForLang(lang, 'panel.bridge.roleHost'),
|
|
282
|
+
bridgeRoleClient: tForLang(lang, 'panel.bridge.roleClient'),
|
|
283
|
+
bridgeInstanceOne: tForLang(lang, 'panel.bridge.instanceOne'),
|
|
284
|
+
bridgeInstanceMany: tForLang(lang, 'panel.bridge.instanceMany'),
|
|
285
|
+
bridgeYou: tForLang(lang, 'panel.bridge.you'),
|
|
286
|
+
tipDeleteTask: tForLang(lang, 'panel.tip.deleteTask'),
|
|
287
|
+
tasksCmdPlaceholder: tForLang(lang, 'panel.tasks.cmdPlaceholder'),
|
|
288
|
+
readAloudCode: tForLang(lang, 'panel.readAloud.code'),
|
|
289
|
+
readAloudCodeLang: tForLang(lang, 'panel.readAloud.codeLang'),
|
|
290
|
+
readAloudFile: tForLang(lang, 'panel.readAloud.file'),
|
|
291
|
+
readAloudContent: tForLang(lang, 'panel.readAloud.content'),
|
|
292
|
+
tabOne: tForLang(lang, 'panel.pizarron.tabOne'),
|
|
293
|
+
tabMany: tForLang(lang, 'panel.pizarron.tabMany'),
|
|
124
294
|
};
|
|
125
295
|
const runtimeConfig = JSON.stringify({
|
|
126
296
|
projectName: cfg.projectName,
|
|
@@ -1907,7 +2077,7 @@ ${renderYujinSplash()}
|
|
|
1907
2077
|
<div id="yf-whats-new-title" class="whats-new-title">Novedades en esta version</div>
|
|
1908
2078
|
<div id="yf-whats-new-sub" class="whats-new-sub"></div>
|
|
1909
2079
|
</div>
|
|
1910
|
-
<button type="button" id="yf-whats-new-dismiss" class="whats-new-close" aria-label="
|
|
2080
|
+
<button type="button" id="yf-whats-new-dismiss" class="whats-new-close" aria-label="${escapeHtml(tForLang(lang, 'panel.whatsNew.dismiss'))}">x</button>
|
|
1911
2081
|
</div>
|
|
1912
2082
|
<div id="yf-whats-new-body" class="whats-new-body"></div>
|
|
1913
2083
|
</div>
|
|
@@ -1935,7 +2105,7 @@ ${renderYujinSplash()}
|
|
|
1935
2105
|
<form id="bar-mini" data-nac-id="yujin.chat.bar-mini" class="bar" onsubmit="return false">
|
|
1936
2106
|
<button type="button" class="mic" data-mic data-mic-btn aria-label="${escapeHtml(tr.micButton)}" title="${escapeHtml(tr.micButton)}">🎙</button>
|
|
1937
2107
|
<button type="button" class="tts-toggle" data-tts-toggle aria-label="${escapeHtml(tr.ttsButton)}" title="${escapeHtml(tr.ttsButton)}">🔊</button>
|
|
1938
|
-
<button type="button" class="clip" data-attach-clip aria-label="
|
|
2108
|
+
<button type="button" class="clip" data-attach-clip aria-label="${escapeHtml(tForLang(lang, 'panel.tip.attachFile'))}" title="${escapeHtml(tForLang(lang, 'panel.tip.attachFile'))}">📎</button>
|
|
1939
2109
|
<textarea class="composer-input" rows="1" placeholder="${escapeHtml(tr.inputPlaceholder)}" aria-label="${escapeHtml(tr.inputPlaceholder)}"></textarea>
|
|
1940
2110
|
<button type="submit" class="send">${escapeHtml(tr.sendButton)}</button>
|
|
1941
2111
|
<button type="button" class="stop" data-stop aria-label="${escapeHtml(tr.stopButton)}" title="${escapeHtml(tr.stopButton)}" style="display:none">■ ${escapeHtml(tr.stopButton)}</button>
|
|
@@ -2048,9 +2218,9 @@ ${renderYujinSplash()}
|
|
|
2048
2218
|
<button data-mcp-close aria-label="${escapeHtml(tr.closeButton)}" title="${escapeHtml(tr.closeButton)}">${escapeHtml(tr.closeButton)}</button>
|
|
2049
2219
|
</div>
|
|
2050
2220
|
<div class="mcp-body">
|
|
2051
|
-
<button class="mcp-refresh" data-mcp-refresh title="
|
|
2221
|
+
<button class="mcp-refresh" data-mcp-refresh title="${escapeHtml(tForLang(lang, 'panel.tip.refreshDiscovery'))}">${escapeHtml(tForLang(lang, 'pizarron.graph.refresh'))}</button>
|
|
2052
2222
|
<div data-mcp-content>
|
|
2053
|
-
<div class="mcp-empty"
|
|
2223
|
+
<div class="mcp-empty">${escapeHtml(tForLang(lang, 'panel.legal.loading'))}</div>
|
|
2054
2224
|
</div>
|
|
2055
2225
|
</div>
|
|
2056
2226
|
</div>
|
|
@@ -2066,24 +2236,24 @@ ${renderYujinSplash()}
|
|
|
2066
2236
|
<p class="act-subtitle">Feed timestamped + bridge inspector + chat-per-agent</p>
|
|
2067
2237
|
</div>
|
|
2068
2238
|
<div style="display:flex;align-items:center;gap:10px;">
|
|
2069
|
-
<select data-act-project-filter aria-label="
|
|
2239
|
+
<select data-act-project-filter aria-label="${escapeHtml(tForLang(lang, 'panel.tip.filterByProject'))}"
|
|
2070
2240
|
style="background:var(--bg-2,#232631);color:var(--ink,#d4d4d4);border:1px solid var(--ink-subtle,#5a5e6a);border-radius:4px;padding:4px 8px;font-family:inherit;font-size:12px;">
|
|
2071
|
-
<option value=""
|
|
2241
|
+
<option value="">${escapeHtml(tForLang(lang, 'panel.activity.allProjects'))}</option>
|
|
2072
2242
|
</select>
|
|
2073
|
-
<span data-act-concurrency aria-label="
|
|
2074
|
-
title="
|
|
2243
|
+
<span data-act-concurrency aria-label="${escapeHtml(tForLang(lang, 'panel.aria.concurrentRuns'))}"
|
|
2244
|
+
title="${escapeHtml(tForLang(lang, 'panel.tip.concurrentRuns'))}"
|
|
2075
2245
|
style="font-family:ui-monospace,Menlo,Consolas,monospace;font-size:11px;color:var(--ink-muted,#8a8a8a);white-space:nowrap;">-/-</span>
|
|
2076
2246
|
<button data-activity-close aria-label="${escapeHtml(tr.closeButton)}" title="${escapeHtml(tr.closeButton)}">${escapeHtml(tr.closeButton)}</button>
|
|
2077
2247
|
</div>
|
|
2078
2248
|
</div>
|
|
2079
2249
|
<div class="act-body">
|
|
2080
2250
|
<div data-act-panel="feed">
|
|
2081
|
-
<div class="act-empty"
|
|
2251
|
+
<div class="act-empty">${escapeHtml(tForLang(lang, 'panel.activity.loadingFeed'))}</div>
|
|
2082
2252
|
</div>
|
|
2083
2253
|
</div>
|
|
2084
2254
|
<div class="act-controls">
|
|
2085
2255
|
<label><input type="checkbox" data-act-autorefresh checked /> auto-refresh 3s</label>
|
|
2086
|
-
<button data-act-refresh
|
|
2256
|
+
<button data-act-refresh>${escapeHtml(tForLang(lang, 'panel.tip.refreshNow'))}</button>
|
|
2087
2257
|
<span data-act-last-update style="margin-left:auto;font-family:ui-monospace,Menlo,Consolas,monospace;"></span>
|
|
2088
2258
|
</div>
|
|
2089
2259
|
</div>
|
|
@@ -2110,49 +2280,49 @@ ${renderYujinSplash()}
|
|
|
2110
2280
|
data-nac-role="action"
|
|
2111
2281
|
data-nac-action="open_mcp"
|
|
2112
2282
|
aria-label="MCP"
|
|
2113
|
-
title="
|
|
2283
|
+
title="${escapeHtml(tForLang(lang, 'panel.tip.mcpBridges'))}">MCP</button>
|
|
2114
2284
|
<button class="agents-btn"
|
|
2115
2285
|
data-activity-open
|
|
2116
2286
|
data-nac-id="yujin.panel.activity"
|
|
2117
2287
|
data-nac-role="action"
|
|
2118
2288
|
data-nac-action="open_activity"
|
|
2119
|
-
aria-label="
|
|
2120
|
-
title="
|
|
2289
|
+
aria-label="${escapeHtml(tForLang(lang, 'panel.aria.activity'))}"
|
|
2290
|
+
title="${escapeHtml(tForLang(lang, 'panel.tip.activity'))}">Activity</button>
|
|
2121
2291
|
<button class="settings-btn"
|
|
2122
2292
|
data-settings-toggle
|
|
2123
2293
|
data-nac-id="yujin.panel.settings"
|
|
2124
2294
|
data-nac-role="action"
|
|
2125
2295
|
data-nac-action="open_settings"
|
|
2126
|
-
aria-label="
|
|
2127
|
-
title="
|
|
2296
|
+
aria-label="${escapeHtml(tForLang(lang, 'panel.aria.settings'))}"
|
|
2297
|
+
title="${escapeHtml(tForLang(lang, 'panel.tip.settings'))}">⚙</button>
|
|
2128
2298
|
<button id="full-zoom-out"
|
|
2129
2299
|
data-zoom-step="-1"
|
|
2130
2300
|
data-nac-id="yujin.panel.zoom-out"
|
|
2131
2301
|
data-nac-role="action"
|
|
2132
2302
|
data-nac-action="zoom_out"
|
|
2133
|
-
aria-label="
|
|
2134
|
-
title="
|
|
2303
|
+
aria-label="${escapeHtml(tForLang(lang, 'panel.aria.zoomOut'))}"
|
|
2304
|
+
title="${escapeHtml(tForLang(lang, 'panel.tip.zoomOut'))}">−</button>
|
|
2135
2305
|
<button id="full-zoom-reset"
|
|
2136
2306
|
data-zoom-reset
|
|
2137
2307
|
data-nac-id="yujin.panel.zoom-reset"
|
|
2138
2308
|
data-nac-role="action"
|
|
2139
2309
|
data-nac-action="zoom_reset"
|
|
2140
2310
|
aria-label="Zoom 100%"
|
|
2141
|
-
title="
|
|
2311
|
+
title="${escapeHtml(tForLang(lang, 'panel.tip.zoomReset'))}">100%</button>
|
|
2142
2312
|
<button id="full-zoom-in"
|
|
2143
2313
|
data-zoom-step="1"
|
|
2144
2314
|
data-nac-id="yujin.panel.zoom-in"
|
|
2145
2315
|
data-nac-role="action"
|
|
2146
2316
|
data-nac-action="zoom_in"
|
|
2147
|
-
aria-label="
|
|
2148
|
-
title="
|
|
2317
|
+
aria-label="${escapeHtml(tForLang(lang, 'panel.aria.zoomIn'))}"
|
|
2318
|
+
title="${escapeHtml(tForLang(lang, 'panel.tip.zoomIn'))}">+</button>
|
|
2149
2319
|
<button id="full-maximize"
|
|
2150
2320
|
data-maximize-toggle
|
|
2151
2321
|
data-nac-id="yujin.panel.maximize"
|
|
2152
2322
|
data-nac-role="action"
|
|
2153
2323
|
data-nac-action="maximize_panel"
|
|
2154
|
-
aria-label="
|
|
2155
|
-
title="
|
|
2324
|
+
aria-label="${escapeHtml(tForLang(lang, 'panel.aria.maximize'))}"
|
|
2325
|
+
title="${escapeHtml(tForLang(lang, 'panel.tip.maximize'))}">☐</button>
|
|
2156
2326
|
<button id="full-mini"
|
|
2157
2327
|
data-nac-id="yujin.panel.full-to-mini"
|
|
2158
2328
|
data-nac-role="action"
|
|
@@ -2262,29 +2432,29 @@ ${renderYujinSplash()}
|
|
|
2262
2432
|
API keys (BYOK) + voice settings + license. -->
|
|
2263
2433
|
<div id="yf-settings" class="hidden settings-panel">
|
|
2264
2434
|
<div class="set-section" data-bridge-section>
|
|
2265
|
-
<div class="set-section-title"
|
|
2435
|
+
<div class="set-section-title">${escapeHtml(tForLang(lang, 'settings.bridge.title'))}
|
|
2266
2436
|
<span class="bridge-status" data-bridge-status></span>
|
|
2267
2437
|
</div>
|
|
2268
|
-
<label class="bridge-toggle"><input type="checkbox" data-bridge-accept>
|
|
2438
|
+
<label class="bridge-toggle"><input type="checkbox" data-bridge-accept> ${escapeHtml(tForLang(lang, 'settings.bridge.accept'))}</label>
|
|
2269
2439
|
<div class="bridge-roster" data-bridge-roster></div>
|
|
2270
2440
|
<div class="bridge-feed" data-bridge-feed></div>
|
|
2271
2441
|
<div class="bridge-compose">
|
|
2272
|
-
<input type="text" class="bridge-input" data-bridge-input placeholder="
|
|
2273
|
-
<button type="button" class="set-btn" data-bridge-send
|
|
2442
|
+
<input type="text" class="bridge-input" data-bridge-input placeholder="${escapeHtml(tForLang(lang, 'settings.bridge.composePlaceholder'))}" maxlength="2000" autocomplete="off">
|
|
2443
|
+
<button type="button" class="set-btn" data-bridge-send>${escapeHtml(tForLang(lang, 'panel.button.send'))}</button>
|
|
2274
2444
|
</div>
|
|
2275
2445
|
</div>
|
|
2276
2446
|
<div class="set-section">
|
|
2277
|
-
<div class="set-section-title"
|
|
2278
|
-
<div class="set-row"><span class="set-lbl"
|
|
2279
|
-
<button type="button" data-voice-mode-btn class="set-btn"
|
|
2447
|
+
<div class="set-section-title">${escapeHtml(tForLang(lang, 'settings.voice.title'))}</div>
|
|
2448
|
+
<div class="set-row"><span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.voice.mode'))}</span>
|
|
2449
|
+
<button type="button" data-voice-mode-btn class="set-btn">${escapeHtml(tForLang(lang, 'settings.voice.modeMic'))}</button>
|
|
2280
2450
|
</div>
|
|
2281
|
-
<div class="set-row"><span class="set-lbl"
|
|
2282
|
-
<button type="button" data-stt-provider-btn class="set-btn"
|
|
2451
|
+
<div class="set-row"><span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.voice.stt'))}</span>
|
|
2452
|
+
<button type="button" data-stt-provider-btn class="set-btn">${escapeHtml(tForLang(lang, 'settings.voice.sttBrowser'))}</button>
|
|
2283
2453
|
</div>
|
|
2284
|
-
<div class="set-row"><span class="set-lbl"
|
|
2285
|
-
<button type="button" data-tts-provider-btn class="set-btn"
|
|
2454
|
+
<div class="set-row"><span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.voice.ttsEngine'))}</span>
|
|
2455
|
+
<button type="button" data-tts-provider-btn class="set-btn">${escapeHtml(tForLang(lang, 'settings.voice.ttsBrowser'))}</button>
|
|
2286
2456
|
</div>
|
|
2287
|
-
<div class="set-row"><span class="set-lbl"
|
|
2457
|
+
<div class="set-row"><span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.voice.ttsVoice'))}</span>
|
|
2288
2458
|
<button type="button" data-tts-voice-btn class="set-btn">es-ES-Studio-C</button>
|
|
2289
2459
|
</div>
|
|
2290
2460
|
</div>
|
|
@@ -2293,63 +2463,63 @@ ${renderYujinSplash()}
|
|
|
2293
2463
|
environment (quiet room / fan / open office / music). All values
|
|
2294
2464
|
hot-apply on the next VAD tick; no reload needed. -->
|
|
2295
2465
|
<div class="set-section vad-section">
|
|
2296
|
-
<div class="set-section-title"
|
|
2297
|
-
<button type="button" data-vad-reset class="set-btn"
|
|
2466
|
+
<div class="set-section-title">${escapeHtml(tForLang(lang, 'settings.vad.title'))}
|
|
2467
|
+
<button type="button" data-vad-reset class="set-btn">${escapeHtml(tForLang(lang, 'settings.vad.resetDefaults'))}</button>
|
|
2298
2468
|
</div>
|
|
2299
|
-
<div class="set-row"><span class="set-lbl"
|
|
2300
|
-
<label class="vad-radio"><input type="radio" name="vad-engine" value="amplitude">
|
|
2301
|
-
<label class="vad-radio"><input type="radio" name="vad-engine" value="freq-band">
|
|
2469
|
+
<div class="set-row"><span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.vad.engine'))}</span>
|
|
2470
|
+
<label class="vad-radio"><input type="radio" name="vad-engine" value="amplitude"> ${escapeHtml(tForLang(lang, 'settings.vad.engineAmplitude'))}</label>
|
|
2471
|
+
<label class="vad-radio"><input type="radio" name="vad-engine" value="freq-band"> ${escapeHtml(tForLang(lang, 'settings.vad.engineBand'))}</label>
|
|
2302
2472
|
</div>
|
|
2303
2473
|
<div class="vad-slider-row" data-vad-field="baseline_ms" data-vad-min="100" data-vad-max="2000" data-vad-step="50">
|
|
2304
|
-
<span class="vad-lbl"
|
|
2474
|
+
<span class="vad-lbl">${escapeHtml(tForLang(lang, 'settings.vad.baselineWindow'))}</span>
|
|
2305
2475
|
<input type="range" class="vad-slider">
|
|
2306
2476
|
<span class="vad-val">600 ms</span>
|
|
2307
2477
|
</div>
|
|
2308
2478
|
<div class="vad-slider-row" data-vad-field="threshold_mult" data-vad-min="1.0" data-vad-max="8.0" data-vad-step="0.1">
|
|
2309
|
-
<span class="vad-lbl"
|
|
2479
|
+
<span class="vad-lbl">${escapeHtml(tForLang(lang, 'settings.vad.thresholdMult'))}</span>
|
|
2310
2480
|
<input type="range" class="vad-slider">
|
|
2311
2481
|
<span class="vad-val">3.0</span>
|
|
2312
2482
|
</div>
|
|
2313
2483
|
<div class="vad-slider-row" data-vad-field="min_threshold" data-vad-min="0.001" data-vad-max="0.1" data-vad-step="0.001">
|
|
2314
|
-
<span class="vad-lbl"
|
|
2484
|
+
<span class="vad-lbl">${escapeHtml(tForLang(lang, 'settings.vad.thresholdMin'))}</span>
|
|
2315
2485
|
<input type="range" class="vad-slider">
|
|
2316
2486
|
<span class="vad-val">0.012</span>
|
|
2317
2487
|
</div>
|
|
2318
2488
|
<div class="vad-slider-row" data-vad-field="max_threshold" data-vad-min="0.005" data-vad-max="0.3" data-vad-step="0.005">
|
|
2319
|
-
<span class="vad-lbl"
|
|
2489
|
+
<span class="vad-lbl">${escapeHtml(tForLang(lang, 'settings.vad.thresholdMax'))}</span>
|
|
2320
2490
|
<input type="range" class="vad-slider">
|
|
2321
2491
|
<span class="vad-val">0.06</span>
|
|
2322
2492
|
</div>
|
|
2323
2493
|
<div class="vad-slider-row" data-vad-field="silence_ms" data-vad-min="200" data-vad-max="4000" data-vad-step="50">
|
|
2324
|
-
<span class="vad-lbl"
|
|
2494
|
+
<span class="vad-lbl">${escapeHtml(tForLang(lang, 'settings.vad.silence'))}</span>
|
|
2325
2495
|
<input type="range" class="vad-slider">
|
|
2326
2496
|
<span class="vad-val">1000 ms</span>
|
|
2327
2497
|
</div>
|
|
2328
2498
|
<div class="vad-slider-row" data-vad-field="min_voiced_ms" data-vad-min="50" data-vad-max="1000" data-vad-step="25">
|
|
2329
|
-
<span class="vad-lbl"
|
|
2499
|
+
<span class="vad-lbl">${escapeHtml(tForLang(lang, 'settings.vad.minVoiced'))}</span>
|
|
2330
2500
|
<input type="range" class="vad-slider">
|
|
2331
2501
|
<span class="vad-val">200 ms</span>
|
|
2332
2502
|
</div>
|
|
2333
2503
|
<div class="vad-slider-row" data-vad-field="tick_ms" data-vad-min="20" data-vad-max="200" data-vad-step="5">
|
|
2334
|
-
<span class="vad-lbl"
|
|
2504
|
+
<span class="vad-lbl">${escapeHtml(tForLang(lang, 'settings.vad.tick'))}</span>
|
|
2335
2505
|
<input type="range" class="vad-slider">
|
|
2336
2506
|
<span class="vad-val">50 ms</span>
|
|
2337
2507
|
</div>
|
|
2338
2508
|
<div class="vad-slider-row vad-freq-only" data-vad-field="freq_low_hz" data-vad-min="20" data-vad-max="500" data-vad-step="10">
|
|
2339
|
-
<span class="vad-lbl"
|
|
2509
|
+
<span class="vad-lbl">${escapeHtml(tForLang(lang, 'settings.vad.bandLow'))}</span>
|
|
2340
2510
|
<input type="range" class="vad-slider">
|
|
2341
2511
|
<span class="vad-val">80</span>
|
|
2342
2512
|
</div>
|
|
2343
2513
|
<div class="vad-slider-row vad-freq-only" data-vad-field="freq_high_hz" data-vad-min="200" data-vad-max="2000" data-vad-step="10">
|
|
2344
|
-
<span class="vad-lbl"
|
|
2514
|
+
<span class="vad-lbl">${escapeHtml(tForLang(lang, 'settings.vad.bandHigh'))}</span>
|
|
2345
2515
|
<input type="range" class="vad-slider">
|
|
2346
2516
|
<span class="vad-val">400</span>
|
|
2347
2517
|
</div>
|
|
2348
2518
|
<div class="set-row">
|
|
2349
|
-
<label class="vad-check"><input type="checkbox" data-vad-toggle="debug_overlay">
|
|
2519
|
+
<label class="vad-check"><input type="checkbox" data-vad-toggle="debug_overlay"> ${escapeHtml(tForLang(lang, 'settings.vad.showOverlay'))}</label>
|
|
2350
2520
|
</div>
|
|
2351
2521
|
<div class="set-row">
|
|
2352
|
-
<label class="vad-check"><input type="checkbox" data-vad-toggle="session_stats">
|
|
2522
|
+
<label class="vad-check"><input type="checkbox" data-vad-toggle="session_stats"> ${escapeHtml(tForLang(lang, 'settings.vad.logStats'))}</label>
|
|
2353
2523
|
<span class="set-status">~/.yujin-forge/vad_sessions.jsonl</span>
|
|
2354
2524
|
</div>
|
|
2355
2525
|
</div>
|
|
@@ -2359,42 +2529,42 @@ ${renderYujinSplash()}
|
|
|
2359
2529
|
<div class="set-section brain-section">
|
|
2360
2530
|
<div class="set-section-title">Brain</div>
|
|
2361
2531
|
<div class="set-row">
|
|
2362
|
-
<span class="set-lbl"
|
|
2363
|
-
<label class="vad-radio"><input type="radio" name="brain-anthropic-mode" value="plan">
|
|
2364
|
-
<label class="vad-radio"><input type="radio" name="brain-anthropic-mode" value="console">
|
|
2532
|
+
<span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.brain.anthropicMode'))}</span>
|
|
2533
|
+
<label class="vad-radio"><input type="radio" name="brain-anthropic-mode" value="plan"> ${escapeHtml(tForLang(lang, 'settings.brain.modePlan'))}</label>
|
|
2534
|
+
<label class="vad-radio"><input type="radio" name="brain-anthropic-mode" value="console"> ${escapeHtml(tForLang(lang, 'settings.brain.modeConsole'))}</label>
|
|
2365
2535
|
</div>
|
|
2366
2536
|
<div class="set-row">
|
|
2367
|
-
<span class="set-status" data-brain-mode-note hidden
|
|
2537
|
+
<span class="set-status" data-brain-mode-note hidden>${escapeHtml(tForLang(lang, 'settings.brain.modeNote'))}</span>
|
|
2368
2538
|
</div>
|
|
2369
2539
|
<div class="set-row">
|
|
2370
|
-
<label class="vad-check"><input type="checkbox" data-brain-eco-enabled>
|
|
2371
|
-
<span class="set-status"
|
|
2540
|
+
<label class="vad-check"><input type="checkbox" data-brain-eco-enabled> ${escapeHtml(tForLang(lang, 'settings.brain.ecoEnabled'))}</label>
|
|
2541
|
+
<span class="set-status">${escapeHtml(tForLang(lang, 'settings.brain.ecoHint'))}</span>
|
|
2372
2542
|
</div>
|
|
2373
2543
|
<div class="set-row">
|
|
2374
2544
|
<label class="vad-check"><input type="checkbox" data-startup-tabs-enabled> ${escapeHtml(tr.startupTabsLabel)}</label>
|
|
2375
2545
|
</div>
|
|
2376
2546
|
<div class="set-row" data-brain-tier-row="eco">
|
|
2377
|
-
<span class="set-lbl"
|
|
2547
|
+
<span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.brain.modelEco'))}</span>
|
|
2378
2548
|
<select class="set-select" data-brain-provider="eco"></select>
|
|
2379
2549
|
<select class="set-select" data-brain-model="eco"></select>
|
|
2380
2550
|
</div>
|
|
2381
2551
|
<div class="set-row" data-brain-tier-row="frontier">
|
|
2382
|
-
<span class="set-lbl"
|
|
2552
|
+
<span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.brain.modelFrontier'))}</span>
|
|
2383
2553
|
<select class="set-select" data-brain-provider="frontier"></select>
|
|
2384
2554
|
<select class="set-select" data-brain-model="frontier"></select>
|
|
2385
2555
|
</div>
|
|
2386
2556
|
<div class="set-row" data-brain-tier-row="multimodal">
|
|
2387
|
-
<span class="set-lbl"
|
|
2557
|
+
<span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.brain.modelMultimodal'))}</span>
|
|
2388
2558
|
<select class="set-select" data-brain-provider="multimodal"></select>
|
|
2389
2559
|
<select class="set-select" data-brain-model="multimodal"></select>
|
|
2390
|
-
<button type="button" class="set-btn" data-brain-multimodal-clear title="
|
|
2560
|
+
<button type="button" class="set-btn" data-brain-multimodal-clear title="${escapeHtml(tForLang(lang, 'settings.brain.multimodalClearTitle'))}">${escapeHtml(tForLang(lang, 'settings.brain.auto'))}</button>
|
|
2391
2561
|
</div>
|
|
2392
2562
|
<div class="set-row">
|
|
2393
|
-
<span class="set-status"
|
|
2563
|
+
<span class="set-status">${escapeHtml(tForLang(lang, 'settings.brain.multimodalHint'))}</span>
|
|
2394
2564
|
</div>
|
|
2395
2565
|
<div class="set-row">
|
|
2396
|
-
<label class="vad-check"><input type="checkbox" data-gemini-explicit-cache>
|
|
2397
|
-
<span class="set-status"
|
|
2566
|
+
<label class="vad-check"><input type="checkbox" data-gemini-explicit-cache> ${escapeHtml(tForLang(lang, 'settings.brain.geminiCache'))}</label>
|
|
2567
|
+
<span class="set-status">${escapeHtml(tForLang(lang, 'settings.brain.geminiCacheHint'))}</span>
|
|
2398
2568
|
</div>
|
|
2399
2569
|
<div class="set-row">
|
|
2400
2570
|
<span class="set-status" data-brain-status></span>
|
|
@@ -2403,129 +2573,129 @@ ${renderYujinSplash()}
|
|
|
2403
2573
|
<!-- F4 brain federation -- conectar al brain de otro Forge
|
|
2404
2574
|
(child) y generar codigos para autorizar/rutear (parent). -->
|
|
2405
2575
|
<div class="set-section federation-section">
|
|
2406
|
-
<div class="set-section-title"
|
|
2576
|
+
<div class="set-section-title">${escapeHtml(tForLang(lang, 'settings.federation.title'))}</div>
|
|
2407
2577
|
<div class="set-row">
|
|
2408
|
-
<span class="set-status"
|
|
2578
|
+
<span class="set-status">${escapeHtml(tForLang(lang, 'settings.federation.intro'))}</span>
|
|
2409
2579
|
</div>
|
|
2410
2580
|
<!-- Estado actual (cuando estas federado como hijo) -->
|
|
2411
2581
|
<div class="set-row fed-linked" data-fed-linked hidden>
|
|
2412
|
-
<span class="set-lbl"
|
|
2582
|
+
<span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.federation.connectedTo'))}</span>
|
|
2413
2583
|
<span class="set-status" data-fed-parent>--</span>
|
|
2414
|
-
<button type="button" class="set-btn set-btn-danger" data-fed-leave
|
|
2584
|
+
<button type="button" class="set-btn set-btn-danger" data-fed-leave>${escapeHtml(tForLang(lang, 'settings.federation.disconnect'))}</button>
|
|
2415
2585
|
</div>
|
|
2416
2586
|
<!-- Conectarme a otro Forge (child) -->
|
|
2417
2587
|
<div class="set-row" data-fed-join-row>
|
|
2418
|
-
<span class="set-lbl"
|
|
2419
|
-
<input type="text" class="set-input" data-fed-token placeholder="
|
|
2420
|
-
<button type="button" class="set-btn" data-fed-join
|
|
2588
|
+
<span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.federation.connectMe'))}</span>
|
|
2589
|
+
<input type="text" class="set-input" data-fed-token placeholder="${escapeHtml(tForLang(lang, 'settings.federation.tokenPlaceholder'))}" autocomplete="off" spellcheck="false">
|
|
2590
|
+
<button type="button" class="set-btn" data-fed-join>${escapeHtml(tForLang(lang, 'settings.federation.connect'))}</button>
|
|
2421
2591
|
</div>
|
|
2422
2592
|
<!-- Prestar mi brain (parent) -->
|
|
2423
2593
|
<div class="set-row" data-fed-invite-row>
|
|
2424
|
-
<span class="set-lbl"
|
|
2425
|
-
<button type="button" class="set-btn" data-fed-invite
|
|
2426
|
-
<label class="vad-check" data-fed-host-wrap hidden><input type="checkbox" data-fed-host>
|
|
2594
|
+
<span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.federation.lendBrain'))}</span>
|
|
2595
|
+
<button type="button" class="set-btn" data-fed-invite>${escapeHtml(tForLang(lang, 'settings.federation.generateCode'))}</button>
|
|
2596
|
+
<label class="vad-check" data-fed-host-wrap hidden><input type="checkbox" data-fed-host> ${escapeHtml(tForLang(lang, 'settings.federation.host'))}</label>
|
|
2427
2597
|
</div>
|
|
2428
2598
|
<div class="set-row" data-fed-brain-row hidden>
|
|
2429
|
-
<span class="set-lbl"
|
|
2599
|
+
<span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.federation.fedBrain'))}</span>
|
|
2430
2600
|
<select class="set-select" data-fed-brain-provider></select>
|
|
2431
2601
|
<select class="set-select" data-fed-brain-model></select>
|
|
2432
2602
|
</div>
|
|
2433
2603
|
<div class="set-row" data-fed-brain-note hidden>
|
|
2434
|
-
<span class="set-status"
|
|
2604
|
+
<span class="set-status">${escapeHtml(tForLang(lang, 'settings.federation.fedBrainNote'))}</span>
|
|
2435
2605
|
</div>
|
|
2436
2606
|
<div class="set-row" data-fed-token-out-row hidden>
|
|
2437
|
-
<span class="set-lbl"
|
|
2607
|
+
<span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.federation.codeOnce'))}</span>
|
|
2438
2608
|
<input type="text" class="set-input" data-fed-token-out readonly>
|
|
2439
|
-
<button type="button" class="set-btn" data-fed-copy
|
|
2609
|
+
<button type="button" class="set-btn" data-fed-copy>${escapeHtml(tForLang(lang, 'settings.federation.copy'))}</button>
|
|
2440
2610
|
</div>
|
|
2441
2611
|
<div class="set-row">
|
|
2442
2612
|
<span class="set-status" data-fed-status></span>
|
|
2443
2613
|
</div>
|
|
2444
2614
|
</div>
|
|
2445
2615
|
<div class="set-section">
|
|
2446
|
-
<div class="set-section-title"
|
|
2447
|
-
<div class="set-key-row" data-key-slot="anthropic_api_key"><span class="set-lbl"
|
|
2616
|
+
<div class="set-section-title">${escapeHtml(tForLang(lang, 'settings.keys.title'))}</div>
|
|
2617
|
+
<div class="set-key-row" data-key-slot="anthropic_api_key"><span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.keys.brainAnthropic'))}</span>
|
|
2448
2618
|
<span class="set-status" data-key-status>--</span>
|
|
2449
|
-
<button type="button" class="set-btn" data-key-edit
|
|
2450
|
-
<button type="button" class="set-btn set-btn-danger" data-key-clear hidden
|
|
2619
|
+
<button type="button" class="set-btn" data-key-edit>${escapeHtml(tForLang(lang, 'settings.keys.configure'))}</button>
|
|
2620
|
+
<button type="button" class="set-btn set-btn-danger" data-key-clear hidden>${escapeHtml(tForLang(lang, 'settings.keys.remove'))}</button>
|
|
2451
2621
|
</div>
|
|
2452
|
-
<div class="set-key-row" data-key-slot="openai_api_key"><span class="set-lbl"
|
|
2622
|
+
<div class="set-key-row" data-key-slot="openai_api_key"><span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.keys.brainOpenai'))}</span>
|
|
2453
2623
|
<span class="set-status" data-key-status>--</span>
|
|
2454
|
-
<button type="button" class="set-btn" data-key-edit
|
|
2455
|
-
<button type="button" class="set-btn set-btn-danger" data-key-clear hidden
|
|
2624
|
+
<button type="button" class="set-btn" data-key-edit>${escapeHtml(tForLang(lang, 'settings.keys.configure'))}</button>
|
|
2625
|
+
<button type="button" class="set-btn set-btn-danger" data-key-clear hidden>${escapeHtml(tForLang(lang, 'settings.keys.remove'))}</button>
|
|
2456
2626
|
</div>
|
|
2457
2627
|
<!-- Bug 1 (Pablo 2026-06-10) -- faltaban los slots de los
|
|
2458
2628
|
demas brain providers BYOK que el selector de modelo
|
|
2459
2629
|
(PND-069) ya ofrece. Slots reales de provider_keys.ts. -->
|
|
2460
|
-
<div class="set-key-row" data-key-slot="google_ai_key"><span class="set-lbl"
|
|
2630
|
+
<div class="set-key-row" data-key-slot="google_ai_key"><span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.keys.brainGoogle'))}</span>
|
|
2461
2631
|
<span class="set-status" data-key-status>--</span>
|
|
2462
|
-
<button type="button" class="set-btn" data-key-edit
|
|
2463
|
-
<button type="button" class="set-btn set-btn-danger" data-key-clear hidden
|
|
2632
|
+
<button type="button" class="set-btn" data-key-edit>${escapeHtml(tForLang(lang, 'settings.keys.configure'))}</button>
|
|
2633
|
+
<button type="button" class="set-btn set-btn-danger" data-key-clear hidden>${escapeHtml(tForLang(lang, 'settings.keys.remove'))}</button>
|
|
2464
2634
|
</div>
|
|
2465
|
-
<div class="set-key-row" data-key-slot="deepseek_api_key"><span class="set-lbl"
|
|
2635
|
+
<div class="set-key-row" data-key-slot="deepseek_api_key"><span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.keys.brainDeepseek'))}</span>
|
|
2466
2636
|
<span class="set-status" data-key-status>--</span>
|
|
2467
|
-
<button type="button" class="set-btn" data-key-edit
|
|
2468
|
-
<button type="button" class="set-btn set-btn-danger" data-key-clear hidden
|
|
2637
|
+
<button type="button" class="set-btn" data-key-edit>${escapeHtml(tForLang(lang, 'settings.keys.configure'))}</button>
|
|
2638
|
+
<button type="button" class="set-btn set-btn-danger" data-key-clear hidden>${escapeHtml(tForLang(lang, 'settings.keys.remove'))}</button>
|
|
2469
2639
|
</div>
|
|
2470
|
-
<div class="set-key-row" data-key-slot="xai_api_key"><span class="set-lbl"
|
|
2640
|
+
<div class="set-key-row" data-key-slot="xai_api_key"><span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.keys.brainXai'))}</span>
|
|
2471
2641
|
<span class="set-status" data-key-status>--</span>
|
|
2472
|
-
<button type="button" class="set-btn" data-key-edit
|
|
2473
|
-
<button type="button" class="set-btn set-btn-danger" data-key-clear hidden
|
|
2642
|
+
<button type="button" class="set-btn" data-key-edit>${escapeHtml(tForLang(lang, 'settings.keys.configure'))}</button>
|
|
2643
|
+
<button type="button" class="set-btn set-btn-danger" data-key-clear hidden>${escapeHtml(tForLang(lang, 'settings.keys.remove'))}</button>
|
|
2474
2644
|
</div>
|
|
2475
|
-
<div class="set-key-row" data-key-slot="mistral_api_key"><span class="set-lbl"
|
|
2645
|
+
<div class="set-key-row" data-key-slot="mistral_api_key"><span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.keys.brainMistral'))}</span>
|
|
2476
2646
|
<span class="set-status" data-key-status>--</span>
|
|
2477
|
-
<button type="button" class="set-btn" data-key-edit
|
|
2478
|
-
<button type="button" class="set-btn set-btn-danger" data-key-clear hidden
|
|
2647
|
+
<button type="button" class="set-btn" data-key-edit>${escapeHtml(tForLang(lang, 'settings.keys.configure'))}</button>
|
|
2648
|
+
<button type="button" class="set-btn set-btn-danger" data-key-clear hidden>${escapeHtml(tForLang(lang, 'settings.keys.remove'))}</button>
|
|
2479
2649
|
</div>
|
|
2480
|
-
<div class="set-key-row" data-key-slot="cohere_api_key"><span class="set-lbl"
|
|
2650
|
+
<div class="set-key-row" data-key-slot="cohere_api_key"><span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.keys.brainCohere'))}</span>
|
|
2481
2651
|
<span class="set-status" data-key-status>--</span>
|
|
2482
|
-
<button type="button" class="set-btn" data-key-edit
|
|
2483
|
-
<button type="button" class="set-btn set-btn-danger" data-key-clear hidden
|
|
2652
|
+
<button type="button" class="set-btn" data-key-edit>${escapeHtml(tForLang(lang, 'settings.keys.configure'))}</button>
|
|
2653
|
+
<button type="button" class="set-btn set-btn-danger" data-key-clear hidden>${escapeHtml(tForLang(lang, 'settings.keys.remove'))}</button>
|
|
2484
2654
|
</div>
|
|
2485
|
-
<div class="set-key-row" data-key-slot="google_stt_key"><span class="set-lbl"
|
|
2655
|
+
<div class="set-key-row" data-key-slot="google_stt_key"><span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.keys.sttGoogle'))}</span>
|
|
2486
2656
|
<span class="set-status" data-key-status>--</span>
|
|
2487
|
-
<button type="button" class="set-btn" data-key-edit
|
|
2488
|
-
<button type="button" class="set-btn set-btn-danger" data-key-clear hidden
|
|
2657
|
+
<button type="button" class="set-btn" data-key-edit>${escapeHtml(tForLang(lang, 'settings.keys.configure'))}</button>
|
|
2658
|
+
<button type="button" class="set-btn set-btn-danger" data-key-clear hidden>${escapeHtml(tForLang(lang, 'settings.keys.remove'))}</button>
|
|
2489
2659
|
</div>
|
|
2490
|
-
<div class="set-key-row" data-key-slot="google_tts_key"><span class="set-lbl"
|
|
2660
|
+
<div class="set-key-row" data-key-slot="google_tts_key"><span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.keys.ttsGoogle'))}</span>
|
|
2491
2661
|
<span class="set-status" data-key-status>--</span>
|
|
2492
|
-
<button type="button" class="set-btn" data-key-edit
|
|
2493
|
-
<button type="button" class="set-btn set-btn-danger" data-key-clear hidden
|
|
2662
|
+
<button type="button" class="set-btn" data-key-edit>${escapeHtml(tForLang(lang, 'settings.keys.configure'))}</button>
|
|
2663
|
+
<button type="button" class="set-btn set-btn-danger" data-key-clear hidden>${escapeHtml(tForLang(lang, 'settings.keys.remove'))}</button>
|
|
2494
2664
|
</div>
|
|
2495
|
-
<div class="set-key-row" data-key-slot="elevenlabs_api_key"><span class="set-lbl"
|
|
2665
|
+
<div class="set-key-row" data-key-slot="elevenlabs_api_key"><span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.keys.ttsElevenlabs'))}</span>
|
|
2496
2666
|
<span class="set-status" data-key-status>--</span>
|
|
2497
|
-
<button type="button" class="set-btn" data-key-edit
|
|
2498
|
-
<button type="button" class="set-btn set-btn-danger" data-key-clear hidden
|
|
2667
|
+
<button type="button" class="set-btn" data-key-edit>${escapeHtml(tForLang(lang, 'settings.keys.configure'))}</button>
|
|
2668
|
+
<button type="button" class="set-btn set-btn-danger" data-key-clear hidden>${escapeHtml(tForLang(lang, 'settings.keys.remove'))}</button>
|
|
2499
2669
|
</div>
|
|
2500
|
-
<div class="set-key-row" data-key-slot="whisper_api_key"><span class="set-lbl"
|
|
2670
|
+
<div class="set-key-row" data-key-slot="whisper_api_key"><span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.keys.sttWhisper'))}</span>
|
|
2501
2671
|
<span class="set-status" data-key-status>--</span>
|
|
2502
|
-
<button type="button" class="set-btn" data-key-edit
|
|
2503
|
-
<button type="button" class="set-btn set-btn-danger" data-key-clear hidden
|
|
2672
|
+
<button type="button" class="set-btn" data-key-edit>${escapeHtml(tForLang(lang, 'settings.keys.configure'))}</button>
|
|
2673
|
+
<button type="button" class="set-btn set-btn-danger" data-key-clear hidden>${escapeHtml(tForLang(lang, 'settings.keys.remove'))}</button>
|
|
2504
2674
|
</div>
|
|
2505
2675
|
</div>
|
|
2506
2676
|
<div class="set-section">
|
|
2507
|
-
<div class="set-section-title"
|
|
2508
|
-
<div class="set-row"><span class="set-lbl"
|
|
2509
|
-
<button type="button" data-lang-open class="set-btn">${escapeHtml(
|
|
2677
|
+
<div class="set-section-title">${escapeHtml(tForLang(lang, 'settings.lang.title'))}</div>
|
|
2678
|
+
<div class="set-row"><span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.lang.panelVoice'))}</span>
|
|
2679
|
+
<button type="button" data-lang-open class="set-btn">${escapeHtml(tForLang(lang, 'settings.lang.changeButton'))}</button>
|
|
2510
2680
|
</div>
|
|
2511
2681
|
</div>
|
|
2512
2682
|
<div class="set-section">
|
|
2513
|
-
<div class="set-section-title"
|
|
2514
|
-
<div class="set-row"><span class="set-lbl"
|
|
2515
|
-
<button type="button" data-vault-open class="set-btn"
|
|
2683
|
+
<div class="set-section-title">${escapeHtml(tForLang(lang, 'settings.vault.title'))}</div>
|
|
2684
|
+
<div class="set-row"><span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.vault.slots'))}</span>
|
|
2685
|
+
<button type="button" data-vault-open class="set-btn">${escapeHtml(tForLang(lang, 'settings.vault.open'))}</button>
|
|
2516
2686
|
</div>
|
|
2517
2687
|
</div>
|
|
2518
2688
|
<div class="set-section">
|
|
2519
|
-
<div class="set-section-title"
|
|
2520
|
-
<div class="set-row"><span class="set-lbl"
|
|
2689
|
+
<div class="set-section-title">${escapeHtml(tForLang(lang, 'settings.license.title'))}</div>
|
|
2690
|
+
<div class="set-row"><span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.plan'))}</span>
|
|
2521
2691
|
<span class="set-status" id="set-license-status">--</span>
|
|
2522
2692
|
</div>
|
|
2523
2693
|
<div class="set-row" id="set-license-user-handle-row" style="display:none;">
|
|
2524
|
-
<span class="set-lbl"
|
|
2694
|
+
<span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.license.email'))}</span>
|
|
2525
2695
|
<span class="set-status" id="set-license-user-handle">--</span>
|
|
2526
2696
|
</div>
|
|
2527
2697
|
<div class="set-row" id="set-license-validated-row" style="display:none;">
|
|
2528
|
-
<span class="set-lbl"
|
|
2698
|
+
<span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.license.validated'))}</span>
|
|
2529
2699
|
<span class="set-status" id="set-license-validated">--</span>
|
|
2530
2700
|
</div>
|
|
2531
2701
|
|
|
@@ -2534,23 +2704,22 @@ ${renderYujinSplash()}
|
|
|
2534
2704
|
/api/forge/license-handle -> writes handle + syncs. -->
|
|
2535
2705
|
<div id="set-license-connect-block" style="display:none;border:1px dashed var(--border, rgba(255,255,255,0.18));border-radius:6px;padding:10px;margin-top:6px;background:var(--bg-2, #131620);">
|
|
2536
2706
|
<div style="font-size:11px;opacity:0.85;margin-bottom:8px;">
|
|
2537
|
-
|
|
2538
|
-
sincronizamos tu plan automaticamente.
|
|
2707
|
+
${escapeHtml(tForLang(lang, 'settings.license.connectHint'))}
|
|
2539
2708
|
</div>
|
|
2540
2709
|
<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap;">
|
|
2541
2710
|
<input type="email" id="set-license-connect-email"
|
|
2542
|
-
placeholder="
|
|
2711
|
+
placeholder="${escapeHtml(tForLang(lang, 'settings.license.emailPlaceholder'))}"
|
|
2543
2712
|
style="flex:1;min-width:200px;background:var(--bg-1,#1a1d27);color:var(--ink,#e5e7eb);border:1px solid var(--border, rgba(255,255,255,0.18));padding:5px 8px;border-radius:4px;font-size:12px;"
|
|
2544
2713
|
autocomplete="email" />
|
|
2545
|
-
<button type="button" class="set-btn" id="set-license-connect-btn"
|
|
2714
|
+
<button type="button" class="set-btn" id="set-license-connect-btn">${escapeHtml(tForLang(lang, 'settings.license.connect'))}</button>
|
|
2546
2715
|
</div>
|
|
2547
2716
|
<div id="set-license-connect-msg" style="font-size:11px;opacity:0.85;margin-top:6px;display:none;"></div>
|
|
2548
2717
|
</div>
|
|
2549
2718
|
|
|
2550
2719
|
<div class="set-row" id="set-license-sync-row" style="gap:6px;display:none;">
|
|
2551
|
-
<button type="button" class="set-btn" id="set-license-sync-btn"
|
|
2720
|
+
<button type="button" class="set-btn" id="set-license-sync-btn">${escapeHtml(tForLang(lang, 'settings.license.sync'))}</button>
|
|
2552
2721
|
<a href="https://yujin.app/yujin-forge/"
|
|
2553
|
-
target="_blank" rel="noopener" class="set-link"
|
|
2722
|
+
target="_blank" rel="noopener" class="set-link">${escapeHtml(tForLang(lang, 'settings.license.buy'))}</a>
|
|
2554
2723
|
</div>
|
|
2555
2724
|
<div class="set-row" id="set-license-sync-msg-row" style="display:none;">
|
|
2556
2725
|
<span class="set-status" id="set-license-sync-msg" style="font-size:11px;opacity:0.85;">--</span>
|
|
@@ -2560,15 +2729,15 @@ ${renderYujinSplash()}
|
|
|
2560
2729
|
tier + renewal date + buy-extension link. Auto-refreshes
|
|
2561
2730
|
on settings open + after each chat response. -->
|
|
2562
2731
|
<div class="set-section" data-nac-id="yujin.panel.budget" data-nac-role="region">
|
|
2563
|
-
<div class="set-section-title"
|
|
2564
|
-
<div class="set-row"><span class="set-lbl"
|
|
2732
|
+
<div class="set-section-title">${escapeHtml(tForLang(lang, 'settings.budget.title'))}</div>
|
|
2733
|
+
<div class="set-row"><span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.plan'))}</span>
|
|
2565
2734
|
<span class="set-status" id="set-budget-plan">--</span>
|
|
2566
2735
|
</div>
|
|
2567
|
-
<div class="set-row"><span class="set-lbl"
|
|
2736
|
+
<div class="set-row"><span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.budget.renews'))}</span>
|
|
2568
2737
|
<span class="set-status" id="set-budget-renewal">--</span>
|
|
2569
2738
|
</div>
|
|
2570
2739
|
<div class="set-row" id="set-budget-eco-row">
|
|
2571
|
-
<span class="set-lbl"
|
|
2740
|
+
<span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.budget.tokensEco'))}</span>
|
|
2572
2741
|
<span class="set-status" id="set-budget-eco-numbers">--</span>
|
|
2573
2742
|
</div>
|
|
2574
2743
|
<div class="set-row" id="set-budget-eco-bar-row" style="margin-top:-6px;">
|
|
@@ -2577,7 +2746,7 @@ ${renderYujinSplash()}
|
|
|
2577
2746
|
</div>
|
|
2578
2747
|
</div>
|
|
2579
2748
|
<div class="set-row" id="set-budget-frontier-row">
|
|
2580
|
-
<span class="set-lbl"
|
|
2749
|
+
<span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.budget.tokensFrontier'))}</span>
|
|
2581
2750
|
<span class="set-status" id="set-budget-frontier-numbers">--</span>
|
|
2582
2751
|
</div>
|
|
2583
2752
|
<div class="set-row" id="set-budget-frontier-bar-row" style="margin-top:-6px;">
|
|
@@ -2586,18 +2755,18 @@ ${renderYujinSplash()}
|
|
|
2586
2755
|
</div>
|
|
2587
2756
|
</div>
|
|
2588
2757
|
<div class="set-row" id="set-budget-extension-row" style="display:none;">
|
|
2589
|
-
<a href="#" target="_blank" rel="noopener" class="set-link" id="set-budget-extension-link"
|
|
2758
|
+
<a href="#" target="_blank" rel="noopener" class="set-link" id="set-budget-extension-link">${escapeHtml(tForLang(lang, 'settings.budget.buyExtension'))}</a>
|
|
2590
2759
|
</div>
|
|
2591
2760
|
</div>
|
|
2592
2761
|
<!-- alpha.59c.2 -- Limits card. Cada limite es un input
|
|
2593
2762
|
numerico; el default va al lado en una pill chica.
|
|
2594
2763
|
Save por blur. Reset restaura todos. -->
|
|
2595
2764
|
<div class="set-section" data-nac-id="yujin.panel.limits" data-nac-role="region">
|
|
2596
|
-
<div class="set-section-title"
|
|
2597
|
-
<div class="set-row"><span class="set-lbl" style="opacity:0.7;"
|
|
2765
|
+
<div class="set-section-title">${escapeHtml(tForLang(lang, 'settings.limits.title'))}</div>
|
|
2766
|
+
<div class="set-row"><span class="set-lbl" style="opacity:0.7;">${escapeHtml(tForLang(lang, 'settings.limits.editHint'))}</span></div>
|
|
2598
2767
|
<div id="set-limits-fields"></div>
|
|
2599
2768
|
<div class="set-row" style="justify-content:flex-end;gap:6px;">
|
|
2600
|
-
<button type="button" class="set-btn" id="set-limits-reset"
|
|
2769
|
+
<button type="button" class="set-btn" id="set-limits-reset">${escapeHtml(tForLang(lang, 'settings.limits.reset'))}</button>
|
|
2601
2770
|
</div>
|
|
2602
2771
|
<div id="set-limits-msg" class="ke-msg"></div>
|
|
2603
2772
|
</div>
|
|
@@ -2607,10 +2776,10 @@ ${renderYujinSplash()}
|
|
|
2607
2776
|
Click row -> envia mensaje hidden al LLM con el
|
|
2608
2777
|
comando para invocar yujin.reader.bookmark-jump. -->
|
|
2609
2778
|
<div class="set-section" data-nac-id="yujin.panel.reader-bookmarks" data-nac-role="region">
|
|
2610
|
-
<div class="set-section-title"
|
|
2779
|
+
<div class="set-section-title">${escapeHtml(tForLang(lang, 'settings.bookmarks.title'))}</div>
|
|
2611
2780
|
<div id="set-bookmarks-list" class="bookmark-list"></div>
|
|
2612
2781
|
<div class="set-row" style="justify-content:flex-end;">
|
|
2613
|
-
<button type="button" class="set-btn" id="set-bookmarks-refresh"
|
|
2782
|
+
<button type="button" class="set-btn" id="set-bookmarks-refresh">${escapeHtml(tForLang(lang, 'pizarron.graph.refresh'))}</button>
|
|
2614
2783
|
</div>
|
|
2615
2784
|
</div>
|
|
2616
2785
|
|
|
@@ -2619,9 +2788,9 @@ ${renderYujinSplash()}
|
|
|
2619
2788
|
user know whether the inverse-hallucination guard is
|
|
2620
2789
|
populated. CLI for full CRUD: yf graph add-node. -->
|
|
2621
2790
|
<div class="set-section" data-nac-id="yujin.panel.graph-status" data-nac-role="region">
|
|
2622
|
-
<div class="set-section-title"
|
|
2791
|
+
<div class="set-section-title">${escapeHtml(tForLang(lang, 'settings.graph.title'))}</div>
|
|
2623
2792
|
<div class="set-row" id="set-graph-summary">
|
|
2624
|
-
<span class="set-lbl"
|
|
2793
|
+
<span class="set-lbl">${escapeHtml(tForLang(lang, 'vault.list.loading'))}</span>
|
|
2625
2794
|
</div>
|
|
2626
2795
|
<div id="set-graph-samples" class="graph-samples"></div>
|
|
2627
2796
|
<div class="set-row" style="justify-content:space-between;align-items:center;">
|
|
@@ -2633,18 +2802,18 @@ ${renderYujinSplash()}
|
|
|
2633
2802
|
<!-- Vincular telefono (dogfood 2026-06-10; Option 3 2026-06-16 --
|
|
2634
2803
|
code-less: el QR es autocontenido, se escanea y listo). -->
|
|
2635
2804
|
<div class="set-section">
|
|
2636
|
-
<div class="set-section-title"
|
|
2805
|
+
<div class="set-section-title">${escapeHtml(tForLang(lang, 'settings.pair.title'))}</div>
|
|
2637
2806
|
<div class="set-row">
|
|
2638
|
-
<span class="set-lbl" style="opacity:0.7;font-size:11px;"
|
|
2639
|
-
<button type="button" class="set-btn" id="set-pair-generate"
|
|
2807
|
+
<span class="set-lbl" style="opacity:0.7;font-size:11px;">${escapeHtml(tForLang(lang, 'settings.pair.hint'))}</span>
|
|
2808
|
+
<button type="button" class="set-btn" id="set-pair-generate">${escapeHtml(tForLang(lang, 'settings.pair.generateQr'))}</button>
|
|
2640
2809
|
</div>
|
|
2641
2810
|
<div id="set-pair-result" class="hidden" style="text-align:center;padding:8px 0;">
|
|
2642
2811
|
<div id="set-pair-qr" style="display:inline-block;background:#ffffff;padding:10px;border-radius:8px;width:220px;height:220px;"></div>
|
|
2643
2812
|
<div id="set-pair-expiry" style="font-size:11px;opacity:0.6;margin-top:6px;"></div>
|
|
2644
|
-
<div style="font-size:11px;opacity:0.7;margin-top:10px;"
|
|
2813
|
+
<div style="font-size:11px;opacity:0.7;margin-top:10px;">${escapeHtml(tForLang(lang, 'settings.pair.cantRead'))}</div>
|
|
2645
2814
|
<div style="display:flex;gap:6px;align-items:center;justify-content:center;margin-top:4px;">
|
|
2646
2815
|
<input id="set-pair-code" readonly style="flex:1;max-width:210px;font-size:10px;font-family:monospace;padding:4px 6px;border:1px solid var(--border);border-radius:4px;background:var(--bg-1);color:var(--ink);" />
|
|
2647
|
-
<button type="button" class="set-btn" id="set-pair-copy"
|
|
2816
|
+
<button type="button" class="set-btn" id="set-pair-copy">${escapeHtml(tForLang(lang, 'settings.federation.copy'))}</button>
|
|
2648
2817
|
</div>
|
|
2649
2818
|
</div>
|
|
2650
2819
|
<div id="set-pair-error" class="hidden" style="font-size:12px;color:var(--err,#c0392b);padding:6px 0;"></div>
|
|
@@ -2655,18 +2824,18 @@ ${renderYujinSplash()}
|
|
|
2655
2824
|
del config. Click en "Ver completo" abre el audit modal
|
|
2656
2825
|
con filtros + lista paginada + detail inline. -->
|
|
2657
2826
|
<div class="set-section audit-log" data-nac-id="yujin.panel.audit-log" data-nac-role="region">
|
|
2658
|
-
<div class="set-section-title"
|
|
2827
|
+
<div class="set-section-title">${escapeHtml(tForLang(lang, 'settings.audit.title'))}</div>
|
|
2659
2828
|
<div id="set-audit-preview" class="audit-preview">
|
|
2660
|
-
<div class="audit-preview-empty"
|
|
2829
|
+
<div class="audit-preview-empty">${escapeHtml(tForLang(lang, 'vault.list.loading'))}</div>
|
|
2661
2830
|
</div>
|
|
2662
2831
|
<div class="set-row" style="justify-content:flex-end;gap:6px;margin-top:6px;">
|
|
2663
|
-
<button type="button" class="set-btn" id="set-audit-refresh"
|
|
2664
|
-
<button type="button" class="set-btn" id="set-audit-open-modal"
|
|
2832
|
+
<button type="button" class="set-btn" id="set-audit-refresh">${escapeHtml(tForLang(lang, 'pizarron.graph.refresh'))}</button>
|
|
2833
|
+
<button type="button" class="set-btn" id="set-audit-open-modal">${escapeHtml(tForLang(lang, 'settings.audit.viewFull'))}</button>
|
|
2665
2834
|
</div>
|
|
2666
2835
|
</div>
|
|
2667
2836
|
<!-- z.111 -- About / legal docs. Populated by fetchAndRenderAbout() on first settings open. -->
|
|
2668
2837
|
<div class="set-section about-section" data-nac-id="yujin.panel.about" data-nac-role="region">
|
|
2669
|
-
<div class="set-section-title"
|
|
2838
|
+
<div class="set-section-title">${escapeHtml(tForLang(lang, 'settings.about.title'))}</div>
|
|
2670
2839
|
<div class="set-row" style="font-size:11px; color:var(--ink-2);">
|
|
2671
2840
|
Forge <span id="set-about-version">--</span>
|
|
2672
2841
|
</div>
|
|
@@ -2681,12 +2850,10 @@ ${renderYujinSplash()}
|
|
|
2681
2850
|
aparece + cada accion se loguea con
|
|
2682
2851
|
approval_mode='danger_zone_bypass'. -->
|
|
2683
2852
|
<div class="set-section danger-zone" data-nac-id="yujin.panel.danger-zone" data-nac-role="region">
|
|
2684
|
-
<div class="set-section-title" style="color:#d97706;">⚠
|
|
2853
|
+
<div class="set-section-title" style="color:#d97706;">⚠ ${escapeHtml(tForLang(lang, 'settings.danger.title'))}</div>
|
|
2685
2854
|
<div class="set-row" style="opacity:0.8;">
|
|
2686
2855
|
<span class="set-lbl" style="font-size:11px;">
|
|
2687
|
-
|
|
2688
|
-
(incluye shell.exec, rm, git push --force, etc). Velocidad sobre
|
|
2689
|
-
seguridad. Todo queda registrado en audit log.
|
|
2856
|
+
${escapeHtml(tForLang(lang, 'settings.danger.warning'))}
|
|
2690
2857
|
</span>
|
|
2691
2858
|
</div>
|
|
2692
2859
|
<div id="set-danger-toggles"></div>
|
|
@@ -2701,22 +2868,22 @@ ${renderYujinSplash()}
|
|
|
2701
2868
|
<div class="audit-modal-backdrop" data-audit-close></div>
|
|
2702
2869
|
<div class="audit-modal-frame" role="dialog" aria-modal="true">
|
|
2703
2870
|
<div class="audit-modal-header">
|
|
2704
|
-
<div class="audit-modal-title"
|
|
2705
|
-
<button type="button" class="set-btn" data-audit-close
|
|
2871
|
+
<div class="audit-modal-title">${escapeHtml(tForLang(lang, 'settings.audit.title'))}</div>
|
|
2872
|
+
<button type="button" class="set-btn" data-audit-close>${escapeHtml(tForLang(lang, 'settings.audit.close'))}</button>
|
|
2706
2873
|
</div>
|
|
2707
2874
|
<div class="audit-modal-toolbar">
|
|
2708
2875
|
<select id="audit-modal-filter-kind" class="audit-filter">
|
|
2709
|
-
<option value=""
|
|
2876
|
+
<option value="">${escapeHtml(tForLang(lang, 'settings.audit.allKinds'))}</option>
|
|
2710
2877
|
</select>
|
|
2711
2878
|
<select id="audit-modal-filter-approval" class="audit-filter">
|
|
2712
|
-
<option value=""
|
|
2879
|
+
<option value="">${escapeHtml(tForLang(lang, 'settings.audit.allModes'))}</option>
|
|
2713
2880
|
<option value="auto_approved">auto_approved</option>
|
|
2714
2881
|
<option value="user_approved">user_approved</option>
|
|
2715
2882
|
<option value="user_rejected">user_rejected</option>
|
|
2716
2883
|
<option value="danger_zone_bypass">danger_zone_bypass</option>
|
|
2717
2884
|
<option value="not_applicable">not_applicable</option>
|
|
2718
2885
|
</select>
|
|
2719
|
-
<button type="button" class="set-btn" id="audit-modal-refresh"
|
|
2886
|
+
<button type="button" class="set-btn" id="audit-modal-refresh">${escapeHtml(tForLang(lang, 'pizarron.graph.refresh'))}</button>
|
|
2720
2887
|
<span id="audit-modal-count" style="font-size:11px;opacity:0.6;margin-left:auto;"></span>
|
|
2721
2888
|
</div>
|
|
2722
2889
|
<div id="audit-modal-list" class="audit-modal-list"></div>
|
|
@@ -2727,8 +2894,8 @@ ${renderYujinSplash()}
|
|
|
2727
2894
|
<div class="audit-modal-detail-sub" id="audit-modal-detail-sub">--</div>
|
|
2728
2895
|
</div>
|
|
2729
2896
|
<div style="display:flex;gap:6px;">
|
|
2730
|
-
<button type="button" class="set-btn" id="audit-modal-detail-copy"
|
|
2731
|
-
<button type="button" class="set-btn" id="audit-modal-detail-close"
|
|
2897
|
+
<button type="button" class="set-btn" id="audit-modal-detail-copy">${escapeHtml(tForLang(lang, 'settings.audit.copyJson'))}</button>
|
|
2898
|
+
<button type="button" class="set-btn" id="audit-modal-detail-close">${escapeHtml(tForLang(lang, 'settings.audit.closeDetail'))}</button>
|
|
2732
2899
|
</div>
|
|
2733
2900
|
</div>
|
|
2734
2901
|
<pre class="audit-modal-detail-body" id="audit-modal-detail-body"></pre>
|
|
@@ -2741,20 +2908,20 @@ ${renderYujinSplash()}
|
|
|
2741
2908
|
<div class="legal-modal-backdrop"></div>
|
|
2742
2909
|
<div class="legal-modal-frame">
|
|
2743
2910
|
<div class="legal-modal-header">
|
|
2744
|
-
<div class="legal-modal-title" id="legal-modal-title"
|
|
2745
|
-
<button type="button" class="set-btn" id="legal-modal-close"
|
|
2911
|
+
<div class="legal-modal-title" id="legal-modal-title">${escapeHtml(tForLang(lang, 'settings.legal.title'))}</div>
|
|
2912
|
+
<button type="button" class="set-btn" id="legal-modal-close">${escapeHtml(tForLang(lang, 'settings.audit.close'))}</button>
|
|
2746
2913
|
</div>
|
|
2747
|
-
<div class="legal-modal-body" id="legal-modal-body"
|
|
2914
|
+
<div class="legal-modal-body" id="legal-modal-body">${escapeHtml(tForLang(lang, 'vault.list.loading'))}</div>
|
|
2748
2915
|
</div>
|
|
2749
2916
|
</div>
|
|
2750
2917
|
<!-- inline edit form (single, repositioned over the active key row) -->
|
|
2751
2918
|
<div id="yf-key-editor" class="hidden key-editor">
|
|
2752
|
-
<div class="ke-title"
|
|
2753
|
-
<input type="password" id="ke-input" placeholder="
|
|
2919
|
+
<div class="ke-title">${escapeHtml(tForLang(lang, 'settings.keys.configure'))} <span id="ke-slot-name"></span></div>
|
|
2920
|
+
<input type="password" id="ke-input" placeholder="${escapeHtml(tForLang(lang, 'settings.keyEditor.placeholder'))}" autocomplete="off"/>
|
|
2754
2921
|
<div class="ke-actions">
|
|
2755
|
-
<button type="button" id="ke-save" class="set-btn"
|
|
2756
|
-
<button type="button" id="ke-clear" class="set-btn set-btn-danger"
|
|
2757
|
-
<button type="button" id="ke-cancel" class="set-btn"
|
|
2922
|
+
<button type="button" id="ke-save" class="set-btn">${escapeHtml(tForLang(lang, 'vault.add.button_save'))}</button>
|
|
2923
|
+
<button type="button" id="ke-clear" class="set-btn set-btn-danger">${escapeHtml(tForLang(lang, 'settings.keys.remove'))}</button>
|
|
2924
|
+
<button type="button" id="ke-cancel" class="set-btn">${escapeHtml(tForLang(lang, 'vault.add.button_cancel'))}</button>
|
|
2758
2925
|
</div>
|
|
2759
2926
|
<div id="ke-msg" class="ke-msg"></div>
|
|
2760
2927
|
</div>
|
|
@@ -2767,7 +2934,7 @@ ${renderYujinSplash()}
|
|
|
2767
2934
|
<form id="bar-full" data-nac-id="yujin.chat.bar-full" class="bar" onsubmit="return false">
|
|
2768
2935
|
<button type="button" class="mic" data-mic data-mic-btn aria-label="${escapeHtml(tr.micButton)}" title="${escapeHtml(tr.micButton)}">🎙</button>
|
|
2769
2936
|
<button type="button" class="tts-toggle" data-tts-toggle aria-label="${escapeHtml(tr.ttsButton)}" title="${escapeHtml(tr.ttsButton)}">🔊</button>
|
|
2770
|
-
<button type="button" class="clip" data-attach-clip aria-label="
|
|
2937
|
+
<button type="button" class="clip" data-attach-clip aria-label="${escapeHtml(tForLang(lang, 'panel.tip.attachFile'))}" title="${escapeHtml(tForLang(lang, 'panel.tip.attachFile'))}">📎</button>
|
|
2771
2938
|
<textarea class="composer-input" rows="1" placeholder="${escapeHtml(tr.inputPlaceholder)}" aria-label="${escapeHtml(tr.inputPlaceholder)}"></textarea>
|
|
2772
2939
|
<button type="submit" class="send">${escapeHtml(tr.sendButton)}</button>
|
|
2773
2940
|
<button type="button" class="stop" data-stop aria-label="${escapeHtml(tr.stopButton)}" title="${escapeHtml(tr.stopButton)}" style="display:none">■ ${escapeHtml(tr.stopButton)}</button>
|
|
@@ -2789,7 +2956,7 @@ ${renderYujinSplash()}
|
|
|
2789
2956
|
data-nac-id="yujin.pizarron.clear-all"
|
|
2790
2957
|
data-nac-role="action"
|
|
2791
2958
|
data-nac-action="clear_pizarron"
|
|
2792
|
-
aria-label="
|
|
2959
|
+
aria-label="${escapeHtml(tForLang(lang, 'panel.aria.closeBoard'))}" title="${escapeHtml(tForLang(lang, 'panel.tip.closeBoard'))}">×</button>
|
|
2793
2960
|
</div>
|
|
2794
2961
|
<div class="pz-tabs" id="pizarron-tabs"
|
|
2795
2962
|
data-nac-id="yujin.pizarron.tabs"
|
|
@@ -2799,17 +2966,12 @@ ${renderYujinSplash()}
|
|
|
2799
2966
|
data-nac-role="region">
|
|
2800
2967
|
<div class="pizarra-empty" id="pizarron-empty">
|
|
2801
2968
|
<div class="kanji-big">板</div>
|
|
2802
|
-
<p
|
|
2969
|
+
<p>${escapeHtml(tForLang(lang, 'panel.hint.boardEmpty'))}</p>
|
|
2803
2970
|
<!-- PND-064 -- aviso cuando el brain corre en modo Plan
|
|
2804
2971
|
(claude-code-cli): en ese modo el pizarron no es
|
|
2805
2972
|
alcanzable. Se muestra solo si el endpoint reporta
|
|
2806
2973
|
ese provider activo. -->
|
|
2807
|
-
<p id="pizarron-plan-hint" class="pizarra-plan-hint hidden">
|
|
2808
|
-
Estas en modo Plan (suscripcion). El pizarron se dibuja
|
|
2809
|
-
cuando el brain corre en modo API: cambialo en
|
|
2810
|
-
<strong>Ajustes > Brain > Anthropic: modalidad > Console</strong>
|
|
2811
|
-
y reabri el panel.
|
|
2812
|
-
</p>
|
|
2974
|
+
<p id="pizarron-plan-hint" class="pizarra-plan-hint hidden">${tForLang(lang, 'panel.hint.planMode')}</p>
|
|
2813
2975
|
</div>
|
|
2814
2976
|
</div>
|
|
2815
2977
|
<!-- Action trace (tool calls log) -- siempre visible
|
|
@@ -2998,10 +3160,10 @@ async function switchProject(slug) {
|
|
|
2998
3160
|
setProj();
|
|
2999
3161
|
setStatus('');
|
|
3000
3162
|
} else {
|
|
3001
|
-
setStatus((j && j.error) ||
|
|
3163
|
+
setStatus((j && j.error) || CONFIG.i18n.panelStatusProjectSwitchFail, true);
|
|
3002
3164
|
}
|
|
3003
3165
|
} catch (e) {
|
|
3004
|
-
setStatus(
|
|
3166
|
+
setStatus(CONFIG.i18n.panelStatusProjectSwitchFail, true);
|
|
3005
3167
|
}
|
|
3006
3168
|
}
|
|
3007
3169
|
/* Remove a project from the list (the "X"). Confirms first; on success drops
|
|
@@ -3022,10 +3184,10 @@ async function removeProjectViaClick(slug, rowEl) {
|
|
|
3022
3184
|
setStatus(((CONFIG.i18n && CONFIG.i18n.projectRemoved) || 'Proyecto quitado de la lista') + ': ' + slug);
|
|
3023
3185
|
setTimeout(function () { setStatus(''); }, 2500);
|
|
3024
3186
|
} else {
|
|
3025
|
-
setStatus((j && j.error) ||
|
|
3187
|
+
setStatus((j && j.error) || CONFIG.i18n.panelStatusProjectRemoveFail, true);
|
|
3026
3188
|
}
|
|
3027
3189
|
} catch (e) {
|
|
3028
|
-
setStatus(
|
|
3190
|
+
setStatus(CONFIG.i18n.panelStatusProjectRemoveFail, true);
|
|
3029
3191
|
}
|
|
3030
3192
|
}
|
|
3031
3193
|
async function addProjectViaPrompt() {
|
|
@@ -3038,10 +3200,10 @@ async function addProjectViaPrompt() {
|
|
|
3038
3200
|
body: JSON.stringify({ path: path.trim() }),
|
|
3039
3201
|
});
|
|
3040
3202
|
const j = await r.json();
|
|
3041
|
-
if (!j || !j.ok) { setStatus((j && j.error) ||
|
|
3203
|
+
if (!j || !j.ok) { setStatus((j && j.error) || CONFIG.i18n.panelStatusProjectAddFail, true); return; }
|
|
3042
3204
|
/* reopen the menu so the user can pick the freshly added project */
|
|
3043
3205
|
openProjectMenu($('#proj-full') || $('#proj-mini'));
|
|
3044
|
-
} catch (e) { setStatus(
|
|
3206
|
+
} catch (e) { setStatus(CONFIG.i18n.panelStatusProjectAddFail, true); }
|
|
3045
3207
|
}
|
|
3046
3208
|
|
|
3047
3209
|
function escapeHtml(s) {
|
|
@@ -3097,7 +3259,7 @@ function renderMd(src) {
|
|
|
3097
3259
|
* /api/forge/open-in-editor. */
|
|
3098
3260
|
out = out.replace(
|
|
3099
3261
|
/(?<![\\w/])((?:src|tests|docs|packages|apps|scripts)\\/[\\w./-]+(?:\\.[\\w]+)(?::\\d+(?::\\d+)?)?)/g,
|
|
3100
|
-
'<a class="fpath" data-fpath="$1" href="#" title="
|
|
3262
|
+
'<a class="fpath" data-fpath="$1" href="#" title="' + escapeHtml(CONFIG.i18n.tipOpenInEditor) + '">$1</a>',
|
|
3101
3263
|
);
|
|
3102
3264
|
return out;
|
|
3103
3265
|
}
|
|
@@ -3143,7 +3305,7 @@ function renderMdRich(src) {
|
|
|
3143
3305
|
/* file paths */
|
|
3144
3306
|
r = r.replace(
|
|
3145
3307
|
/(?<![\\w/])((?:src|tests|docs|packages|apps|scripts)\\/[\\w./-]+(?:\\.[\\w]+)(?::\\d+(?::\\d+)?)?)/g,
|
|
3146
|
-
'<a class="fpath" data-fpath="$1" href="#" title="
|
|
3308
|
+
'<a class="fpath" data-fpath="$1" href="#" title="' + escapeHtml(CONFIG.i18n.tipOpen) + '">$1</a>',
|
|
3147
3309
|
);
|
|
3148
3310
|
return r;
|
|
3149
3311
|
}
|
|
@@ -3397,7 +3559,7 @@ function renderStream() {
|
|
|
3397
3559
|
'<div class="tp-hint">' + hint + ' <span class="tp-count">(' + sp.round + '/' + sp.max_rounds + ')</span></div>' +
|
|
3398
3560
|
'</div>';
|
|
3399
3561
|
} else {
|
|
3400
|
-
tdiv.innerHTML = '<div class="thinking" aria-label="
|
|
3562
|
+
tdiv.innerHTML = '<div class="thinking" aria-label="' + escapeHtml(CONFIG.i18n.ariaThinking) + '"><span></span><span></span><span></span></div>';
|
|
3401
3563
|
}
|
|
3402
3564
|
target.appendChild(tdiv);
|
|
3403
3565
|
}
|
|
@@ -3421,7 +3583,7 @@ function renderStream() {
|
|
|
3421
3583
|
const body = await r.json();
|
|
3422
3584
|
if (!body.ok && body.path) {
|
|
3423
3585
|
navigator.clipboard.writeText(body.path).catch(function () {});
|
|
3424
|
-
alert('
|
|
3586
|
+
alert(CONFIG.i18n.alertNoEditor.replace('{path}', body.path));
|
|
3425
3587
|
}
|
|
3426
3588
|
fetch('/api/forge/trust-event', {
|
|
3427
3589
|
method: 'POST',
|
|
@@ -3507,13 +3669,13 @@ async function openLegalModal(title, url) {
|
|
|
3507
3669
|
const titleEl = document.getElementById('legal-modal-title');
|
|
3508
3670
|
const bodyEl = document.getElementById('legal-modal-body');
|
|
3509
3671
|
if (!modal || !titleEl || !bodyEl) return;
|
|
3510
|
-
titleEl.textContent = title ||
|
|
3511
|
-
bodyEl.textContent =
|
|
3672
|
+
titleEl.textContent = title || CONFIG.i18n.legalDefaultTitle;
|
|
3673
|
+
bodyEl.textContent = CONFIG.i18n.legalLoading;
|
|
3512
3674
|
modal.classList.remove('hidden');
|
|
3513
3675
|
try {
|
|
3514
3676
|
const r = await fetch(url);
|
|
3515
3677
|
if (!r.ok) {
|
|
3516
|
-
bodyEl.textContent = '
|
|
3678
|
+
bodyEl.textContent = CONFIG.i18n.legalLoadError.replace('{status}', r.status);
|
|
3517
3679
|
return;
|
|
3518
3680
|
}
|
|
3519
3681
|
const md = await r.text();
|
|
@@ -3523,7 +3685,7 @@ async function openLegalModal(title, url) {
|
|
|
3523
3685
|
bodyEl.textContent = md;
|
|
3524
3686
|
bodyEl.scrollTop = 0;
|
|
3525
3687
|
} catch (err) {
|
|
3526
|
-
bodyEl.textContent = '
|
|
3688
|
+
bodyEl.textContent = CONFIG.i18n.legalNetworkError.replace('{err}', (err && err.message ? err.message : 'unknown'));
|
|
3527
3689
|
}
|
|
3528
3690
|
}
|
|
3529
3691
|
|
|
@@ -3946,20 +4108,20 @@ async function refreshLimitsCard() {
|
|
|
3946
4108
|
if (jj && jj.ok) {
|
|
3947
4109
|
input.dataset.original = String(v);
|
|
3948
4110
|
if (msgEl) {
|
|
3949
|
-
msgEl.textContent = fieldPath + ' = ' + v + ' (
|
|
4111
|
+
msgEl.textContent = fieldPath + ' = ' + v + ' (' + CONFIG.i18n.fbSavedShort + ')';
|
|
3950
4112
|
msgEl.style.color = '#5fb35f';
|
|
3951
4113
|
setTimeout(function () { if (msgEl) msgEl.textContent = ''; }, 2000);
|
|
3952
4114
|
}
|
|
3953
4115
|
} else {
|
|
3954
4116
|
if (msgEl) {
|
|
3955
|
-
msgEl.textContent = jj && jj.error ? String(jj.error) :
|
|
4117
|
+
msgEl.textContent = jj && jj.error ? String(jj.error) : CONFIG.i18n.fbSaveError;
|
|
3956
4118
|
msgEl.style.color = '#d97706';
|
|
3957
4119
|
}
|
|
3958
4120
|
input.value = original || String(f.value);
|
|
3959
4121
|
}
|
|
3960
4122
|
} catch (e) {
|
|
3961
4123
|
if (msgEl) {
|
|
3962
|
-
msgEl.textContent =
|
|
4124
|
+
msgEl.textContent = CONFIG.i18n.fbNetErrorSaving;
|
|
3963
4125
|
msgEl.style.color = '#d97706';
|
|
3964
4126
|
}
|
|
3965
4127
|
}
|
|
@@ -4000,7 +4162,7 @@ async function pairGenerate() {
|
|
|
4000
4162
|
const btn = document.getElementById('set-pair-generate');
|
|
4001
4163
|
const result = document.getElementById('set-pair-result');
|
|
4002
4164
|
const errEl = document.getElementById('set-pair-error');
|
|
4003
|
-
if (btn) { btn.disabled = true; btn.textContent =
|
|
4165
|
+
if (btn) { btn.disabled = true; btn.textContent = CONFIG.i18n.pairGenerating; }
|
|
4004
4166
|
if (errEl) errEl.classList.add('hidden');
|
|
4005
4167
|
try {
|
|
4006
4168
|
const r = await fetch('/api/forge/mobile-pair', { method: 'POST' });
|
|
@@ -4016,25 +4178,25 @@ async function pairGenerate() {
|
|
|
4016
4178
|
if (copyBtn && !copyBtn.__wired) {
|
|
4017
4179
|
copyBtn.__wired = true;
|
|
4018
4180
|
copyBtn.addEventListener('click', function () {
|
|
4019
|
-
try { navigator.clipboard.writeText(codeEl.value); copyBtn.textContent =
|
|
4181
|
+
try { navigator.clipboard.writeText(codeEl.value); copyBtn.textContent = CONFIG.i18n.fbCopied; setTimeout(function () { copyBtn.textContent = CONFIG.i18n.pairCopy; }, 1500); }
|
|
4020
4182
|
catch (e) { codeEl.select(); }
|
|
4021
4183
|
});
|
|
4022
4184
|
}
|
|
4023
4185
|
}
|
|
4024
4186
|
if (exp && j.expires_at) {
|
|
4025
4187
|
const mins = Math.max(0, Math.round((new Date(j.expires_at).getTime() - Date.now()) / 60000));
|
|
4026
|
-
exp.textContent = '
|
|
4188
|
+
exp.textContent = CONFIG.i18n.pairExpiry.replace('{mins}', mins);
|
|
4027
4189
|
}
|
|
4028
4190
|
if (result) result.classList.remove('hidden');
|
|
4029
4191
|
} else if (errEl) {
|
|
4030
|
-
errEl.textContent = (j && j.error ? j.error :
|
|
4192
|
+
errEl.textContent = (j && j.error ? j.error : CONFIG.i18n.pairQrFailed)
|
|
4031
4193
|
+ (j && j.hint ? ' (corre: ' + j.hint + ')' : '');
|
|
4032
4194
|
errEl.classList.remove('hidden');
|
|
4033
4195
|
}
|
|
4034
4196
|
} catch (e) {
|
|
4035
|
-
if (errEl) { errEl.textContent =
|
|
4197
|
+
if (errEl) { errEl.textContent = CONFIG.i18n.pairQrNetError; errEl.classList.remove('hidden'); }
|
|
4036
4198
|
}
|
|
4037
|
-
if (btn) { btn.disabled = false; btn.textContent =
|
|
4199
|
+
if (btn) { btn.disabled = false; btn.textContent = CONFIG.i18n.pairGenerateQr; }
|
|
4038
4200
|
}
|
|
4039
4201
|
|
|
4040
4202
|
/* alpha.59r -- project knowledge graph status card. Reads
|
|
@@ -4145,7 +4307,7 @@ async function refreshBookmarksList() {
|
|
|
4145
4307
|
const j = await r.json();
|
|
4146
4308
|
if (!j || !j.ok || !Array.isArray(j.bookmarks)) return;
|
|
4147
4309
|
if (j.bookmarks.length === 0) {
|
|
4148
|
-
host.innerHTML = '<div class="set-row" style="opacity:0.55;font-size:11px;">
|
|
4310
|
+
host.innerHTML = '<div class="set-row" style="opacity:0.55;font-size:11px;">' + escapeHtml(CONFIG.i18n.hintBookmarksEmpty) + '</div>';
|
|
4149
4311
|
return;
|
|
4150
4312
|
}
|
|
4151
4313
|
host.innerHTML = j.bookmarks.slice(0, 40).map((bm) => {
|
|
@@ -4323,8 +4485,8 @@ function showAuditDetailInline(entry) {
|
|
|
4323
4485
|
copyBtn.onclick = async function () {
|
|
4324
4486
|
try {
|
|
4325
4487
|
await navigator.clipboard.writeText(pretty);
|
|
4326
|
-
copyBtn.textContent =
|
|
4327
|
-
setTimeout(function () { copyBtn.textContent =
|
|
4488
|
+
copyBtn.textContent = CONFIG.i18n.fbCopied;
|
|
4489
|
+
setTimeout(function () { copyBtn.textContent = CONFIG.i18n.auditCopyJson; }, 1200);
|
|
4328
4490
|
} catch (_) { /* swallow */ }
|
|
4329
4491
|
};
|
|
4330
4492
|
}
|
|
@@ -4358,11 +4520,11 @@ function openAuditDetail(entry) {
|
|
|
4358
4520
|
'<div class="audit-detail-title">' + escapeHtml(kind) + ' <span class="a-mode ' + escapeHtml(mode) + '" style="margin-left:6px;">' + escapeHtml(mode) + '</span></div>' +
|
|
4359
4521
|
'<div class="audit-detail-sub">' + escapeHtml(ts) + ' -- ' + escapeHtml(summary) + '</div>' +
|
|
4360
4522
|
'</div>' +
|
|
4361
|
-
'<button type="button" class="audit-detail-close" aria-label="
|
|
4523
|
+
'<button type="button" class="audit-detail-close" aria-label="' + escapeHtml(CONFIG.i18n.closeButton) + '">x</button>' +
|
|
4362
4524
|
'</div>' +
|
|
4363
4525
|
'<pre class="audit-detail-body"></pre>' +
|
|
4364
4526
|
'<div class="audit-detail-actions">' +
|
|
4365
|
-
'<button type="button" class="set-btn" data-audit-copy>
|
|
4527
|
+
'<button type="button" class="set-btn" data-audit-copy>' + escapeHtml(CONFIG.i18n.auditCopyJson) + '</button>' +
|
|
4366
4528
|
'<span class="audit-detail-msg" id="audit-detail-msg"></span>' +
|
|
4367
4529
|
'</div>' +
|
|
4368
4530
|
'</div>';
|
|
@@ -4387,14 +4549,14 @@ function openAuditDetail(entry) {
|
|
|
4387
4549
|
try {
|
|
4388
4550
|
if (navigator && navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
|
|
4389
4551
|
navigator.clipboard.writeText(pretty).then(function () {
|
|
4390
|
-
if (msg) { msg.textContent =
|
|
4552
|
+
if (msg) { msg.textContent = CONFIG.i18n.fbCopied; setTimeout(function () { if (msg) msg.textContent = ''; }, 1800); }
|
|
4391
4553
|
}, function () {
|
|
4392
|
-
if (msg) { msg.textContent =
|
|
4554
|
+
if (msg) { msg.textContent = CONFIG.i18n.fbCopyFailed; }
|
|
4393
4555
|
});
|
|
4394
4556
|
} else if (msg) {
|
|
4395
|
-
msg.textContent =
|
|
4557
|
+
msg.textContent = CONFIG.i18n.fbClipboardUnavailable;
|
|
4396
4558
|
}
|
|
4397
|
-
} catch (_) { if (msg) msg.textContent =
|
|
4559
|
+
} catch (_) { if (msg) msg.textContent = CONFIG.i18n.fbCopyFailed; }
|
|
4398
4560
|
});
|
|
4399
4561
|
}
|
|
4400
4562
|
}
|
|
@@ -4410,7 +4572,7 @@ async function resetLimitsCard() {
|
|
|
4410
4572
|
const j = await r.json();
|
|
4411
4573
|
if (j && j.ok) {
|
|
4412
4574
|
if (msgEl) {
|
|
4413
|
-
msgEl.textContent =
|
|
4575
|
+
msgEl.textContent = CONFIG.i18n.limitsResetApplied;
|
|
4414
4576
|
msgEl.style.color = '#5fb35f';
|
|
4415
4577
|
setTimeout(function () { if (msgEl) msgEl.textContent = ''; }, 2500);
|
|
4416
4578
|
}
|
|
@@ -4470,14 +4632,14 @@ async function refreshDangerZoneToggles() {
|
|
|
4470
4632
|
} else {
|
|
4471
4633
|
cb.checked = !newVal;
|
|
4472
4634
|
if (msgEl) {
|
|
4473
|
-
msgEl.textContent = jj && jj.error ? String(jj.error) :
|
|
4635
|
+
msgEl.textContent = jj && jj.error ? String(jj.error) : CONFIG.i18n.fbSaveError;
|
|
4474
4636
|
msgEl.style.color = '#d97706';
|
|
4475
4637
|
}
|
|
4476
4638
|
}
|
|
4477
4639
|
} catch (_e) {
|
|
4478
4640
|
cb.checked = !newVal;
|
|
4479
4641
|
if (msgEl) {
|
|
4480
|
-
msgEl.textContent =
|
|
4642
|
+
msgEl.textContent = CONFIG.i18n.fbNetError;
|
|
4481
4643
|
msgEl.style.color = '#d97706';
|
|
4482
4644
|
}
|
|
4483
4645
|
}
|
|
@@ -4630,10 +4792,10 @@ async function killShellTab(tabId) {
|
|
|
4630
4792
|
if (tab) tab.content = tab.content + '\\n[CANCELLED by user]\\n';
|
|
4631
4793
|
renderPizarron();
|
|
4632
4794
|
} else {
|
|
4633
|
-
setStatus('
|
|
4795
|
+
setStatus(CONFIG.i18n.panelStatusCancelFail.replace('{reason}', (j && j.reason ? j.reason : 'unknown')), true);
|
|
4634
4796
|
}
|
|
4635
4797
|
} catch (e) {
|
|
4636
|
-
setStatus(
|
|
4798
|
+
setStatus(CONFIG.i18n.panelStatusCancelNetError, true);
|
|
4637
4799
|
}
|
|
4638
4800
|
}
|
|
4639
4801
|
|
|
@@ -4800,27 +4962,27 @@ function nac3OpenApprovalModal(envelope) {
|
|
|
4800
4962
|
|
|
4801
4963
|
const sideEffects = document.createElement('div');
|
|
4802
4964
|
sideEffects.style.cssText = 'font-size:11px;opacity:0.7;margin-bottom:16px;';
|
|
4803
|
-
sideEffects.textContent = '
|
|
4965
|
+
sideEffects.textContent = CONFIG.i18n.nac3Effects.replace('{list}', (Array.isArray(verb.side_effects) ? verb.side_effects.join(', ') : CONFIG.i18n.nac3EffectsUnknown));
|
|
4804
4966
|
|
|
4805
4967
|
const btnRow = document.createElement('div');
|
|
4806
4968
|
btnRow.style.cssText = 'display:flex;gap:8px;justify-content:flex-end;';
|
|
4807
4969
|
|
|
4808
4970
|
const btnCancel = document.createElement('button');
|
|
4809
|
-
btnCancel.textContent =
|
|
4971
|
+
btnCancel.textContent = CONFIG.i18n.nac3Cancel;
|
|
4810
4972
|
btnCancel.setAttribute('data-nac-id', 'yujin.modal.nac3-approval.cancel');
|
|
4811
4973
|
btnCancel.setAttribute('data-nac-role', 'button');
|
|
4812
4974
|
btnCancel.setAttribute('data-nac-action', 'cancel');
|
|
4813
4975
|
btnCancel.style.cssText = 'padding:8px 16px;border-radius:6px;border:1px solid #3a4258;background:transparent;color:inherit;cursor:pointer;font-size:13px;';
|
|
4814
4976
|
|
|
4815
4977
|
const btnReject = document.createElement('button');
|
|
4816
|
-
btnReject.textContent =
|
|
4978
|
+
btnReject.textContent = CONFIG.i18n.nac3Reject;
|
|
4817
4979
|
btnReject.setAttribute('data-nac-id', 'yujin.modal.nac3-approval.reject');
|
|
4818
4980
|
btnReject.setAttribute('data-nac-role', 'button');
|
|
4819
4981
|
btnReject.setAttribute('data-nac-action', 'reject');
|
|
4820
4982
|
btnReject.style.cssText = 'padding:8px 16px;border-radius:6px;border:1px solid #803030;background:#3a1818;color:#f4b8b8;cursor:pointer;font-size:13px;';
|
|
4821
4983
|
|
|
4822
4984
|
const btnApprove = document.createElement('button');
|
|
4823
|
-
btnApprove.textContent = verb.policy === 'destructive' ?
|
|
4985
|
+
btnApprove.textContent = verb.policy === 'destructive' ? CONFIG.i18n.nac3ApproveDestructive : CONFIG.i18n.nac3Approve;
|
|
4824
4986
|
btnApprove.setAttribute('data-nac-id', 'yujin.modal.nac3-approval.approve');
|
|
4825
4987
|
btnApprove.setAttribute('data-nac-role', 'button');
|
|
4826
4988
|
btnApprove.setAttribute('data-nac-action', 'approve');
|
|
@@ -4861,7 +5023,7 @@ function nac3OpenApprovalModal(envelope) {
|
|
|
4861
5023
|
|
|
4862
5024
|
btnApprove.addEventListener('click', async () => {
|
|
4863
5025
|
btnApprove.disabled = true; btnReject.disabled = true;
|
|
4864
|
-
btnApprove.textContent =
|
|
5026
|
+
btnApprove.textContent = CONFIG.i18n.nac3Executing;
|
|
4865
5027
|
let payloadNote = '';
|
|
4866
5028
|
try {
|
|
4867
5029
|
const r = await fetch('/api/forge/nac3-approve', {
|
|
@@ -4995,7 +5157,7 @@ function pizarronCloseItem(id) {
|
|
|
4995
5157
|
if (idx < 0) return;
|
|
4996
5158
|
const it = state.pizarron.items[idx];
|
|
4997
5159
|
if (it.dirty) {
|
|
4998
|
-
if (!confirm('
|
|
5160
|
+
if (!confirm(CONFIG.i18n.panelConfirmTabUnsaved.replace('{title}', it.title))) return;
|
|
4999
5161
|
}
|
|
5000
5162
|
/* Startup tabs (dogfood 2026-06-10) -- si el usuario cierra una
|
|
5001
5163
|
* pestania de startup (o el lienzo Coding), recordarlo: no se
|
|
@@ -5018,7 +5180,7 @@ function pizarronCloseItem(id) {
|
|
|
5018
5180
|
|
|
5019
5181
|
function pizarronClearAll() {
|
|
5020
5182
|
const dirty = state.pizarron.items.find((x) => x.dirty);
|
|
5021
|
-
if (dirty && !confirm(
|
|
5183
|
+
if (dirty && !confirm(CONFIG.i18n.panelConfirmTabsUnsaved)) return;
|
|
5022
5184
|
state.pizarron.items = [];
|
|
5023
5185
|
state.pizarron.activeId = null;
|
|
5024
5186
|
renderPizarron();
|
|
@@ -5028,7 +5190,7 @@ function pizarronStartEdit(id) {
|
|
|
5028
5190
|
const it = state.pizarron.items.find((x) => x.id === id);
|
|
5029
5191
|
if (!it) return;
|
|
5030
5192
|
if (it.kind !== 'code' && it.kind !== 'markdown' && it.kind !== 'document') {
|
|
5031
|
-
setStatus(
|
|
5193
|
+
setStatus(CONFIG.i18n.panelStatusNotEditable, true);
|
|
5032
5194
|
return;
|
|
5033
5195
|
}
|
|
5034
5196
|
it.isEditing = true;
|
|
@@ -5059,22 +5221,22 @@ async function pizarronSaveAs(id) {
|
|
|
5059
5221
|
it.dirty = false;
|
|
5060
5222
|
it.file_path = handle.name; /* solo el nombre; el browser
|
|
5061
5223
|
no expone full path */
|
|
5062
|
-
setStatus('
|
|
5224
|
+
setStatus(CONFIG.i18n.panelStatusSavedAsPicker.replace('{name}', handle.name));
|
|
5063
5225
|
renderPizarron();
|
|
5064
5226
|
return;
|
|
5065
5227
|
} catch (err) {
|
|
5066
5228
|
/* AbortError = el user cancelo el dialog -- silencioso. */
|
|
5067
5229
|
if (err && err.name === 'AbortError') return;
|
|
5068
|
-
setStatus('
|
|
5230
|
+
setStatus(CONFIG.i18n.panelStatusSaveAsFail.replace('{error}', (err && err.message ? err.message : err)), true);
|
|
5069
5231
|
}
|
|
5070
5232
|
}
|
|
5071
5233
|
/* Fallback: prompt con path relativo al projectRoot. */
|
|
5072
5234
|
const initial = it.file_path || 'untitled.txt';
|
|
5073
|
-
const nextPath = prompt(
|
|
5235
|
+
const nextPath = prompt(CONFIG.i18n.panelPromptSaveAs, initial);
|
|
5074
5236
|
if (!nextPath || nextPath.trim() === '') return;
|
|
5075
5237
|
const rel = nextPath.trim();
|
|
5076
5238
|
if (rel.startsWith('/') || rel.includes('..')) {
|
|
5077
|
-
alert(
|
|
5239
|
+
alert(CONFIG.i18n.alertPathRelative);
|
|
5078
5240
|
return;
|
|
5079
5241
|
}
|
|
5080
5242
|
try {
|
|
@@ -5085,17 +5247,17 @@ async function pizarronSaveAs(id) {
|
|
|
5085
5247
|
});
|
|
5086
5248
|
const data = await r.json();
|
|
5087
5249
|
if (!r.ok || !data.ok) {
|
|
5088
|
-
setStatus('
|
|
5250
|
+
setStatus(CONFIG.i18n.panelStatusSaveFail.replace('{error}', (data.error || r.status)), true);
|
|
5089
5251
|
return;
|
|
5090
5252
|
}
|
|
5091
5253
|
it.content = it.editValue;
|
|
5092
5254
|
it.file_path = rel;
|
|
5093
5255
|
it.title = rel.split('/').pop() || rel;
|
|
5094
5256
|
it.dirty = false;
|
|
5095
|
-
setStatus('
|
|
5257
|
+
setStatus(CONFIG.i18n.panelStatusSavedAs.replace('{path}', rel).replace('{bytes}', data.bytes_written));
|
|
5096
5258
|
renderPizarron();
|
|
5097
5259
|
} catch (err) {
|
|
5098
|
-
setStatus('
|
|
5260
|
+
setStatus(CONFIG.i18n.panelStatusNetError.replace('{error}', (err && err.message ? err.message : err)), true);
|
|
5099
5261
|
}
|
|
5100
5262
|
}
|
|
5101
5263
|
|
|
@@ -5106,7 +5268,7 @@ async function pizarronSave(id) {
|
|
|
5106
5268
|
/* Snippet-only edit: persist back to the in-memory item. */
|
|
5107
5269
|
it.content = it.editValue;
|
|
5108
5270
|
it.dirty = false;
|
|
5109
|
-
setStatus(
|
|
5271
|
+
setStatus(CONFIG.i18n.panelStatusSavedTabOnly);
|
|
5110
5272
|
renderPizarron();
|
|
5111
5273
|
return;
|
|
5112
5274
|
}
|
|
@@ -5119,22 +5281,22 @@ async function pizarronSave(id) {
|
|
|
5119
5281
|
});
|
|
5120
5282
|
const data = await r.json();
|
|
5121
5283
|
if (!r.ok || !data.ok) {
|
|
5122
|
-
setStatus('
|
|
5284
|
+
setStatus(CONFIG.i18n.panelStatusSaveFail.replace('{error}', (data.error || r.status)), true);
|
|
5123
5285
|
return;
|
|
5124
5286
|
}
|
|
5125
5287
|
it.content = it.editValue;
|
|
5126
5288
|
it.dirty = false;
|
|
5127
|
-
setStatus('
|
|
5289
|
+
setStatus(CONFIG.i18n.panelStatusSaved.replace('{path}', it.file_path).replace('{bytes}', data.bytes_written));
|
|
5128
5290
|
renderPizarron();
|
|
5129
5291
|
} catch (err) {
|
|
5130
|
-
setStatus('
|
|
5292
|
+
setStatus(CONFIG.i18n.panelStatusNetError.replace('{error}', (err && err.message ? err.message : err)), true);
|
|
5131
5293
|
}
|
|
5132
5294
|
}
|
|
5133
5295
|
|
|
5134
5296
|
function pizarronDiscard(id) {
|
|
5135
5297
|
const it = state.pizarron.items.find((x) => x.id === id);
|
|
5136
5298
|
if (!it) return;
|
|
5137
|
-
if (it.dirty && !confirm(
|
|
5299
|
+
if (it.dirty && !confirm(CONFIG.i18n.panelConfirmDiscardChanges)) return;
|
|
5138
5300
|
it.isEditing = false;
|
|
5139
5301
|
it.editValue = it.content;
|
|
5140
5302
|
it.dirty = false;
|
|
@@ -5147,9 +5309,9 @@ function pizarronReadAloud(id) {
|
|
|
5147
5309
|
/* Strip markdown structure to make it readable, then TTS. */
|
|
5148
5310
|
let txt = it.content;
|
|
5149
5311
|
if (it.kind === 'code') {
|
|
5150
|
-
txt =
|
|
5151
|
-
+ (it.file_path ? '
|
|
5152
|
-
+ '
|
|
5312
|
+
txt = (it.language ? CONFIG.i18n.readAloudCodeLang.replace('{lang}', it.language) : CONFIG.i18n.readAloudCode) + '. '
|
|
5313
|
+
+ (it.file_path ? CONFIG.i18n.readAloudFile.replace('{file}', it.file_path) + '. ' : '')
|
|
5314
|
+
+ CONFIG.i18n.readAloudContent.replace('{content}', txt);
|
|
5153
5315
|
}
|
|
5154
5316
|
playTtsForText(txt);
|
|
5155
5317
|
}
|
|
@@ -5160,8 +5322,8 @@ function pizarronCopy(id) {
|
|
|
5160
5322
|
const value = it.isEditing ? it.editValue : it.content;
|
|
5161
5323
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
5162
5324
|
navigator.clipboard.writeText(value)
|
|
5163
|
-
.then(() => setStatus(
|
|
5164
|
-
.catch(() => setStatus(
|
|
5325
|
+
.then(() => setStatus(CONFIG.i18n.panelStatusCopied))
|
|
5326
|
+
.catch(() => setStatus(CONFIG.i18n.panelStatusCopyFail, true));
|
|
5165
5327
|
}
|
|
5166
5328
|
}
|
|
5167
5329
|
|
|
@@ -5178,11 +5340,11 @@ function renderPizarronCardBody(it) {
|
|
|
5178
5340
|
+ ' <button type="button" class="pz-save" data-pz-save data-pz-item-id="' + it.id + '"'
|
|
5179
5341
|
+ ' data-nac-id="yujin.pizarron.editor.' + it.id + '.save"'
|
|
5180
5342
|
+ ' data-nac-role="action" data-nac-action="save_file"'
|
|
5181
|
-
+ ' title="
|
|
5343
|
+
+ ' title="' + escapeHtml(CONFIG.i18n.tipSaveTo.replace('{path}', it.file_path || CONFIG.i18n.tipSaveToMemory)) + '">' + escapeHtml(CONFIG.i18n.btnSave) + '</button>'
|
|
5182
5344
|
+ ' <button type="button" data-pz-save-as data-pz-item-id="' + it.id + '"'
|
|
5183
5345
|
+ ' data-nac-id="yujin.pizarron.editor.' + it.id + '.save-as"'
|
|
5184
5346
|
+ ' data-nac-role="action" data-nac-action="save_file_as"'
|
|
5185
|
-
+ ' title="
|
|
5347
|
+
+ ' title="' + escapeHtml(CONFIG.i18n.tipSaveAs) + '">' + escapeHtml(CONFIG.i18n.btnSaveAs) + '</button>'
|
|
5186
5348
|
+ ' <button type="button" data-pz-discard data-pz-item-id="' + it.id + '"'
|
|
5187
5349
|
+ ' data-nac-id="yujin.pizarron.editor.' + it.id + '.discard"'
|
|
5188
5350
|
+ ' data-nac-role="action" data-nac-action="discard_changes">Descartar</button>'
|
|
@@ -5242,8 +5404,8 @@ function renderPizarronCardBody(it) {
|
|
|
5242
5404
|
const cmode = codingMode();
|
|
5243
5405
|
return '<div class="pz-body pz-coding-wrap m-' + cmode + '" data-nac-id="yujin.pizarron.item.' + it.id + '.body" data-nac-role="region" tabindex="0">'
|
|
5244
5406
|
+ '<div class="pz-coding-switch" role="tablist">'
|
|
5245
|
-
+ '<button type="button" class="pz-cmode' + (cmode === 'detail' ? ' on' : '') + '" data-pz-coding-mode="detail" title="
|
|
5246
|
-
+ '<button type="button" class="pz-cmode' + (cmode === 'zen' ? ' on' : '') + '" data-pz-coding-mode="zen" title="
|
|
5407
|
+
+ '<button type="button" class="pz-cmode' + (cmode === 'detail' ? ' on' : '') + '" data-pz-coding-mode="detail" title="' + escapeHtml(CONFIG.i18n.tipCodeModeDetail) + '">codewrapper</button>'
|
|
5408
|
+
+ '<button type="button" class="pz-cmode' + (cmode === 'zen' ? ' on' : '') + '" data-pz-coding-mode="zen" title="' + escapeHtml(CONFIG.i18n.tipCodeModeZen) + '">vibecoder</button>'
|
|
5247
5409
|
+ '</div>'
|
|
5248
5410
|
+ (cmode === 'zen'
|
|
5249
5411
|
? '<div class="pz-coding-zen"><canvas data-pz-coding-canvas class="pz-coding-canvas"></canvas><div class="pz-coding-hint" data-pz-coding-hint>esperando a que el brain trabaje...</div></div>'
|
|
@@ -5255,7 +5417,7 @@ function renderPizarronCardBody(it) {
|
|
|
5255
5417
|
if (it.kind === 'tasks') {
|
|
5256
5418
|
return '<div class="pz-body pz-tasks-wrap pz-sumi" data-nac-id="yujin.pizarron.item.' + it.id + '.body" data-nac-role="region" tabindex="0">'
|
|
5257
5419
|
+ '<div class="pz-tasks-new">'
|
|
5258
|
-
+ '<input type="text" class="pz-tasks-input" data-pz-tasks-cmd placeholder="
|
|
5420
|
+
+ '<input type="text" class="pz-tasks-input" data-pz-tasks-cmd placeholder="' + escapeHtml(CONFIG.i18n.tasksCmdPlaceholder) + '" />'
|
|
5259
5421
|
+ '<button type="button" class="pz-tasks-run" data-pz-tasks-run>Lanzar</button>'
|
|
5260
5422
|
+ '</div>'
|
|
5261
5423
|
+ '<div class="pz-tasks-list" data-pz-tasks-list><div class="pz-tasks-empty">cargando...</div></div>'
|
|
@@ -5333,7 +5495,7 @@ function renderPizarron() {
|
|
|
5333
5495
|
const empty = $('#pizarron-empty');
|
|
5334
5496
|
const count = $('#pizarron-count');
|
|
5335
5497
|
if (!tabs || !body || !count) return;
|
|
5336
|
-
count.textContent = state.pizarron.items.length + (state.pizarron.items.length === 1 ?
|
|
5498
|
+
count.textContent = state.pizarron.items.length + ' ' + (state.pizarron.items.length === 1 ? CONFIG.i18n.tabOne : CONFIG.i18n.tabMany);
|
|
5337
5499
|
/* Render tabs */
|
|
5338
5500
|
applySavedTabOrder(); /* PND-137 -- respeta el orden elegido por drag */
|
|
5339
5501
|
const tabsHtml = state.pizarron.items.map((it) => {
|
|
@@ -5351,12 +5513,12 @@ function renderPizarron() {
|
|
|
5351
5513
|
+ '<span class="pz-tab-name">' + escapeHtml(it.title) + '</span>'
|
|
5352
5514
|
+ (showKill
|
|
5353
5515
|
? '<button type="button" class="pz-tab-kill" data-pz-kill data-pz-item-id="' + it.id + '"'
|
|
5354
|
-
+ ' title="
|
|
5516
|
+
+ ' title="' + escapeHtml(CONFIG.i18n.tipCancelShell) + '" aria-label="cancel">⏹</button>'
|
|
5355
5517
|
: '')
|
|
5356
5518
|
+ '<button type="button" class="pz-tab-close" data-pz-close data-pz-item-id="' + it.id + '"'
|
|
5357
5519
|
+ ' data-nac-id="yujin.pizarron.tab.' + it.id + '.close"'
|
|
5358
5520
|
+ ' data-nac-role="action" data-nac-action="close_tab"'
|
|
5359
|
-
+ ' title="
|
|
5521
|
+
+ ' title="' + escapeHtml(CONFIG.i18n.tipCloseTab) + '" aria-label="cerrar">×</button>'
|
|
5360
5522
|
+ '</div>';
|
|
5361
5523
|
}).join('');
|
|
5362
5524
|
/* "+" para reabrir pestanas nativas cerradas (Grafo/Workflow/
|
|
@@ -5365,7 +5527,7 @@ function renderPizarron() {
|
|
|
5365
5527
|
let restoreHtml = '';
|
|
5366
5528
|
if (closedNative.length) {
|
|
5367
5529
|
restoreHtml = '<div class="pz-restore-wrap">'
|
|
5368
|
-
+ '<button type="button" class="pz-restore-btn" data-pz-restore title="
|
|
5530
|
+
+ '<button type="button" class="pz-restore-btn" data-pz-restore title="' + escapeHtml(CONFIG.i18n.tipReopenNativeTabs) + '">+</button>'
|
|
5369
5531
|
+ '<div class="pz-restore-menu hidden" data-pz-restore-menu>'
|
|
5370
5532
|
+ '<div class="pz-restore-head">Reabrir</div>'
|
|
5371
5533
|
+ closedNative.map(function (k) {
|
|
@@ -5379,7 +5541,7 @@ function renderPizarron() {
|
|
|
5379
5541
|
if (state.pizarron.items.length === 0) {
|
|
5380
5542
|
if (empty) empty.style.display = '';
|
|
5381
5543
|
body.innerHTML = '<div class="pizarra-empty" id="pizarron-empty"><div class="kanji-big">板</div>'
|
|
5382
|
-
+ '<p>
|
|
5544
|
+
+ '<p>' + escapeHtml(CONFIG.i18n.hintBoardEmpty) + '</p></div>';
|
|
5383
5545
|
return;
|
|
5384
5546
|
}
|
|
5385
5547
|
const it = pizarronGetActive() || state.pizarron.items[state.pizarron.items.length - 1];
|
|
@@ -5505,7 +5667,7 @@ function renderTasksList(tasks) {
|
|
|
5505
5667
|
+ (t.status === 'running'
|
|
5506
5668
|
? '<button type="button" class="pz-task-kill" data-pz-task-kill="' + escapeHtml(t.id) + '">stop</button>'
|
|
5507
5669
|
: '<button type="button" class="pz-task-log" data-pz-task-log="' + escapeHtml(t.id) + '">log</button>')
|
|
5508
|
-
+ '<button type="button" class="pz-task-del" data-pz-task-del="' + escapeHtml(t.id) + '" title="
|
|
5670
|
+
+ '<button type="button" class="pz-task-del" data-pz-task-del="' + escapeHtml(t.id) + '" title="' + escapeHtml(CONFIG.i18n.tipDeleteTask) + '" aria-label="' + escapeHtml(CONFIG.i18n.tipDeleteTask) + '">×</button>'
|
|
5509
5671
|
+ '</div>'
|
|
5510
5672
|
+ '</div>'
|
|
5511
5673
|
+ '<div class="pz-task-logbox" data-pz-task-logbox="' + escapeHtml(t.id) + '"></div>';
|
|
@@ -7673,7 +7835,7 @@ async function handleSlashCommand(raw) {
|
|
|
7673
7835
|
};
|
|
7674
7836
|
const target = map[arg];
|
|
7675
7837
|
if (!target) {
|
|
7676
|
-
setStatus(
|
|
7838
|
+
setStatus(CONFIG.i18n.panelStatusModeAvailable, true);
|
|
7677
7839
|
return true;
|
|
7678
7840
|
}
|
|
7679
7841
|
try {
|
|
@@ -7684,13 +7846,13 @@ async function handleSlashCommand(raw) {
|
|
|
7684
7846
|
});
|
|
7685
7847
|
const data = await r.json();
|
|
7686
7848
|
if (!data.ok) {
|
|
7687
|
-
setStatus('
|
|
7849
|
+
setStatus(CONFIG.i18n.panelStatusModeChangeFail.replace('{error}', (data.error || 'unknown')), true);
|
|
7688
7850
|
return true;
|
|
7689
7851
|
}
|
|
7690
7852
|
try { document.cookie = 'yf-mode=' + target + '; path=/; max-age=' + (60*60*24*365) + '; SameSite=Lax'; } catch (_) {}
|
|
7691
|
-
setStatus('
|
|
7853
|
+
setStatus(CONFIG.i18n.panelStatusModeChanged.replace('{target}', target), false);
|
|
7692
7854
|
} catch (err) {
|
|
7693
|
-
setStatus('
|
|
7855
|
+
setStatus(CONFIG.i18n.panelStatusGenericError.replace('{error}', String(err && err.message ? err.message : err)), true);
|
|
7694
7856
|
}
|
|
7695
7857
|
return true;
|
|
7696
7858
|
}
|
|
@@ -7703,8 +7865,8 @@ async function handleSlashCommand(raw) {
|
|
|
7703
7865
|
state.brainTierOverride = target;
|
|
7704
7866
|
setStatus(
|
|
7705
7867
|
target === null
|
|
7706
|
-
?
|
|
7707
|
-
: '
|
|
7868
|
+
? CONFIG.i18n.panelStatusBrainTierAuto
|
|
7869
|
+
: CONFIG.i18n.panelStatusBrainTierForced.replace('{target}', target),
|
|
7708
7870
|
false,
|
|
7709
7871
|
);
|
|
7710
7872
|
return true;
|
|
@@ -7754,7 +7916,7 @@ async function vaultRefreshList() {
|
|
|
7754
7916
|
return;
|
|
7755
7917
|
}
|
|
7756
7918
|
const rows = data.slots.map((s) => {
|
|
7757
|
-
let testCell = '<span class="t-pending" title="
|
|
7919
|
+
let testCell = '<span class="t-pending" title="' + escapeHtml(CONFIG.i18n.tipNotTestedYet) + '">--</span>';
|
|
7758
7920
|
if (s.last_test_ok === true) testCell = '<span class="t-ok" title="tested ok at ' + escapeHtml(s.last_test_at || '') + '">ok</span>';
|
|
7759
7921
|
else if (s.last_test_ok === false) testCell = '<span class="t-fail" title="tested fail at ' + escapeHtml(s.last_test_at || '') + '">fail</span>';
|
|
7760
7922
|
const expiryCell = s.expiry ? '<span class="t-exp" title="expires ' + escapeHtml(s.expiry) + '">exp</span>' : '';
|
|
@@ -7825,7 +7987,7 @@ async function vaultAdd() {
|
|
|
7825
7987
|
}
|
|
7826
7988
|
}
|
|
7827
7989
|
async function vaultRemove(slot) {
|
|
7828
|
-
if (!confirm('
|
|
7990
|
+
if (!confirm(CONFIG.i18n.panelConfirmRemoveSlot.replace('{slot}', slot))) return;
|
|
7829
7991
|
try {
|
|
7830
7992
|
const r = await fetch('/api/vault/remove', {
|
|
7831
7993
|
method: 'POST',
|
|
@@ -7914,9 +8076,8 @@ function cycleTtsProvider() {
|
|
|
7914
8076
|
function updateTtsProviderButtons() {
|
|
7915
8077
|
const p = ttsProvider();
|
|
7916
8078
|
document.querySelectorAll('[data-tts-provider-btn]').forEach((b) => {
|
|
7917
|
-
b.textContent = p === 'browser' ?
|
|
7918
|
-
b.title =
|
|
7919
|
-
+ 'nativo, voces del SO) y Google Cloud TTS (server-side).';
|
|
8079
|
+
b.textContent = p === 'browser' ? CONFIG.i18n.ttsProviderBrowser : CONFIG.i18n.ttsProviderGoogle;
|
|
8080
|
+
b.title = CONFIG.i18n.ttsProviderTitle;
|
|
7920
8081
|
});
|
|
7921
8082
|
}
|
|
7922
8083
|
|
|
@@ -7940,13 +8101,12 @@ function setSttProvider(p) {
|
|
|
7940
8101
|
}
|
|
7941
8102
|
function updateSttProviderButtons() {
|
|
7942
8103
|
const p = sttProvider();
|
|
7943
|
-
const label = p === 'browser' ?
|
|
7944
|
-
: p === 'google' ?
|
|
7945
|
-
:
|
|
8104
|
+
const label = p === 'browser' ? CONFIG.i18n.sttProviderBrowser
|
|
8105
|
+
: p === 'google' ? CONFIG.i18n.sttProviderGoogle
|
|
8106
|
+
: CONFIG.i18n.sttProviderWhisper;
|
|
7946
8107
|
document.querySelectorAll('[data-stt-provider-btn]').forEach((b) => {
|
|
7947
8108
|
b.textContent = label;
|
|
7948
|
-
b.title =
|
|
7949
|
-
+ 'Google Cloud STT (server-side, BYOK) o Whisper (server-side, BYOK).';
|
|
8109
|
+
b.title = CONFIG.i18n.sttProviderTitle;
|
|
7950
8110
|
});
|
|
7951
8111
|
}
|
|
7952
8112
|
function cycleSttProvider() {
|
|
@@ -8004,10 +8164,9 @@ function updateTtsVoiceButtons() {
|
|
|
8004
8164
|
const meta = TTS_VOICES.find((v) => v.id === cur) || TTS_VOICES[0];
|
|
8005
8165
|
document.querySelectorAll('[data-tts-voice-btn]').forEach((b) => {
|
|
8006
8166
|
b.textContent = meta.persona + ' [' + meta.style + ']';
|
|
8007
|
-
b.title =
|
|
8008
|
-
|
|
8009
|
-
|
|
8010
|
-
+ 'natural espaniol (aspira s finales).';
|
|
8167
|
+
b.title = CONFIG.i18n.ttsVoiceTitle
|
|
8168
|
+
.replace('{id}', meta.id).replace('{persona}', meta.persona)
|
|
8169
|
+
.replace('{gender}', meta.gender).replace('{style}', meta.style);
|
|
8011
8170
|
});
|
|
8012
8171
|
}
|
|
8013
8172
|
|
|
@@ -8364,18 +8523,18 @@ function brainConfigBind() {
|
|
|
8364
8523
|
radio.addEventListener('change', async () => {
|
|
8365
8524
|
if (!radio.checked) return;
|
|
8366
8525
|
const note = document.querySelector('[data-brain-mode-note]');
|
|
8367
|
-
const modeLabel = radio.value === 'plan' ?
|
|
8368
|
-
const data = await brainConfigSave({ anthropic_mode: radio.value }, '
|
|
8526
|
+
const modeLabel = radio.value === 'plan' ? CONFIG.i18n.brainModePlan : CONFIG.i18n.brainModeConsole;
|
|
8527
|
+
const data = await brainConfigSave({ anthropic_mode: radio.value }, CONFIG.i18n.brainApplying.replace('{mode}', modeLabel));
|
|
8369
8528
|
/* Hot-swap (server applies live now). Only show the restart hint if
|
|
8370
8529
|
* the swap did NOT apply live (e.g. test client). */
|
|
8371
8530
|
const live = data && data.applied_live;
|
|
8372
8531
|
if (note) {
|
|
8373
8532
|
note.hidden = false;
|
|
8374
8533
|
note.textContent = live
|
|
8375
|
-
?
|
|
8376
|
-
:
|
|
8534
|
+
? CONFIG.i18n.brainApplied.replace('{mode}', modeLabel)
|
|
8535
|
+
: CONFIG.i18n.brainReopenHint;
|
|
8377
8536
|
}
|
|
8378
|
-
if (live) brainStatus(
|
|
8537
|
+
if (live) brainStatus(CONFIG.i18n.brainActive.replace('{mode}', modeLabel));
|
|
8379
8538
|
});
|
|
8380
8539
|
});
|
|
8381
8540
|
const ecoToggle = document.querySelector('[data-brain-eco-enabled]');
|
|
@@ -8493,7 +8652,7 @@ async function federationLoad() {
|
|
|
8493
8652
|
if (!note) {
|
|
8494
8653
|
const span = document.createElement('span');
|
|
8495
8654
|
span.className = 'set-status'; span.setAttribute('data-fed-invite-note', '');
|
|
8496
|
-
span.textContent =
|
|
8655
|
+
span.textContent = CONFIG.i18n.licLendNeedsLicense;
|
|
8497
8656
|
inviteRow.appendChild(span);
|
|
8498
8657
|
}
|
|
8499
8658
|
}
|
|
@@ -8516,7 +8675,7 @@ async function federationLoad() {
|
|
|
8516
8675
|
await fedBrainModelsPopulate(d.host_brain.provider, d.host_brain.model);
|
|
8517
8676
|
}
|
|
8518
8677
|
} catch (err) {
|
|
8519
|
-
fedStatus('
|
|
8678
|
+
fedStatus(CONFIG.i18n.licErrorWithCode.replace('{code}', (err && err.message ? err.message : String(err))));
|
|
8520
8679
|
}
|
|
8521
8680
|
}
|
|
8522
8681
|
|
|
@@ -8572,7 +8731,7 @@ async function fedBrainSave() {
|
|
|
8572
8731
|
if (!r.ok || !d.ok) { fedStatus('no pude guardar el brain: ' + (d.error || ('HTTP ' + r.status))); return; }
|
|
8573
8732
|
fedStatus('brain de federacion: ' + provider + (model ? ' / ' + model : ''));
|
|
8574
8733
|
} catch (err) {
|
|
8575
|
-
fedStatus('
|
|
8734
|
+
fedStatus(CONFIG.i18n.licErrorWithCode.replace('{code}', (err && err.message ? err.message : String(err))));
|
|
8576
8735
|
}
|
|
8577
8736
|
}
|
|
8578
8737
|
|
|
@@ -8595,7 +8754,7 @@ function federationBind() {
|
|
|
8595
8754
|
fedStatus('conectado a ' + d.parent_handle + '. El proximo turno usa su brain.');
|
|
8596
8755
|
await federationLoad();
|
|
8597
8756
|
} catch (err) {
|
|
8598
|
-
fedStatus('
|
|
8757
|
+
fedStatus(CONFIG.i18n.licErrorWithCode.replace('{code}', (err && err.message ? err.message : String(err))));
|
|
8599
8758
|
}
|
|
8600
8759
|
});
|
|
8601
8760
|
}
|
|
@@ -8610,7 +8769,7 @@ function federationBind() {
|
|
|
8610
8769
|
fedStatus('desconectado. Volves a tu brain local.');
|
|
8611
8770
|
await federationLoad();
|
|
8612
8771
|
} catch (err) {
|
|
8613
|
-
fedStatus('
|
|
8772
|
+
fedStatus(CONFIG.i18n.licErrorWithCode.replace('{code}', (err && err.message ? err.message : String(err))));
|
|
8614
8773
|
}
|
|
8615
8774
|
});
|
|
8616
8775
|
}
|
|
@@ -8632,7 +8791,7 @@ function federationBind() {
|
|
|
8632
8791
|
const mins = Math.round((d.expires_in || 1800) / 60);
|
|
8633
8792
|
fedStatus('codigo de 1 solo uso, vence en ' + mins + ' min. Compartilo y deja "Hospedar" activo.');
|
|
8634
8793
|
} catch (err) {
|
|
8635
|
-
fedStatus('
|
|
8794
|
+
fedStatus(CONFIG.i18n.licErrorWithCode.replace('{code}', (err && err.message ? err.message : String(err))));
|
|
8636
8795
|
}
|
|
8637
8796
|
});
|
|
8638
8797
|
}
|
|
@@ -8670,7 +8829,7 @@ function federationBind() {
|
|
|
8670
8829
|
: 'host detenido.');
|
|
8671
8830
|
} catch (err) {
|
|
8672
8831
|
hostChk.checked = !enabled;
|
|
8673
|
-
fedStatus('
|
|
8832
|
+
fedStatus(CONFIG.i18n.licErrorWithCode.replace('{code}', (err && err.message ? err.message : String(err))));
|
|
8674
8833
|
}
|
|
8675
8834
|
});
|
|
8676
8835
|
}
|
|
@@ -8699,7 +8858,7 @@ async function refreshKeysStatus() {
|
|
|
8699
8858
|
const r = await fetch('/api/forge/keys-status');
|
|
8700
8859
|
const data = await r.json();
|
|
8701
8860
|
if (!r.ok || !data.ok) {
|
|
8702
|
-
if (licEl) licEl.textContent = '
|
|
8861
|
+
if (licEl) licEl.textContent = CONFIG.i18n.licErrorWithCode.replace('{code}', (data.error || r.status));
|
|
8703
8862
|
return;
|
|
8704
8863
|
}
|
|
8705
8864
|
const k = data.keys || {};
|
|
@@ -8763,7 +8922,7 @@ async function refreshKeysStatus() {
|
|
|
8763
8922
|
syncLicenseFromPanel({ silent: true });
|
|
8764
8923
|
}
|
|
8765
8924
|
} catch (err) {
|
|
8766
|
-
if (licEl) licEl.textContent = '
|
|
8925
|
+
if (licEl) licEl.textContent = CONFIG.i18n.licErrorWithCode.replace('{code}', (err && err.message ? err.message : 'unknown'));
|
|
8767
8926
|
}
|
|
8768
8927
|
}
|
|
8769
8928
|
|
|
@@ -8784,13 +8943,13 @@ async function connectLicenseHandle() {
|
|
|
8784
8943
|
if (!handle || handle.indexOf('@') < 1) {
|
|
8785
8944
|
msg.style.display = '';
|
|
8786
8945
|
msg.style.color = '#ff8b7a';
|
|
8787
|
-
msg.textContent =
|
|
8946
|
+
msg.textContent = CONFIG.i18n.licEmailInvalid;
|
|
8788
8947
|
return;
|
|
8789
8948
|
}
|
|
8790
|
-
if (btn) { btn.disabled = true; btn.textContent =
|
|
8949
|
+
if (btn) { btn.disabled = true; btn.textContent = CONFIG.i18n.licConnecting; }
|
|
8791
8950
|
msg.style.display = '';
|
|
8792
8951
|
msg.style.color = '';
|
|
8793
|
-
msg.textContent =
|
|
8952
|
+
msg.textContent = CONFIG.i18n.licSavingEmail;
|
|
8794
8953
|
try {
|
|
8795
8954
|
const r = await fetch('/api/forge/license-handle', {
|
|
8796
8955
|
method: 'POST',
|
|
@@ -8800,19 +8959,19 @@ async function connectLicenseHandle() {
|
|
|
8800
8959
|
const data = await r.json();
|
|
8801
8960
|
if (!data.ok) {
|
|
8802
8961
|
msg.style.color = '#ff8b7a';
|
|
8803
|
-
msg.textContent =
|
|
8804
|
-
|
|
8805
|
-
|
|
8962
|
+
msg.textContent = CONFIG.i18n.licSyncFailedRetry
|
|
8963
|
+
.replace('{err}', (data.error || 'unknown'))
|
|
8964
|
+
.replace('{http}', (data.http_status ? ' (HTTP ' + data.http_status + ')' : ''));
|
|
8806
8965
|
} else {
|
|
8807
8966
|
const plan = data.local_plan_after || 'none';
|
|
8808
8967
|
msg.style.color = '#6dff8b';
|
|
8809
|
-
msg.textContent = '
|
|
8968
|
+
msg.textContent = CONFIG.i18n.licPlanOk.replace('{plan}', plan);
|
|
8810
8969
|
}
|
|
8811
8970
|
} catch (err) {
|
|
8812
8971
|
msg.style.color = '#ff8b7a';
|
|
8813
|
-
msg.textContent = '
|
|
8972
|
+
msg.textContent = CONFIG.i18n.licNetError.replace('{err}', (err && err.message ? err.message : 'unknown'));
|
|
8814
8973
|
} finally {
|
|
8815
|
-
if (btn) { btn.disabled = false; btn.textContent =
|
|
8974
|
+
if (btn) { btn.disabled = false; btn.textContent = CONFIG.i18n.licConnect; }
|
|
8816
8975
|
/* Always refresh -- if handle saved, the connect-block hides
|
|
8817
8976
|
* and the sync row appears. */
|
|
8818
8977
|
refreshKeysStatus();
|
|
@@ -8827,10 +8986,10 @@ async function syncLicenseFromPanel(opts) {
|
|
|
8827
8986
|
const btn = document.getElementById('set-license-sync-btn');
|
|
8828
8987
|
const msgRow = document.getElementById('set-license-sync-msg-row');
|
|
8829
8988
|
const msgEl = document.getElementById('set-license-sync-msg');
|
|
8830
|
-
if (btn) { btn.disabled = true; btn.textContent =
|
|
8989
|
+
if (btn) { btn.disabled = true; btn.textContent = CONFIG.i18n.licSyncing; }
|
|
8831
8990
|
if (msgRow && msgEl && !silent) {
|
|
8832
8991
|
msgRow.style.display = '';
|
|
8833
|
-
msgEl.textContent =
|
|
8992
|
+
msgEl.textContent = CONFIG.i18n.licQuerying;
|
|
8834
8993
|
msgEl.style.color = '';
|
|
8835
8994
|
}
|
|
8836
8995
|
try {
|
|
@@ -8839,8 +8998,9 @@ async function syncLicenseFromPanel(opts) {
|
|
|
8839
8998
|
if (!data.ok) {
|
|
8840
8999
|
if (msgRow && msgEl) {
|
|
8841
9000
|
msgRow.style.display = '';
|
|
8842
|
-
msgEl.textContent =
|
|
8843
|
-
|
|
9001
|
+
msgEl.textContent = CONFIG.i18n.licSyncError
|
|
9002
|
+
.replace('{err}', (data.error || 'unknown'))
|
|
9003
|
+
.replace('{http}', (data.http_status ? ' (HTTP ' + data.http_status + ')' : ''));
|
|
8844
9004
|
msgEl.style.color = '#ff8b7a';
|
|
8845
9005
|
}
|
|
8846
9006
|
return;
|
|
@@ -8849,8 +9009,8 @@ async function syncLicenseFromPanel(opts) {
|
|
|
8849
9009
|
const before = data.local_plan_before || 'none';
|
|
8850
9010
|
const after = data.local_plan_after || 'none';
|
|
8851
9011
|
msgEl.textContent = before === after
|
|
8852
|
-
? '
|
|
8853
|
-
: '
|
|
9012
|
+
? CONFIG.i18n.licPlanUnchanged.replace('{plan}', after)
|
|
9013
|
+
: CONFIG.i18n.licPlanUpdated.replace('{before}', before).replace('{after}', after);
|
|
8854
9014
|
msgEl.style.color = '#6dff8b';
|
|
8855
9015
|
}
|
|
8856
9016
|
/* Refresh the keys status row to show the new plan. */
|
|
@@ -8858,11 +9018,13 @@ async function syncLicenseFromPanel(opts) {
|
|
|
8858
9018
|
} catch (err) {
|
|
8859
9019
|
if (msgRow && msgEl) {
|
|
8860
9020
|
msgRow.style.display = '';
|
|
8861
|
-
msgEl.textContent =
|
|
9021
|
+
msgEl.textContent = CONFIG.i18n.licSyncError
|
|
9022
|
+
.replace('{err}', (err && err.message ? err.message : 'unknown'))
|
|
9023
|
+
.replace('{http}', '');
|
|
8862
9024
|
msgEl.style.color = '#ff8b7a';
|
|
8863
9025
|
}
|
|
8864
9026
|
} finally {
|
|
8865
|
-
if (btn) { btn.disabled = false; btn.textContent =
|
|
9027
|
+
if (btn) { btn.disabled = false; btn.textContent = CONFIG.i18n.licSyncWithPolar; }
|
|
8866
9028
|
}
|
|
8867
9029
|
}
|
|
8868
9030
|
|
|
@@ -8893,11 +9055,11 @@ async function saveKeyEditor() {
|
|
|
8893
9055
|
if (!input || !msg || !_keyEditorSlot) return;
|
|
8894
9056
|
const value = input.value.trim();
|
|
8895
9057
|
if (value.length < 8) {
|
|
8896
|
-
msg.textContent =
|
|
9058
|
+
msg.textContent = CONFIG.i18n.keyEditorTooShort;
|
|
8897
9059
|
msg.className = 'ke-msg error';
|
|
8898
9060
|
return;
|
|
8899
9061
|
}
|
|
8900
|
-
msg.textContent =
|
|
9062
|
+
msg.textContent = CONFIG.i18n.fbSaving;
|
|
8901
9063
|
msg.className = 'ke-msg';
|
|
8902
9064
|
try {
|
|
8903
9065
|
const r = await fetch('/api/forge/keys-set', {
|
|
@@ -8907,15 +9069,15 @@ async function saveKeyEditor() {
|
|
|
8907
9069
|
});
|
|
8908
9070
|
const data = await r.json();
|
|
8909
9071
|
if (!r.ok || !data.ok) {
|
|
8910
|
-
msg.textContent = '
|
|
9072
|
+
msg.textContent = CONFIG.i18n.fbErrorPrefix.replace('{detail}', (data.error || r.status));
|
|
8911
9073
|
msg.className = 'ke-msg error';
|
|
8912
9074
|
return;
|
|
8913
9075
|
}
|
|
8914
|
-
msg.textContent =
|
|
9076
|
+
msg.textContent = CONFIG.i18n.fbSaved;
|
|
8915
9077
|
msg.className = 'ke-msg success';
|
|
8916
9078
|
setTimeout(() => { closeKeyEditor(); refreshKeysStatus(); }, 600);
|
|
8917
9079
|
} catch (err) {
|
|
8918
|
-
msg.textContent = '
|
|
9080
|
+
msg.textContent = CONFIG.i18n.fbErrorPrefix.replace('{detail}', (err && err.message ? err.message : 'unknown'));
|
|
8919
9081
|
msg.className = 'ke-msg error';
|
|
8920
9082
|
}
|
|
8921
9083
|
}
|
|
@@ -9062,11 +9224,11 @@ async function micToggle(button) {
|
|
|
9062
9224
|
return _micToggleWebSpeech(button);
|
|
9063
9225
|
}
|
|
9064
9226
|
if (provider === 'browser' && !_webSpeechAvailable()) {
|
|
9065
|
-
setStatus(
|
|
9227
|
+
setStatus(CONFIG.i18n.panelVoiceSttFallback);
|
|
9066
9228
|
/* Fall through to MediaRecorder path. */
|
|
9067
9229
|
}
|
|
9068
9230
|
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
9069
|
-
setStatus(
|
|
9231
|
+
setStatus(CONFIG.i18n.panelVoiceNoMicSupport, true);
|
|
9070
9232
|
return;
|
|
9071
9233
|
}
|
|
9072
9234
|
try {
|
|
@@ -9280,12 +9442,12 @@ async function micToggle(button) {
|
|
|
9280
9442
|
}
|
|
9281
9443
|
const secLeft = Math.max(0, Math.ceil((stopAt - Date.now()) / 1000));
|
|
9282
9444
|
if (secLeft <= 10) {
|
|
9283
|
-
setStatus('
|
|
9445
|
+
setStatus(CONFIG.i18n.panelVoiceRecordingCountdown.replace('{n}', secLeft));
|
|
9284
9446
|
}
|
|
9285
9447
|
if (secLeft <= 0) {
|
|
9286
9448
|
clearInterval(countdown);
|
|
9287
9449
|
try { mediaRecorder.stop(); } catch { /* ignore */ }
|
|
9288
|
-
setStatus(
|
|
9450
|
+
setStatus(CONFIG.i18n.panelVoiceAutoStopped, true);
|
|
9289
9451
|
}
|
|
9290
9452
|
}, 1000);
|
|
9291
9453
|
mediaRecorder.onstop = async () => {
|
|
@@ -9295,7 +9457,7 @@ async function micToggle(button) {
|
|
|
9295
9457
|
mediaRecorder = null;
|
|
9296
9458
|
if (blob.size === 0) {
|
|
9297
9459
|
setMicButtonsState('idle');
|
|
9298
|
-
setStatus(
|
|
9460
|
+
setStatus(CONFIG.i18n.panelVoiceNoAudio, true);
|
|
9299
9461
|
return;
|
|
9300
9462
|
}
|
|
9301
9463
|
/* Client-side size guard. 4 MB ~ 60s of WebM/Opus at
|
|
@@ -9303,7 +9465,7 @@ async function micToggle(button) {
|
|
|
9303
9465
|
Refuse before even hitting the network. */
|
|
9304
9466
|
if (blob.size > 4_000_000) {
|
|
9305
9467
|
setMicButtonsState('idle');
|
|
9306
|
-
setStatus(
|
|
9468
|
+
setStatus(CONFIG.i18n.panelVoiceAudioTooLong, true);
|
|
9307
9469
|
return;
|
|
9308
9470
|
}
|
|
9309
9471
|
setMicButtonsState('processing');
|
|
@@ -9343,7 +9505,7 @@ async function micToggle(button) {
|
|
|
9343
9505
|
}
|
|
9344
9506
|
const text = (data.transcript && data.transcript.text) || '';
|
|
9345
9507
|
if (!text) {
|
|
9346
|
-
setStatus(
|
|
9508
|
+
setStatus(CONFIG.i18n.panelVoiceDidNotCatch, true);
|
|
9347
9509
|
return;
|
|
9348
9510
|
}
|
|
9349
9511
|
/* Mark this turn as voice-originated so playTtsForText
|
|
@@ -9376,8 +9538,7 @@ async function micToggle(button) {
|
|
|
9376
9538
|
setMicButtonsState('recording');
|
|
9377
9539
|
setStatus('');
|
|
9378
9540
|
} catch (err) {
|
|
9379
|
-
setStatus('
|
|
9380
|
-
+ String(err && err.message ? err.message : err), true);
|
|
9541
|
+
setStatus(CONFIG.i18n.panelVoiceMicDenied.replace('{error}', String(err && err.message ? err.message : err)), true);
|
|
9381
9542
|
}
|
|
9382
9543
|
}
|
|
9383
9544
|
|
|
@@ -9571,7 +9732,7 @@ async function _fetchTtsChunk(text) {
|
|
|
9571
9732
|
});
|
|
9572
9733
|
if (!r.ok) {
|
|
9573
9734
|
const data = await r.json().catch(() => ({}));
|
|
9574
|
-
setStatus('
|
|
9735
|
+
setStatus(CONFIG.i18n.panelVoiceReplyFailed.replace('{err}', (data.error || ('HTTP ' + r.status))), true);
|
|
9575
9736
|
return null;
|
|
9576
9737
|
}
|
|
9577
9738
|
const ab = await r.arrayBuffer();
|
|
@@ -9579,7 +9740,7 @@ async function _fetchTtsChunk(text) {
|
|
|
9579
9740
|
const blob = new Blob([ab], { type: contentType });
|
|
9580
9741
|
return URL.createObjectURL(blob);
|
|
9581
9742
|
} catch (err) {
|
|
9582
|
-
setStatus('
|
|
9743
|
+
setStatus(CONFIG.i18n.panelVoiceReplyError.replace('{err}', String(err && err.message ? err.message : err)), true);
|
|
9583
9744
|
return null;
|
|
9584
9745
|
}
|
|
9585
9746
|
}
|
|
@@ -9593,7 +9754,7 @@ function _playChunkUrl(url) {
|
|
|
9593
9754
|
audio.addEventListener('ended', done, { once: true });
|
|
9594
9755
|
audio.addEventListener('error', done, { once: true });
|
|
9595
9756
|
audio.play().catch((err) => {
|
|
9596
|
-
setStatus('
|
|
9757
|
+
setStatus(CONFIG.i18n.panelStatusAudioPlayError.replace('{error}', String(err && err.message ? err.message : err)), true);
|
|
9597
9758
|
done();
|
|
9598
9759
|
});
|
|
9599
9760
|
});
|
|
@@ -9617,7 +9778,7 @@ function _playChunkUrl(url) {
|
|
|
9617
9778
|
*/
|
|
9618
9779
|
async function ingestSpecFile(file) {
|
|
9619
9780
|
if (!file) return;
|
|
9620
|
-
setStatus('
|
|
9781
|
+
setStatus(CONFIG.i18n.panelStatusReading.replace('{filename}', file.name));
|
|
9621
9782
|
/* Render an in-progress system message so the user sees
|
|
9622
9783
|
immediate feedback. */
|
|
9623
9784
|
state.messages.push({
|
|
@@ -9638,10 +9799,10 @@ async function ingestSpecFile(file) {
|
|
|
9638
9799
|
const data = await r.json();
|
|
9639
9800
|
if (!r.ok || !data.ok) {
|
|
9640
9801
|
const msg = data && data.error ? data.error : ('HTTP ' + r.status);
|
|
9641
|
-
setStatus('
|
|
9802
|
+
setStatus(CONFIG.i18n.panelStatusIngestFailed.replace('{error}', msg), true);
|
|
9642
9803
|
state.messages.push({
|
|
9643
9804
|
role: 'assistant',
|
|
9644
|
-
content: '
|
|
9805
|
+
content: CONFIG.i18n.panelStatusReadFailed.replace('{error}', msg),
|
|
9645
9806
|
});
|
|
9646
9807
|
renderStream();
|
|
9647
9808
|
return;
|
|
@@ -9674,7 +9835,7 @@ async function ingestSpecFile(file) {
|
|
|
9674
9835
|
renderTrace();
|
|
9675
9836
|
setStatus('');
|
|
9676
9837
|
} catch (err) {
|
|
9677
|
-
setStatus('
|
|
9838
|
+
setStatus(CONFIG.i18n.panelStatusIngestFailed.replace('{error}', String(err && err.message ? err.message : err)), true);
|
|
9678
9839
|
}
|
|
9679
9840
|
}
|
|
9680
9841
|
|
|
@@ -9824,11 +9985,11 @@ function closeMicForTts() {
|
|
|
9824
9985
|
if (_webSpeechRecognition) {
|
|
9825
9986
|
try { _webSpeechRecognition.abort(); } catch (_) {}
|
|
9826
9987
|
_webSpeechRecognition = null;
|
|
9827
|
-
setStatus(
|
|
9988
|
+
setStatus(CONFIG.i18n.panelVoiceMicClosedHalfDuplex);
|
|
9828
9989
|
}
|
|
9829
9990
|
if (mediaRecorder && mediaRecorder.state === 'recording') {
|
|
9830
9991
|
try { mediaRecorder.stop(); } catch { /* ignore */ }
|
|
9831
|
-
setStatus(
|
|
9992
|
+
setStatus(CONFIG.i18n.panelVoiceMicClosedHalfDuplex);
|
|
9832
9993
|
}
|
|
9833
9994
|
}
|
|
9834
9995
|
|
|
@@ -9848,11 +10009,11 @@ async function _micToggleWebSpeech(button) {
|
|
|
9848
10009
|
} catch (err) {
|
|
9849
10010
|
const name = (err && err.name) ? String(err.name) : '';
|
|
9850
10011
|
if (name === 'NotAllowedError' || name === 'SecurityError') {
|
|
9851
|
-
setStatus(
|
|
10012
|
+
setStatus(CONFIG.i18n.panelVoiceMicBlocked, true);
|
|
9852
10013
|
} else if (name === 'NotFoundError' || name === 'OverconstrainedError') {
|
|
9853
|
-
setStatus(
|
|
10014
|
+
setStatus(CONFIG.i18n.panelVoiceNoMicDevice, true);
|
|
9854
10015
|
} else {
|
|
9855
|
-
setStatus('
|
|
10016
|
+
setStatus(CONFIG.i18n.panelVoiceMicOpenFail.replace('{error}', (name || (err && err.message) || 'error')), true);
|
|
9856
10017
|
}
|
|
9857
10018
|
setMicButtonsState('idle');
|
|
9858
10019
|
return;
|
|
@@ -9895,8 +10056,8 @@ async function _micToggleWebSpeech(button) {
|
|
|
9895
10056
|
try { rec.stop(); } catch (_) {}
|
|
9896
10057
|
}, SILENCE_MS);
|
|
9897
10058
|
};
|
|
9898
|
-
rec.onaudiostart = () => { audioStarted = true; setStatus(
|
|
9899
|
-
rec.onspeechstart = () => { speechStarted = true; setStatus(
|
|
10059
|
+
rec.onaudiostart = () => { audioStarted = true; setStatus(CONFIG.i18n.panelVoiceMicOpenListening); };
|
|
10060
|
+
rec.onspeechstart = () => { speechStarted = true; setStatus(CONFIG.i18n.panelVoiceListening); };
|
|
9900
10061
|
rec.onresult = (e) => {
|
|
9901
10062
|
interim = '';
|
|
9902
10063
|
for (let i = e.resultIndex; i < e.results.length; i += 1) {
|
|
@@ -9905,7 +10066,7 @@ async function _micToggleWebSpeech(button) {
|
|
|
9905
10066
|
else interim += r[0].transcript;
|
|
9906
10067
|
}
|
|
9907
10068
|
if (interim || finalText) gotAnyResult = true;
|
|
9908
|
-
if (interim) setStatus('
|
|
10069
|
+
if (interim) setStatus(CONFIG.i18n.panelVoiceListeningInterim.replace('{text}', interim));
|
|
9909
10070
|
/* Only reset the silence countdown when something actually
|
|
9910
10071
|
* changed since the last frame. A repeated interim is Chrome
|
|
9911
10072
|
* keeping the channel open on background noise -- NOT user
|
|
@@ -9934,39 +10095,19 @@ async function _micToggleWebSpeech(button) {
|
|
|
9934
10095
|
break;
|
|
9935
10096
|
case 'not-allowed':
|
|
9936
10097
|
case 'service-not-allowed':
|
|
9937
|
-
setStatus(
|
|
9938
|
-
'Microfono bloqueado por el navegador. Hace click en el icono '
|
|
9939
|
-
+ 'del candado en la barra de URL y permiti microfono, despues '
|
|
9940
|
-
+ 'recarga la pagina. O cambia STT a "google" / "whisper" en '
|
|
9941
|
-
+ 'la configuracion si tenes la API key.',
|
|
9942
|
-
true,
|
|
9943
|
-
);
|
|
10098
|
+
setStatus(CONFIG.i18n.panelVoiceMicBlockedBrowser, true);
|
|
9944
10099
|
break;
|
|
9945
10100
|
case 'audio-capture':
|
|
9946
|
-
setStatus(
|
|
9947
|
-
'No detecto microfono fisico. Conecta uno o cambia STT a '
|
|
9948
|
-
+ '"google" / "whisper" en la configuracion.',
|
|
9949
|
-
true,
|
|
9950
|
-
);
|
|
10101
|
+
setStatus(CONFIG.i18n.panelVoiceNoMicPhysical, true);
|
|
9951
10102
|
break;
|
|
9952
10103
|
case 'network':
|
|
9953
|
-
setStatus(
|
|
9954
|
-
'Web Speech requiere conexion a internet (Chrome usa Google '
|
|
9955
|
-
+ 'detras). Probate sin conexion intermitente o cambia STT '
|
|
9956
|
-
+ 'a "whisper" si tenes API key.',
|
|
9957
|
-
true,
|
|
9958
|
-
);
|
|
10104
|
+
setStatus(CONFIG.i18n.panelVoiceNetworkRequired, true);
|
|
9959
10105
|
break;
|
|
9960
10106
|
case 'language-not-supported':
|
|
9961
|
-
setStatus(
|
|
9962
|
-
'Tu navegador no soporta este idioma para STT. Cambia el '
|
|
9963
|
-
+ 'idioma del panel a uno soportado (en, es, pt) o cambia '
|
|
9964
|
-
+ 'STT a "google" / "whisper".',
|
|
9965
|
-
true,
|
|
9966
|
-
);
|
|
10107
|
+
setStatus(CONFIG.i18n.panelVoiceLangNotSupported, true);
|
|
9967
10108
|
break;
|
|
9968
10109
|
default:
|
|
9969
|
-
setStatus('
|
|
10110
|
+
setStatus(CONFIG.i18n.panelVoiceMicError.replace('{error}', errKind), true);
|
|
9970
10111
|
}
|
|
9971
10112
|
};
|
|
9972
10113
|
rec.onend = () => {
|
|
@@ -9993,20 +10134,20 @@ async function _micToggleWebSpeech(button) {
|
|
|
9993
10134
|
/* Precise diagnostic so a mobile user (and we) can tell capture vs STT
|
|
9994
10135
|
* apart instead of a generic "did not catch that". (Pablo 2026-06-19.) */
|
|
9995
10136
|
if (!audioStarted) {
|
|
9996
|
-
setStatus(
|
|
10137
|
+
setStatus(CONFIG.i18n.panelVoiceNoAudioToRecognizer, true);
|
|
9997
10138
|
} else if (!speechStarted && !gotAnyResult) {
|
|
9998
|
-
setStatus(
|
|
10139
|
+
setStatus(CONFIG.i18n.panelVoiceMicOpenNoVoice, true);
|
|
9999
10140
|
} else if (!gotAnyResult) {
|
|
10000
|
-
setStatus(
|
|
10141
|
+
setStatus(CONFIG.i18n.panelVoiceVoiceNoText, true);
|
|
10001
10142
|
} else {
|
|
10002
|
-
setStatus(
|
|
10143
|
+
setStatus(CONFIG.i18n.panelVoiceDidNotCatch, true);
|
|
10003
10144
|
}
|
|
10004
10145
|
};
|
|
10005
10146
|
try {
|
|
10006
10147
|
_webSpeechRecognition = rec;
|
|
10007
10148
|
rec.start();
|
|
10008
10149
|
setMicButtonsState('recording');
|
|
10009
|
-
setStatus(
|
|
10150
|
+
setStatus(CONFIG.i18n.panelVoiceStartingDictation);
|
|
10010
10151
|
/* IMPORTANT (alpha.29 bugfix): DO NOT arm the silence timer
|
|
10011
10152
|
here. If we did, and the user takes >900ms to start
|
|
10012
10153
|
talking (typical when they click + think), the timer would
|
|
@@ -10015,7 +10156,7 @@ async function _micToggleWebSpeech(button) {
|
|
|
10015
10156
|
once the FIRST interim/final result lands. */
|
|
10016
10157
|
} catch (err) {
|
|
10017
10158
|
_webSpeechRecognition = null;
|
|
10018
|
-
setStatus('
|
|
10159
|
+
setStatus(CONFIG.i18n.panelVoiceCouldNotStartMic.replace('{error}', (err && err.message ? err.message : String(err))), true);
|
|
10019
10160
|
}
|
|
10020
10161
|
}
|
|
10021
10162
|
|
|
@@ -10028,7 +10169,7 @@ async function _micToggleWebSpeech(button) {
|
|
|
10028
10169
|
function _speakBrowser(text, langHint) {
|
|
10029
10170
|
return new Promise((resolve) => {
|
|
10030
10171
|
if (typeof window === 'undefined' || !window.speechSynthesis) {
|
|
10031
|
-
setStatus(
|
|
10172
|
+
setStatus(CONFIG.i18n.panelVoiceTtsUnavailable, true);
|
|
10032
10173
|
resolve();
|
|
10033
10174
|
return;
|
|
10034
10175
|
}
|
|
@@ -10524,18 +10665,18 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
10524
10665
|
try { const r = await fetch('/api/forge/bridge/state'); data = await r.json(); }
|
|
10525
10666
|
catch (_) { return; }
|
|
10526
10667
|
if (!data || !data.ok || !data.enabled) {
|
|
10527
|
-
statusEl.textContent =
|
|
10528
|
-
rosterEl.innerHTML = '<div class="bridge-empty">
|
|
10668
|
+
statusEl.textContent = CONFIG.i18n.bridgeStatusUnavailable;
|
|
10669
|
+
rosterEl.innerHTML = '<div class="bridge-empty">' + escapeHtml(CONFIG.i18n.bridgeCoordUnavailable) + '</div>';
|
|
10529
10670
|
return;
|
|
10530
10671
|
}
|
|
10531
10672
|
const insts = Array.isArray(data.instances) ? data.instances : [];
|
|
10532
|
-
statusEl.textContent = (data.role === 'host' ?
|
|
10533
|
-
+ ' - ' + insts.length + (insts.length === 1 ?
|
|
10673
|
+
statusEl.textContent = (data.role === 'host' ? CONFIG.i18n.bridgeRoleHost : CONFIG.i18n.bridgeRoleClient)
|
|
10674
|
+
+ ' - ' + insts.length + ' ' + (insts.length === 1 ? CONFIG.i18n.bridgeInstanceOne : CONFIG.i18n.bridgeInstanceMany);
|
|
10534
10675
|
rosterEl.innerHTML = insts.length ? insts.map((i) => {
|
|
10535
10676
|
const self = i.id === data.self_id;
|
|
10536
10677
|
const name = escapeHtml(i.project_name || i.project_slug || 'forge');
|
|
10537
10678
|
return '<div class="bridge-inst' + (self ? ' is-self' : '') + '">'
|
|
10538
|
-
+ '<span class="dot"></span><span>' + name + (self ? ' (
|
|
10679
|
+
+ '<span class="dot"></span><span>' + name + (self ? ' (' + CONFIG.i18n.bridgeYou + ')' : '')
|
|
10539
10680
|
+ '</span><span class="meta">:' + (i.panel_port || '?') + '</span></div>';
|
|
10540
10681
|
}).join('') : '<div class="bridge-empty">Sin otras instancias.</div>';
|
|
10541
10682
|
const msgs = Array.isArray(data.messages) ? data.messages : [];
|
|
@@ -10657,7 +10798,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
10657
10798
|
$$('[data-key-clear]').forEach((b) => b.addEventListener('click', (e) => {
|
|
10658
10799
|
const row = e.currentTarget.closest('[data-key-slot]');
|
|
10659
10800
|
if (!row) return;
|
|
10660
|
-
if (confirm('
|
|
10801
|
+
if (confirm(CONFIG.i18n.panelConfirmRemoveKey.replace('{slot}', row.getAttribute('data-key-slot')))) {
|
|
10661
10802
|
clearKey(row.getAttribute('data-key-slot'));
|
|
10662
10803
|
}
|
|
10663
10804
|
}));
|
|
@@ -10667,7 +10808,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
10667
10808
|
if (keSave) keSave.addEventListener('click', saveKeyEditor);
|
|
10668
10809
|
if (keCancel) keCancel.addEventListener('click', closeKeyEditor);
|
|
10669
10810
|
if (keClear) keClear.addEventListener('click', () => {
|
|
10670
|
-
if (_keyEditorSlot && confirm('
|
|
10811
|
+
if (_keyEditorSlot && confirm(CONFIG.i18n.panelConfirmRemoveKey.replace('{slot}', _keyEditorSlot))) {
|
|
10671
10812
|
clearKey(_keyEditorSlot);
|
|
10672
10813
|
closeKeyEditor();
|
|
10673
10814
|
}
|
|
@@ -10756,7 +10897,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
10756
10897
|
/* Not a supported image -> keep the spec-ingest path. */
|
|
10757
10898
|
if (SUPPORTED_IMG.indexOf(mt) < 0) { await ingestSpecFile(file); return; }
|
|
10758
10899
|
if (file.size > 6 * 1024 * 1024) {
|
|
10759
|
-
setStatus(
|
|
10900
|
+
setStatus(CONFIG.i18n.panelStatusImageTooLarge.replace('{name}', file.name), true);
|
|
10760
10901
|
return;
|
|
10761
10902
|
}
|
|
10762
10903
|
try {
|
|
@@ -10764,9 +10905,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
10764
10905
|
state.pendingAttachments = state.pendingAttachments || [];
|
|
10765
10906
|
state.pendingAttachments.push({ media_type: mt, data: data, name: file.name });
|
|
10766
10907
|
renderAttachChips();
|
|
10767
|
-
setStatus('
|
|
10908
|
+
setStatus(CONFIG.i18n.panelStatusImageReady.replace('{name}', file.name));
|
|
10768
10909
|
} catch (err) {
|
|
10769
|
-
setStatus('
|
|
10910
|
+
setStatus(CONFIG.i18n.panelStatusImageReadFail.replace('{error}', ((err && err.message) || err)), true);
|
|
10770
10911
|
}
|
|
10771
10912
|
}
|
|
10772
10913
|
/* Expose so send() + drag&drop can reach them. */
|
|
@@ -10852,7 +10993,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
10852
10993
|
/* Reload to apply translated strings everywhere. */
|
|
10853
10994
|
location.reload();
|
|
10854
10995
|
} catch (err) {
|
|
10855
|
-
setStatus('
|
|
10996
|
+
setStatus(CONFIG.i18n.panelStatusLanguageSwitchFailed.replace('{err}', String(err && err.message ? err.message : err)), true);
|
|
10856
10997
|
}
|
|
10857
10998
|
});
|
|
10858
10999
|
});
|
|
@@ -10866,7 +11007,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
10866
11007
|
}
|
|
10867
11008
|
const data = await loadLangData();
|
|
10868
11009
|
if (!data) {
|
|
10869
|
-
setStatus(
|
|
11010
|
+
setStatus(CONFIG.i18n.panelStatusLanguageListFailed, true);
|
|
10870
11011
|
return;
|
|
10871
11012
|
}
|
|
10872
11013
|
renderLangDropdown(data);
|
|
@@ -11265,7 +11406,7 @@ function mcpClose() {
|
|
|
11265
11406
|
|
|
11266
11407
|
async function mcpLoadBridges(/* force */) {
|
|
11267
11408
|
if (!yfMcp.contentEl) return;
|
|
11268
|
-
yfMcp.contentEl.innerHTML = '<div class="mcp-empty">
|
|
11409
|
+
yfMcp.contentEl.innerHTML = '<div class="mcp-empty">' + escapeHtml(CONFIG.i18n.mcpLoadingBridges) + '</div>';
|
|
11269
11410
|
try {
|
|
11270
11411
|
const r = await fetch('/api/forge/mcp/bridges');
|
|
11271
11412
|
const body = await r.json();
|
|
@@ -11352,7 +11493,7 @@ async function mcpLoadTools(bridgeId, cardEl) {
|
|
|
11352
11493
|
const panel = document.createElement('div');
|
|
11353
11494
|
panel.className = 'mcp-tools-list';
|
|
11354
11495
|
panel.dataset.mcpToolsFor = bridgeId;
|
|
11355
|
-
panel.textContent =
|
|
11496
|
+
panel.textContent = CONFIG.i18n.toolsLoading;
|
|
11356
11497
|
cardEl.parentElement.insertBefore(panel, cardEl.nextSibling);
|
|
11357
11498
|
try {
|
|
11358
11499
|
const r = await fetch('/api/forge/mcp/tools?bridge_id=' + encodeURIComponent(bridgeId));
|
|
@@ -11714,7 +11855,7 @@ async function activityLoadBridgeMessages(pairId) {
|
|
|
11714
11855
|
const msgs = Array.isArray(body.messages) ? body.messages : [];
|
|
11715
11856
|
panel.innerHTML = '';
|
|
11716
11857
|
const back = document.createElement('button');
|
|
11717
|
-
back.textContent =
|
|
11858
|
+
back.textContent = CONFIG.i18n.agentsChatBackPairs;
|
|
11718
11859
|
back.style.cssText = 'background:transparent;color:inherit;border:1px solid var(--ink-subtle,#5a5e6a);border-radius:4px;padding:4px 10px;font-family:inherit;font-size:12px;cursor:pointer;margin-bottom:10px;';
|
|
11719
11860
|
back.addEventListener('click', () => { yfActivity.selectedPair = null; activityLoadBridgePairs(); });
|
|
11720
11861
|
panel.appendChild(back);
|
|
@@ -11735,7 +11876,7 @@ async function activityLoadBridgeMessages(pairId) {
|
|
|
11735
11876
|
if (msgs.length === 0) {
|
|
11736
11877
|
const empty = document.createElement('div');
|
|
11737
11878
|
empty.className = 'act-empty';
|
|
11738
|
-
empty.textContent =
|
|
11879
|
+
empty.textContent = CONFIG.i18n.agentsChatNoMessages;
|
|
11739
11880
|
panel.appendChild(empty);
|
|
11740
11881
|
}
|
|
11741
11882
|
} catch (err) {
|
|
@@ -11761,7 +11902,7 @@ async function activityPopulateChatSlugs() {
|
|
|
11761
11902
|
if (agents.length === 0) {
|
|
11762
11903
|
const opt = document.createElement('option');
|
|
11763
11904
|
opt.value = '';
|
|
11764
|
-
opt.textContent =
|
|
11905
|
+
opt.textContent = CONFIG.i18n.agentsChatNoAgents;
|
|
11765
11906
|
select.appendChild(opt);
|
|
11766
11907
|
}
|
|
11767
11908
|
} catch (_err) { /* silent */ }
|
|
@@ -11775,8 +11916,8 @@ async function activityChatSend() {
|
|
|
11775
11916
|
if (!select || !input || !result) return;
|
|
11776
11917
|
const slug = String(select.value || '').trim();
|
|
11777
11918
|
const text = String(input.value || '').trim();
|
|
11778
|
-
if (!slug || !text) { result.textContent =
|
|
11779
|
-
result.textContent =
|
|
11919
|
+
if (!slug || !text) { result.textContent = CONFIG.i18n.agentsChatPickAgent; return; }
|
|
11920
|
+
result.textContent = CONFIG.i18n.agentsChatSending;
|
|
11780
11921
|
try {
|
|
11781
11922
|
const r = await fetch('/api/agent/inject', {
|
|
11782
11923
|
method: 'POST',
|
|
@@ -11795,7 +11936,7 @@ async function activityChatSend() {
|
|
|
11795
11936
|
/* Trigger a feed refresh so the user sees the new run. */
|
|
11796
11937
|
if (yfActivity.activeTab === 'feed') setTimeout(() => activityRefresh(true), 800);
|
|
11797
11938
|
} catch (err) {
|
|
11798
|
-
result.textContent = '
|
|
11939
|
+
result.textContent = CONFIG.i18n.agentsChatErrorGeneric.replace('{err}', (err.message || String(err)));
|
|
11799
11940
|
}
|
|
11800
11941
|
}
|
|
11801
11942
|
|