@nac3/forge-cli 1.0.54 → 1.0.56
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/panel.d.ts.map +1 -1
- package/dist/chat/panel.js +553 -387
- package/dist/chat/panel.js.map +1 -1
- package/dist/chat/server.js +2 -2
- package/dist/chat/server.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- 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,
|
|
@@ -910,6 +1080,9 @@ body {
|
|
|
910
1080
|
.header .brand .t { display: flex; flex-direction: column; line-height: 1.1; }
|
|
911
1081
|
.header .brand .t .name { font-size: 14px; font-weight: 600; }
|
|
912
1082
|
.header .brand .t .proj { font-size: 11px; opacity: 0.8; }
|
|
1083
|
+
.header .brand .t .proj-base { font-size: 9.5px; opacity: 0.55; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1084
|
+
max-width: 280px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; direction: rtl; text-align: left; }
|
|
1085
|
+
.header .brand .t .proj-base:empty { display: none; }
|
|
913
1086
|
.header .brand .t .proj-switch { cursor: pointer; display: inline-flex; align-items: center;
|
|
914
1087
|
align-self: flex-start; gap: 4px; padding: 1px 6px; margin-left: -4px; border-radius: 6px;
|
|
915
1088
|
border: 1px solid transparent; transition: background 0.12s, border-color 0.12s; }
|
|
@@ -1904,7 +2077,7 @@ ${renderYujinSplash()}
|
|
|
1904
2077
|
<div id="yf-whats-new-title" class="whats-new-title">Novedades en esta version</div>
|
|
1905
2078
|
<div id="yf-whats-new-sub" class="whats-new-sub"></div>
|
|
1906
2079
|
</div>
|
|
1907
|
-
<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>
|
|
1908
2081
|
</div>
|
|
1909
2082
|
<div id="yf-whats-new-body" class="whats-new-body"></div>
|
|
1910
2083
|
</div>
|
|
@@ -1918,7 +2091,7 @@ ${renderYujinSplash()}
|
|
|
1918
2091
|
<div class="header">
|
|
1919
2092
|
<div class="brand">
|
|
1920
2093
|
<span class="k">${escapeHtml(tr.brandKanji)}</span>
|
|
1921
|
-
<div class="t"><span class="name">Yujin Forge</span><span class="proj proj-switch" id="proj-mini" data-proj-switch role="button" tabindex="0" title="${escapeHtml(tr.projectSwitchHint)}" data-nac-id="yujin.panel.project-mini"></span></div>
|
|
2094
|
+
<div class="t"><span class="name">Yujin Forge</span><span class="proj proj-switch" id="proj-mini" data-proj-switch role="button" tabindex="0" title="${escapeHtml(tr.projectSwitchHint)}" data-nac-id="yujin.panel.project-mini"></span><span class="proj-base" id="proj-base-mini" data-proj-base></span></div>
|
|
1922
2095
|
</div>
|
|
1923
2096
|
<div class="actions">
|
|
1924
2097
|
<button class="keys-btn" data-vault-open aria-label="${escapeHtml(tr.keysButton)}" title="${escapeHtml(tr.keysButton)}">${escapeHtml(tr.keysButton)}</button>
|
|
@@ -1932,7 +2105,7 @@ ${renderYujinSplash()}
|
|
|
1932
2105
|
<form id="bar-mini" data-nac-id="yujin.chat.bar-mini" class="bar" onsubmit="return false">
|
|
1933
2106
|
<button type="button" class="mic" data-mic data-mic-btn aria-label="${escapeHtml(tr.micButton)}" title="${escapeHtml(tr.micButton)}">🎙</button>
|
|
1934
2107
|
<button type="button" class="tts-toggle" data-tts-toggle aria-label="${escapeHtml(tr.ttsButton)}" title="${escapeHtml(tr.ttsButton)}">🔊</button>
|
|
1935
|
-
<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>
|
|
1936
2109
|
<textarea class="composer-input" rows="1" placeholder="${escapeHtml(tr.inputPlaceholder)}" aria-label="${escapeHtml(tr.inputPlaceholder)}"></textarea>
|
|
1937
2110
|
<button type="submit" class="send">${escapeHtml(tr.sendButton)}</button>
|
|
1938
2111
|
<button type="button" class="stop" data-stop aria-label="${escapeHtml(tr.stopButton)}" title="${escapeHtml(tr.stopButton)}" style="display:none">■ ${escapeHtml(tr.stopButton)}</button>
|
|
@@ -2045,9 +2218,9 @@ ${renderYujinSplash()}
|
|
|
2045
2218
|
<button data-mcp-close aria-label="${escapeHtml(tr.closeButton)}" title="${escapeHtml(tr.closeButton)}">${escapeHtml(tr.closeButton)}</button>
|
|
2046
2219
|
</div>
|
|
2047
2220
|
<div class="mcp-body">
|
|
2048
|
-
<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>
|
|
2049
2222
|
<div data-mcp-content>
|
|
2050
|
-
<div class="mcp-empty"
|
|
2223
|
+
<div class="mcp-empty">${escapeHtml(tForLang(lang, 'panel.legal.loading'))}</div>
|
|
2051
2224
|
</div>
|
|
2052
2225
|
</div>
|
|
2053
2226
|
</div>
|
|
@@ -2063,24 +2236,24 @@ ${renderYujinSplash()}
|
|
|
2063
2236
|
<p class="act-subtitle">Feed timestamped + bridge inspector + chat-per-agent</p>
|
|
2064
2237
|
</div>
|
|
2065
2238
|
<div style="display:flex;align-items:center;gap:10px;">
|
|
2066
|
-
<select data-act-project-filter aria-label="
|
|
2239
|
+
<select data-act-project-filter aria-label="${escapeHtml(tForLang(lang, 'panel.tip.filterByProject'))}"
|
|
2067
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;">
|
|
2068
|
-
<option value=""
|
|
2241
|
+
<option value="">${escapeHtml(tForLang(lang, 'panel.activity.allProjects'))}</option>
|
|
2069
2242
|
</select>
|
|
2070
|
-
<span data-act-concurrency aria-label="
|
|
2071
|
-
title="
|
|
2243
|
+
<span data-act-concurrency aria-label="${escapeHtml(tForLang(lang, 'panel.aria.concurrentRuns'))}"
|
|
2244
|
+
title="${escapeHtml(tForLang(lang, 'panel.tip.concurrentRuns'))}"
|
|
2072
2245
|
style="font-family:ui-monospace,Menlo,Consolas,monospace;font-size:11px;color:var(--ink-muted,#8a8a8a);white-space:nowrap;">-/-</span>
|
|
2073
2246
|
<button data-activity-close aria-label="${escapeHtml(tr.closeButton)}" title="${escapeHtml(tr.closeButton)}">${escapeHtml(tr.closeButton)}</button>
|
|
2074
2247
|
</div>
|
|
2075
2248
|
</div>
|
|
2076
2249
|
<div class="act-body">
|
|
2077
2250
|
<div data-act-panel="feed">
|
|
2078
|
-
<div class="act-empty"
|
|
2251
|
+
<div class="act-empty">${escapeHtml(tForLang(lang, 'panel.activity.loadingFeed'))}</div>
|
|
2079
2252
|
</div>
|
|
2080
2253
|
</div>
|
|
2081
2254
|
<div class="act-controls">
|
|
2082
2255
|
<label><input type="checkbox" data-act-autorefresh checked /> auto-refresh 3s</label>
|
|
2083
|
-
<button data-act-refresh
|
|
2256
|
+
<button data-act-refresh>${escapeHtml(tForLang(lang, 'panel.tip.refreshNow'))}</button>
|
|
2084
2257
|
<span data-act-last-update style="margin-left:auto;font-family:ui-monospace,Menlo,Consolas,monospace;"></span>
|
|
2085
2258
|
</div>
|
|
2086
2259
|
</div>
|
|
@@ -2091,7 +2264,7 @@ ${renderYujinSplash()}
|
|
|
2091
2264
|
<div class="header">
|
|
2092
2265
|
<div class="brand">
|
|
2093
2266
|
<span class="k">${escapeHtml(tr.brandKanji)}</span>
|
|
2094
|
-
<div class="t"><span class="name">Yujin Forge</span><span class="proj proj-switch" id="proj-full" data-proj-switch role="button" tabindex="0" title="${escapeHtml(tr.projectSwitchHint)}" data-nac-id="yujin.panel.project-full"></span></div>
|
|
2267
|
+
<div class="t"><span class="name">Yujin Forge</span><span class="proj proj-switch" id="proj-full" data-proj-switch role="button" tabindex="0" title="${escapeHtml(tr.projectSwitchHint)}" data-nac-id="yujin.panel.project-full"></span><span class="proj-base" id="proj-base-full" data-proj-base></span></div>
|
|
2095
2268
|
</div>
|
|
2096
2269
|
<div class="actions">
|
|
2097
2270
|
<button class="agents-btn"
|
|
@@ -2107,49 +2280,49 @@ ${renderYujinSplash()}
|
|
|
2107
2280
|
data-nac-role="action"
|
|
2108
2281
|
data-nac-action="open_mcp"
|
|
2109
2282
|
aria-label="MCP"
|
|
2110
|
-
title="
|
|
2283
|
+
title="${escapeHtml(tForLang(lang, 'panel.tip.mcpBridges'))}">MCP</button>
|
|
2111
2284
|
<button class="agents-btn"
|
|
2112
2285
|
data-activity-open
|
|
2113
2286
|
data-nac-id="yujin.panel.activity"
|
|
2114
2287
|
data-nac-role="action"
|
|
2115
2288
|
data-nac-action="open_activity"
|
|
2116
|
-
aria-label="
|
|
2117
|
-
title="
|
|
2289
|
+
aria-label="${escapeHtml(tForLang(lang, 'panel.aria.activity'))}"
|
|
2290
|
+
title="${escapeHtml(tForLang(lang, 'panel.tip.activity'))}">Activity</button>
|
|
2118
2291
|
<button class="settings-btn"
|
|
2119
2292
|
data-settings-toggle
|
|
2120
2293
|
data-nac-id="yujin.panel.settings"
|
|
2121
2294
|
data-nac-role="action"
|
|
2122
2295
|
data-nac-action="open_settings"
|
|
2123
|
-
aria-label="
|
|
2124
|
-
title="
|
|
2296
|
+
aria-label="${escapeHtml(tForLang(lang, 'panel.aria.settings'))}"
|
|
2297
|
+
title="${escapeHtml(tForLang(lang, 'panel.tip.settings'))}">⚙</button>
|
|
2125
2298
|
<button id="full-zoom-out"
|
|
2126
2299
|
data-zoom-step="-1"
|
|
2127
2300
|
data-nac-id="yujin.panel.zoom-out"
|
|
2128
2301
|
data-nac-role="action"
|
|
2129
2302
|
data-nac-action="zoom_out"
|
|
2130
|
-
aria-label="
|
|
2131
|
-
title="
|
|
2303
|
+
aria-label="${escapeHtml(tForLang(lang, 'panel.aria.zoomOut'))}"
|
|
2304
|
+
title="${escapeHtml(tForLang(lang, 'panel.tip.zoomOut'))}">−</button>
|
|
2132
2305
|
<button id="full-zoom-reset"
|
|
2133
2306
|
data-zoom-reset
|
|
2134
2307
|
data-nac-id="yujin.panel.zoom-reset"
|
|
2135
2308
|
data-nac-role="action"
|
|
2136
2309
|
data-nac-action="zoom_reset"
|
|
2137
2310
|
aria-label="Zoom 100%"
|
|
2138
|
-
title="
|
|
2311
|
+
title="${escapeHtml(tForLang(lang, 'panel.tip.zoomReset'))}">100%</button>
|
|
2139
2312
|
<button id="full-zoom-in"
|
|
2140
2313
|
data-zoom-step="1"
|
|
2141
2314
|
data-nac-id="yujin.panel.zoom-in"
|
|
2142
2315
|
data-nac-role="action"
|
|
2143
2316
|
data-nac-action="zoom_in"
|
|
2144
|
-
aria-label="
|
|
2145
|
-
title="
|
|
2317
|
+
aria-label="${escapeHtml(tForLang(lang, 'panel.aria.zoomIn'))}"
|
|
2318
|
+
title="${escapeHtml(tForLang(lang, 'panel.tip.zoomIn'))}">+</button>
|
|
2146
2319
|
<button id="full-maximize"
|
|
2147
2320
|
data-maximize-toggle
|
|
2148
2321
|
data-nac-id="yujin.panel.maximize"
|
|
2149
2322
|
data-nac-role="action"
|
|
2150
2323
|
data-nac-action="maximize_panel"
|
|
2151
|
-
aria-label="
|
|
2152
|
-
title="
|
|
2324
|
+
aria-label="${escapeHtml(tForLang(lang, 'panel.aria.maximize'))}"
|
|
2325
|
+
title="${escapeHtml(tForLang(lang, 'panel.tip.maximize'))}">☐</button>
|
|
2153
2326
|
<button id="full-mini"
|
|
2154
2327
|
data-nac-id="yujin.panel.full-to-mini"
|
|
2155
2328
|
data-nac-role="action"
|
|
@@ -2259,29 +2432,29 @@ ${renderYujinSplash()}
|
|
|
2259
2432
|
API keys (BYOK) + voice settings + license. -->
|
|
2260
2433
|
<div id="yf-settings" class="hidden settings-panel">
|
|
2261
2434
|
<div class="set-section" data-bridge-section>
|
|
2262
|
-
<div class="set-section-title"
|
|
2435
|
+
<div class="set-section-title">${escapeHtml(tForLang(lang, 'settings.bridge.title'))}
|
|
2263
2436
|
<span class="bridge-status" data-bridge-status></span>
|
|
2264
2437
|
</div>
|
|
2265
|
-
<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>
|
|
2266
2439
|
<div class="bridge-roster" data-bridge-roster></div>
|
|
2267
2440
|
<div class="bridge-feed" data-bridge-feed></div>
|
|
2268
2441
|
<div class="bridge-compose">
|
|
2269
|
-
<input type="text" class="bridge-input" data-bridge-input placeholder="
|
|
2270
|
-
<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>
|
|
2271
2444
|
</div>
|
|
2272
2445
|
</div>
|
|
2273
2446
|
<div class="set-section">
|
|
2274
|
-
<div class="set-section-title"
|
|
2275
|
-
<div class="set-row"><span class="set-lbl"
|
|
2276
|
-
<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>
|
|
2277
2450
|
</div>
|
|
2278
|
-
<div class="set-row"><span class="set-lbl"
|
|
2279
|
-
<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>
|
|
2280
2453
|
</div>
|
|
2281
|
-
<div class="set-row"><span class="set-lbl"
|
|
2282
|
-
<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>
|
|
2283
2456
|
</div>
|
|
2284
|
-
<div class="set-row"><span class="set-lbl"
|
|
2457
|
+
<div class="set-row"><span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.voice.ttsVoice'))}</span>
|
|
2285
2458
|
<button type="button" data-tts-voice-btn class="set-btn">es-ES-Studio-C</button>
|
|
2286
2459
|
</div>
|
|
2287
2460
|
</div>
|
|
@@ -2290,63 +2463,63 @@ ${renderYujinSplash()}
|
|
|
2290
2463
|
environment (quiet room / fan / open office / music). All values
|
|
2291
2464
|
hot-apply on the next VAD tick; no reload needed. -->
|
|
2292
2465
|
<div class="set-section vad-section">
|
|
2293
|
-
<div class="set-section-title"
|
|
2294
|
-
<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>
|
|
2295
2468
|
</div>
|
|
2296
|
-
<div class="set-row"><span class="set-lbl"
|
|
2297
|
-
<label class="vad-radio"><input type="radio" name="vad-engine" value="amplitude">
|
|
2298
|
-
<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>
|
|
2299
2472
|
</div>
|
|
2300
2473
|
<div class="vad-slider-row" data-vad-field="baseline_ms" data-vad-min="100" data-vad-max="2000" data-vad-step="50">
|
|
2301
|
-
<span class="vad-lbl"
|
|
2474
|
+
<span class="vad-lbl">${escapeHtml(tForLang(lang, 'settings.vad.baselineWindow'))}</span>
|
|
2302
2475
|
<input type="range" class="vad-slider">
|
|
2303
2476
|
<span class="vad-val">600 ms</span>
|
|
2304
2477
|
</div>
|
|
2305
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">
|
|
2306
|
-
<span class="vad-lbl"
|
|
2479
|
+
<span class="vad-lbl">${escapeHtml(tForLang(lang, 'settings.vad.thresholdMult'))}</span>
|
|
2307
2480
|
<input type="range" class="vad-slider">
|
|
2308
2481
|
<span class="vad-val">3.0</span>
|
|
2309
2482
|
</div>
|
|
2310
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">
|
|
2311
|
-
<span class="vad-lbl"
|
|
2484
|
+
<span class="vad-lbl">${escapeHtml(tForLang(lang, 'settings.vad.thresholdMin'))}</span>
|
|
2312
2485
|
<input type="range" class="vad-slider">
|
|
2313
2486
|
<span class="vad-val">0.012</span>
|
|
2314
2487
|
</div>
|
|
2315
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">
|
|
2316
|
-
<span class="vad-lbl"
|
|
2489
|
+
<span class="vad-lbl">${escapeHtml(tForLang(lang, 'settings.vad.thresholdMax'))}</span>
|
|
2317
2490
|
<input type="range" class="vad-slider">
|
|
2318
2491
|
<span class="vad-val">0.06</span>
|
|
2319
2492
|
</div>
|
|
2320
2493
|
<div class="vad-slider-row" data-vad-field="silence_ms" data-vad-min="200" data-vad-max="4000" data-vad-step="50">
|
|
2321
|
-
<span class="vad-lbl"
|
|
2494
|
+
<span class="vad-lbl">${escapeHtml(tForLang(lang, 'settings.vad.silence'))}</span>
|
|
2322
2495
|
<input type="range" class="vad-slider">
|
|
2323
2496
|
<span class="vad-val">1000 ms</span>
|
|
2324
2497
|
</div>
|
|
2325
2498
|
<div class="vad-slider-row" data-vad-field="min_voiced_ms" data-vad-min="50" data-vad-max="1000" data-vad-step="25">
|
|
2326
|
-
<span class="vad-lbl"
|
|
2499
|
+
<span class="vad-lbl">${escapeHtml(tForLang(lang, 'settings.vad.minVoiced'))}</span>
|
|
2327
2500
|
<input type="range" class="vad-slider">
|
|
2328
2501
|
<span class="vad-val">200 ms</span>
|
|
2329
2502
|
</div>
|
|
2330
2503
|
<div class="vad-slider-row" data-vad-field="tick_ms" data-vad-min="20" data-vad-max="200" data-vad-step="5">
|
|
2331
|
-
<span class="vad-lbl"
|
|
2504
|
+
<span class="vad-lbl">${escapeHtml(tForLang(lang, 'settings.vad.tick'))}</span>
|
|
2332
2505
|
<input type="range" class="vad-slider">
|
|
2333
2506
|
<span class="vad-val">50 ms</span>
|
|
2334
2507
|
</div>
|
|
2335
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">
|
|
2336
|
-
<span class="vad-lbl"
|
|
2509
|
+
<span class="vad-lbl">${escapeHtml(tForLang(lang, 'settings.vad.bandLow'))}</span>
|
|
2337
2510
|
<input type="range" class="vad-slider">
|
|
2338
2511
|
<span class="vad-val">80</span>
|
|
2339
2512
|
</div>
|
|
2340
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">
|
|
2341
|
-
<span class="vad-lbl"
|
|
2514
|
+
<span class="vad-lbl">${escapeHtml(tForLang(lang, 'settings.vad.bandHigh'))}</span>
|
|
2342
2515
|
<input type="range" class="vad-slider">
|
|
2343
2516
|
<span class="vad-val">400</span>
|
|
2344
2517
|
</div>
|
|
2345
2518
|
<div class="set-row">
|
|
2346
|
-
<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>
|
|
2347
2520
|
</div>
|
|
2348
2521
|
<div class="set-row">
|
|
2349
|
-
<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>
|
|
2350
2523
|
<span class="set-status">~/.yujin-forge/vad_sessions.jsonl</span>
|
|
2351
2524
|
</div>
|
|
2352
2525
|
</div>
|
|
@@ -2356,42 +2529,42 @@ ${renderYujinSplash()}
|
|
|
2356
2529
|
<div class="set-section brain-section">
|
|
2357
2530
|
<div class="set-section-title">Brain</div>
|
|
2358
2531
|
<div class="set-row">
|
|
2359
|
-
<span class="set-lbl"
|
|
2360
|
-
<label class="vad-radio"><input type="radio" name="brain-anthropic-mode" value="plan">
|
|
2361
|
-
<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>
|
|
2362
2535
|
</div>
|
|
2363
2536
|
<div class="set-row">
|
|
2364
|
-
<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>
|
|
2365
2538
|
</div>
|
|
2366
2539
|
<div class="set-row">
|
|
2367
|
-
<label class="vad-check"><input type="checkbox" data-brain-eco-enabled>
|
|
2368
|
-
<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>
|
|
2369
2542
|
</div>
|
|
2370
2543
|
<div class="set-row">
|
|
2371
2544
|
<label class="vad-check"><input type="checkbox" data-startup-tabs-enabled> ${escapeHtml(tr.startupTabsLabel)}</label>
|
|
2372
2545
|
</div>
|
|
2373
2546
|
<div class="set-row" data-brain-tier-row="eco">
|
|
2374
|
-
<span class="set-lbl"
|
|
2547
|
+
<span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.brain.modelEco'))}</span>
|
|
2375
2548
|
<select class="set-select" data-brain-provider="eco"></select>
|
|
2376
2549
|
<select class="set-select" data-brain-model="eco"></select>
|
|
2377
2550
|
</div>
|
|
2378
2551
|
<div class="set-row" data-brain-tier-row="frontier">
|
|
2379
|
-
<span class="set-lbl"
|
|
2552
|
+
<span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.brain.modelFrontier'))}</span>
|
|
2380
2553
|
<select class="set-select" data-brain-provider="frontier"></select>
|
|
2381
2554
|
<select class="set-select" data-brain-model="frontier"></select>
|
|
2382
2555
|
</div>
|
|
2383
2556
|
<div class="set-row" data-brain-tier-row="multimodal">
|
|
2384
|
-
<span class="set-lbl"
|
|
2557
|
+
<span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.brain.modelMultimodal'))}</span>
|
|
2385
2558
|
<select class="set-select" data-brain-provider="multimodal"></select>
|
|
2386
2559
|
<select class="set-select" data-brain-model="multimodal"></select>
|
|
2387
|
-
<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>
|
|
2388
2561
|
</div>
|
|
2389
2562
|
<div class="set-row">
|
|
2390
|
-
<span class="set-status"
|
|
2563
|
+
<span class="set-status">${escapeHtml(tForLang(lang, 'settings.brain.multimodalHint'))}</span>
|
|
2391
2564
|
</div>
|
|
2392
2565
|
<div class="set-row">
|
|
2393
|
-
<label class="vad-check"><input type="checkbox" data-gemini-explicit-cache>
|
|
2394
|
-
<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>
|
|
2395
2568
|
</div>
|
|
2396
2569
|
<div class="set-row">
|
|
2397
2570
|
<span class="set-status" data-brain-status></span>
|
|
@@ -2400,129 +2573,129 @@ ${renderYujinSplash()}
|
|
|
2400
2573
|
<!-- F4 brain federation -- conectar al brain de otro Forge
|
|
2401
2574
|
(child) y generar codigos para autorizar/rutear (parent). -->
|
|
2402
2575
|
<div class="set-section federation-section">
|
|
2403
|
-
<div class="set-section-title"
|
|
2576
|
+
<div class="set-section-title">${escapeHtml(tForLang(lang, 'settings.federation.title'))}</div>
|
|
2404
2577
|
<div class="set-row">
|
|
2405
|
-
<span class="set-status"
|
|
2578
|
+
<span class="set-status">${escapeHtml(tForLang(lang, 'settings.federation.intro'))}</span>
|
|
2406
2579
|
</div>
|
|
2407
2580
|
<!-- Estado actual (cuando estas federado como hijo) -->
|
|
2408
2581
|
<div class="set-row fed-linked" data-fed-linked hidden>
|
|
2409
|
-
<span class="set-lbl"
|
|
2582
|
+
<span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.federation.connectedTo'))}</span>
|
|
2410
2583
|
<span class="set-status" data-fed-parent>--</span>
|
|
2411
|
-
<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>
|
|
2412
2585
|
</div>
|
|
2413
2586
|
<!-- Conectarme a otro Forge (child) -->
|
|
2414
2587
|
<div class="set-row" data-fed-join-row>
|
|
2415
|
-
<span class="set-lbl"
|
|
2416
|
-
<input type="text" class="set-input" data-fed-token placeholder="
|
|
2417
|
-
<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>
|
|
2418
2591
|
</div>
|
|
2419
2592
|
<!-- Prestar mi brain (parent) -->
|
|
2420
2593
|
<div class="set-row" data-fed-invite-row>
|
|
2421
|
-
<span class="set-lbl"
|
|
2422
|
-
<button type="button" class="set-btn" data-fed-invite
|
|
2423
|
-
<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>
|
|
2424
2597
|
</div>
|
|
2425
2598
|
<div class="set-row" data-fed-brain-row hidden>
|
|
2426
|
-
<span class="set-lbl"
|
|
2599
|
+
<span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.federation.fedBrain'))}</span>
|
|
2427
2600
|
<select class="set-select" data-fed-brain-provider></select>
|
|
2428
2601
|
<select class="set-select" data-fed-brain-model></select>
|
|
2429
2602
|
</div>
|
|
2430
2603
|
<div class="set-row" data-fed-brain-note hidden>
|
|
2431
|
-
<span class="set-status"
|
|
2604
|
+
<span class="set-status">${escapeHtml(tForLang(lang, 'settings.federation.fedBrainNote'))}</span>
|
|
2432
2605
|
</div>
|
|
2433
2606
|
<div class="set-row" data-fed-token-out-row hidden>
|
|
2434
|
-
<span class="set-lbl"
|
|
2607
|
+
<span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.federation.codeOnce'))}</span>
|
|
2435
2608
|
<input type="text" class="set-input" data-fed-token-out readonly>
|
|
2436
|
-
<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>
|
|
2437
2610
|
</div>
|
|
2438
2611
|
<div class="set-row">
|
|
2439
2612
|
<span class="set-status" data-fed-status></span>
|
|
2440
2613
|
</div>
|
|
2441
2614
|
</div>
|
|
2442
2615
|
<div class="set-section">
|
|
2443
|
-
<div class="set-section-title"
|
|
2444
|
-
<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>
|
|
2445
2618
|
<span class="set-status" data-key-status>--</span>
|
|
2446
|
-
<button type="button" class="set-btn" data-key-edit
|
|
2447
|
-
<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>
|
|
2448
2621
|
</div>
|
|
2449
|
-
<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>
|
|
2450
2623
|
<span class="set-status" data-key-status>--</span>
|
|
2451
|
-
<button type="button" class="set-btn" data-key-edit
|
|
2452
|
-
<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>
|
|
2453
2626
|
</div>
|
|
2454
2627
|
<!-- Bug 1 (Pablo 2026-06-10) -- faltaban los slots de los
|
|
2455
2628
|
demas brain providers BYOK que el selector de modelo
|
|
2456
2629
|
(PND-069) ya ofrece. Slots reales de provider_keys.ts. -->
|
|
2457
|
-
<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>
|
|
2458
2631
|
<span class="set-status" data-key-status>--</span>
|
|
2459
|
-
<button type="button" class="set-btn" data-key-edit
|
|
2460
|
-
<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>
|
|
2461
2634
|
</div>
|
|
2462
|
-
<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>
|
|
2463
2636
|
<span class="set-status" data-key-status>--</span>
|
|
2464
|
-
<button type="button" class="set-btn" data-key-edit
|
|
2465
|
-
<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>
|
|
2466
2639
|
</div>
|
|
2467
|
-
<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>
|
|
2468
2641
|
<span class="set-status" data-key-status>--</span>
|
|
2469
|
-
<button type="button" class="set-btn" data-key-edit
|
|
2470
|
-
<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>
|
|
2471
2644
|
</div>
|
|
2472
|
-
<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>
|
|
2473
2646
|
<span class="set-status" data-key-status>--</span>
|
|
2474
|
-
<button type="button" class="set-btn" data-key-edit
|
|
2475
|
-
<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>
|
|
2476
2649
|
</div>
|
|
2477
|
-
<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>
|
|
2478
2651
|
<span class="set-status" data-key-status>--</span>
|
|
2479
|
-
<button type="button" class="set-btn" data-key-edit
|
|
2480
|
-
<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>
|
|
2481
2654
|
</div>
|
|
2482
|
-
<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>
|
|
2483
2656
|
<span class="set-status" data-key-status>--</span>
|
|
2484
|
-
<button type="button" class="set-btn" data-key-edit
|
|
2485
|
-
<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>
|
|
2486
2659
|
</div>
|
|
2487
|
-
<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>
|
|
2488
2661
|
<span class="set-status" data-key-status>--</span>
|
|
2489
|
-
<button type="button" class="set-btn" data-key-edit
|
|
2490
|
-
<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>
|
|
2491
2664
|
</div>
|
|
2492
|
-
<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>
|
|
2493
2666
|
<span class="set-status" data-key-status>--</span>
|
|
2494
|
-
<button type="button" class="set-btn" data-key-edit
|
|
2495
|
-
<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>
|
|
2496
2669
|
</div>
|
|
2497
|
-
<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>
|
|
2498
2671
|
<span class="set-status" data-key-status>--</span>
|
|
2499
|
-
<button type="button" class="set-btn" data-key-edit
|
|
2500
|
-
<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>
|
|
2501
2674
|
</div>
|
|
2502
2675
|
</div>
|
|
2503
2676
|
<div class="set-section">
|
|
2504
|
-
<div class="set-section-title"
|
|
2505
|
-
<div class="set-row"><span class="set-lbl"
|
|
2506
|
-
<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>
|
|
2507
2680
|
</div>
|
|
2508
2681
|
</div>
|
|
2509
2682
|
<div class="set-section">
|
|
2510
|
-
<div class="set-section-title"
|
|
2511
|
-
<div class="set-row"><span class="set-lbl"
|
|
2512
|
-
<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>
|
|
2513
2686
|
</div>
|
|
2514
2687
|
</div>
|
|
2515
2688
|
<div class="set-section">
|
|
2516
|
-
<div class="set-section-title"
|
|
2517
|
-
<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>
|
|
2518
2691
|
<span class="set-status" id="set-license-status">--</span>
|
|
2519
2692
|
</div>
|
|
2520
2693
|
<div class="set-row" id="set-license-user-handle-row" style="display:none;">
|
|
2521
|
-
<span class="set-lbl"
|
|
2694
|
+
<span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.license.email'))}</span>
|
|
2522
2695
|
<span class="set-status" id="set-license-user-handle">--</span>
|
|
2523
2696
|
</div>
|
|
2524
2697
|
<div class="set-row" id="set-license-validated-row" style="display:none;">
|
|
2525
|
-
<span class="set-lbl"
|
|
2698
|
+
<span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.license.validated'))}</span>
|
|
2526
2699
|
<span class="set-status" id="set-license-validated">--</span>
|
|
2527
2700
|
</div>
|
|
2528
2701
|
|
|
@@ -2531,23 +2704,22 @@ ${renderYujinSplash()}
|
|
|
2531
2704
|
/api/forge/license-handle -> writes handle + syncs. -->
|
|
2532
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);">
|
|
2533
2706
|
<div style="font-size:11px;opacity:0.85;margin-bottom:8px;">
|
|
2534
|
-
|
|
2535
|
-
sincronizamos tu plan automaticamente.
|
|
2707
|
+
${escapeHtml(tForLang(lang, 'settings.license.connectHint'))}
|
|
2536
2708
|
</div>
|
|
2537
2709
|
<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap;">
|
|
2538
2710
|
<input type="email" id="set-license-connect-email"
|
|
2539
|
-
placeholder="
|
|
2711
|
+
placeholder="${escapeHtml(tForLang(lang, 'settings.license.emailPlaceholder'))}"
|
|
2540
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;"
|
|
2541
2713
|
autocomplete="email" />
|
|
2542
|
-
<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>
|
|
2543
2715
|
</div>
|
|
2544
2716
|
<div id="set-license-connect-msg" style="font-size:11px;opacity:0.85;margin-top:6px;display:none;"></div>
|
|
2545
2717
|
</div>
|
|
2546
2718
|
|
|
2547
2719
|
<div class="set-row" id="set-license-sync-row" style="gap:6px;display:none;">
|
|
2548
|
-
<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>
|
|
2549
2721
|
<a href="https://yujin.app/yujin-forge/"
|
|
2550
|
-
target="_blank" rel="noopener" class="set-link"
|
|
2722
|
+
target="_blank" rel="noopener" class="set-link">${escapeHtml(tForLang(lang, 'settings.license.buy'))}</a>
|
|
2551
2723
|
</div>
|
|
2552
2724
|
<div class="set-row" id="set-license-sync-msg-row" style="display:none;">
|
|
2553
2725
|
<span class="set-status" id="set-license-sync-msg" style="font-size:11px;opacity:0.85;">--</span>
|
|
@@ -2557,15 +2729,15 @@ ${renderYujinSplash()}
|
|
|
2557
2729
|
tier + renewal date + buy-extension link. Auto-refreshes
|
|
2558
2730
|
on settings open + after each chat response. -->
|
|
2559
2731
|
<div class="set-section" data-nac-id="yujin.panel.budget" data-nac-role="region">
|
|
2560
|
-
<div class="set-section-title"
|
|
2561
|
-
<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>
|
|
2562
2734
|
<span class="set-status" id="set-budget-plan">--</span>
|
|
2563
2735
|
</div>
|
|
2564
|
-
<div class="set-row"><span class="set-lbl"
|
|
2736
|
+
<div class="set-row"><span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.budget.renews'))}</span>
|
|
2565
2737
|
<span class="set-status" id="set-budget-renewal">--</span>
|
|
2566
2738
|
</div>
|
|
2567
2739
|
<div class="set-row" id="set-budget-eco-row">
|
|
2568
|
-
<span class="set-lbl"
|
|
2740
|
+
<span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.budget.tokensEco'))}</span>
|
|
2569
2741
|
<span class="set-status" id="set-budget-eco-numbers">--</span>
|
|
2570
2742
|
</div>
|
|
2571
2743
|
<div class="set-row" id="set-budget-eco-bar-row" style="margin-top:-6px;">
|
|
@@ -2574,7 +2746,7 @@ ${renderYujinSplash()}
|
|
|
2574
2746
|
</div>
|
|
2575
2747
|
</div>
|
|
2576
2748
|
<div class="set-row" id="set-budget-frontier-row">
|
|
2577
|
-
<span class="set-lbl"
|
|
2749
|
+
<span class="set-lbl">${escapeHtml(tForLang(lang, 'settings.budget.tokensFrontier'))}</span>
|
|
2578
2750
|
<span class="set-status" id="set-budget-frontier-numbers">--</span>
|
|
2579
2751
|
</div>
|
|
2580
2752
|
<div class="set-row" id="set-budget-frontier-bar-row" style="margin-top:-6px;">
|
|
@@ -2583,18 +2755,18 @@ ${renderYujinSplash()}
|
|
|
2583
2755
|
</div>
|
|
2584
2756
|
</div>
|
|
2585
2757
|
<div class="set-row" id="set-budget-extension-row" style="display:none;">
|
|
2586
|
-
<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>
|
|
2587
2759
|
</div>
|
|
2588
2760
|
</div>
|
|
2589
2761
|
<!-- alpha.59c.2 -- Limits card. Cada limite es un input
|
|
2590
2762
|
numerico; el default va al lado en una pill chica.
|
|
2591
2763
|
Save por blur. Reset restaura todos. -->
|
|
2592
2764
|
<div class="set-section" data-nac-id="yujin.panel.limits" data-nac-role="region">
|
|
2593
|
-
<div class="set-section-title"
|
|
2594
|
-
<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>
|
|
2595
2767
|
<div id="set-limits-fields"></div>
|
|
2596
2768
|
<div class="set-row" style="justify-content:flex-end;gap:6px;">
|
|
2597
|
-
<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>
|
|
2598
2770
|
</div>
|
|
2599
2771
|
<div id="set-limits-msg" class="ke-msg"></div>
|
|
2600
2772
|
</div>
|
|
@@ -2604,10 +2776,10 @@ ${renderYujinSplash()}
|
|
|
2604
2776
|
Click row -> envia mensaje hidden al LLM con el
|
|
2605
2777
|
comando para invocar yujin.reader.bookmark-jump. -->
|
|
2606
2778
|
<div class="set-section" data-nac-id="yujin.panel.reader-bookmarks" data-nac-role="region">
|
|
2607
|
-
<div class="set-section-title"
|
|
2779
|
+
<div class="set-section-title">${escapeHtml(tForLang(lang, 'settings.bookmarks.title'))}</div>
|
|
2608
2780
|
<div id="set-bookmarks-list" class="bookmark-list"></div>
|
|
2609
2781
|
<div class="set-row" style="justify-content:flex-end;">
|
|
2610
|
-
<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>
|
|
2611
2783
|
</div>
|
|
2612
2784
|
</div>
|
|
2613
2785
|
|
|
@@ -2616,9 +2788,9 @@ ${renderYujinSplash()}
|
|
|
2616
2788
|
user know whether the inverse-hallucination guard is
|
|
2617
2789
|
populated. CLI for full CRUD: yf graph add-node. -->
|
|
2618
2790
|
<div class="set-section" data-nac-id="yujin.panel.graph-status" data-nac-role="region">
|
|
2619
|
-
<div class="set-section-title"
|
|
2791
|
+
<div class="set-section-title">${escapeHtml(tForLang(lang, 'settings.graph.title'))}</div>
|
|
2620
2792
|
<div class="set-row" id="set-graph-summary">
|
|
2621
|
-
<span class="set-lbl"
|
|
2793
|
+
<span class="set-lbl">${escapeHtml(tForLang(lang, 'vault.list.loading'))}</span>
|
|
2622
2794
|
</div>
|
|
2623
2795
|
<div id="set-graph-samples" class="graph-samples"></div>
|
|
2624
2796
|
<div class="set-row" style="justify-content:space-between;align-items:center;">
|
|
@@ -2630,18 +2802,18 @@ ${renderYujinSplash()}
|
|
|
2630
2802
|
<!-- Vincular telefono (dogfood 2026-06-10; Option 3 2026-06-16 --
|
|
2631
2803
|
code-less: el QR es autocontenido, se escanea y listo). -->
|
|
2632
2804
|
<div class="set-section">
|
|
2633
|
-
<div class="set-section-title"
|
|
2805
|
+
<div class="set-section-title">${escapeHtml(tForLang(lang, 'settings.pair.title'))}</div>
|
|
2634
2806
|
<div class="set-row">
|
|
2635
|
-
<span class="set-lbl" style="opacity:0.7;font-size:11px;"
|
|
2636
|
-
<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>
|
|
2637
2809
|
</div>
|
|
2638
2810
|
<div id="set-pair-result" class="hidden" style="text-align:center;padding:8px 0;">
|
|
2639
2811
|
<div id="set-pair-qr" style="display:inline-block;background:#ffffff;padding:10px;border-radius:8px;width:220px;height:220px;"></div>
|
|
2640
2812
|
<div id="set-pair-expiry" style="font-size:11px;opacity:0.6;margin-top:6px;"></div>
|
|
2641
|
-
<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>
|
|
2642
2814
|
<div style="display:flex;gap:6px;align-items:center;justify-content:center;margin-top:4px;">
|
|
2643
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);" />
|
|
2644
|
-
<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>
|
|
2645
2817
|
</div>
|
|
2646
2818
|
</div>
|
|
2647
2819
|
<div id="set-pair-error" class="hidden" style="font-size:12px;color:var(--err,#c0392b);padding:6px 0;"></div>
|
|
@@ -2652,18 +2824,18 @@ ${renderYujinSplash()}
|
|
|
2652
2824
|
del config. Click en "Ver completo" abre el audit modal
|
|
2653
2825
|
con filtros + lista paginada + detail inline. -->
|
|
2654
2826
|
<div class="set-section audit-log" data-nac-id="yujin.panel.audit-log" data-nac-role="region">
|
|
2655
|
-
<div class="set-section-title"
|
|
2827
|
+
<div class="set-section-title">${escapeHtml(tForLang(lang, 'settings.audit.title'))}</div>
|
|
2656
2828
|
<div id="set-audit-preview" class="audit-preview">
|
|
2657
|
-
<div class="audit-preview-empty"
|
|
2829
|
+
<div class="audit-preview-empty">${escapeHtml(tForLang(lang, 'vault.list.loading'))}</div>
|
|
2658
2830
|
</div>
|
|
2659
2831
|
<div class="set-row" style="justify-content:flex-end;gap:6px;margin-top:6px;">
|
|
2660
|
-
<button type="button" class="set-btn" id="set-audit-refresh"
|
|
2661
|
-
<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>
|
|
2662
2834
|
</div>
|
|
2663
2835
|
</div>
|
|
2664
2836
|
<!-- z.111 -- About / legal docs. Populated by fetchAndRenderAbout() on first settings open. -->
|
|
2665
2837
|
<div class="set-section about-section" data-nac-id="yujin.panel.about" data-nac-role="region">
|
|
2666
|
-
<div class="set-section-title"
|
|
2838
|
+
<div class="set-section-title">${escapeHtml(tForLang(lang, 'settings.about.title'))}</div>
|
|
2667
2839
|
<div class="set-row" style="font-size:11px; color:var(--ink-2);">
|
|
2668
2840
|
Forge <span id="set-about-version">--</span>
|
|
2669
2841
|
</div>
|
|
@@ -2678,12 +2850,10 @@ ${renderYujinSplash()}
|
|
|
2678
2850
|
aparece + cada accion se loguea con
|
|
2679
2851
|
approval_mode='danger_zone_bypass'. -->
|
|
2680
2852
|
<div class="set-section danger-zone" data-nac-id="yujin.panel.danger-zone" data-nac-role="region">
|
|
2681
|
-
<div class="set-section-title" style="color:#d97706;">⚠
|
|
2853
|
+
<div class="set-section-title" style="color:#d97706;">⚠ ${escapeHtml(tForLang(lang, 'settings.danger.title'))}</div>
|
|
2682
2854
|
<div class="set-row" style="opacity:0.8;">
|
|
2683
2855
|
<span class="set-lbl" style="font-size:11px;">
|
|
2684
|
-
|
|
2685
|
-
(incluye shell.exec, rm, git push --force, etc). Velocidad sobre
|
|
2686
|
-
seguridad. Todo queda registrado en audit log.
|
|
2856
|
+
${escapeHtml(tForLang(lang, 'settings.danger.warning'))}
|
|
2687
2857
|
</span>
|
|
2688
2858
|
</div>
|
|
2689
2859
|
<div id="set-danger-toggles"></div>
|
|
@@ -2698,22 +2868,22 @@ ${renderYujinSplash()}
|
|
|
2698
2868
|
<div class="audit-modal-backdrop" data-audit-close></div>
|
|
2699
2869
|
<div class="audit-modal-frame" role="dialog" aria-modal="true">
|
|
2700
2870
|
<div class="audit-modal-header">
|
|
2701
|
-
<div class="audit-modal-title"
|
|
2702
|
-
<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>
|
|
2703
2873
|
</div>
|
|
2704
2874
|
<div class="audit-modal-toolbar">
|
|
2705
2875
|
<select id="audit-modal-filter-kind" class="audit-filter">
|
|
2706
|
-
<option value=""
|
|
2876
|
+
<option value="">${escapeHtml(tForLang(lang, 'settings.audit.allKinds'))}</option>
|
|
2707
2877
|
</select>
|
|
2708
2878
|
<select id="audit-modal-filter-approval" class="audit-filter">
|
|
2709
|
-
<option value=""
|
|
2879
|
+
<option value="">${escapeHtml(tForLang(lang, 'settings.audit.allModes'))}</option>
|
|
2710
2880
|
<option value="auto_approved">auto_approved</option>
|
|
2711
2881
|
<option value="user_approved">user_approved</option>
|
|
2712
2882
|
<option value="user_rejected">user_rejected</option>
|
|
2713
2883
|
<option value="danger_zone_bypass">danger_zone_bypass</option>
|
|
2714
2884
|
<option value="not_applicable">not_applicable</option>
|
|
2715
2885
|
</select>
|
|
2716
|
-
<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>
|
|
2717
2887
|
<span id="audit-modal-count" style="font-size:11px;opacity:0.6;margin-left:auto;"></span>
|
|
2718
2888
|
</div>
|
|
2719
2889
|
<div id="audit-modal-list" class="audit-modal-list"></div>
|
|
@@ -2724,8 +2894,8 @@ ${renderYujinSplash()}
|
|
|
2724
2894
|
<div class="audit-modal-detail-sub" id="audit-modal-detail-sub">--</div>
|
|
2725
2895
|
</div>
|
|
2726
2896
|
<div style="display:flex;gap:6px;">
|
|
2727
|
-
<button type="button" class="set-btn" id="audit-modal-detail-copy"
|
|
2728
|
-
<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>
|
|
2729
2899
|
</div>
|
|
2730
2900
|
</div>
|
|
2731
2901
|
<pre class="audit-modal-detail-body" id="audit-modal-detail-body"></pre>
|
|
@@ -2738,20 +2908,20 @@ ${renderYujinSplash()}
|
|
|
2738
2908
|
<div class="legal-modal-backdrop"></div>
|
|
2739
2909
|
<div class="legal-modal-frame">
|
|
2740
2910
|
<div class="legal-modal-header">
|
|
2741
|
-
<div class="legal-modal-title" id="legal-modal-title"
|
|
2742
|
-
<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>
|
|
2743
2913
|
</div>
|
|
2744
|
-
<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>
|
|
2745
2915
|
</div>
|
|
2746
2916
|
</div>
|
|
2747
2917
|
<!-- inline edit form (single, repositioned over the active key row) -->
|
|
2748
2918
|
<div id="yf-key-editor" class="hidden key-editor">
|
|
2749
|
-
<div class="ke-title"
|
|
2750
|
-
<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"/>
|
|
2751
2921
|
<div class="ke-actions">
|
|
2752
|
-
<button type="button" id="ke-save" class="set-btn"
|
|
2753
|
-
<button type="button" id="ke-clear" class="set-btn set-btn-danger"
|
|
2754
|
-
<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>
|
|
2755
2925
|
</div>
|
|
2756
2926
|
<div id="ke-msg" class="ke-msg"></div>
|
|
2757
2927
|
</div>
|
|
@@ -2764,7 +2934,7 @@ ${renderYujinSplash()}
|
|
|
2764
2934
|
<form id="bar-full" data-nac-id="yujin.chat.bar-full" class="bar" onsubmit="return false">
|
|
2765
2935
|
<button type="button" class="mic" data-mic data-mic-btn aria-label="${escapeHtml(tr.micButton)}" title="${escapeHtml(tr.micButton)}">🎙</button>
|
|
2766
2936
|
<button type="button" class="tts-toggle" data-tts-toggle aria-label="${escapeHtml(tr.ttsButton)}" title="${escapeHtml(tr.ttsButton)}">🔊</button>
|
|
2767
|
-
<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>
|
|
2768
2938
|
<textarea class="composer-input" rows="1" placeholder="${escapeHtml(tr.inputPlaceholder)}" aria-label="${escapeHtml(tr.inputPlaceholder)}"></textarea>
|
|
2769
2939
|
<button type="submit" class="send">${escapeHtml(tr.sendButton)}</button>
|
|
2770
2940
|
<button type="button" class="stop" data-stop aria-label="${escapeHtml(tr.stopButton)}" title="${escapeHtml(tr.stopButton)}" style="display:none">■ ${escapeHtml(tr.stopButton)}</button>
|
|
@@ -2786,7 +2956,7 @@ ${renderYujinSplash()}
|
|
|
2786
2956
|
data-nac-id="yujin.pizarron.clear-all"
|
|
2787
2957
|
data-nac-role="action"
|
|
2788
2958
|
data-nac-action="clear_pizarron"
|
|
2789
|
-
aria-label="
|
|
2959
|
+
aria-label="${escapeHtml(tForLang(lang, 'panel.aria.closeBoard'))}" title="${escapeHtml(tForLang(lang, 'panel.tip.closeBoard'))}">×</button>
|
|
2790
2960
|
</div>
|
|
2791
2961
|
<div class="pz-tabs" id="pizarron-tabs"
|
|
2792
2962
|
data-nac-id="yujin.pizarron.tabs"
|
|
@@ -2796,17 +2966,12 @@ ${renderYujinSplash()}
|
|
|
2796
2966
|
data-nac-role="region">
|
|
2797
2967
|
<div class="pizarra-empty" id="pizarron-empty">
|
|
2798
2968
|
<div class="kanji-big">板</div>
|
|
2799
|
-
<p
|
|
2969
|
+
<p>${escapeHtml(tForLang(lang, 'panel.hint.boardEmpty'))}</p>
|
|
2800
2970
|
<!-- PND-064 -- aviso cuando el brain corre en modo Plan
|
|
2801
2971
|
(claude-code-cli): en ese modo el pizarron no es
|
|
2802
2972
|
alcanzable. Se muestra solo si el endpoint reporta
|
|
2803
2973
|
ese provider activo. -->
|
|
2804
|
-
<p id="pizarron-plan-hint" class="pizarra-plan-hint hidden">
|
|
2805
|
-
Estas en modo Plan (suscripcion). El pizarron se dibuja
|
|
2806
|
-
cuando el brain corre en modo API: cambialo en
|
|
2807
|
-
<strong>Ajustes > Brain > Anthropic: modalidad > Console</strong>
|
|
2808
|
-
y reabri el panel.
|
|
2809
|
-
</p>
|
|
2974
|
+
<p id="pizarron-plan-hint" class="pizarra-plan-hint hidden">${tForLang(lang, 'panel.hint.planMode')}</p>
|
|
2810
2975
|
</div>
|
|
2811
2976
|
</div>
|
|
2812
2977
|
<!-- Action trace (tool calls log) -- siempre visible
|
|
@@ -2903,6 +3068,14 @@ function setProj() {
|
|
|
2903
3068
|
el.textContent = label;
|
|
2904
3069
|
el.setAttribute('title', title);
|
|
2905
3070
|
}
|
|
3071
|
+
/* base path (project root) in the top bar -- follows project switches */
|
|
3072
|
+
const base = CONFIG.projectRoot || '';
|
|
3073
|
+
for (const id of ['#proj-base-mini', '#proj-base-full']) {
|
|
3074
|
+
const el = $(id);
|
|
3075
|
+
if (!el) continue;
|
|
3076
|
+
el.textContent = base;
|
|
3077
|
+
el.setAttribute('title', base);
|
|
3078
|
+
}
|
|
2906
3079
|
}
|
|
2907
3080
|
|
|
2908
3081
|
/* Project selector (task #33) -- the project label in the header is a
|
|
@@ -2987,10 +3160,10 @@ async function switchProject(slug) {
|
|
|
2987
3160
|
setProj();
|
|
2988
3161
|
setStatus('');
|
|
2989
3162
|
} else {
|
|
2990
|
-
setStatus((j && j.error) ||
|
|
3163
|
+
setStatus((j && j.error) || CONFIG.i18n.panelStatusProjectSwitchFail, true);
|
|
2991
3164
|
}
|
|
2992
3165
|
} catch (e) {
|
|
2993
|
-
setStatus(
|
|
3166
|
+
setStatus(CONFIG.i18n.panelStatusProjectSwitchFail, true);
|
|
2994
3167
|
}
|
|
2995
3168
|
}
|
|
2996
3169
|
/* Remove a project from the list (the "X"). Confirms first; on success drops
|
|
@@ -3011,10 +3184,10 @@ async function removeProjectViaClick(slug, rowEl) {
|
|
|
3011
3184
|
setStatus(((CONFIG.i18n && CONFIG.i18n.projectRemoved) || 'Proyecto quitado de la lista') + ': ' + slug);
|
|
3012
3185
|
setTimeout(function () { setStatus(''); }, 2500);
|
|
3013
3186
|
} else {
|
|
3014
|
-
setStatus((j && j.error) ||
|
|
3187
|
+
setStatus((j && j.error) || CONFIG.i18n.panelStatusProjectRemoveFail, true);
|
|
3015
3188
|
}
|
|
3016
3189
|
} catch (e) {
|
|
3017
|
-
setStatus(
|
|
3190
|
+
setStatus(CONFIG.i18n.panelStatusProjectRemoveFail, true);
|
|
3018
3191
|
}
|
|
3019
3192
|
}
|
|
3020
3193
|
async function addProjectViaPrompt() {
|
|
@@ -3027,10 +3200,10 @@ async function addProjectViaPrompt() {
|
|
|
3027
3200
|
body: JSON.stringify({ path: path.trim() }),
|
|
3028
3201
|
});
|
|
3029
3202
|
const j = await r.json();
|
|
3030
|
-
if (!j || !j.ok) { setStatus((j && j.error) ||
|
|
3203
|
+
if (!j || !j.ok) { setStatus((j && j.error) || CONFIG.i18n.panelStatusProjectAddFail, true); return; }
|
|
3031
3204
|
/* reopen the menu so the user can pick the freshly added project */
|
|
3032
3205
|
openProjectMenu($('#proj-full') || $('#proj-mini'));
|
|
3033
|
-
} catch (e) { setStatus(
|
|
3206
|
+
} catch (e) { setStatus(CONFIG.i18n.panelStatusProjectAddFail, true); }
|
|
3034
3207
|
}
|
|
3035
3208
|
|
|
3036
3209
|
function escapeHtml(s) {
|
|
@@ -3086,7 +3259,7 @@ function renderMd(src) {
|
|
|
3086
3259
|
* /api/forge/open-in-editor. */
|
|
3087
3260
|
out = out.replace(
|
|
3088
3261
|
/(?<![\\w/])((?:src|tests|docs|packages|apps|scripts)\\/[\\w./-]+(?:\\.[\\w]+)(?::\\d+(?::\\d+)?)?)/g,
|
|
3089
|
-
'<a class="fpath" data-fpath="$1" href="#" title="
|
|
3262
|
+
'<a class="fpath" data-fpath="$1" href="#" title="' + escapeHtml(CONFIG.i18n.tipOpenInEditor) + '">$1</a>',
|
|
3090
3263
|
);
|
|
3091
3264
|
return out;
|
|
3092
3265
|
}
|
|
@@ -3132,7 +3305,7 @@ function renderMdRich(src) {
|
|
|
3132
3305
|
/* file paths */
|
|
3133
3306
|
r = r.replace(
|
|
3134
3307
|
/(?<![\\w/])((?:src|tests|docs|packages|apps|scripts)\\/[\\w./-]+(?:\\.[\\w]+)(?::\\d+(?::\\d+)?)?)/g,
|
|
3135
|
-
'<a class="fpath" data-fpath="$1" href="#" title="
|
|
3308
|
+
'<a class="fpath" data-fpath="$1" href="#" title="' + escapeHtml(CONFIG.i18n.tipOpen) + '">$1</a>',
|
|
3136
3309
|
);
|
|
3137
3310
|
return r;
|
|
3138
3311
|
}
|
|
@@ -3386,7 +3559,7 @@ function renderStream() {
|
|
|
3386
3559
|
'<div class="tp-hint">' + hint + ' <span class="tp-count">(' + sp.round + '/' + sp.max_rounds + ')</span></div>' +
|
|
3387
3560
|
'</div>';
|
|
3388
3561
|
} else {
|
|
3389
|
-
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>';
|
|
3390
3563
|
}
|
|
3391
3564
|
target.appendChild(tdiv);
|
|
3392
3565
|
}
|
|
@@ -3410,7 +3583,7 @@ function renderStream() {
|
|
|
3410
3583
|
const body = await r.json();
|
|
3411
3584
|
if (!body.ok && body.path) {
|
|
3412
3585
|
navigator.clipboard.writeText(body.path).catch(function () {});
|
|
3413
|
-
alert('
|
|
3586
|
+
alert(CONFIG.i18n.alertNoEditor.replace('{path}', body.path));
|
|
3414
3587
|
}
|
|
3415
3588
|
fetch('/api/forge/trust-event', {
|
|
3416
3589
|
method: 'POST',
|
|
@@ -3496,13 +3669,13 @@ async function openLegalModal(title, url) {
|
|
|
3496
3669
|
const titleEl = document.getElementById('legal-modal-title');
|
|
3497
3670
|
const bodyEl = document.getElementById('legal-modal-body');
|
|
3498
3671
|
if (!modal || !titleEl || !bodyEl) return;
|
|
3499
|
-
titleEl.textContent = title ||
|
|
3500
|
-
bodyEl.textContent =
|
|
3672
|
+
titleEl.textContent = title || CONFIG.i18n.legalDefaultTitle;
|
|
3673
|
+
bodyEl.textContent = CONFIG.i18n.legalLoading;
|
|
3501
3674
|
modal.classList.remove('hidden');
|
|
3502
3675
|
try {
|
|
3503
3676
|
const r = await fetch(url);
|
|
3504
3677
|
if (!r.ok) {
|
|
3505
|
-
bodyEl.textContent = '
|
|
3678
|
+
bodyEl.textContent = CONFIG.i18n.legalLoadError.replace('{status}', r.status);
|
|
3506
3679
|
return;
|
|
3507
3680
|
}
|
|
3508
3681
|
const md = await r.text();
|
|
@@ -3512,7 +3685,7 @@ async function openLegalModal(title, url) {
|
|
|
3512
3685
|
bodyEl.textContent = md;
|
|
3513
3686
|
bodyEl.scrollTop = 0;
|
|
3514
3687
|
} catch (err) {
|
|
3515
|
-
bodyEl.textContent = '
|
|
3688
|
+
bodyEl.textContent = CONFIG.i18n.legalNetworkError.replace('{err}', (err && err.message ? err.message : 'unknown'));
|
|
3516
3689
|
}
|
|
3517
3690
|
}
|
|
3518
3691
|
|
|
@@ -3935,20 +4108,20 @@ async function refreshLimitsCard() {
|
|
|
3935
4108
|
if (jj && jj.ok) {
|
|
3936
4109
|
input.dataset.original = String(v);
|
|
3937
4110
|
if (msgEl) {
|
|
3938
|
-
msgEl.textContent = fieldPath + ' = ' + v + ' (
|
|
4111
|
+
msgEl.textContent = fieldPath + ' = ' + v + ' (' + CONFIG.i18n.fbSavedShort + ')';
|
|
3939
4112
|
msgEl.style.color = '#5fb35f';
|
|
3940
4113
|
setTimeout(function () { if (msgEl) msgEl.textContent = ''; }, 2000);
|
|
3941
4114
|
}
|
|
3942
4115
|
} else {
|
|
3943
4116
|
if (msgEl) {
|
|
3944
|
-
msgEl.textContent = jj && jj.error ? String(jj.error) :
|
|
4117
|
+
msgEl.textContent = jj && jj.error ? String(jj.error) : CONFIG.i18n.fbSaveError;
|
|
3945
4118
|
msgEl.style.color = '#d97706';
|
|
3946
4119
|
}
|
|
3947
4120
|
input.value = original || String(f.value);
|
|
3948
4121
|
}
|
|
3949
4122
|
} catch (e) {
|
|
3950
4123
|
if (msgEl) {
|
|
3951
|
-
msgEl.textContent =
|
|
4124
|
+
msgEl.textContent = CONFIG.i18n.fbNetErrorSaving;
|
|
3952
4125
|
msgEl.style.color = '#d97706';
|
|
3953
4126
|
}
|
|
3954
4127
|
}
|
|
@@ -3989,7 +4162,7 @@ async function pairGenerate() {
|
|
|
3989
4162
|
const btn = document.getElementById('set-pair-generate');
|
|
3990
4163
|
const result = document.getElementById('set-pair-result');
|
|
3991
4164
|
const errEl = document.getElementById('set-pair-error');
|
|
3992
|
-
if (btn) { btn.disabled = true; btn.textContent =
|
|
4165
|
+
if (btn) { btn.disabled = true; btn.textContent = CONFIG.i18n.pairGenerating; }
|
|
3993
4166
|
if (errEl) errEl.classList.add('hidden');
|
|
3994
4167
|
try {
|
|
3995
4168
|
const r = await fetch('/api/forge/mobile-pair', { method: 'POST' });
|
|
@@ -4005,25 +4178,25 @@ async function pairGenerate() {
|
|
|
4005
4178
|
if (copyBtn && !copyBtn.__wired) {
|
|
4006
4179
|
copyBtn.__wired = true;
|
|
4007
4180
|
copyBtn.addEventListener('click', function () {
|
|
4008
|
-
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); }
|
|
4009
4182
|
catch (e) { codeEl.select(); }
|
|
4010
4183
|
});
|
|
4011
4184
|
}
|
|
4012
4185
|
}
|
|
4013
4186
|
if (exp && j.expires_at) {
|
|
4014
4187
|
const mins = Math.max(0, Math.round((new Date(j.expires_at).getTime() - Date.now()) / 60000));
|
|
4015
|
-
exp.textContent = '
|
|
4188
|
+
exp.textContent = CONFIG.i18n.pairExpiry.replace('{mins}', mins);
|
|
4016
4189
|
}
|
|
4017
4190
|
if (result) result.classList.remove('hidden');
|
|
4018
4191
|
} else if (errEl) {
|
|
4019
|
-
errEl.textContent = (j && j.error ? j.error :
|
|
4192
|
+
errEl.textContent = (j && j.error ? j.error : CONFIG.i18n.pairQrFailed)
|
|
4020
4193
|
+ (j && j.hint ? ' (corre: ' + j.hint + ')' : '');
|
|
4021
4194
|
errEl.classList.remove('hidden');
|
|
4022
4195
|
}
|
|
4023
4196
|
} catch (e) {
|
|
4024
|
-
if (errEl) { errEl.textContent =
|
|
4197
|
+
if (errEl) { errEl.textContent = CONFIG.i18n.pairQrNetError; errEl.classList.remove('hidden'); }
|
|
4025
4198
|
}
|
|
4026
|
-
if (btn) { btn.disabled = false; btn.textContent =
|
|
4199
|
+
if (btn) { btn.disabled = false; btn.textContent = CONFIG.i18n.pairGenerateQr; }
|
|
4027
4200
|
}
|
|
4028
4201
|
|
|
4029
4202
|
/* alpha.59r -- project knowledge graph status card. Reads
|
|
@@ -4134,7 +4307,7 @@ async function refreshBookmarksList() {
|
|
|
4134
4307
|
const j = await r.json();
|
|
4135
4308
|
if (!j || !j.ok || !Array.isArray(j.bookmarks)) return;
|
|
4136
4309
|
if (j.bookmarks.length === 0) {
|
|
4137
|
-
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>';
|
|
4138
4311
|
return;
|
|
4139
4312
|
}
|
|
4140
4313
|
host.innerHTML = j.bookmarks.slice(0, 40).map((bm) => {
|
|
@@ -4312,8 +4485,8 @@ function showAuditDetailInline(entry) {
|
|
|
4312
4485
|
copyBtn.onclick = async function () {
|
|
4313
4486
|
try {
|
|
4314
4487
|
await navigator.clipboard.writeText(pretty);
|
|
4315
|
-
copyBtn.textContent =
|
|
4316
|
-
setTimeout(function () { copyBtn.textContent =
|
|
4488
|
+
copyBtn.textContent = CONFIG.i18n.fbCopied;
|
|
4489
|
+
setTimeout(function () { copyBtn.textContent = CONFIG.i18n.auditCopyJson; }, 1200);
|
|
4317
4490
|
} catch (_) { /* swallow */ }
|
|
4318
4491
|
};
|
|
4319
4492
|
}
|
|
@@ -4347,11 +4520,11 @@ function openAuditDetail(entry) {
|
|
|
4347
4520
|
'<div class="audit-detail-title">' + escapeHtml(kind) + ' <span class="a-mode ' + escapeHtml(mode) + '" style="margin-left:6px;">' + escapeHtml(mode) + '</span></div>' +
|
|
4348
4521
|
'<div class="audit-detail-sub">' + escapeHtml(ts) + ' -- ' + escapeHtml(summary) + '</div>' +
|
|
4349
4522
|
'</div>' +
|
|
4350
|
-
'<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>' +
|
|
4351
4524
|
'</div>' +
|
|
4352
4525
|
'<pre class="audit-detail-body"></pre>' +
|
|
4353
4526
|
'<div class="audit-detail-actions">' +
|
|
4354
|
-
'<button type="button" class="set-btn" data-audit-copy>
|
|
4527
|
+
'<button type="button" class="set-btn" data-audit-copy>' + escapeHtml(CONFIG.i18n.auditCopyJson) + '</button>' +
|
|
4355
4528
|
'<span class="audit-detail-msg" id="audit-detail-msg"></span>' +
|
|
4356
4529
|
'</div>' +
|
|
4357
4530
|
'</div>';
|
|
@@ -4376,14 +4549,14 @@ function openAuditDetail(entry) {
|
|
|
4376
4549
|
try {
|
|
4377
4550
|
if (navigator && navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
|
|
4378
4551
|
navigator.clipboard.writeText(pretty).then(function () {
|
|
4379
|
-
if (msg) { msg.textContent =
|
|
4552
|
+
if (msg) { msg.textContent = CONFIG.i18n.fbCopied; setTimeout(function () { if (msg) msg.textContent = ''; }, 1800); }
|
|
4380
4553
|
}, function () {
|
|
4381
|
-
if (msg) { msg.textContent =
|
|
4554
|
+
if (msg) { msg.textContent = CONFIG.i18n.fbCopyFailed; }
|
|
4382
4555
|
});
|
|
4383
4556
|
} else if (msg) {
|
|
4384
|
-
msg.textContent =
|
|
4557
|
+
msg.textContent = CONFIG.i18n.fbClipboardUnavailable;
|
|
4385
4558
|
}
|
|
4386
|
-
} catch (_) { if (msg) msg.textContent =
|
|
4559
|
+
} catch (_) { if (msg) msg.textContent = CONFIG.i18n.fbCopyFailed; }
|
|
4387
4560
|
});
|
|
4388
4561
|
}
|
|
4389
4562
|
}
|
|
@@ -4399,7 +4572,7 @@ async function resetLimitsCard() {
|
|
|
4399
4572
|
const j = await r.json();
|
|
4400
4573
|
if (j && j.ok) {
|
|
4401
4574
|
if (msgEl) {
|
|
4402
|
-
msgEl.textContent =
|
|
4575
|
+
msgEl.textContent = CONFIG.i18n.limitsResetApplied;
|
|
4403
4576
|
msgEl.style.color = '#5fb35f';
|
|
4404
4577
|
setTimeout(function () { if (msgEl) msgEl.textContent = ''; }, 2500);
|
|
4405
4578
|
}
|
|
@@ -4459,14 +4632,14 @@ async function refreshDangerZoneToggles() {
|
|
|
4459
4632
|
} else {
|
|
4460
4633
|
cb.checked = !newVal;
|
|
4461
4634
|
if (msgEl) {
|
|
4462
|
-
msgEl.textContent = jj && jj.error ? String(jj.error) :
|
|
4635
|
+
msgEl.textContent = jj && jj.error ? String(jj.error) : CONFIG.i18n.fbSaveError;
|
|
4463
4636
|
msgEl.style.color = '#d97706';
|
|
4464
4637
|
}
|
|
4465
4638
|
}
|
|
4466
4639
|
} catch (_e) {
|
|
4467
4640
|
cb.checked = !newVal;
|
|
4468
4641
|
if (msgEl) {
|
|
4469
|
-
msgEl.textContent =
|
|
4642
|
+
msgEl.textContent = CONFIG.i18n.fbNetError;
|
|
4470
4643
|
msgEl.style.color = '#d97706';
|
|
4471
4644
|
}
|
|
4472
4645
|
}
|
|
@@ -4619,10 +4792,10 @@ async function killShellTab(tabId) {
|
|
|
4619
4792
|
if (tab) tab.content = tab.content + '\\n[CANCELLED by user]\\n';
|
|
4620
4793
|
renderPizarron();
|
|
4621
4794
|
} else {
|
|
4622
|
-
setStatus('
|
|
4795
|
+
setStatus(CONFIG.i18n.panelStatusCancelFail.replace('{reason}', (j && j.reason ? j.reason : 'unknown')), true);
|
|
4623
4796
|
}
|
|
4624
4797
|
} catch (e) {
|
|
4625
|
-
setStatus(
|
|
4798
|
+
setStatus(CONFIG.i18n.panelStatusCancelNetError, true);
|
|
4626
4799
|
}
|
|
4627
4800
|
}
|
|
4628
4801
|
|
|
@@ -4789,27 +4962,27 @@ function nac3OpenApprovalModal(envelope) {
|
|
|
4789
4962
|
|
|
4790
4963
|
const sideEffects = document.createElement('div');
|
|
4791
4964
|
sideEffects.style.cssText = 'font-size:11px;opacity:0.7;margin-bottom:16px;';
|
|
4792
|
-
sideEffects.textContent = '
|
|
4965
|
+
sideEffects.textContent = CONFIG.i18n.nac3Effects.replace('{list}', (Array.isArray(verb.side_effects) ? verb.side_effects.join(', ') : CONFIG.i18n.nac3EffectsUnknown));
|
|
4793
4966
|
|
|
4794
4967
|
const btnRow = document.createElement('div');
|
|
4795
4968
|
btnRow.style.cssText = 'display:flex;gap:8px;justify-content:flex-end;';
|
|
4796
4969
|
|
|
4797
4970
|
const btnCancel = document.createElement('button');
|
|
4798
|
-
btnCancel.textContent =
|
|
4971
|
+
btnCancel.textContent = CONFIG.i18n.nac3Cancel;
|
|
4799
4972
|
btnCancel.setAttribute('data-nac-id', 'yujin.modal.nac3-approval.cancel');
|
|
4800
4973
|
btnCancel.setAttribute('data-nac-role', 'button');
|
|
4801
4974
|
btnCancel.setAttribute('data-nac-action', 'cancel');
|
|
4802
4975
|
btnCancel.style.cssText = 'padding:8px 16px;border-radius:6px;border:1px solid #3a4258;background:transparent;color:inherit;cursor:pointer;font-size:13px;';
|
|
4803
4976
|
|
|
4804
4977
|
const btnReject = document.createElement('button');
|
|
4805
|
-
btnReject.textContent =
|
|
4978
|
+
btnReject.textContent = CONFIG.i18n.nac3Reject;
|
|
4806
4979
|
btnReject.setAttribute('data-nac-id', 'yujin.modal.nac3-approval.reject');
|
|
4807
4980
|
btnReject.setAttribute('data-nac-role', 'button');
|
|
4808
4981
|
btnReject.setAttribute('data-nac-action', 'reject');
|
|
4809
4982
|
btnReject.style.cssText = 'padding:8px 16px;border-radius:6px;border:1px solid #803030;background:#3a1818;color:#f4b8b8;cursor:pointer;font-size:13px;';
|
|
4810
4983
|
|
|
4811
4984
|
const btnApprove = document.createElement('button');
|
|
4812
|
-
btnApprove.textContent = verb.policy === 'destructive' ?
|
|
4985
|
+
btnApprove.textContent = verb.policy === 'destructive' ? CONFIG.i18n.nac3ApproveDestructive : CONFIG.i18n.nac3Approve;
|
|
4813
4986
|
btnApprove.setAttribute('data-nac-id', 'yujin.modal.nac3-approval.approve');
|
|
4814
4987
|
btnApprove.setAttribute('data-nac-role', 'button');
|
|
4815
4988
|
btnApprove.setAttribute('data-nac-action', 'approve');
|
|
@@ -4850,7 +5023,7 @@ function nac3OpenApprovalModal(envelope) {
|
|
|
4850
5023
|
|
|
4851
5024
|
btnApprove.addEventListener('click', async () => {
|
|
4852
5025
|
btnApprove.disabled = true; btnReject.disabled = true;
|
|
4853
|
-
btnApprove.textContent =
|
|
5026
|
+
btnApprove.textContent = CONFIG.i18n.nac3Executing;
|
|
4854
5027
|
let payloadNote = '';
|
|
4855
5028
|
try {
|
|
4856
5029
|
const r = await fetch('/api/forge/nac3-approve', {
|
|
@@ -4984,7 +5157,7 @@ function pizarronCloseItem(id) {
|
|
|
4984
5157
|
if (idx < 0) return;
|
|
4985
5158
|
const it = state.pizarron.items[idx];
|
|
4986
5159
|
if (it.dirty) {
|
|
4987
|
-
if (!confirm('
|
|
5160
|
+
if (!confirm(CONFIG.i18n.panelConfirmTabUnsaved.replace('{title}', it.title))) return;
|
|
4988
5161
|
}
|
|
4989
5162
|
/* Startup tabs (dogfood 2026-06-10) -- si el usuario cierra una
|
|
4990
5163
|
* pestania de startup (o el lienzo Coding), recordarlo: no se
|
|
@@ -5007,7 +5180,7 @@ function pizarronCloseItem(id) {
|
|
|
5007
5180
|
|
|
5008
5181
|
function pizarronClearAll() {
|
|
5009
5182
|
const dirty = state.pizarron.items.find((x) => x.dirty);
|
|
5010
|
-
if (dirty && !confirm(
|
|
5183
|
+
if (dirty && !confirm(CONFIG.i18n.panelConfirmTabsUnsaved)) return;
|
|
5011
5184
|
state.pizarron.items = [];
|
|
5012
5185
|
state.pizarron.activeId = null;
|
|
5013
5186
|
renderPizarron();
|
|
@@ -5017,7 +5190,7 @@ function pizarronStartEdit(id) {
|
|
|
5017
5190
|
const it = state.pizarron.items.find((x) => x.id === id);
|
|
5018
5191
|
if (!it) return;
|
|
5019
5192
|
if (it.kind !== 'code' && it.kind !== 'markdown' && it.kind !== 'document') {
|
|
5020
|
-
setStatus(
|
|
5193
|
+
setStatus(CONFIG.i18n.panelStatusNotEditable, true);
|
|
5021
5194
|
return;
|
|
5022
5195
|
}
|
|
5023
5196
|
it.isEditing = true;
|
|
@@ -5048,22 +5221,22 @@ async function pizarronSaveAs(id) {
|
|
|
5048
5221
|
it.dirty = false;
|
|
5049
5222
|
it.file_path = handle.name; /* solo el nombre; el browser
|
|
5050
5223
|
no expone full path */
|
|
5051
|
-
setStatus('
|
|
5224
|
+
setStatus(CONFIG.i18n.panelStatusSavedAsPicker.replace('{name}', handle.name));
|
|
5052
5225
|
renderPizarron();
|
|
5053
5226
|
return;
|
|
5054
5227
|
} catch (err) {
|
|
5055
5228
|
/* AbortError = el user cancelo el dialog -- silencioso. */
|
|
5056
5229
|
if (err && err.name === 'AbortError') return;
|
|
5057
|
-
setStatus('
|
|
5230
|
+
setStatus(CONFIG.i18n.panelStatusSaveAsFail.replace('{error}', (err && err.message ? err.message : err)), true);
|
|
5058
5231
|
}
|
|
5059
5232
|
}
|
|
5060
5233
|
/* Fallback: prompt con path relativo al projectRoot. */
|
|
5061
5234
|
const initial = it.file_path || 'untitled.txt';
|
|
5062
|
-
const nextPath = prompt(
|
|
5235
|
+
const nextPath = prompt(CONFIG.i18n.panelPromptSaveAs, initial);
|
|
5063
5236
|
if (!nextPath || nextPath.trim() === '') return;
|
|
5064
5237
|
const rel = nextPath.trim();
|
|
5065
5238
|
if (rel.startsWith('/') || rel.includes('..')) {
|
|
5066
|
-
alert(
|
|
5239
|
+
alert(CONFIG.i18n.alertPathRelative);
|
|
5067
5240
|
return;
|
|
5068
5241
|
}
|
|
5069
5242
|
try {
|
|
@@ -5074,17 +5247,17 @@ async function pizarronSaveAs(id) {
|
|
|
5074
5247
|
});
|
|
5075
5248
|
const data = await r.json();
|
|
5076
5249
|
if (!r.ok || !data.ok) {
|
|
5077
|
-
setStatus('
|
|
5250
|
+
setStatus(CONFIG.i18n.panelStatusSaveFail.replace('{error}', (data.error || r.status)), true);
|
|
5078
5251
|
return;
|
|
5079
5252
|
}
|
|
5080
5253
|
it.content = it.editValue;
|
|
5081
5254
|
it.file_path = rel;
|
|
5082
5255
|
it.title = rel.split('/').pop() || rel;
|
|
5083
5256
|
it.dirty = false;
|
|
5084
|
-
setStatus('
|
|
5257
|
+
setStatus(CONFIG.i18n.panelStatusSavedAs.replace('{path}', rel).replace('{bytes}', data.bytes_written));
|
|
5085
5258
|
renderPizarron();
|
|
5086
5259
|
} catch (err) {
|
|
5087
|
-
setStatus('
|
|
5260
|
+
setStatus(CONFIG.i18n.panelStatusNetError.replace('{error}', (err && err.message ? err.message : err)), true);
|
|
5088
5261
|
}
|
|
5089
5262
|
}
|
|
5090
5263
|
|
|
@@ -5095,7 +5268,7 @@ async function pizarronSave(id) {
|
|
|
5095
5268
|
/* Snippet-only edit: persist back to the in-memory item. */
|
|
5096
5269
|
it.content = it.editValue;
|
|
5097
5270
|
it.dirty = false;
|
|
5098
|
-
setStatus(
|
|
5271
|
+
setStatus(CONFIG.i18n.panelStatusSavedTabOnly);
|
|
5099
5272
|
renderPizarron();
|
|
5100
5273
|
return;
|
|
5101
5274
|
}
|
|
@@ -5108,22 +5281,22 @@ async function pizarronSave(id) {
|
|
|
5108
5281
|
});
|
|
5109
5282
|
const data = await r.json();
|
|
5110
5283
|
if (!r.ok || !data.ok) {
|
|
5111
|
-
setStatus('
|
|
5284
|
+
setStatus(CONFIG.i18n.panelStatusSaveFail.replace('{error}', (data.error || r.status)), true);
|
|
5112
5285
|
return;
|
|
5113
5286
|
}
|
|
5114
5287
|
it.content = it.editValue;
|
|
5115
5288
|
it.dirty = false;
|
|
5116
|
-
setStatus('
|
|
5289
|
+
setStatus(CONFIG.i18n.panelStatusSaved.replace('{path}', it.file_path).replace('{bytes}', data.bytes_written));
|
|
5117
5290
|
renderPizarron();
|
|
5118
5291
|
} catch (err) {
|
|
5119
|
-
setStatus('
|
|
5292
|
+
setStatus(CONFIG.i18n.panelStatusNetError.replace('{error}', (err && err.message ? err.message : err)), true);
|
|
5120
5293
|
}
|
|
5121
5294
|
}
|
|
5122
5295
|
|
|
5123
5296
|
function pizarronDiscard(id) {
|
|
5124
5297
|
const it = state.pizarron.items.find((x) => x.id === id);
|
|
5125
5298
|
if (!it) return;
|
|
5126
|
-
if (it.dirty && !confirm(
|
|
5299
|
+
if (it.dirty && !confirm(CONFIG.i18n.panelConfirmDiscardChanges)) return;
|
|
5127
5300
|
it.isEditing = false;
|
|
5128
5301
|
it.editValue = it.content;
|
|
5129
5302
|
it.dirty = false;
|
|
@@ -5136,9 +5309,9 @@ function pizarronReadAloud(id) {
|
|
|
5136
5309
|
/* Strip markdown structure to make it readable, then TTS. */
|
|
5137
5310
|
let txt = it.content;
|
|
5138
5311
|
if (it.kind === 'code') {
|
|
5139
|
-
txt =
|
|
5140
|
-
+ (it.file_path ? '
|
|
5141
|
-
+ '
|
|
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);
|
|
5142
5315
|
}
|
|
5143
5316
|
playTtsForText(txt);
|
|
5144
5317
|
}
|
|
@@ -5149,8 +5322,8 @@ function pizarronCopy(id) {
|
|
|
5149
5322
|
const value = it.isEditing ? it.editValue : it.content;
|
|
5150
5323
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
5151
5324
|
navigator.clipboard.writeText(value)
|
|
5152
|
-
.then(() => setStatus(
|
|
5153
|
-
.catch(() => setStatus(
|
|
5325
|
+
.then(() => setStatus(CONFIG.i18n.panelStatusCopied))
|
|
5326
|
+
.catch(() => setStatus(CONFIG.i18n.panelStatusCopyFail, true));
|
|
5154
5327
|
}
|
|
5155
5328
|
}
|
|
5156
5329
|
|
|
@@ -5167,11 +5340,11 @@ function renderPizarronCardBody(it) {
|
|
|
5167
5340
|
+ ' <button type="button" class="pz-save" data-pz-save data-pz-item-id="' + it.id + '"'
|
|
5168
5341
|
+ ' data-nac-id="yujin.pizarron.editor.' + it.id + '.save"'
|
|
5169
5342
|
+ ' data-nac-role="action" data-nac-action="save_file"'
|
|
5170
|
-
+ ' title="
|
|
5343
|
+
+ ' title="' + escapeHtml(CONFIG.i18n.tipSaveTo.replace('{path}', it.file_path || CONFIG.i18n.tipSaveToMemory)) + '">' + escapeHtml(CONFIG.i18n.btnSave) + '</button>'
|
|
5171
5344
|
+ ' <button type="button" data-pz-save-as data-pz-item-id="' + it.id + '"'
|
|
5172
5345
|
+ ' data-nac-id="yujin.pizarron.editor.' + it.id + '.save-as"'
|
|
5173
5346
|
+ ' data-nac-role="action" data-nac-action="save_file_as"'
|
|
5174
|
-
+ ' title="
|
|
5347
|
+
+ ' title="' + escapeHtml(CONFIG.i18n.tipSaveAs) + '">' + escapeHtml(CONFIG.i18n.btnSaveAs) + '</button>'
|
|
5175
5348
|
+ ' <button type="button" data-pz-discard data-pz-item-id="' + it.id + '"'
|
|
5176
5349
|
+ ' data-nac-id="yujin.pizarron.editor.' + it.id + '.discard"'
|
|
5177
5350
|
+ ' data-nac-role="action" data-nac-action="discard_changes">Descartar</button>'
|
|
@@ -5231,8 +5404,8 @@ function renderPizarronCardBody(it) {
|
|
|
5231
5404
|
const cmode = codingMode();
|
|
5232
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">'
|
|
5233
5406
|
+ '<div class="pz-coding-switch" role="tablist">'
|
|
5234
|
-
+ '<button type="button" class="pz-cmode' + (cmode === 'detail' ? ' on' : '') + '" data-pz-coding-mode="detail" title="
|
|
5235
|
-
+ '<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>'
|
|
5236
5409
|
+ '</div>'
|
|
5237
5410
|
+ (cmode === 'zen'
|
|
5238
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>'
|
|
@@ -5244,7 +5417,7 @@ function renderPizarronCardBody(it) {
|
|
|
5244
5417
|
if (it.kind === 'tasks') {
|
|
5245
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">'
|
|
5246
5419
|
+ '<div class="pz-tasks-new">'
|
|
5247
|
-
+ '<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) + '" />'
|
|
5248
5421
|
+ '<button type="button" class="pz-tasks-run" data-pz-tasks-run>Lanzar</button>'
|
|
5249
5422
|
+ '</div>'
|
|
5250
5423
|
+ '<div class="pz-tasks-list" data-pz-tasks-list><div class="pz-tasks-empty">cargando...</div></div>'
|
|
@@ -5322,7 +5495,7 @@ function renderPizarron() {
|
|
|
5322
5495
|
const empty = $('#pizarron-empty');
|
|
5323
5496
|
const count = $('#pizarron-count');
|
|
5324
5497
|
if (!tabs || !body || !count) return;
|
|
5325
|
-
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);
|
|
5326
5499
|
/* Render tabs */
|
|
5327
5500
|
applySavedTabOrder(); /* PND-137 -- respeta el orden elegido por drag */
|
|
5328
5501
|
const tabsHtml = state.pizarron.items.map((it) => {
|
|
@@ -5340,12 +5513,12 @@ function renderPizarron() {
|
|
|
5340
5513
|
+ '<span class="pz-tab-name">' + escapeHtml(it.title) + '</span>'
|
|
5341
5514
|
+ (showKill
|
|
5342
5515
|
? '<button type="button" class="pz-tab-kill" data-pz-kill data-pz-item-id="' + it.id + '"'
|
|
5343
|
-
+ ' title="
|
|
5516
|
+
+ ' title="' + escapeHtml(CONFIG.i18n.tipCancelShell) + '" aria-label="cancel">⏹</button>'
|
|
5344
5517
|
: '')
|
|
5345
5518
|
+ '<button type="button" class="pz-tab-close" data-pz-close data-pz-item-id="' + it.id + '"'
|
|
5346
5519
|
+ ' data-nac-id="yujin.pizarron.tab.' + it.id + '.close"'
|
|
5347
5520
|
+ ' data-nac-role="action" data-nac-action="close_tab"'
|
|
5348
|
-
+ ' title="
|
|
5521
|
+
+ ' title="' + escapeHtml(CONFIG.i18n.tipCloseTab) + '" aria-label="cerrar">×</button>'
|
|
5349
5522
|
+ '</div>';
|
|
5350
5523
|
}).join('');
|
|
5351
5524
|
/* "+" para reabrir pestanas nativas cerradas (Grafo/Workflow/
|
|
@@ -5354,7 +5527,7 @@ function renderPizarron() {
|
|
|
5354
5527
|
let restoreHtml = '';
|
|
5355
5528
|
if (closedNative.length) {
|
|
5356
5529
|
restoreHtml = '<div class="pz-restore-wrap">'
|
|
5357
|
-
+ '<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>'
|
|
5358
5531
|
+ '<div class="pz-restore-menu hidden" data-pz-restore-menu>'
|
|
5359
5532
|
+ '<div class="pz-restore-head">Reabrir</div>'
|
|
5360
5533
|
+ closedNative.map(function (k) {
|
|
@@ -5368,7 +5541,7 @@ function renderPizarron() {
|
|
|
5368
5541
|
if (state.pizarron.items.length === 0) {
|
|
5369
5542
|
if (empty) empty.style.display = '';
|
|
5370
5543
|
body.innerHTML = '<div class="pizarra-empty" id="pizarron-empty"><div class="kanji-big">板</div>'
|
|
5371
|
-
+ '<p>
|
|
5544
|
+
+ '<p>' + escapeHtml(CONFIG.i18n.hintBoardEmpty) + '</p></div>';
|
|
5372
5545
|
return;
|
|
5373
5546
|
}
|
|
5374
5547
|
const it = pizarronGetActive() || state.pizarron.items[state.pizarron.items.length - 1];
|
|
@@ -5494,7 +5667,7 @@ function renderTasksList(tasks) {
|
|
|
5494
5667
|
+ (t.status === 'running'
|
|
5495
5668
|
? '<button type="button" class="pz-task-kill" data-pz-task-kill="' + escapeHtml(t.id) + '">stop</button>'
|
|
5496
5669
|
: '<button type="button" class="pz-task-log" data-pz-task-log="' + escapeHtml(t.id) + '">log</button>')
|
|
5497
|
-
+ '<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>'
|
|
5498
5671
|
+ '</div>'
|
|
5499
5672
|
+ '</div>'
|
|
5500
5673
|
+ '<div class="pz-task-logbox" data-pz-task-logbox="' + escapeHtml(t.id) + '"></div>';
|
|
@@ -7662,7 +7835,7 @@ async function handleSlashCommand(raw) {
|
|
|
7662
7835
|
};
|
|
7663
7836
|
const target = map[arg];
|
|
7664
7837
|
if (!target) {
|
|
7665
|
-
setStatus(
|
|
7838
|
+
setStatus(CONFIG.i18n.panelStatusModeAvailable, true);
|
|
7666
7839
|
return true;
|
|
7667
7840
|
}
|
|
7668
7841
|
try {
|
|
@@ -7673,13 +7846,13 @@ async function handleSlashCommand(raw) {
|
|
|
7673
7846
|
});
|
|
7674
7847
|
const data = await r.json();
|
|
7675
7848
|
if (!data.ok) {
|
|
7676
|
-
setStatus('
|
|
7849
|
+
setStatus(CONFIG.i18n.panelStatusModeChangeFail.replace('{error}', (data.error || 'unknown')), true);
|
|
7677
7850
|
return true;
|
|
7678
7851
|
}
|
|
7679
7852
|
try { document.cookie = 'yf-mode=' + target + '; path=/; max-age=' + (60*60*24*365) + '; SameSite=Lax'; } catch (_) {}
|
|
7680
|
-
setStatus('
|
|
7853
|
+
setStatus(CONFIG.i18n.panelStatusModeChanged.replace('{target}', target), false);
|
|
7681
7854
|
} catch (err) {
|
|
7682
|
-
setStatus('
|
|
7855
|
+
setStatus(CONFIG.i18n.panelStatusGenericError.replace('{error}', String(err && err.message ? err.message : err)), true);
|
|
7683
7856
|
}
|
|
7684
7857
|
return true;
|
|
7685
7858
|
}
|
|
@@ -7692,8 +7865,8 @@ async function handleSlashCommand(raw) {
|
|
|
7692
7865
|
state.brainTierOverride = target;
|
|
7693
7866
|
setStatus(
|
|
7694
7867
|
target === null
|
|
7695
|
-
?
|
|
7696
|
-
: '
|
|
7868
|
+
? CONFIG.i18n.panelStatusBrainTierAuto
|
|
7869
|
+
: CONFIG.i18n.panelStatusBrainTierForced.replace('{target}', target),
|
|
7697
7870
|
false,
|
|
7698
7871
|
);
|
|
7699
7872
|
return true;
|
|
@@ -7743,7 +7916,7 @@ async function vaultRefreshList() {
|
|
|
7743
7916
|
return;
|
|
7744
7917
|
}
|
|
7745
7918
|
const rows = data.slots.map((s) => {
|
|
7746
|
-
let testCell = '<span class="t-pending" title="
|
|
7919
|
+
let testCell = '<span class="t-pending" title="' + escapeHtml(CONFIG.i18n.tipNotTestedYet) + '">--</span>';
|
|
7747
7920
|
if (s.last_test_ok === true) testCell = '<span class="t-ok" title="tested ok at ' + escapeHtml(s.last_test_at || '') + '">ok</span>';
|
|
7748
7921
|
else if (s.last_test_ok === false) testCell = '<span class="t-fail" title="tested fail at ' + escapeHtml(s.last_test_at || '') + '">fail</span>';
|
|
7749
7922
|
const expiryCell = s.expiry ? '<span class="t-exp" title="expires ' + escapeHtml(s.expiry) + '">exp</span>' : '';
|
|
@@ -7814,7 +7987,7 @@ async function vaultAdd() {
|
|
|
7814
7987
|
}
|
|
7815
7988
|
}
|
|
7816
7989
|
async function vaultRemove(slot) {
|
|
7817
|
-
if (!confirm('
|
|
7990
|
+
if (!confirm(CONFIG.i18n.panelConfirmRemoveSlot.replace('{slot}', slot))) return;
|
|
7818
7991
|
try {
|
|
7819
7992
|
const r = await fetch('/api/vault/remove', {
|
|
7820
7993
|
method: 'POST',
|
|
@@ -7903,9 +8076,8 @@ function cycleTtsProvider() {
|
|
|
7903
8076
|
function updateTtsProviderButtons() {
|
|
7904
8077
|
const p = ttsProvider();
|
|
7905
8078
|
document.querySelectorAll('[data-tts-provider-btn]').forEach((b) => {
|
|
7906
|
-
b.textContent = p === 'browser' ?
|
|
7907
|
-
b.title =
|
|
7908
|
-
+ '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;
|
|
7909
8081
|
});
|
|
7910
8082
|
}
|
|
7911
8083
|
|
|
@@ -7929,13 +8101,12 @@ function setSttProvider(p) {
|
|
|
7929
8101
|
}
|
|
7930
8102
|
function updateSttProviderButtons() {
|
|
7931
8103
|
const p = sttProvider();
|
|
7932
|
-
const label = p === 'browser' ?
|
|
7933
|
-
: p === 'google' ?
|
|
7934
|
-
:
|
|
8104
|
+
const label = p === 'browser' ? CONFIG.i18n.sttProviderBrowser
|
|
8105
|
+
: p === 'google' ? CONFIG.i18n.sttProviderGoogle
|
|
8106
|
+
: CONFIG.i18n.sttProviderWhisper;
|
|
7935
8107
|
document.querySelectorAll('[data-stt-provider-btn]').forEach((b) => {
|
|
7936
8108
|
b.textContent = label;
|
|
7937
|
-
b.title =
|
|
7938
|
-
+ 'Google Cloud STT (server-side, BYOK) o Whisper (server-side, BYOK).';
|
|
8109
|
+
b.title = CONFIG.i18n.sttProviderTitle;
|
|
7939
8110
|
});
|
|
7940
8111
|
}
|
|
7941
8112
|
function cycleSttProvider() {
|
|
@@ -7993,10 +8164,9 @@ function updateTtsVoiceButtons() {
|
|
|
7993
8164
|
const meta = TTS_VOICES.find((v) => v.id === cur) || TTS_VOICES[0];
|
|
7994
8165
|
document.querySelectorAll('[data-tts-voice-btn]').forEach((b) => {
|
|
7995
8166
|
b.textContent = meta.persona + ' [' + meta.style + ']';
|
|
7996
|
-
b.title =
|
|
7997
|
-
|
|
7998
|
-
|
|
7999
|
-
+ '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);
|
|
8000
8170
|
});
|
|
8001
8171
|
}
|
|
8002
8172
|
|
|
@@ -8130,9 +8300,23 @@ function materializePanelDispatch(d) {
|
|
|
8130
8300
|
/* Project selector (task #33) -- update the header label to the new
|
|
8131
8301
|
* project name the server reported. */
|
|
8132
8302
|
try {
|
|
8133
|
-
if (d.args && d.args.name) { CONFIG.projectName = String(d.args.name);
|
|
8303
|
+
if (d.args && d.args.name) { CONFIG.projectName = String(d.args.name); }
|
|
8304
|
+
if (d.args && d.args.root) { CONFIG.projectRoot = String(d.args.root); }
|
|
8305
|
+
setProj();
|
|
8134
8306
|
} catch (e) { /* */ }
|
|
8135
|
-
|
|
8307
|
+
/* new project root -> the file-explorer tab must follow it. Drop the old
|
|
8308
|
+
* project's expanded-paths memory, then re-fetch the tree once the tabs
|
|
8309
|
+
* have re-materialized (the placeholder content is identical across
|
|
8310
|
+
* projects, so the upsert alone may not re-run initFilesTab). */
|
|
8311
|
+
try { for (var _k in _filesExpandByTab) delete _filesExpandByTab[_k]; } catch (e) { /* */ }
|
|
8312
|
+
loadStartupTabs(false).then(function () {
|
|
8313
|
+
setTimeout(function () {
|
|
8314
|
+
try {
|
|
8315
|
+
var f = state.pizarron.items.find(function (x) { return x.id === 'startup_files'; });
|
|
8316
|
+
if (f) initFilesTab(f);
|
|
8317
|
+
} catch (e) { /* */ }
|
|
8318
|
+
}, 30);
|
|
8319
|
+
}).catch(function () {});
|
|
8136
8320
|
return;
|
|
8137
8321
|
}
|
|
8138
8322
|
if (d.verb === 'system.remote-turn') {
|
|
@@ -8339,18 +8523,18 @@ function brainConfigBind() {
|
|
|
8339
8523
|
radio.addEventListener('change', async () => {
|
|
8340
8524
|
if (!radio.checked) return;
|
|
8341
8525
|
const note = document.querySelector('[data-brain-mode-note]');
|
|
8342
|
-
const modeLabel = radio.value === 'plan' ?
|
|
8343
|
-
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));
|
|
8344
8528
|
/* Hot-swap (server applies live now). Only show the restart hint if
|
|
8345
8529
|
* the swap did NOT apply live (e.g. test client). */
|
|
8346
8530
|
const live = data && data.applied_live;
|
|
8347
8531
|
if (note) {
|
|
8348
8532
|
note.hidden = false;
|
|
8349
8533
|
note.textContent = live
|
|
8350
|
-
?
|
|
8351
|
-
:
|
|
8534
|
+
? CONFIG.i18n.brainApplied.replace('{mode}', modeLabel)
|
|
8535
|
+
: CONFIG.i18n.brainReopenHint;
|
|
8352
8536
|
}
|
|
8353
|
-
if (live) brainStatus(
|
|
8537
|
+
if (live) brainStatus(CONFIG.i18n.brainActive.replace('{mode}', modeLabel));
|
|
8354
8538
|
});
|
|
8355
8539
|
});
|
|
8356
8540
|
const ecoToggle = document.querySelector('[data-brain-eco-enabled]');
|
|
@@ -8468,7 +8652,7 @@ async function federationLoad() {
|
|
|
8468
8652
|
if (!note) {
|
|
8469
8653
|
const span = document.createElement('span');
|
|
8470
8654
|
span.className = 'set-status'; span.setAttribute('data-fed-invite-note', '');
|
|
8471
|
-
span.textContent =
|
|
8655
|
+
span.textContent = CONFIG.i18n.licLendNeedsLicense;
|
|
8472
8656
|
inviteRow.appendChild(span);
|
|
8473
8657
|
}
|
|
8474
8658
|
}
|
|
@@ -8491,7 +8675,7 @@ async function federationLoad() {
|
|
|
8491
8675
|
await fedBrainModelsPopulate(d.host_brain.provider, d.host_brain.model);
|
|
8492
8676
|
}
|
|
8493
8677
|
} catch (err) {
|
|
8494
|
-
fedStatus('
|
|
8678
|
+
fedStatus(CONFIG.i18n.licErrorWithCode.replace('{code}', (err && err.message ? err.message : String(err))));
|
|
8495
8679
|
}
|
|
8496
8680
|
}
|
|
8497
8681
|
|
|
@@ -8547,7 +8731,7 @@ async function fedBrainSave() {
|
|
|
8547
8731
|
if (!r.ok || !d.ok) { fedStatus('no pude guardar el brain: ' + (d.error || ('HTTP ' + r.status))); return; }
|
|
8548
8732
|
fedStatus('brain de federacion: ' + provider + (model ? ' / ' + model : ''));
|
|
8549
8733
|
} catch (err) {
|
|
8550
|
-
fedStatus('
|
|
8734
|
+
fedStatus(CONFIG.i18n.licErrorWithCode.replace('{code}', (err && err.message ? err.message : String(err))));
|
|
8551
8735
|
}
|
|
8552
8736
|
}
|
|
8553
8737
|
|
|
@@ -8570,7 +8754,7 @@ function federationBind() {
|
|
|
8570
8754
|
fedStatus('conectado a ' + d.parent_handle + '. El proximo turno usa su brain.');
|
|
8571
8755
|
await federationLoad();
|
|
8572
8756
|
} catch (err) {
|
|
8573
|
-
fedStatus('
|
|
8757
|
+
fedStatus(CONFIG.i18n.licErrorWithCode.replace('{code}', (err && err.message ? err.message : String(err))));
|
|
8574
8758
|
}
|
|
8575
8759
|
});
|
|
8576
8760
|
}
|
|
@@ -8585,7 +8769,7 @@ function federationBind() {
|
|
|
8585
8769
|
fedStatus('desconectado. Volves a tu brain local.');
|
|
8586
8770
|
await federationLoad();
|
|
8587
8771
|
} catch (err) {
|
|
8588
|
-
fedStatus('
|
|
8772
|
+
fedStatus(CONFIG.i18n.licErrorWithCode.replace('{code}', (err && err.message ? err.message : String(err))));
|
|
8589
8773
|
}
|
|
8590
8774
|
});
|
|
8591
8775
|
}
|
|
@@ -8607,7 +8791,7 @@ function federationBind() {
|
|
|
8607
8791
|
const mins = Math.round((d.expires_in || 1800) / 60);
|
|
8608
8792
|
fedStatus('codigo de 1 solo uso, vence en ' + mins + ' min. Compartilo y deja "Hospedar" activo.');
|
|
8609
8793
|
} catch (err) {
|
|
8610
|
-
fedStatus('
|
|
8794
|
+
fedStatus(CONFIG.i18n.licErrorWithCode.replace('{code}', (err && err.message ? err.message : String(err))));
|
|
8611
8795
|
}
|
|
8612
8796
|
});
|
|
8613
8797
|
}
|
|
@@ -8645,7 +8829,7 @@ function federationBind() {
|
|
|
8645
8829
|
: 'host detenido.');
|
|
8646
8830
|
} catch (err) {
|
|
8647
8831
|
hostChk.checked = !enabled;
|
|
8648
|
-
fedStatus('
|
|
8832
|
+
fedStatus(CONFIG.i18n.licErrorWithCode.replace('{code}', (err && err.message ? err.message : String(err))));
|
|
8649
8833
|
}
|
|
8650
8834
|
});
|
|
8651
8835
|
}
|
|
@@ -8674,7 +8858,7 @@ async function refreshKeysStatus() {
|
|
|
8674
8858
|
const r = await fetch('/api/forge/keys-status');
|
|
8675
8859
|
const data = await r.json();
|
|
8676
8860
|
if (!r.ok || !data.ok) {
|
|
8677
|
-
if (licEl) licEl.textContent = '
|
|
8861
|
+
if (licEl) licEl.textContent = CONFIG.i18n.licErrorWithCode.replace('{code}', (data.error || r.status));
|
|
8678
8862
|
return;
|
|
8679
8863
|
}
|
|
8680
8864
|
const k = data.keys || {};
|
|
@@ -8738,7 +8922,7 @@ async function refreshKeysStatus() {
|
|
|
8738
8922
|
syncLicenseFromPanel({ silent: true });
|
|
8739
8923
|
}
|
|
8740
8924
|
} catch (err) {
|
|
8741
|
-
if (licEl) licEl.textContent = '
|
|
8925
|
+
if (licEl) licEl.textContent = CONFIG.i18n.licErrorWithCode.replace('{code}', (err && err.message ? err.message : 'unknown'));
|
|
8742
8926
|
}
|
|
8743
8927
|
}
|
|
8744
8928
|
|
|
@@ -8759,13 +8943,13 @@ async function connectLicenseHandle() {
|
|
|
8759
8943
|
if (!handle || handle.indexOf('@') < 1) {
|
|
8760
8944
|
msg.style.display = '';
|
|
8761
8945
|
msg.style.color = '#ff8b7a';
|
|
8762
|
-
msg.textContent =
|
|
8946
|
+
msg.textContent = CONFIG.i18n.licEmailInvalid;
|
|
8763
8947
|
return;
|
|
8764
8948
|
}
|
|
8765
|
-
if (btn) { btn.disabled = true; btn.textContent =
|
|
8949
|
+
if (btn) { btn.disabled = true; btn.textContent = CONFIG.i18n.licConnecting; }
|
|
8766
8950
|
msg.style.display = '';
|
|
8767
8951
|
msg.style.color = '';
|
|
8768
|
-
msg.textContent =
|
|
8952
|
+
msg.textContent = CONFIG.i18n.licSavingEmail;
|
|
8769
8953
|
try {
|
|
8770
8954
|
const r = await fetch('/api/forge/license-handle', {
|
|
8771
8955
|
method: 'POST',
|
|
@@ -8775,19 +8959,19 @@ async function connectLicenseHandle() {
|
|
|
8775
8959
|
const data = await r.json();
|
|
8776
8960
|
if (!data.ok) {
|
|
8777
8961
|
msg.style.color = '#ff8b7a';
|
|
8778
|
-
msg.textContent =
|
|
8779
|
-
|
|
8780
|
-
|
|
8962
|
+
msg.textContent = CONFIG.i18n.licSyncFailedRetry
|
|
8963
|
+
.replace('{err}', (data.error || 'unknown'))
|
|
8964
|
+
.replace('{http}', (data.http_status ? ' (HTTP ' + data.http_status + ')' : ''));
|
|
8781
8965
|
} else {
|
|
8782
8966
|
const plan = data.local_plan_after || 'none';
|
|
8783
8967
|
msg.style.color = '#6dff8b';
|
|
8784
|
-
msg.textContent = '
|
|
8968
|
+
msg.textContent = CONFIG.i18n.licPlanOk.replace('{plan}', plan);
|
|
8785
8969
|
}
|
|
8786
8970
|
} catch (err) {
|
|
8787
8971
|
msg.style.color = '#ff8b7a';
|
|
8788
|
-
msg.textContent = '
|
|
8972
|
+
msg.textContent = CONFIG.i18n.licNetError.replace('{err}', (err && err.message ? err.message : 'unknown'));
|
|
8789
8973
|
} finally {
|
|
8790
|
-
if (btn) { btn.disabled = false; btn.textContent =
|
|
8974
|
+
if (btn) { btn.disabled = false; btn.textContent = CONFIG.i18n.licConnect; }
|
|
8791
8975
|
/* Always refresh -- if handle saved, the connect-block hides
|
|
8792
8976
|
* and the sync row appears. */
|
|
8793
8977
|
refreshKeysStatus();
|
|
@@ -8802,10 +8986,10 @@ async function syncLicenseFromPanel(opts) {
|
|
|
8802
8986
|
const btn = document.getElementById('set-license-sync-btn');
|
|
8803
8987
|
const msgRow = document.getElementById('set-license-sync-msg-row');
|
|
8804
8988
|
const msgEl = document.getElementById('set-license-sync-msg');
|
|
8805
|
-
if (btn) { btn.disabled = true; btn.textContent =
|
|
8989
|
+
if (btn) { btn.disabled = true; btn.textContent = CONFIG.i18n.licSyncing; }
|
|
8806
8990
|
if (msgRow && msgEl && !silent) {
|
|
8807
8991
|
msgRow.style.display = '';
|
|
8808
|
-
msgEl.textContent =
|
|
8992
|
+
msgEl.textContent = CONFIG.i18n.licQuerying;
|
|
8809
8993
|
msgEl.style.color = '';
|
|
8810
8994
|
}
|
|
8811
8995
|
try {
|
|
@@ -8814,8 +8998,9 @@ async function syncLicenseFromPanel(opts) {
|
|
|
8814
8998
|
if (!data.ok) {
|
|
8815
8999
|
if (msgRow && msgEl) {
|
|
8816
9000
|
msgRow.style.display = '';
|
|
8817
|
-
msgEl.textContent =
|
|
8818
|
-
|
|
9001
|
+
msgEl.textContent = CONFIG.i18n.licSyncError
|
|
9002
|
+
.replace('{err}', (data.error || 'unknown'))
|
|
9003
|
+
.replace('{http}', (data.http_status ? ' (HTTP ' + data.http_status + ')' : ''));
|
|
8819
9004
|
msgEl.style.color = '#ff8b7a';
|
|
8820
9005
|
}
|
|
8821
9006
|
return;
|
|
@@ -8824,8 +9009,8 @@ async function syncLicenseFromPanel(opts) {
|
|
|
8824
9009
|
const before = data.local_plan_before || 'none';
|
|
8825
9010
|
const after = data.local_plan_after || 'none';
|
|
8826
9011
|
msgEl.textContent = before === after
|
|
8827
|
-
? '
|
|
8828
|
-
: '
|
|
9012
|
+
? CONFIG.i18n.licPlanUnchanged.replace('{plan}', after)
|
|
9013
|
+
: CONFIG.i18n.licPlanUpdated.replace('{before}', before).replace('{after}', after);
|
|
8829
9014
|
msgEl.style.color = '#6dff8b';
|
|
8830
9015
|
}
|
|
8831
9016
|
/* Refresh the keys status row to show the new plan. */
|
|
@@ -8833,11 +9018,13 @@ async function syncLicenseFromPanel(opts) {
|
|
|
8833
9018
|
} catch (err) {
|
|
8834
9019
|
if (msgRow && msgEl) {
|
|
8835
9020
|
msgRow.style.display = '';
|
|
8836
|
-
msgEl.textContent =
|
|
9021
|
+
msgEl.textContent = CONFIG.i18n.licSyncError
|
|
9022
|
+
.replace('{err}', (err && err.message ? err.message : 'unknown'))
|
|
9023
|
+
.replace('{http}', '');
|
|
8837
9024
|
msgEl.style.color = '#ff8b7a';
|
|
8838
9025
|
}
|
|
8839
9026
|
} finally {
|
|
8840
|
-
if (btn) { btn.disabled = false; btn.textContent =
|
|
9027
|
+
if (btn) { btn.disabled = false; btn.textContent = CONFIG.i18n.licSyncWithPolar; }
|
|
8841
9028
|
}
|
|
8842
9029
|
}
|
|
8843
9030
|
|
|
@@ -8868,11 +9055,11 @@ async function saveKeyEditor() {
|
|
|
8868
9055
|
if (!input || !msg || !_keyEditorSlot) return;
|
|
8869
9056
|
const value = input.value.trim();
|
|
8870
9057
|
if (value.length < 8) {
|
|
8871
|
-
msg.textContent =
|
|
9058
|
+
msg.textContent = CONFIG.i18n.keyEditorTooShort;
|
|
8872
9059
|
msg.className = 'ke-msg error';
|
|
8873
9060
|
return;
|
|
8874
9061
|
}
|
|
8875
|
-
msg.textContent =
|
|
9062
|
+
msg.textContent = CONFIG.i18n.fbSaving;
|
|
8876
9063
|
msg.className = 'ke-msg';
|
|
8877
9064
|
try {
|
|
8878
9065
|
const r = await fetch('/api/forge/keys-set', {
|
|
@@ -8882,15 +9069,15 @@ async function saveKeyEditor() {
|
|
|
8882
9069
|
});
|
|
8883
9070
|
const data = await r.json();
|
|
8884
9071
|
if (!r.ok || !data.ok) {
|
|
8885
|
-
msg.textContent = '
|
|
9072
|
+
msg.textContent = CONFIG.i18n.fbErrorPrefix.replace('{detail}', (data.error || r.status));
|
|
8886
9073
|
msg.className = 'ke-msg error';
|
|
8887
9074
|
return;
|
|
8888
9075
|
}
|
|
8889
|
-
msg.textContent =
|
|
9076
|
+
msg.textContent = CONFIG.i18n.fbSaved;
|
|
8890
9077
|
msg.className = 'ke-msg success';
|
|
8891
9078
|
setTimeout(() => { closeKeyEditor(); refreshKeysStatus(); }, 600);
|
|
8892
9079
|
} catch (err) {
|
|
8893
|
-
msg.textContent = '
|
|
9080
|
+
msg.textContent = CONFIG.i18n.fbErrorPrefix.replace('{detail}', (err && err.message ? err.message : 'unknown'));
|
|
8894
9081
|
msg.className = 'ke-msg error';
|
|
8895
9082
|
}
|
|
8896
9083
|
}
|
|
@@ -9037,11 +9224,11 @@ async function micToggle(button) {
|
|
|
9037
9224
|
return _micToggleWebSpeech(button);
|
|
9038
9225
|
}
|
|
9039
9226
|
if (provider === 'browser' && !_webSpeechAvailable()) {
|
|
9040
|
-
setStatus(
|
|
9227
|
+
setStatus(CONFIG.i18n.panelVoiceSttFallback);
|
|
9041
9228
|
/* Fall through to MediaRecorder path. */
|
|
9042
9229
|
}
|
|
9043
9230
|
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
9044
|
-
setStatus(
|
|
9231
|
+
setStatus(CONFIG.i18n.panelVoiceNoMicSupport, true);
|
|
9045
9232
|
return;
|
|
9046
9233
|
}
|
|
9047
9234
|
try {
|
|
@@ -9255,12 +9442,12 @@ async function micToggle(button) {
|
|
|
9255
9442
|
}
|
|
9256
9443
|
const secLeft = Math.max(0, Math.ceil((stopAt - Date.now()) / 1000));
|
|
9257
9444
|
if (secLeft <= 10) {
|
|
9258
|
-
setStatus('
|
|
9445
|
+
setStatus(CONFIG.i18n.panelVoiceRecordingCountdown.replace('{n}', secLeft));
|
|
9259
9446
|
}
|
|
9260
9447
|
if (secLeft <= 0) {
|
|
9261
9448
|
clearInterval(countdown);
|
|
9262
9449
|
try { mediaRecorder.stop(); } catch { /* ignore */ }
|
|
9263
|
-
setStatus(
|
|
9450
|
+
setStatus(CONFIG.i18n.panelVoiceAutoStopped, true);
|
|
9264
9451
|
}
|
|
9265
9452
|
}, 1000);
|
|
9266
9453
|
mediaRecorder.onstop = async () => {
|
|
@@ -9270,7 +9457,7 @@ async function micToggle(button) {
|
|
|
9270
9457
|
mediaRecorder = null;
|
|
9271
9458
|
if (blob.size === 0) {
|
|
9272
9459
|
setMicButtonsState('idle');
|
|
9273
|
-
setStatus(
|
|
9460
|
+
setStatus(CONFIG.i18n.panelVoiceNoAudio, true);
|
|
9274
9461
|
return;
|
|
9275
9462
|
}
|
|
9276
9463
|
/* Client-side size guard. 4 MB ~ 60s of WebM/Opus at
|
|
@@ -9278,7 +9465,7 @@ async function micToggle(button) {
|
|
|
9278
9465
|
Refuse before even hitting the network. */
|
|
9279
9466
|
if (blob.size > 4_000_000) {
|
|
9280
9467
|
setMicButtonsState('idle');
|
|
9281
|
-
setStatus(
|
|
9468
|
+
setStatus(CONFIG.i18n.panelVoiceAudioTooLong, true);
|
|
9282
9469
|
return;
|
|
9283
9470
|
}
|
|
9284
9471
|
setMicButtonsState('processing');
|
|
@@ -9318,7 +9505,7 @@ async function micToggle(button) {
|
|
|
9318
9505
|
}
|
|
9319
9506
|
const text = (data.transcript && data.transcript.text) || '';
|
|
9320
9507
|
if (!text) {
|
|
9321
|
-
setStatus(
|
|
9508
|
+
setStatus(CONFIG.i18n.panelVoiceDidNotCatch, true);
|
|
9322
9509
|
return;
|
|
9323
9510
|
}
|
|
9324
9511
|
/* Mark this turn as voice-originated so playTtsForText
|
|
@@ -9351,8 +9538,7 @@ async function micToggle(button) {
|
|
|
9351
9538
|
setMicButtonsState('recording');
|
|
9352
9539
|
setStatus('');
|
|
9353
9540
|
} catch (err) {
|
|
9354
|
-
setStatus('
|
|
9355
|
-
+ String(err && err.message ? err.message : err), true);
|
|
9541
|
+
setStatus(CONFIG.i18n.panelVoiceMicDenied.replace('{error}', String(err && err.message ? err.message : err)), true);
|
|
9356
9542
|
}
|
|
9357
9543
|
}
|
|
9358
9544
|
|
|
@@ -9546,7 +9732,7 @@ async function _fetchTtsChunk(text) {
|
|
|
9546
9732
|
});
|
|
9547
9733
|
if (!r.ok) {
|
|
9548
9734
|
const data = await r.json().catch(() => ({}));
|
|
9549
|
-
setStatus('
|
|
9735
|
+
setStatus(CONFIG.i18n.panelVoiceReplyFailed.replace('{err}', (data.error || ('HTTP ' + r.status))), true);
|
|
9550
9736
|
return null;
|
|
9551
9737
|
}
|
|
9552
9738
|
const ab = await r.arrayBuffer();
|
|
@@ -9554,7 +9740,7 @@ async function _fetchTtsChunk(text) {
|
|
|
9554
9740
|
const blob = new Blob([ab], { type: contentType });
|
|
9555
9741
|
return URL.createObjectURL(blob);
|
|
9556
9742
|
} catch (err) {
|
|
9557
|
-
setStatus('
|
|
9743
|
+
setStatus(CONFIG.i18n.panelVoiceReplyError.replace('{err}', String(err && err.message ? err.message : err)), true);
|
|
9558
9744
|
return null;
|
|
9559
9745
|
}
|
|
9560
9746
|
}
|
|
@@ -9568,7 +9754,7 @@ function _playChunkUrl(url) {
|
|
|
9568
9754
|
audio.addEventListener('ended', done, { once: true });
|
|
9569
9755
|
audio.addEventListener('error', done, { once: true });
|
|
9570
9756
|
audio.play().catch((err) => {
|
|
9571
|
-
setStatus('
|
|
9757
|
+
setStatus(CONFIG.i18n.panelStatusAudioPlayError.replace('{error}', String(err && err.message ? err.message : err)), true);
|
|
9572
9758
|
done();
|
|
9573
9759
|
});
|
|
9574
9760
|
});
|
|
@@ -9592,7 +9778,7 @@ function _playChunkUrl(url) {
|
|
|
9592
9778
|
*/
|
|
9593
9779
|
async function ingestSpecFile(file) {
|
|
9594
9780
|
if (!file) return;
|
|
9595
|
-
setStatus('
|
|
9781
|
+
setStatus(CONFIG.i18n.panelStatusReading.replace('{filename}', file.name));
|
|
9596
9782
|
/* Render an in-progress system message so the user sees
|
|
9597
9783
|
immediate feedback. */
|
|
9598
9784
|
state.messages.push({
|
|
@@ -9613,10 +9799,10 @@ async function ingestSpecFile(file) {
|
|
|
9613
9799
|
const data = await r.json();
|
|
9614
9800
|
if (!r.ok || !data.ok) {
|
|
9615
9801
|
const msg = data && data.error ? data.error : ('HTTP ' + r.status);
|
|
9616
|
-
setStatus('
|
|
9802
|
+
setStatus(CONFIG.i18n.panelStatusIngestFailed.replace('{error}', msg), true);
|
|
9617
9803
|
state.messages.push({
|
|
9618
9804
|
role: 'assistant',
|
|
9619
|
-
content: '
|
|
9805
|
+
content: CONFIG.i18n.panelStatusReadFailed.replace('{error}', msg),
|
|
9620
9806
|
});
|
|
9621
9807
|
renderStream();
|
|
9622
9808
|
return;
|
|
@@ -9649,7 +9835,7 @@ async function ingestSpecFile(file) {
|
|
|
9649
9835
|
renderTrace();
|
|
9650
9836
|
setStatus('');
|
|
9651
9837
|
} catch (err) {
|
|
9652
|
-
setStatus('
|
|
9838
|
+
setStatus(CONFIG.i18n.panelStatusIngestFailed.replace('{error}', String(err && err.message ? err.message : err)), true);
|
|
9653
9839
|
}
|
|
9654
9840
|
}
|
|
9655
9841
|
|
|
@@ -9799,11 +9985,11 @@ function closeMicForTts() {
|
|
|
9799
9985
|
if (_webSpeechRecognition) {
|
|
9800
9986
|
try { _webSpeechRecognition.abort(); } catch (_) {}
|
|
9801
9987
|
_webSpeechRecognition = null;
|
|
9802
|
-
setStatus(
|
|
9988
|
+
setStatus(CONFIG.i18n.panelVoiceMicClosedHalfDuplex);
|
|
9803
9989
|
}
|
|
9804
9990
|
if (mediaRecorder && mediaRecorder.state === 'recording') {
|
|
9805
9991
|
try { mediaRecorder.stop(); } catch { /* ignore */ }
|
|
9806
|
-
setStatus(
|
|
9992
|
+
setStatus(CONFIG.i18n.panelVoiceMicClosedHalfDuplex);
|
|
9807
9993
|
}
|
|
9808
9994
|
}
|
|
9809
9995
|
|
|
@@ -9823,11 +10009,11 @@ async function _micToggleWebSpeech(button) {
|
|
|
9823
10009
|
} catch (err) {
|
|
9824
10010
|
const name = (err && err.name) ? String(err.name) : '';
|
|
9825
10011
|
if (name === 'NotAllowedError' || name === 'SecurityError') {
|
|
9826
|
-
setStatus(
|
|
10012
|
+
setStatus(CONFIG.i18n.panelVoiceMicBlocked, true);
|
|
9827
10013
|
} else if (name === 'NotFoundError' || name === 'OverconstrainedError') {
|
|
9828
|
-
setStatus(
|
|
10014
|
+
setStatus(CONFIG.i18n.panelVoiceNoMicDevice, true);
|
|
9829
10015
|
} else {
|
|
9830
|
-
setStatus('
|
|
10016
|
+
setStatus(CONFIG.i18n.panelVoiceMicOpenFail.replace('{error}', (name || (err && err.message) || 'error')), true);
|
|
9831
10017
|
}
|
|
9832
10018
|
setMicButtonsState('idle');
|
|
9833
10019
|
return;
|
|
@@ -9870,8 +10056,8 @@ async function _micToggleWebSpeech(button) {
|
|
|
9870
10056
|
try { rec.stop(); } catch (_) {}
|
|
9871
10057
|
}, SILENCE_MS);
|
|
9872
10058
|
};
|
|
9873
|
-
rec.onaudiostart = () => { audioStarted = true; setStatus(
|
|
9874
|
-
rec.onspeechstart = () => { speechStarted = true; setStatus(
|
|
10059
|
+
rec.onaudiostart = () => { audioStarted = true; setStatus(CONFIG.i18n.panelVoiceMicOpenListening); };
|
|
10060
|
+
rec.onspeechstart = () => { speechStarted = true; setStatus(CONFIG.i18n.panelVoiceListening); };
|
|
9875
10061
|
rec.onresult = (e) => {
|
|
9876
10062
|
interim = '';
|
|
9877
10063
|
for (let i = e.resultIndex; i < e.results.length; i += 1) {
|
|
@@ -9880,7 +10066,7 @@ async function _micToggleWebSpeech(button) {
|
|
|
9880
10066
|
else interim += r[0].transcript;
|
|
9881
10067
|
}
|
|
9882
10068
|
if (interim || finalText) gotAnyResult = true;
|
|
9883
|
-
if (interim) setStatus('
|
|
10069
|
+
if (interim) setStatus(CONFIG.i18n.panelVoiceListeningInterim.replace('{text}', interim));
|
|
9884
10070
|
/* Only reset the silence countdown when something actually
|
|
9885
10071
|
* changed since the last frame. A repeated interim is Chrome
|
|
9886
10072
|
* keeping the channel open on background noise -- NOT user
|
|
@@ -9909,39 +10095,19 @@ async function _micToggleWebSpeech(button) {
|
|
|
9909
10095
|
break;
|
|
9910
10096
|
case 'not-allowed':
|
|
9911
10097
|
case 'service-not-allowed':
|
|
9912
|
-
setStatus(
|
|
9913
|
-
'Microfono bloqueado por el navegador. Hace click en el icono '
|
|
9914
|
-
+ 'del candado en la barra de URL y permiti microfono, despues '
|
|
9915
|
-
+ 'recarga la pagina. O cambia STT a "google" / "whisper" en '
|
|
9916
|
-
+ 'la configuracion si tenes la API key.',
|
|
9917
|
-
true,
|
|
9918
|
-
);
|
|
10098
|
+
setStatus(CONFIG.i18n.panelVoiceMicBlockedBrowser, true);
|
|
9919
10099
|
break;
|
|
9920
10100
|
case 'audio-capture':
|
|
9921
|
-
setStatus(
|
|
9922
|
-
'No detecto microfono fisico. Conecta uno o cambia STT a '
|
|
9923
|
-
+ '"google" / "whisper" en la configuracion.',
|
|
9924
|
-
true,
|
|
9925
|
-
);
|
|
10101
|
+
setStatus(CONFIG.i18n.panelVoiceNoMicPhysical, true);
|
|
9926
10102
|
break;
|
|
9927
10103
|
case 'network':
|
|
9928
|
-
setStatus(
|
|
9929
|
-
'Web Speech requiere conexion a internet (Chrome usa Google '
|
|
9930
|
-
+ 'detras). Probate sin conexion intermitente o cambia STT '
|
|
9931
|
-
+ 'a "whisper" si tenes API key.',
|
|
9932
|
-
true,
|
|
9933
|
-
);
|
|
10104
|
+
setStatus(CONFIG.i18n.panelVoiceNetworkRequired, true);
|
|
9934
10105
|
break;
|
|
9935
10106
|
case 'language-not-supported':
|
|
9936
|
-
setStatus(
|
|
9937
|
-
'Tu navegador no soporta este idioma para STT. Cambia el '
|
|
9938
|
-
+ 'idioma del panel a uno soportado (en, es, pt) o cambia '
|
|
9939
|
-
+ 'STT a "google" / "whisper".',
|
|
9940
|
-
true,
|
|
9941
|
-
);
|
|
10107
|
+
setStatus(CONFIG.i18n.panelVoiceLangNotSupported, true);
|
|
9942
10108
|
break;
|
|
9943
10109
|
default:
|
|
9944
|
-
setStatus('
|
|
10110
|
+
setStatus(CONFIG.i18n.panelVoiceMicError.replace('{error}', errKind), true);
|
|
9945
10111
|
}
|
|
9946
10112
|
};
|
|
9947
10113
|
rec.onend = () => {
|
|
@@ -9968,20 +10134,20 @@ async function _micToggleWebSpeech(button) {
|
|
|
9968
10134
|
/* Precise diagnostic so a mobile user (and we) can tell capture vs STT
|
|
9969
10135
|
* apart instead of a generic "did not catch that". (Pablo 2026-06-19.) */
|
|
9970
10136
|
if (!audioStarted) {
|
|
9971
|
-
setStatus(
|
|
10137
|
+
setStatus(CONFIG.i18n.panelVoiceNoAudioToRecognizer, true);
|
|
9972
10138
|
} else if (!speechStarted && !gotAnyResult) {
|
|
9973
|
-
setStatus(
|
|
10139
|
+
setStatus(CONFIG.i18n.panelVoiceMicOpenNoVoice, true);
|
|
9974
10140
|
} else if (!gotAnyResult) {
|
|
9975
|
-
setStatus(
|
|
10141
|
+
setStatus(CONFIG.i18n.panelVoiceVoiceNoText, true);
|
|
9976
10142
|
} else {
|
|
9977
|
-
setStatus(
|
|
10143
|
+
setStatus(CONFIG.i18n.panelVoiceDidNotCatch, true);
|
|
9978
10144
|
}
|
|
9979
10145
|
};
|
|
9980
10146
|
try {
|
|
9981
10147
|
_webSpeechRecognition = rec;
|
|
9982
10148
|
rec.start();
|
|
9983
10149
|
setMicButtonsState('recording');
|
|
9984
|
-
setStatus(
|
|
10150
|
+
setStatus(CONFIG.i18n.panelVoiceStartingDictation);
|
|
9985
10151
|
/* IMPORTANT (alpha.29 bugfix): DO NOT arm the silence timer
|
|
9986
10152
|
here. If we did, and the user takes >900ms to start
|
|
9987
10153
|
talking (typical when they click + think), the timer would
|
|
@@ -9990,7 +10156,7 @@ async function _micToggleWebSpeech(button) {
|
|
|
9990
10156
|
once the FIRST interim/final result lands. */
|
|
9991
10157
|
} catch (err) {
|
|
9992
10158
|
_webSpeechRecognition = null;
|
|
9993
|
-
setStatus('
|
|
10159
|
+
setStatus(CONFIG.i18n.panelVoiceCouldNotStartMic.replace('{error}', (err && err.message ? err.message : String(err))), true);
|
|
9994
10160
|
}
|
|
9995
10161
|
}
|
|
9996
10162
|
|
|
@@ -10003,7 +10169,7 @@ async function _micToggleWebSpeech(button) {
|
|
|
10003
10169
|
function _speakBrowser(text, langHint) {
|
|
10004
10170
|
return new Promise((resolve) => {
|
|
10005
10171
|
if (typeof window === 'undefined' || !window.speechSynthesis) {
|
|
10006
|
-
setStatus(
|
|
10172
|
+
setStatus(CONFIG.i18n.panelVoiceTtsUnavailable, true);
|
|
10007
10173
|
resolve();
|
|
10008
10174
|
return;
|
|
10009
10175
|
}
|
|
@@ -10499,18 +10665,18 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
10499
10665
|
try { const r = await fetch('/api/forge/bridge/state'); data = await r.json(); }
|
|
10500
10666
|
catch (_) { return; }
|
|
10501
10667
|
if (!data || !data.ok || !data.enabled) {
|
|
10502
|
-
statusEl.textContent =
|
|
10503
|
-
rosterEl.innerHTML = '<div class="bridge-empty">
|
|
10668
|
+
statusEl.textContent = CONFIG.i18n.bridgeStatusUnavailable;
|
|
10669
|
+
rosterEl.innerHTML = '<div class="bridge-empty">' + escapeHtml(CONFIG.i18n.bridgeCoordUnavailable) + '</div>';
|
|
10504
10670
|
return;
|
|
10505
10671
|
}
|
|
10506
10672
|
const insts = Array.isArray(data.instances) ? data.instances : [];
|
|
10507
|
-
statusEl.textContent = (data.role === 'host' ?
|
|
10508
|
-
+ ' - ' + 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);
|
|
10509
10675
|
rosterEl.innerHTML = insts.length ? insts.map((i) => {
|
|
10510
10676
|
const self = i.id === data.self_id;
|
|
10511
10677
|
const name = escapeHtml(i.project_name || i.project_slug || 'forge');
|
|
10512
10678
|
return '<div class="bridge-inst' + (self ? ' is-self' : '') + '">'
|
|
10513
|
-
+ '<span class="dot"></span><span>' + name + (self ? ' (
|
|
10679
|
+
+ '<span class="dot"></span><span>' + name + (self ? ' (' + CONFIG.i18n.bridgeYou + ')' : '')
|
|
10514
10680
|
+ '</span><span class="meta">:' + (i.panel_port || '?') + '</span></div>';
|
|
10515
10681
|
}).join('') : '<div class="bridge-empty">Sin otras instancias.</div>';
|
|
10516
10682
|
const msgs = Array.isArray(data.messages) ? data.messages : [];
|
|
@@ -10632,7 +10798,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
10632
10798
|
$$('[data-key-clear]').forEach((b) => b.addEventListener('click', (e) => {
|
|
10633
10799
|
const row = e.currentTarget.closest('[data-key-slot]');
|
|
10634
10800
|
if (!row) return;
|
|
10635
|
-
if (confirm('
|
|
10801
|
+
if (confirm(CONFIG.i18n.panelConfirmRemoveKey.replace('{slot}', row.getAttribute('data-key-slot')))) {
|
|
10636
10802
|
clearKey(row.getAttribute('data-key-slot'));
|
|
10637
10803
|
}
|
|
10638
10804
|
}));
|
|
@@ -10642,7 +10808,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
10642
10808
|
if (keSave) keSave.addEventListener('click', saveKeyEditor);
|
|
10643
10809
|
if (keCancel) keCancel.addEventListener('click', closeKeyEditor);
|
|
10644
10810
|
if (keClear) keClear.addEventListener('click', () => {
|
|
10645
|
-
if (_keyEditorSlot && confirm('
|
|
10811
|
+
if (_keyEditorSlot && confirm(CONFIG.i18n.panelConfirmRemoveKey.replace('{slot}', _keyEditorSlot))) {
|
|
10646
10812
|
clearKey(_keyEditorSlot);
|
|
10647
10813
|
closeKeyEditor();
|
|
10648
10814
|
}
|
|
@@ -10731,7 +10897,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
10731
10897
|
/* Not a supported image -> keep the spec-ingest path. */
|
|
10732
10898
|
if (SUPPORTED_IMG.indexOf(mt) < 0) { await ingestSpecFile(file); return; }
|
|
10733
10899
|
if (file.size > 6 * 1024 * 1024) {
|
|
10734
|
-
setStatus(
|
|
10900
|
+
setStatus(CONFIG.i18n.panelStatusImageTooLarge.replace('{name}', file.name), true);
|
|
10735
10901
|
return;
|
|
10736
10902
|
}
|
|
10737
10903
|
try {
|
|
@@ -10739,9 +10905,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
10739
10905
|
state.pendingAttachments = state.pendingAttachments || [];
|
|
10740
10906
|
state.pendingAttachments.push({ media_type: mt, data: data, name: file.name });
|
|
10741
10907
|
renderAttachChips();
|
|
10742
|
-
setStatus('
|
|
10908
|
+
setStatus(CONFIG.i18n.panelStatusImageReady.replace('{name}', file.name));
|
|
10743
10909
|
} catch (err) {
|
|
10744
|
-
setStatus('
|
|
10910
|
+
setStatus(CONFIG.i18n.panelStatusImageReadFail.replace('{error}', ((err && err.message) || err)), true);
|
|
10745
10911
|
}
|
|
10746
10912
|
}
|
|
10747
10913
|
/* Expose so send() + drag&drop can reach them. */
|
|
@@ -10827,7 +10993,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
10827
10993
|
/* Reload to apply translated strings everywhere. */
|
|
10828
10994
|
location.reload();
|
|
10829
10995
|
} catch (err) {
|
|
10830
|
-
setStatus('
|
|
10996
|
+
setStatus(CONFIG.i18n.panelStatusLanguageSwitchFailed.replace('{err}', String(err && err.message ? err.message : err)), true);
|
|
10831
10997
|
}
|
|
10832
10998
|
});
|
|
10833
10999
|
});
|
|
@@ -10841,7 +11007,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
10841
11007
|
}
|
|
10842
11008
|
const data = await loadLangData();
|
|
10843
11009
|
if (!data) {
|
|
10844
|
-
setStatus(
|
|
11010
|
+
setStatus(CONFIG.i18n.panelStatusLanguageListFailed, true);
|
|
10845
11011
|
return;
|
|
10846
11012
|
}
|
|
10847
11013
|
renderLangDropdown(data);
|
|
@@ -11240,7 +11406,7 @@ function mcpClose() {
|
|
|
11240
11406
|
|
|
11241
11407
|
async function mcpLoadBridges(/* force */) {
|
|
11242
11408
|
if (!yfMcp.contentEl) return;
|
|
11243
|
-
yfMcp.contentEl.innerHTML = '<div class="mcp-empty">
|
|
11409
|
+
yfMcp.contentEl.innerHTML = '<div class="mcp-empty">' + escapeHtml(CONFIG.i18n.mcpLoadingBridges) + '</div>';
|
|
11244
11410
|
try {
|
|
11245
11411
|
const r = await fetch('/api/forge/mcp/bridges');
|
|
11246
11412
|
const body = await r.json();
|
|
@@ -11327,7 +11493,7 @@ async function mcpLoadTools(bridgeId, cardEl) {
|
|
|
11327
11493
|
const panel = document.createElement('div');
|
|
11328
11494
|
panel.className = 'mcp-tools-list';
|
|
11329
11495
|
panel.dataset.mcpToolsFor = bridgeId;
|
|
11330
|
-
panel.textContent =
|
|
11496
|
+
panel.textContent = CONFIG.i18n.toolsLoading;
|
|
11331
11497
|
cardEl.parentElement.insertBefore(panel, cardEl.nextSibling);
|
|
11332
11498
|
try {
|
|
11333
11499
|
const r = await fetch('/api/forge/mcp/tools?bridge_id=' + encodeURIComponent(bridgeId));
|
|
@@ -11689,7 +11855,7 @@ async function activityLoadBridgeMessages(pairId) {
|
|
|
11689
11855
|
const msgs = Array.isArray(body.messages) ? body.messages : [];
|
|
11690
11856
|
panel.innerHTML = '';
|
|
11691
11857
|
const back = document.createElement('button');
|
|
11692
|
-
back.textContent =
|
|
11858
|
+
back.textContent = CONFIG.i18n.agentsChatBackPairs;
|
|
11693
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;';
|
|
11694
11860
|
back.addEventListener('click', () => { yfActivity.selectedPair = null; activityLoadBridgePairs(); });
|
|
11695
11861
|
panel.appendChild(back);
|
|
@@ -11710,7 +11876,7 @@ async function activityLoadBridgeMessages(pairId) {
|
|
|
11710
11876
|
if (msgs.length === 0) {
|
|
11711
11877
|
const empty = document.createElement('div');
|
|
11712
11878
|
empty.className = 'act-empty';
|
|
11713
|
-
empty.textContent =
|
|
11879
|
+
empty.textContent = CONFIG.i18n.agentsChatNoMessages;
|
|
11714
11880
|
panel.appendChild(empty);
|
|
11715
11881
|
}
|
|
11716
11882
|
} catch (err) {
|
|
@@ -11736,7 +11902,7 @@ async function activityPopulateChatSlugs() {
|
|
|
11736
11902
|
if (agents.length === 0) {
|
|
11737
11903
|
const opt = document.createElement('option');
|
|
11738
11904
|
opt.value = '';
|
|
11739
|
-
opt.textContent =
|
|
11905
|
+
opt.textContent = CONFIG.i18n.agentsChatNoAgents;
|
|
11740
11906
|
select.appendChild(opt);
|
|
11741
11907
|
}
|
|
11742
11908
|
} catch (_err) { /* silent */ }
|
|
@@ -11750,8 +11916,8 @@ async function activityChatSend() {
|
|
|
11750
11916
|
if (!select || !input || !result) return;
|
|
11751
11917
|
const slug = String(select.value || '').trim();
|
|
11752
11918
|
const text = String(input.value || '').trim();
|
|
11753
|
-
if (!slug || !text) { result.textContent =
|
|
11754
|
-
result.textContent =
|
|
11919
|
+
if (!slug || !text) { result.textContent = CONFIG.i18n.agentsChatPickAgent; return; }
|
|
11920
|
+
result.textContent = CONFIG.i18n.agentsChatSending;
|
|
11755
11921
|
try {
|
|
11756
11922
|
const r = await fetch('/api/agent/inject', {
|
|
11757
11923
|
method: 'POST',
|
|
@@ -11770,7 +11936,7 @@ async function activityChatSend() {
|
|
|
11770
11936
|
/* Trigger a feed refresh so the user sees the new run. */
|
|
11771
11937
|
if (yfActivity.activeTab === 'feed') setTimeout(() => activityRefresh(true), 800);
|
|
11772
11938
|
} catch (err) {
|
|
11773
|
-
result.textContent = '
|
|
11939
|
+
result.textContent = CONFIG.i18n.agentsChatErrorGeneric.replace('{err}', (err.message || String(err)));
|
|
11774
11940
|
}
|
|
11775
11941
|
}
|
|
11776
11942
|
|