ratacode 0.2.5
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/CREDITS.md +80 -0
- package/LICENSE +21 -0
- package/README.md +289 -0
- package/apreton/README.md +54 -0
- package/apreton/handshake.md +33 -0
- package/apreton/headless.md +58 -0
- package/apreton/mcp.md +221 -0
- package/apreton/navegador.md +58 -0
- package/bin/instalacion.js +148 -0
- package/bin/ratacode.js +1251 -0
- package/fabrica/settings.yaml +247 -0
- package/mcp/README.md +253 -0
- package/mcp/bin/ratacode-mcp.js +215 -0
- package/mcp/lib/actividad.js +49 -0
- package/mcp/lib/casa.js +126 -0
- package/mcp/lib/claves.js +148 -0
- package/mcp/lib/espacios.js +117 -0
- package/mcp/lib/http.js +184 -0
- package/mcp/lib/lectura.js +210 -0
- package/mcp/lib/modelos.js +235 -0
- package/mcp/lib/nucleo.js +332 -0
- package/mcp/lib/registro.js +21 -0
- package/mcp/lib/seguridad.js +283 -0
- package/mcp/lib/servidor.js +397 -0
- package/mcp/lib/tareas.js +406 -0
- package/mcp/package.json +22 -0
- package/mcp/tunel.mjs +209 -0
- package/modos/arquitecto/agent.cordis.yml +120 -0
- package/modos/arquitecto/preset.yml +3 -0
- package/modos/capataz/agent.cordis.yml +120 -0
- package/modos/capataz/preset.yml +3 -0
- package/modos/faro/agent.cordis.yml +120 -0
- package/modos/faro/preset.yml +3 -0
- package/modos/gepeto/agent.cordis.yml +191 -0
- package/modos/gepeto/preset.yml +3 -0
- package/modos/hero/agent.cordis.yml +198 -0
- package/modos/hero/preset.yml +3 -0
- package/modos/modo-rata/agent.cordis.yml +198 -0
- package/modos/modo-rata/preset.yml +3 -0
- package/modos/nex/agent.cordis.yml +213 -0
- package/modos/nex/preset.yml +3 -0
- package/modos/nex/skills/cordis-plugin-development/SKILL.md +420 -0
- package/modos/nex/skills/editing-cordis-compositions/SKILL.md +165 -0
- package/modos/pix/agent.cordis.yml +207 -0
- package/modos/pix/preset.yml +3 -0
- package/modos/tirita/agent.cordis.yml +130 -0
- package/modos/tirita/preset.yml +3 -0
- package/package.json +49 -0
- package/piel/activos/ratacode-emblema.svg +14 -0
- package/piel/activos/ratacode-es.js +1331 -0
- package/piel/activos/ratacode-identidad.css +50 -0
- package/piel/activos/ratacode-piel.css +217 -0
- package/piel/activos/ratacode-piel.js +487 -0
- package/piel/activos/ratacode-vida.js +424 -0
- package/piel/cordis.patch.yml +6 -0
- package/piel/lib/cliente.js +1020 -0
- package/piel/lib/index.js +1230 -0
- package/piel/package.json +36 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
/* RATACODE · sólo rótulos de interfaz; nunca recorrer el contenido de las conversaciones. */
|
|
2
|
+
(() => {
|
|
3
|
+
// ── el título de la pestaña: SIEMPRE «<sesión> — RATACODE» ────────────────
|
|
4
|
+
// El frontend escribe el título desde `dsh-client-ui-layout/lib/client.js:62`:
|
|
5
|
+
// document.title = title === undefined ? productTitle : `${title} — ${productTitle}`
|
|
6
|
+
// y su `productTitle` es la cadena "DeepSeek Harness" (:263). En vez de pelearse
|
|
7
|
+
// con React, se intercepta la PROPIEDAD: cualquier asignación pasa por aquí y
|
|
8
|
+
// sale con RATACODE. Así nunca se ve «DeepSeek Harness», póngalo quien lo ponga.
|
|
9
|
+
try{
|
|
10
|
+
const d=Object.getOwnPropertyDescriptor(Document.prototype,'title');
|
|
11
|
+
if(d&&d.set&&d.get){
|
|
12
|
+
Object.defineProperty(document,'title',{configurable:true,enumerable:true,
|
|
13
|
+
get(){return d.get.call(document)},
|
|
14
|
+
set(v){d.set.call(document,String(v).replace(/DeepSeek Harness/gi,'RATACODE'))}});
|
|
15
|
+
document.title=document.title; // normaliza lo que ya estuviera puesto
|
|
16
|
+
}
|
|
17
|
+
}catch(e){/* si no se puede, queda el reemplazo del index y el de apply() */}
|
|
18
|
+
window.__ratacodeSkinObserver?.disconnect();
|
|
19
|
+
document.documentElement.dataset.ratacodePiel='punk-1';
|
|
20
|
+
const labels=new Map([['New Session','Nueva sesión'],['New session','Nueva sesión'],['Workspaces','Espacios de trabajo'],['Settings','Ajustes'],['Session log','Registro'],['Trajectory','Actividad'],['Collapse sidebar','Recoger barra lateral'],['Expand sidebar','Mostrar barra lateral'],['Search sessions','Buscar sesiones'],['Add workspace','Añadir espacio de trabajo'],['View options','Opciones de vista'],['No sessions yet','Todavía no hay sesiones'],['Choose workspace','Elegir espacio de trabajo']]);
|
|
21
|
+
// La caja del encargo pone su texto desde el diccionario del frontend
|
|
22
|
+
// (`dsh-client-ui-conversation/lib/client.js`, claves `placeholder.default`,
|
|
23
|
+
// `placeholder.hero` y `placeholder.workspace`) y React lo reescribe al
|
|
24
|
+
// cambiar de vista. Por eso NO se fuerza un texto fijo: se traduce el que
|
|
25
|
+
// haya, para no pisar el estado (hero, sesión o «elige espacio»).
|
|
26
|
+
const PLACEHOLDERS=new Map([
|
|
27
|
+
['Message or run a task, / commands, @ files or sessions','Escribe tu encargo… / comandos · @ archivos y sesiones'],
|
|
28
|
+
['Describe what you want to build, / commands, @ files or sessions','Describe lo que quieres construir… / comandos · @ archivos y sesiones'],
|
|
29
|
+
['Choose a workspace to start','Elige un espacio de trabajo para empezar'],
|
|
30
|
+
['Session unavailable','Sesión no disponible'],
|
|
31
|
+
]);
|
|
32
|
+
const placeholder='Escribe tu encargo… / comandos · @ archivos y sesiones';
|
|
33
|
+
const normalizar=(t)=>String(t==null?'':t).replace(/\s+/g,' ').trim();
|
|
34
|
+
// El frontend pone el texto de la caja en TRES sitios a la vez
|
|
35
|
+
// (`dsh-client-ui-conversation/lib/client.js`: el div `[data-composer-placeholder]`,
|
|
36
|
+
// el atributo `data-placeholder` del editor y su `aria-label`), y puede tener
|
|
37
|
+
// MÁS DE UNA caja montada (la portada y la de la sesión): traducir sólo la
|
|
38
|
+
// primera —que es lo que se hacía— dejaba la de la sesión en inglés. Se
|
|
39
|
+
// traducen todas, y por el texto que ponga el frontend, sin forzar uno fijo.
|
|
40
|
+
function traducirCajas(){
|
|
41
|
+
document.querySelectorAll('[data-composer-placeholder]').forEach(el=>{
|
|
42
|
+
const suyo=normalizar(el.textContent);
|
|
43
|
+
const traducido=PLACEHOLDERS.get(suyo);
|
|
44
|
+
if(traducido){if(normalizar(el.textContent)!==traducido)el.textContent=traducido;}
|
|
45
|
+
else if(suyo==='')el.textContent=placeholder;
|
|
46
|
+
});
|
|
47
|
+
document.querySelectorAll('[data-placeholder]').forEach(el=>{
|
|
48
|
+
const suyo=normalizar(el.getAttribute('data-placeholder'));
|
|
49
|
+
const traducido=PLACEHOLDERS.get(suyo);
|
|
50
|
+
if(traducido&&suyo!==traducido)el.setAttribute('data-placeholder',traducido);
|
|
51
|
+
const etiqueta=normalizar(el.getAttribute('aria-label'));
|
|
52
|
+
const traducida=PLACEHOLDERS.get(etiqueta)||labels.get(etiqueta);
|
|
53
|
+
if(traducida&&etiqueta!==traducida)el.setAttribute('aria-label',traducida);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function wordmark(){
|
|
57
|
+
const word=document.createElement('span');word.className='mr-wordmark mr-dsh-word';word.append('RATA');
|
|
58
|
+
const code=document.createElement('b');code.textContent='CODE';word.append(code);
|
|
59
|
+
return word;
|
|
60
|
+
}
|
|
61
|
+
// ── R12 · LA VENTANA NATIVA DE DSH, QUE LLEVE A AJUSTES › MODELS ───────────
|
|
62
|
+
// DSH saca su propia ventana de primera vez, «Add an API key to get started»,
|
|
63
|
+
// PERO SÓLO PIDE LA CLAVE DE DEEPSEEK («Configure the official DeepSeek
|
|
64
|
+
// provider…», dsh-client-ui-settings-models/lib/client.js:2743-2747) y hay que
|
|
65
|
+
// desmontarla con su «Configure later» (:2745). RATACODE tiene 8 APIs y 2
|
|
66
|
+
// motores locales (los locales tienen su pestaña desde R18), así que esa
|
|
67
|
+
// ventana estorba a quien use Groq, Gemini, SambaNova…: en vez de taparla (lo
|
|
68
|
+
// que hacía la ventana de las 3 claves, ya retirada), se le mete una nota en
|
|
69
|
+
// español y un botón que la cierra y abre Ajustes › Models.
|
|
70
|
+
const TITULOS_NATIVOS=['Add an API key to get started','添加一个 API Key 开始使用'];
|
|
71
|
+
const ATRAS_NATIVOS=['Configure later','稍后配置'];
|
|
72
|
+
const PROVEEDORES='B.AI, OpenRouter, DeepSeek, Groq, Google Gemini, NVIDIA NIM, SambaNova y Cloudflare Workers AI (las APIs, que van con clave)';
|
|
73
|
+
/** Abre Ajustes y deja seleccionada una sección del menú (el panel abre en General). */
|
|
74
|
+
function abrirAjustesSeccion(nombre){
|
|
75
|
+
const disparador=Array.from(document.querySelectorAll('button[aria-haspopup="dialog"]'))
|
|
76
|
+
.find(b=>['Ajustes','Settings'].indexOf((b.getAttribute('aria-label')||'').trim())!==-1);
|
|
77
|
+
if(!disparador)return false;
|
|
78
|
+
disparador.click();
|
|
79
|
+
const buscar=()=>{
|
|
80
|
+
const fila=Array.from(document.querySelectorAll('[class*="_navList"] button'))
|
|
81
|
+
.find(b=>(b.textContent||'').trim()===nombre);
|
|
82
|
+
if(fila)fila.click();
|
|
83
|
+
};
|
|
84
|
+
setTimeout(buscar,60);setTimeout(buscar,240);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
/** Abre Ajustes y deja seleccionado «Models». */
|
|
88
|
+
function abrirAjustesModels(){return abrirAjustesSeccion('Models');}
|
|
89
|
+
/** R18 · Abre Ajustes y deja seleccionado «Modelos locales». */
|
|
90
|
+
function abrirAjustesLocales(){return abrirAjustesSeccion('Modelos locales');}
|
|
91
|
+
/** ¿Ese nodo es la ventana nativa de DSH que sólo pide la clave de DeepSeek? */
|
|
92
|
+
function contenedorNativo(hijo){
|
|
93
|
+
const h2s=hijo.querySelectorAll?hijo.querySelectorAll('h2'):[];
|
|
94
|
+
for(const t of h2s){if(TITULOS_NATIVOS.indexOf((t.textContent||'').trim())!==-1)return true;}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
function guiarNativa(){
|
|
98
|
+
for(const hijo of Array.from(document.body.children)){
|
|
99
|
+
if(hijo.nodeType!==1||!contenedorNativo(hijo))continue;
|
|
100
|
+
if(hijo.querySelector('.mr-guia'))continue;
|
|
101
|
+
// La nota va DENTRO del cuadro del diálogo, no al lado: el contenedor de la
|
|
102
|
+
// ventana nativa es un flex de pantalla completa con la máscara dentro
|
|
103
|
+
// (`_root_…` → `_mask_…` + `_dialog_…`), y añadida al lado desplazaba el
|
|
104
|
+
// cuadro y quedaba a merced de la máscara. Medido el 28-sep-2026.
|
|
105
|
+
const caja=hijo.querySelector('[role="dialog"]')||hijo;
|
|
106
|
+
const nota=document.createElement('div');
|
|
107
|
+
nota.className='mr-guia';
|
|
108
|
+
const linea=document.createElement('div');
|
|
109
|
+
linea.append('RATACODE trae ');
|
|
110
|
+
const fuerte=document.createElement('b');fuerte.textContent='8 APIs y 2 motores locales';linea.append(fuerte);
|
|
111
|
+
linea.append(': '+PROVEEDORES+'; Ollama y LM Studio, que no piden clave, tienen su propia pestaña (Ajustes › Modelos locales). Esta ventana sólo pide la clave de DeepSeek.');
|
|
112
|
+
const boton=document.createElement('button');
|
|
113
|
+
boton.type='button';boton.className='mr-guia-boton';
|
|
114
|
+
boton.textContent='Usar otro proveedor: abrir Ajustes › Models';
|
|
115
|
+
boton.addEventListener('click',()=>{
|
|
116
|
+
let atras=null;
|
|
117
|
+
for(const b of caja.querySelectorAll('button')){
|
|
118
|
+
if(ATRAS_NATIVOS.indexOf((b.textContent||'').trim())!==-1){atras=b;break;}
|
|
119
|
+
}
|
|
120
|
+
if(atras){try{atras.click();}catch(e){/* da igual: el botón de Ajustes abre igual */}}
|
|
121
|
+
const raiz=document.getElementById('root');
|
|
122
|
+
if(raiz&&raiz.inert)raiz.inert=false;
|
|
123
|
+
setTimeout(()=>{abrirAjustesModels();},120);
|
|
124
|
+
});
|
|
125
|
+
nota.append(linea,boton);
|
|
126
|
+
caja.append(nota);
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
function vigilarNativa(){
|
|
132
|
+
guiarNativa();
|
|
133
|
+
let pendiente=false;
|
|
134
|
+
const mo=new MutationObserver(()=>{
|
|
135
|
+
if(pendiente)return;
|
|
136
|
+
pendiente=true;
|
|
137
|
+
setTimeout(()=>{pendiente=false;guiarNativa();},60);
|
|
138
|
+
});
|
|
139
|
+
mo.observe(document.body,{childList:true,subtree:true});
|
|
140
|
+
}
|
|
141
|
+
// ── R17 · LA CLAVE QUE FALTA (y el error del motor, en español) ─────────────
|
|
142
|
+
// El motor ya NO saca su ventana «Add an API key to get started»: con Ollama y
|
|
143
|
+
// LM Studio declarados sin clave, `providerUsable` da la casa por lista, así
|
|
144
|
+
// que un usuario sin claves abría el panel con el modelo de fábrica (B.AI) y se
|
|
145
|
+
// comía el MISSING_CREDENTIAL en inglés. La piel pregunta por su ruta
|
|
146
|
+
// `/ratacode/clave` —que responde con el `describe` del servicio de credenciales
|
|
147
|
+
// del motor: aquí no se lee ningún fichero de claves— si al modelo por defecto
|
|
148
|
+
// de la casa le falta la clave, y avisa en español ENCIMA de la caja de escribir.
|
|
149
|
+
// El aviso se va solo: cada 3 s (y al volver a la pestaña) se vuelve a mirar.
|
|
150
|
+
const AVISO_CLAVE='mr-clave';
|
|
151
|
+
const MENCION_PROVEEDOR=/provider route "([^"]+)"/;
|
|
152
|
+
let claveVista=null; // lo último que dijo el motor; null = no hay nada que avisar
|
|
153
|
+
let nombresVistos={}; // ruta del proveedor → nombre visible, aunque no falte clave
|
|
154
|
+
function textoDeLaClave(d){
|
|
155
|
+
// R23 · una línea y un botón, y nada más:
|
|
156
|
+
// · un modelo LOCAL no necesita clave: lo que pasa es que su motor está apagado;
|
|
157
|
+
// · una API, si le falta la clave, se dice en cuatro palabras.
|
|
158
|
+
if(d.local&&d.local.encendido===false){
|
|
159
|
+
return (d.local.nombre||d.nombre||d.proveedor)+' está apagado.';
|
|
160
|
+
}
|
|
161
|
+
return 'Falta la clave de '+(d.nombre||d.proveedor)+'.';
|
|
162
|
+
}
|
|
163
|
+
/** El botón del aviso: al runtime local apagado se le lleva a SU pestaña. */
|
|
164
|
+
function botonDeAviso(esLocal){
|
|
165
|
+
const boton=document.createElement('button');
|
|
166
|
+
boton.type='button';boton.className='mr-clave-boton';boton.dataset.tipo=esLocal?'local':'clave';
|
|
167
|
+
boton.textContent=esLocal?'Abrir Ajustes › Modelos locales':'Abrir Ajustes › Models';
|
|
168
|
+
boton.addEventListener('click',()=>{if(esLocal)abrirAjustesLocales();else abrirAjustesModels();});
|
|
169
|
+
return boton;
|
|
170
|
+
}
|
|
171
|
+
/** ¿Este aviso es por una clave que falta (no por un runtime local apagado)? */
|
|
172
|
+
function esAvisoDeClave(d){return !(d&&d.local&&d.local.encendido===false);}
|
|
173
|
+
function botonDeAjustes(){return botonDeAviso(false);}
|
|
174
|
+
function quitarAvisoClave(){
|
|
175
|
+
document.querySelectorAll('.'+AVISO_CLAVE).forEach(n=>n.remove());
|
|
176
|
+
}
|
|
177
|
+
/** El aviso, encima de CADA caja de escribir montada (la portada y la sesión). */
|
|
178
|
+
function pintarAvisoClave(){
|
|
179
|
+
if(claveVista===null){quitarAvisoClave();return;}
|
|
180
|
+
const texto=textoDeLaClave(claveVista);
|
|
181
|
+
const esLocal=!esAvisoDeClave(claveVista);
|
|
182
|
+
for(const caja of document.querySelectorAll('[data-composer-card]')){
|
|
183
|
+
const padre=caja.parentElement;if(!padre)continue;
|
|
184
|
+
// Se busca entre los hermanos, no sólo el de justo antes: el aviso de
|
|
185
|
+
// migración también va encima de la caja y, si no, se duplican sin fin.
|
|
186
|
+
let aviso=padre.querySelector(':scope > .'+AVISO_CLAVE);
|
|
187
|
+
if(!aviso){
|
|
188
|
+
aviso=document.createElement('div');aviso.className=AVISO_CLAVE;
|
|
189
|
+
aviso.append(Object.assign(document.createElement('span'),{className:'mr-clave-texto'}));
|
|
190
|
+
padre.insertBefore(aviso,caja);
|
|
191
|
+
}
|
|
192
|
+
const suyo=aviso.querySelector('.mr-clave-texto');
|
|
193
|
+
if(suyo&&suyo.textContent!==texto)suyo.textContent=texto;
|
|
194
|
+
let boton=aviso.querySelector('.mr-clave-boton');
|
|
195
|
+
if(boton&&boton.dataset.tipo!==(esLocal?'local':'clave')){boton.remove();boton=null;}
|
|
196
|
+
if(!boton)aviso.append(botonDeAviso(esLocal));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/** Pregunta a la piel del motor si falta la clave (o si el local está apagado). */
|
|
200
|
+
function mirarClave(){
|
|
201
|
+
return fetch('/ratacode/clave',{credentials:'same-origin',cache:'no-store'})
|
|
202
|
+
.then(r=>r.ok?r.json():null)
|
|
203
|
+
.then(d=>{
|
|
204
|
+
if(d&&d.proveedores)nombresVistos=d.proveedores;
|
|
205
|
+
const apagadoLocal=d&&d.ok===true&&d.local&&d.local.encendido===false;
|
|
206
|
+
claveVista=d&&d.ok===true&&(d.falta===true||apagadoLocal)?d:null;
|
|
207
|
+
pintarAvisoClave();arreglarErrorDeClave();
|
|
208
|
+
})
|
|
209
|
+
.catch(()=>{/* sin ruta (motor viejo) o sin conexión: ni un aviso de más */});
|
|
210
|
+
}
|
|
211
|
+
// El error del turno (R17 §2): el motor lo pinta con su código en inglés
|
|
212
|
+
// (`dsh-client-ui-chat`: `turnErrorRow` + `turnErrorCopy` + `turnErrorCode`).
|
|
213
|
+
// Se reescribe ESE cuadro —y sólo ése: no se recorre ninguna conversación—
|
|
214
|
+
// para que diga lo mismo, en español, con el botón.
|
|
215
|
+
function arreglarErrorDeClave(){
|
|
216
|
+
document.querySelectorAll('[class*="_turnErrorRow"]').forEach(fila=>{
|
|
217
|
+
const codigo=fila.querySelector('[class*="_turnErrorCode"]');
|
|
218
|
+
if(!codigo||(codigo.textContent||'').trim()!=='MISSING_CREDENTIAL')return;
|
|
219
|
+
const copia=fila.querySelector('[class*="_turnErrorCopy"]');if(!copia)return;
|
|
220
|
+
if(copia.querySelector('.mr-clave-boton'))return;
|
|
221
|
+
const mensaje=(fila.querySelector('[class*="_turnErrorMessage"]')||{}).textContent||'';
|
|
222
|
+
const m=MENCION_PROVEEDOR.exec(mensaje);
|
|
223
|
+
const proveedor=m?m[1]:null;
|
|
224
|
+
const nombre=nombresVistos[proveedor]||proveedor||'el modelo';
|
|
225
|
+
const titulo=copia.querySelector('[class*="_turnErrorTitle"]');
|
|
226
|
+
const cuerpo=copia.querySelector('[class*="_turnErrorMessage"]');
|
|
227
|
+
const suTitulo='Falta la clave de '+nombre+'.';
|
|
228
|
+
const suCuerpo='Pégala en RATACODE › Ajustes › Models.';
|
|
229
|
+
// Sólo se escribe si de verdad cambia: escribir lo mismo dispara otra vuelta
|
|
230
|
+
// del observador y esto se quedaría girando sin parar.
|
|
231
|
+
if(titulo&&titulo.textContent!==suTitulo)titulo.textContent=suTitulo;
|
|
232
|
+
if(cuerpo&&cuerpo.textContent!==suCuerpo)cuerpo.textContent=suCuerpo;
|
|
233
|
+
copia.append(botonDeAjustes());
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
// ── R18 · LOS LOCALES NO SE MEZCLAN CON LAS APIs ───────────────────────────
|
|
237
|
+
// Ajustes › Models los pinta el frontend desde `llm/listProviders` +
|
|
238
|
+
// `llm/listConfigurableProviders` (dsh-client-ui-settings-models/lib/client.js:
|
|
239
|
+
// 991-995 y 2013, `_rowCard`), y esa lista NO tiene ningún gancho oficial para
|
|
240
|
+
// filtrar filas (medido: no hay slot ni campo `hidden` en el esquema,
|
|
241
|
+
// dsh-llm/lib/typert.host.js:17-28). La vía limpia que queda es el filtro de la
|
|
242
|
+
// PIEL sobre la lista ya pintada: se esconden las DOS filas de los locales
|
|
243
|
+
// (por su nombre visible, el de fabrica/settings.yaml) y se deja una nota que
|
|
244
|
+
// lleva a su pestaña. Los locales SIGUEN declarados en la casa, así que el
|
|
245
|
+
// selector de modelos de la caja los sigue ofreciendo igual.
|
|
246
|
+
const NOMBRES_LOCALES=['Ollama (local, sin clave)','LM Studio (local, sin clave)'];
|
|
247
|
+
const NOTA_LOCALES='mr-locales-nota';
|
|
248
|
+
function ocultarLocalesEnModels(){
|
|
249
|
+
let seccion=null;
|
|
250
|
+
document.querySelectorAll('[class*="_rowCard"]').forEach(fila=>{
|
|
251
|
+
const nombre=fila.querySelector('[class*="_rowName"]');
|
|
252
|
+
const suyo=nombre?(nombre.textContent||'').trim():'';
|
|
253
|
+
if(suyo==='')return;
|
|
254
|
+
// El prefijo vale también para nombres largos («Ollama (local, sin clave)»).
|
|
255
|
+
const local=NOMBRES_LOCALES.some(n=>suyo===n||suyo.indexOf(n)===0);
|
|
256
|
+
if(local){
|
|
257
|
+
if(fila.style.display!=='none')fila.style.display='none';
|
|
258
|
+
if(seccion===null)seccion=fila.closest('[class*="_section"]');
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
// Si el usuario vuelve a Models tras un cambio, ninguna otra fila se toca.
|
|
262
|
+
if(fila.style.display==='none')fila.style.display='';
|
|
263
|
+
});
|
|
264
|
+
if(seccion===null||seccion.querySelector('.'+NOTA_LOCALES))return;
|
|
265
|
+
const lista=seccion.querySelector('[class*="_rows"]');
|
|
266
|
+
const nota=document.createElement('p');
|
|
267
|
+
nota.className=NOTA_LOCALES;
|
|
268
|
+
nota.append('Ollama y LM Studio (sin clave) están en ');
|
|
269
|
+
const boton=document.createElement('button');
|
|
270
|
+
boton.type='button';boton.className='mr-clave-boton';
|
|
271
|
+
boton.textContent='Abrir Ajustes › Modelos locales';
|
|
272
|
+
boton.addEventListener('click',()=>{abrirAjustesLocales();});
|
|
273
|
+
nota.append(boton);
|
|
274
|
+
if(lista&&lista.parentElement)lista.parentElement.insertBefore(nota,lista);
|
|
275
|
+
else seccion.append(nota);
|
|
276
|
+
}
|
|
277
|
+
// ── R22 §4 · EL AVISO DE MIGRACIÓN DE CLAVES (una línea, y no vuelve) ──────
|
|
278
|
+
// La primera vez que esta casa arranca con la versión que ya NO lee claves del
|
|
279
|
+
// entorno, si en Windows hay variables de claves de proveedores, la piel lo dice
|
|
280
|
+
// en UNA línea encima de la caja —«Tienes B_AI_API_KEY en Windows. RATACODE ya
|
|
281
|
+
// no la usa: pega tu clave en Ajustes › Models.»— con su botón de cerrar. Al
|
|
282
|
+
// cerrarlo se apunta en la casa y no vuelve nunca más.
|
|
283
|
+
const AVISO_MIGRACION='mr-migracion';
|
|
284
|
+
function quitarAvisoMigracion(){
|
|
285
|
+
document.querySelectorAll('.'+AVISO_MIGRACION).forEach(n=>n.remove());
|
|
286
|
+
}
|
|
287
|
+
function pintarAvisoMigracion(texto){
|
|
288
|
+
for(const caja of document.querySelectorAll('[data-composer-card]')){
|
|
289
|
+
const padre=caja.parentElement;if(!padre)continue;
|
|
290
|
+
const previo=padre.querySelector(':scope > .'+AVISO_MIGRACION);
|
|
291
|
+
if(previo){
|
|
292
|
+
const suyo=previo.querySelector('.mr-migracion-texto');
|
|
293
|
+
if(suyo&&suyo.textContent!==texto)suyo.textContent=texto;
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const aviso=document.createElement('div');aviso.className=AVISO_MIGRACION;
|
|
297
|
+
aviso.append(Object.assign(document.createElement('span'),{className:'mr-migracion-texto',textContent:texto}));
|
|
298
|
+
const cerrar=document.createElement('button');
|
|
299
|
+
cerrar.type='button';cerrar.className='mr-migracion-cerrar';cerrar.textContent='Cerrar';
|
|
300
|
+
cerrar.addEventListener('click',()=>{
|
|
301
|
+
quitarAvisoMigracion();
|
|
302
|
+
fetch('/ratacode/migracion',{method:'POST',credentials:'same-origin',cache:'no-store'}).catch(()=>{});
|
|
303
|
+
});
|
|
304
|
+
aviso.append(cerrar);
|
|
305
|
+
padre.insertBefore(aviso,caja);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function mirarMigracion(){
|
|
309
|
+
return fetch('/ratacode/migracion',{credentials:'same-origin',cache:'no-store'})
|
|
310
|
+
.then(r=>r.ok?r.json():null)
|
|
311
|
+
.then(d=>{
|
|
312
|
+
if(d&&d.aviso&&typeof d.aviso.texto==='string')pintarAvisoMigracion(d.aviso.texto);
|
|
313
|
+
else quitarAvisoMigracion();
|
|
314
|
+
})
|
|
315
|
+
.catch(()=>{/* motor viejo o sin conexión: ni un aviso de más */});
|
|
316
|
+
}
|
|
317
|
+
// ── R23 · LOS MODOS, EN 3×3 ────────────────────────────────────────────────
|
|
318
|
+
// La pestaña de modos la pinta el plugin de presets del motor: tarjetas en dos
|
|
319
|
+
// columnas con scroll, la etiqueta «Custom», el id técnico, los iconos de
|
|
320
|
+
// carpeta/duplicar/borrar y una frase en inglés. No hay slot para sustituirla,
|
|
321
|
+
// así que la piel la ORDENA encima: se marca la sección (sólo lo marcado se
|
|
322
|
+
// toca), se renombra a «Modos», cada tarjeta se queda con su NOMBRE grande (con
|
|
323
|
+
// el color de su personaje), su oficio en UNA línea y «En uso» en el que manda,
|
|
324
|
+
// y los iconos se esconden detrás de un «⋯». El CSS hace la cuadrícula de 3×3.
|
|
325
|
+
// R21 §6: la sección se reconoce por su frase de entrada, y desde que el panel
|
|
326
|
+
// va en español (vía oficial de idiomas) la frase llega en español: se
|
|
327
|
+
// reconocen las DOS, para que la cuadrícula 3×3 no se caiga al traducir.
|
|
328
|
+
const MODOS_TITULO='Modos';
|
|
329
|
+
const MODOS_ORIGEN=/^(A preset is the plugin composition|Un preset es la composición de plugins)/;
|
|
330
|
+
const MODOS_COLOR={
|
|
331
|
+
'MODO-RATA':'#ff268e','ARQUITECTO':'#e4f226','CAPATAZ':'#26c6cc','HERO':'#ff8a3d','TIRITA':'#7dd3fc',
|
|
332
|
+
'GEPETO':'#c084fc','FARO':'#f5d90a','PIX':'#4ade80','NEX':'#f87171'
|
|
333
|
+
};
|
|
334
|
+
const MODOS_LINEA={
|
|
335
|
+
'MODO-RATA':'Tareas normales.',
|
|
336
|
+
'ARQUITECTO':'Decide cómo se construye.',
|
|
337
|
+
'CAPATAZ':'Convierte el objetivo en plan.',
|
|
338
|
+
'HERO':'Las manos: implementa.',
|
|
339
|
+
'TIRITA':'Cura fallos: causa y arreglo.',
|
|
340
|
+
'GEPETO':'Segunda opinión y pesquisa.',
|
|
341
|
+
'FARO':'Escribe claro y sin inventar.',
|
|
342
|
+
'PIX':'Un solo programa.',
|
|
343
|
+
'NEX':'Modos y plugins.'
|
|
344
|
+
};
|
|
345
|
+
function arreglarModos(){
|
|
346
|
+
// El menú de Ajustes: «Agent presets» se llama «Modos». R21 §6: el rótulo se
|
|
347
|
+
// cambia SÓLO en su texto —antes se pisaba `textContent` del botón entero, y
|
|
348
|
+
// eso se llevaba por delante el ICONO de la fila—. El nombre puede llegar en
|
|
349
|
+
// inglés (motor sin diccionario) o ya en español (R21, vía oficial de
|
|
350
|
+
// idiomas: `settings.agentPreset.nav` = «Presets de agente»); los dos valen.
|
|
351
|
+
const NOMBRES_MODOS=['Agent presets','Presets de agente'];
|
|
352
|
+
for(const b of document.querySelectorAll('[class*="_navList"] button')){
|
|
353
|
+
const etiqueta=b.querySelector('[class*="_navLabel"]')||b;
|
|
354
|
+
if(NOMBRES_MODOS.indexOf((etiqueta.textContent||'').trim())!==-1&&etiqueta.textContent!==MODOS_TITULO)etiqueta.textContent=MODOS_TITULO;
|
|
355
|
+
}
|
|
356
|
+
// La sección: se reconoce por su frase de entrada (en inglés o ya en español,
|
|
357
|
+
// que es como llega desde R21 por la vía oficial de idiomas), y se marca.
|
|
358
|
+
let seccion=null;
|
|
359
|
+
for(const p of document.querySelectorAll('p[class*="_intro"]')){
|
|
360
|
+
if(MODOS_ORIGEN.test((p.textContent||'').trim())){seccion=p.closest('[class*="_section"]');break;}
|
|
361
|
+
}
|
|
362
|
+
if(!seccion)return;
|
|
363
|
+
seccion.classList.add('mr-modos');
|
|
364
|
+
const titulo=seccion.querySelector('h2[class*="_title"]');
|
|
365
|
+
if(titulo&&(titulo.textContent||'').trim()!==MODOS_TITULO)titulo.textContent=MODOS_TITULO;
|
|
366
|
+
for(const cab of seccion.querySelectorAll('[class*="_groupHead"]')){if(cab.style.display!=='none')cab.style.display='none';}
|
|
367
|
+
for(const tarjeta of seccion.querySelectorAll('li[class*="_card"]')){
|
|
368
|
+
const nombre=(tarjeta.querySelector('[class*="_cardName"]')?.textContent||'').trim();
|
|
369
|
+
const elNombre=tarjeta.querySelector('[class*="_cardName"]');
|
|
370
|
+
const color=MODOS_COLOR[nombre];
|
|
371
|
+
if(elNombre&&color!==undefined&&elNombre.style.color!=='rgb('+[1,3,5].map(i=>parseInt(color.slice(i,i+2),16)).join(', ')+')'){
|
|
372
|
+
elNombre.style.color=color;
|
|
373
|
+
}
|
|
374
|
+
// Fuera las etiquetas «Custom»/«Built-in» —y sus versiones en español—;
|
|
375
|
+
// «In use» pasa a «En uso» (aquí ya llega traducido, pero se deja por si el
|
|
376
|
+
// motor vuelve a pintarlo en inglés al cambiar de idioma).
|
|
377
|
+
for(const etiqueta of tarjeta.querySelectorAll('[class*="_cardHead"] > span')){
|
|
378
|
+
const suyo=(etiqueta.textContent||'').trim();
|
|
379
|
+
if(suyo==='Custom'||suyo==='Built-in'||suyo==='Personalizado'||suyo==='De serie'){if(etiqueta.style.display!=='none')etiqueta.style.display='none';}
|
|
380
|
+
else if(suyo==='In use')etiqueta.textContent='En uso';
|
|
381
|
+
}
|
|
382
|
+
// El oficio, en UNA línea (el suyo, corto; el del fichero es un párrafo).
|
|
383
|
+
const desc=tarjeta.querySelector('[class*="_cardDesc"]');
|
|
384
|
+
const linea=MODOS_LINEA[nombre];
|
|
385
|
+
if(desc&&linea!==undefined&&(desc.textContent||'').trim()!==linea)desc.textContent=linea;
|
|
386
|
+
// Los iconos, detrás de un «⋯» discreto.
|
|
387
|
+
const pie=tarjeta.querySelector('[class*="_cardFoot"]');
|
|
388
|
+
if(pie&&!tarjeta.querySelector('.mr-modos-puntos')){
|
|
389
|
+
const puntos=document.createElement('button');
|
|
390
|
+
puntos.type='button';puntos.className='mr-modos-puntos';puntos.textContent='⋯';
|
|
391
|
+
puntos.setAttribute('aria-label','Más acciones: '+nombre);
|
|
392
|
+
puntos.addEventListener('click',(ev)=>{
|
|
393
|
+
ev.stopPropagation();
|
|
394
|
+
tarjeta.dataset.abierto=tarjeta.dataset.abierto==='si'?'no':'si';
|
|
395
|
+
});
|
|
396
|
+
tarjeta.insertBefore(puntos,pie);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function apply(){
|
|
401
|
+
const title=document.title.replace(/DeepSeek Harness/gi,'RATACODE');if(title!==document.title)document.title=title;
|
|
402
|
+
// Sólo el saludo nativo del motor: nunca mensajes, nombres de modelo ni errores.
|
|
403
|
+
const hero=document.querySelector('[data-phase="hero"] [class*="_headline"]');
|
|
404
|
+
if(hero){
|
|
405
|
+
hero.classList.add('mr-welcome');
|
|
406
|
+
const mark=hero.querySelector('[class*="_fishHitbox"]');
|
|
407
|
+
if(mark){mark.classList.add('mr-welcome-mark');mark.setAttribute('role','img');mark.setAttribute('aria-label','Emblema RATACODE');}
|
|
408
|
+
const group=hero.querySelector('[class*="_titleGroup"]');
|
|
409
|
+
const heading=group?.firstElementChild;
|
|
410
|
+
if(heading&&heading.textContent!=='RATACODE'){heading.textContent='RATACODE';heading.classList.add('mr-welcome-title');}
|
|
411
|
+
const badge=hero.querySelector('[class*="_previewBadge"]');
|
|
412
|
+
if(badge&&badge.textContent!=='TRABAJO BRUTO. CONTROL TOTAL.')badge.textContent='TRABAJO BRUTO. CONTROL TOTAL.';
|
|
413
|
+
}
|
|
414
|
+
document.querySelectorAll('[class*="_sidebarCol"] button, [class*="_sidebarCol"] [class*="_empty"], [data-phase="hero"] [class*="_workspace"]').forEach(e=>{
|
|
415
|
+
for(const attr of ['aria-label','title']){const value=e.getAttribute(attr);if(labels.has(value))e.setAttribute(attr,labels.get(value));}
|
|
416
|
+
if(!e.childElementCount&&labels.has(e.textContent.trim()))e.textContent=labels.get(e.textContent.trim());
|
|
417
|
+
});
|
|
418
|
+
const brand=document.querySelector('[class*="_brandName"]');
|
|
419
|
+
if(brand&&!brand.querySelector('.mr-dsh-word')){
|
|
420
|
+
const word=document.createElement('span');word.className='mr-dsh-word';word.append('RATA');
|
|
421
|
+
const code=document.createElement('b');code.textContent='CODE';word.append(code);
|
|
422
|
+
brand.append(word);
|
|
423
|
+
}
|
|
424
|
+
const header=document.querySelector('header[class*="_header"]');
|
|
425
|
+
if(header&&!header.querySelector('.mr-dsh-kicker')){const kicker=document.createElement('div');kicker.className='mr-dsh-kicker';kicker.append(wordmark(),' / TRABAJO BRUTO. CONTROL TOTAL.');header.prepend(kicker)}
|
|
426
|
+
// Los rótulos también viven en la portada y en la tarjeta de la caja, no sólo
|
|
427
|
+
// en la columna y la cabecera: «Choose workspace» sale en la portada.
|
|
428
|
+
document.querySelectorAll('[class*="_sidebarCol"] button, header button, [data-phase="hero"] button, [data-phase="hero"] [role="button"], [data-composer-card] button').forEach(root=>{
|
|
429
|
+
const walker=document.createTreeWalker(root,NodeFilter.SHOW_TEXT);let n;
|
|
430
|
+
while((n=walker.nextNode())){const text=n.nodeValue.trim();if(labels.has(text))n.nodeValue=n.nodeValue.replace(text,labels.get(text));}
|
|
431
|
+
});
|
|
432
|
+
document.querySelectorAll('[data-slot="sidebar.workspaces"] [class*="_sectionLabel"]').forEach(e=>{if(e.textContent==='Workspaces')e.textContent='Espacios de trabajo';});
|
|
433
|
+
document.querySelectorAll('[data-slot="sidebar.workspaces"] [class*="_projectRow"] [class*="_title"]').forEach(e=>{if(e.textContent==='Ungrouped')e.textContent='Sin agrupar';});
|
|
434
|
+
// «Show N more sessions», en cristiano: el número CAMBIA, así que no vale un rótulo fijo: se lee.
|
|
435
|
+
document.querySelectorAll('[class*="_sessionOverflowButton"], [data-slot="sidebar.workspaces"] button, [data-slot="sidebar.workspaces"] span').forEach(e=>{
|
|
436
|
+
if(e.childElementCount)return;
|
|
437
|
+
const m=(e.textContent||'').trim().match(/^Show\s+(\d+)\s+more\s+sessions?$/i);
|
|
438
|
+
if(m)e.textContent=(m[1]==='1')?'Ver 1 sesión más':('Ver '+m[1]+' sesiones más');
|
|
439
|
+
});
|
|
440
|
+
// La caja: se traduce el texto que ponga el frontend, no se fuerza uno fijo.
|
|
441
|
+
traducirCajas();
|
|
442
|
+
// R17: el aviso de la clave que falta y el error del motor, en español. Se
|
|
443
|
+
// repintan aquí porque React reescribe el cuadro de la caja al re-renderizar.
|
|
444
|
+
pintarAvisoClave();
|
|
445
|
+
arreglarErrorDeClave();
|
|
446
|
+
// R18: en Ajustes › Models sólo quedan las APIs con clave.
|
|
447
|
+
ocultarLocalesEnModels();
|
|
448
|
+
// R23: los modos, en 3×3 y en cristiano.
|
|
449
|
+
arreglarModos();
|
|
450
|
+
const input=document.querySelector('[data-composer-input]');if(input)input.setAttribute('aria-label','Mensaje para RATACODE');
|
|
451
|
+
const search=document.querySelector('[class*="_searchInput"]');if(search)search.setAttribute('placeholder','Buscar sesiones…');
|
|
452
|
+
}
|
|
453
|
+
apply();
|
|
454
|
+
// ── volver a aplicar cuando el frontend reescribe ────────────────────────
|
|
455
|
+
// Dos cosas aprendidas a golpes (R5, medido en el DOM real):
|
|
456
|
+
// 1) React escribe el texto de la caja en ATRIBUTOS (`data-placeholder`,
|
|
457
|
+
// `aria-label`) al re-renderizar. Un observador sólo de
|
|
458
|
+
// `childList`/`characterData` no ve eso: los valores se quedaban en inglés.
|
|
459
|
+
// 2) El rebote con `requestAnimationFrame` se queda colgado cuando la pestaña
|
|
460
|
+
// deja de pintar (segundo plano, ventana tapada): `pending` se quedaba en
|
|
461
|
+
// true y el observador dejaba de aplicar NADA. Con `setTimeout` no pasa.
|
|
462
|
+
let pending=false;
|
|
463
|
+
const correr=()=>{if(!pending)return;pending=false;apply()};
|
|
464
|
+
const programar=()=>{if(pending)return;pending=true;setTimeout(correr,16)};
|
|
465
|
+
const observer=new MutationObserver(programar);
|
|
466
|
+
observer.observe(document.body,{childList:true,subtree:true,characterData:true,attributes:true,attributeFilter:['data-placeholder','aria-label']});
|
|
467
|
+
window.__ratacodeSkinObserver=observer;
|
|
468
|
+
// Y si la pestaña vuelve a primer plano, una pasada más: barato y cierra el
|
|
469
|
+
// hueco de lo que pasó mientras no se pintaba.
|
|
470
|
+
document.addEventListener('visibilitychange',programar);
|
|
471
|
+
window.addEventListener('focus',programar);
|
|
472
|
+
// R12: la ventana nativa de DSH (sólo DeepSeek) lleva una nota y un botón a
|
|
473
|
+
// Ajustes › Models. Un solo vigilante, montado una sola vez.
|
|
474
|
+
vigilarNativa();
|
|
475
|
+
// R17: la clave que falta, mirada al abrir, cada 3 s y al volver a la pestaña.
|
|
476
|
+
// Así el aviso se va SOLO en cuanto se guarda la clave o se elige un modelo
|
|
477
|
+
// que la tenga (y vuelve si se elige uno que no la tenga), sin recargar nada.
|
|
478
|
+
mirarClave();
|
|
479
|
+
setInterval(mirarClave,3000);
|
|
480
|
+
document.addEventListener('visibilitychange',()=>{if(!document.hidden)mirarClave();});
|
|
481
|
+
window.addEventListener('focus',()=>{mirarClave();});
|
|
482
|
+
// R22 §4: el aviso de migración de claves, al abrir y cada 5 s (se va en cuanto
|
|
483
|
+
// el usuario lo cierra: la casa apunta que ya lo ha visto).
|
|
484
|
+
mirarMigracion();
|
|
485
|
+
setInterval(mirarMigracion,5000);
|
|
486
|
+
document.addEventListener('visibilitychange',()=>{if(!document.hidden)mirarMigracion();});
|
|
487
|
+
})();
|