@tiagojct/subsub 0.11.4 → 0.12.1
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/CHANGELOG.md +78 -0
- package/README.md +8 -3
- package/dist/bench.js +3 -2
- package/dist/cli.js +6 -0
- package/dist/config.js +13 -2
- package/dist/init.js +31 -5
- package/dist/keys.js +38 -8
- package/dist/preview.js +130 -26
- package/dist/roles.js +12 -1
- package/dist/subsub.js +67 -17
- package/dist/usage.js +69 -0
- package/dist/web.js +2 -5
- package/package.json +4 -3
- package/roles/librarian.md +13 -1
- package/roles/researcher.md +12 -0
- package/src/bench.ts +3 -2
- package/src/cli.ts +6 -0
- package/src/config.ts +16 -3
- package/src/init.ts +48 -4
- package/src/keys.ts +37 -10
- package/src/preview.ts +126 -25
- package/src/roles.ts +12 -1
- package/src/subsub.ts +66 -16
- package/src/usage.ts +75 -0
- package/src/web.ts +2 -5
- package/web/app.css +11 -0
- package/web/app.js +50 -0
- package/web/i18n.js +36 -0
- package/web/index.html +1 -0
package/src/web.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { homedir } from "node:os";
|
|
|
22
22
|
import { extname, join, normalize, resolve, sep } from "node:path";
|
|
23
23
|
import { StringDecoder } from "node:string_decoder";
|
|
24
24
|
import { agentDirFor, chooseCwd, PACKAGE_DIR, packageVersion } from "./cli.ts";
|
|
25
|
-
import { configPath, expand, loadConfig, RECOMMENDED_MODELS, readConfigFile, saveConfig, type SubsubConfig, withStarbuck } from "./config.ts";
|
|
25
|
+
import { configPath, expand, loadConfig, RECOMMENDED_MODELS, readConfigFile, saveConfig, type SubsubConfig, uiLanguage, withStarbuck } from "./config.ts";
|
|
26
26
|
import { TESTED_MODELS } from "./init.ts";
|
|
27
27
|
import { KEY_PROVIDERS, saveApiKey } from "./keys.ts";
|
|
28
28
|
import { obsidianRoot } from "./layout.ts";
|
|
@@ -56,10 +56,7 @@ export function parseWebArgs(args: string[]): WebOptions {
|
|
|
56
56
|
return o;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
export function uiLanguage(language: string | undefined): "pt" | "en" {
|
|
61
|
-
return /portug|^pt\b|^pt-/i.test(language ?? "") ? "pt" : "en";
|
|
62
|
-
}
|
|
59
|
+
export { uiLanguage } from "./config.ts";
|
|
63
60
|
|
|
64
61
|
// ---------------------------------------------------------------- providers (API keys)
|
|
65
62
|
|
package/web/app.css
CHANGED
|
@@ -883,3 +883,14 @@ body > p.empty { text-align: center; color: var(--muted); padding: 30vh 1rem 0;
|
|
|
883
883
|
@media (prefers-reduced-motion: reduce) {
|
|
884
884
|
*, *::before, *::after { animation: none !important; transition: none !important; }
|
|
885
885
|
}
|
|
886
|
+
|
|
887
|
+
/* First run: what is still missing */
|
|
888
|
+
.setup { margin-block: 1.4rem 1rem; padding: 0.9rem 1rem; border: 1px solid var(--rule); border-left: 3px solid var(--accent); border-radius: 6px; background: var(--raised); text-align: left; max-width: 40rem; }
|
|
889
|
+
.setup h2 { margin: 0 0 0.5rem; font-size: 0.95rem; }
|
|
890
|
+
.setup ol { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.55rem; }
|
|
891
|
+
.setup li { display: grid; grid-template-columns: 4.5rem 1fr auto; gap: 0.6rem; align-items: start; font-size: 0.88rem; line-height: 1.45; }
|
|
892
|
+
.setup li > * { min-width: 0; }
|
|
893
|
+
.setup .mark { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; padding-top: 0.15rem; color: var(--muted); }
|
|
894
|
+
.setup li.todo .mark { color: var(--rem); }
|
|
895
|
+
.setup li.ok .step-text { color: var(--muted); }
|
|
896
|
+
@media (max-width: 560px) { .setup li { grid-template-columns: 1fr; gap: 0.2rem; } }
|
package/web/app.js
CHANGED
|
@@ -370,6 +370,7 @@ function scrollDown(force = false) {
|
|
|
370
370
|
|
|
371
371
|
// A provider error in plain words (keep in step with providerTrouble in src/keys.ts), with the raw text below.
|
|
372
372
|
function troubleOf(text) {
|
|
373
|
+
if (/requires Global regions|not available in your region|region.{0,40}not (supported|allowed)/i.test(text)) return "region";
|
|
373
374
|
if (/RESOURCE_EXHAUSTED|\b429\b|quota|rate.?limit|too many requests|usage limit/i.test(text)) return "quota";
|
|
374
375
|
if (/\b(503|529)\b|UNAVAILABLE|high demand|overloaded/i.test(text)) return "busy";
|
|
375
376
|
if (/RECITATION|SAFETY|PROHIBITED_CONTENT/.test(text)) return "blocked";
|
|
@@ -395,6 +396,36 @@ function clearEmpty() {
|
|
|
395
396
|
log.querySelector(".empty")?.remove();
|
|
396
397
|
}
|
|
397
398
|
|
|
399
|
+
/** First run: what is still missing (Zotero, a model, the servers), each with its fix. Hidden when all is ready. */
|
|
400
|
+
function setupList() {
|
|
401
|
+
const L = state?.library;
|
|
402
|
+
const m = state?.model ?? session?.model;
|
|
403
|
+
const steps = [
|
|
404
|
+
{ ok: L?.zotero === "reachable", wait: !L || L.zotero === "checking", text: t("stepZotero") },
|
|
405
|
+
{ ok: Boolean(m && m.id && m.id !== "unknown"), text: t("stepModel"), action: { label: t("connect"), run: openModels } },
|
|
406
|
+
{ ok: !state?.down?.length, text: t("stepServers", { x: (state?.down ?? []).join(", ") }) },
|
|
407
|
+
];
|
|
408
|
+
if (steps.every((x) => x.ok || x.wait)) return null;
|
|
409
|
+
return el(
|
|
410
|
+
"div",
|
|
411
|
+
{ class: "setup" },
|
|
412
|
+
el("h2", { text: t("setupTitle") }),
|
|
413
|
+
el(
|
|
414
|
+
"ol",
|
|
415
|
+
{},
|
|
416
|
+
steps.map((x) =>
|
|
417
|
+
el(
|
|
418
|
+
"li",
|
|
419
|
+
{ class: x.ok ? "ok" : x.wait ? "wait" : "todo" },
|
|
420
|
+
el("span", { class: "mark", text: x.ok ? t("stepDone") : x.wait ? "…" : t("stepTodo") }),
|
|
421
|
+
el("span", { class: "step-text", text: x.ok ? x.text.split(". ")[0] : x.text }),
|
|
422
|
+
!x.ok && x.action ? el("button", { type: "button", class: "primary small-btn", text: x.action.label, onclick: x.action.run }) : null,
|
|
423
|
+
),
|
|
424
|
+
),
|
|
425
|
+
),
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
|
|
398
429
|
function renderEmpty() {
|
|
399
430
|
if (log.children.length) return;
|
|
400
431
|
const s = SUGGESTIONS[mode()].map((x) => x[lang] ?? x.en);
|
|
@@ -405,6 +436,7 @@ function renderEmpty() {
|
|
|
405
436
|
el("p", { class: "kicker", text: t(mode()) }),
|
|
406
437
|
el("h1", { text: t("emptyTitle") }),
|
|
407
438
|
el("p", { class: "lede", text: t(mode() === "librarian" ? "emptyLibrarian" : "emptyResearcher") }),
|
|
439
|
+
setupList(),
|
|
408
440
|
el(
|
|
409
441
|
"div",
|
|
410
442
|
{ class: "chips" },
|
|
@@ -505,6 +537,23 @@ function finishTool(id, result, isError) {
|
|
|
505
537
|
row.querySelector("pre")?.remove();
|
|
506
538
|
if (text) row.append(el("pre", { text: text.length > 6000 ? `${text.slice(0, 6000)}\n...` : text }));
|
|
507
539
|
if (isError) row.querySelector(".label").textContent = `${toolLabel(row.dataset.name)} (${t("failed")})`;
|
|
540
|
+
// An applied library change gets an Undo button; it runs /undo with its id, which previews first.
|
|
541
|
+
const jid = !isError && !/undo/.test(row.dataset.name) && /"journal_id":\s*"(\d{8}-\d{6}-\d{2}-[a-z_]+)"/.exec(text ?? "")?.[1];
|
|
542
|
+
if (jid && !row.querySelector(".undo")) {
|
|
543
|
+
row.querySelector("summary").append(
|
|
544
|
+
el("button", {
|
|
545
|
+
type: "button",
|
|
546
|
+
class: "open undo",
|
|
547
|
+
text: t("undo"),
|
|
548
|
+
title: t("undoTitle"),
|
|
549
|
+
onclick: (e) => {
|
|
550
|
+
e.preventDefault();
|
|
551
|
+
if (busy) return note(t("busy"), "warning");
|
|
552
|
+
sendMessage(`/undo ${jid}`);
|
|
553
|
+
},
|
|
554
|
+
}),
|
|
555
|
+
);
|
|
556
|
+
}
|
|
508
557
|
}
|
|
509
558
|
|
|
510
559
|
function attachCopyButtons(root) {
|
|
@@ -925,6 +974,7 @@ async function openModels() {
|
|
|
925
974
|
const free = { google: "freeGoogle", openrouter: "freeOpenRouter" }[sel.value];
|
|
926
975
|
$("login-free").hidden = !free;
|
|
927
976
|
$("login-free").textContent = free ? t(free) : "";
|
|
977
|
+
$("login-where").textContent = p?.where ? t(`where_${p.where}`) : "";
|
|
928
978
|
};
|
|
929
979
|
sel.onchange = setLink;
|
|
930
980
|
setLink();
|
package/web/i18n.js
CHANGED
|
@@ -53,6 +53,7 @@ export const STRINGS = {
|
|
|
53
53
|
rec_tested: "tested default",
|
|
54
54
|
rec_cheaper: "tested, cheaper",
|
|
55
55
|
rec_free: "tested, free",
|
|
56
|
+
rec_eu: "tested, EU",
|
|
56
57
|
needsKey: "needs a key from {x}",
|
|
57
58
|
noModels: "No models yet. Connect a provider below.",
|
|
58
59
|
connectProvider: "Connect a provider",
|
|
@@ -67,7 +68,20 @@ export const STRINGS = {
|
|
|
67
68
|
trouble_busy: "The model provider is busy at the moment. Try again in a few minutes, or choose another model with the model button.",
|
|
68
69
|
trouble_blocked: "The model provider stopped the reply (for example, because it repeated a source word for word). Ask again in other words, or choose another model.",
|
|
69
70
|
trouble_key: "The model provider did not accept the API key. Enter it again with the model button.",
|
|
71
|
+
trouble_region: "The provider refuses this model with your account's region setting. In OpenCode, allow Global regions in the workspace's Privacy settings, or choose a model your region allows with the model button.",
|
|
70
72
|
details: "Details",
|
|
73
|
+
undo: "Undo",
|
|
74
|
+
where_eu: "Where your data goes: servers in the European Union. Covered by the GDPR, the choice for material you would not send outside the EU.",
|
|
75
|
+
where_us: "Where your data goes: servers outside the EU, mostly in the United States, under the provider's terms for the EU. Read them before you send unpublished or sensitive material.",
|
|
76
|
+
where_cn: "Where your data goes: servers in China. Do not send unpublished or sensitive material.",
|
|
77
|
+
where_varies: "Where your data goes: it depends on the model. Each model runs at another company, in the United States, China or elsewhere. For material you would not send outside the EU, choose Mistral.",
|
|
78
|
+
setupTitle: "Before you start",
|
|
79
|
+
stepZotero: "Zotero is open and lets Sub-Sub in. If not: start Zotero, then in Settings > Advanced turn on \"Allow other applications on this computer to communicate with Zotero\".",
|
|
80
|
+
stepModel: "A model is connected. If not: select Connect and paste an API key. To try Sub-Sub for free, choose Google Gemini and get a free key there.",
|
|
81
|
+
stepServers: "The Zotero servers are running. If not ({x}): close Sub-Sub, open a terminal and type subsub doctor. Each line marked FIX says what to do.",
|
|
82
|
+
stepDone: "done",
|
|
83
|
+
stepTodo: "to do",
|
|
84
|
+
undoTitle: "Undo this change. Sub-Sub shows what it would restore before it changes anything.",
|
|
71
85
|
oauthNote: "To sign in through a provider's website (for example with a subscription), open a terminal, type subsub, then /login.",
|
|
72
86
|
saving: "Saving",
|
|
73
87
|
saved: "Saved.",
|
|
@@ -164,6 +178,7 @@ export const STRINGS = {
|
|
|
164
178
|
rec_tested: "testado, por omissão",
|
|
165
179
|
rec_cheaper: "testado, mais barato",
|
|
166
180
|
rec_free: "testado, gratuito",
|
|
181
|
+
rec_eu: "testado, UE",
|
|
167
182
|
needsKey: "precisa de uma chave de {x}",
|
|
168
183
|
noModels: "Ainda não há modelos. Ligue um fornecedor abaixo.",
|
|
169
184
|
connectProvider: "Ligar um fornecedor",
|
|
@@ -178,7 +193,20 @@ export const STRINGS = {
|
|
|
178
193
|
trouble_busy: "O fornecedor do modelo está sobrecarregado neste momento. Tente outra vez dentro de alguns minutos ou escolha outro modelo no botão do modelo.",
|
|
179
194
|
trouble_blocked: "O fornecedor do modelo interrompeu a resposta (por exemplo, por repetir uma fonte palavra por palavra). Peça outra vez por outras palavras ou escolha outro modelo.",
|
|
180
195
|
trouble_key: "O fornecedor do modelo não aceitou a chave da API. Volte a introduzi-la no botão do modelo.",
|
|
196
|
+
trouble_region: "O fornecedor recusa este modelo com a definição de região da sua conta. No OpenCode, permita as regiões globais (Global) nas definições de privacidade do workspace, ou escolha no botão do modelo um modelo que a sua região permita.",
|
|
181
197
|
details: "Detalhes",
|
|
198
|
+
undo: "Anular",
|
|
199
|
+
where_eu: "Para onde vão os seus dados: servidores na União Europeia. Abrangidos pelo RGPD; a escolha para material que não enviaria para fora da UE.",
|
|
200
|
+
where_us: "Para onde vão os seus dados: servidores fora da UE, sobretudo nos Estados Unidos, com os termos do fornecedor para a UE. Leia-os antes de enviar material inédito ou sensível.",
|
|
201
|
+
where_cn: "Para onde vão os seus dados: servidores na China. Não envie material inédito ou sensível.",
|
|
202
|
+
where_varies: "Para onde vão os seus dados: depende do modelo. Cada modelo funciona noutra empresa, nos Estados Unidos, na China ou noutro país. Para material que não enviaria para fora da UE, escolha a Mistral.",
|
|
203
|
+
setupTitle: "Antes de começar",
|
|
204
|
+
stepZotero: "O Zotero está aberto e deixa o Sub-Sub entrar. Se não: abra o Zotero e, em Definições > Avançado, ative \"Allow other applications on this computer to communicate with Zotero\".",
|
|
205
|
+
stepModel: "Há um modelo ligado. Se não: selecione Ligar e cole uma chave de API. Para experimentar sem pagar, escolha Google Gemini e obtenha lá uma chave gratuita.",
|
|
206
|
+
stepServers: "Os servidores do Zotero estão a funcionar. Se não ({x}): feche o Sub-Sub, abra um terminal e escreva subsub doctor. Cada linha marcada FIX diz o que fazer.",
|
|
207
|
+
stepDone: "feito",
|
|
208
|
+
stepTodo: "por fazer",
|
|
209
|
+
undoTitle: "Anular esta alteração. O Sub-Sub mostra o que repõe antes de alterar alguma coisa.",
|
|
182
210
|
oauthNote: "Para entrar através do site de um fornecedor (por exemplo, com uma subscrição), abra um terminal, escreva subsub e depois /login.",
|
|
183
211
|
saving: "A guardar",
|
|
184
212
|
saved: "Guardado.",
|
|
@@ -272,6 +300,10 @@ export const SUGGESTIONS = {
|
|
|
272
300
|
export const TOOLS = {
|
|
273
301
|
en: {
|
|
274
302
|
find_items: "search the library",
|
|
303
|
+
standalone_items: "list files without a parent item",
|
|
304
|
+
set_parent_items: "give files a parent item",
|
|
305
|
+
find_reference: "look up a reference",
|
|
306
|
+
web_search: "search the web",
|
|
275
307
|
get_item: "read an item",
|
|
276
308
|
get_fulltext: "read the full text",
|
|
277
309
|
library_overview: "library overview",
|
|
@@ -328,6 +360,10 @@ export const TOOLS = {
|
|
|
328
360
|
},
|
|
329
361
|
pt: {
|
|
330
362
|
find_items: "pesquisar na biblioteca",
|
|
363
|
+
standalone_items: "listar ficheiros sem item principal",
|
|
364
|
+
set_parent_items: "dar um item principal aos ficheiros",
|
|
365
|
+
find_reference: "procurar uma referência",
|
|
366
|
+
web_search: "pesquisar na web",
|
|
331
367
|
get_item: "ler um item",
|
|
332
368
|
get_fulltext: "ler o texto completo",
|
|
333
369
|
library_overview: "resumo da biblioteca",
|
package/web/index.html
CHANGED
|
@@ -122,6 +122,7 @@
|
|
|
122
122
|
<input type="password" id="login-key" autocomplete="off" spellcheck="false" required>
|
|
123
123
|
</label>
|
|
124
124
|
<p class="small"><a id="login-link" href="#" target="_blank" rel="noopener noreferrer" data-i18n="getKey">Get a key</a></p>
|
|
125
|
+
<p class="small muted" id="login-where"></p>
|
|
125
126
|
<p class="small free-note" id="login-free" hidden></p>
|
|
126
127
|
<p class="small muted" data-i18n="keyNote"></p>
|
|
127
128
|
<div class="modal-buttons"><button type="submit" class="primary" id="login-save" data-i18n="save">Save</button></div>
|