Compare commits

...
1 Commits
Author SHA1 Message Date
Laan Tungir 89fed0c818 This is a meaningful git commit message 2026-04-28 10:39:51 -04:00
2 changed files with 181 additions and 14 deletions
+178 -11
View File
@@ -911,6 +911,8 @@
const DOC_CHAT_D_PREFIX = 'doc-chat:';
const AVAILABLE_TOOLS = [];
const DEFAULT_SKILL_TEMPLATE = 'system:\nYou are a helpful assistant.\n\nuser:\n{{message}}';
const DEFAULT_DOCUMENT_SKILL_SLUG = 'default-document-skill';
const DEFAULT_DOCUMENT_SKILL_DESCRIPTION = 'Default skill for document chat/edit JSON contract.';
const NOTE_KINDS = [30023, 30024];
const DOCUMENT_EDITOR_WRAPPER_SYSTEM = `You are collaborating with the user on a single markdown document.
Return ONLY valid JSON with fields: {"chat": string, "document": string|null}.
@@ -918,6 +920,16 @@ Return ONLY valid JSON with fields: {"chat": string, "document": string|null}.
- document: full updated markdown document when making edits.
- If no edit is needed, set document to null.
Never include extra keys or text outside JSON.`;
const DEFAULT_DOCUMENT_SKILL_TEMPLATE = `system:
${DOCUMENT_EDITOR_WRAPPER_SYSTEM}
Current document:
<document>
{{document}}
</document>
user:
{{message}}`;
let updateIntervalId = null;
let currentPubkey = null;
@@ -1260,6 +1272,7 @@ Never include extra keys or text outside JSON.`;
title,
modelId: '',
skillKeys: [],
removedSkillKeys: [],
messages: [],
createdAt: nowTs(),
updatedAt: nowTs()
@@ -1586,13 +1599,22 @@ Never include extra keys or text outside JSON.`;
};
}
function parseSkillTemplate(template, userInput) {
function parseSkillTemplate(template, userInput, context = {}) {
const parts = splitSkillTemplate(template);
const system = String(parts.system || '').trim();
const rawSystem = String(parts.system || '').trim();
const userTemplate = String(parts.userTemplate || '').trim();
const messageText = String(userInput || '').trim();
const documentText = String(context?.document || '').trim();
const system = rawSystem
.replaceAll('{{message}}', messageText)
.replaceAll('{{document}}', documentText)
.trim();
const user = userTemplate
? userTemplate.replaceAll('{{message}}', String(userInput || '').trim()).trim()
: String(userInput || '').trim();
? userTemplate
.replaceAll('{{message}}', messageText)
.replaceAll('{{document}}', documentText)
.trim()
: messageText;
return { system, user, userTemplate };
}
@@ -1759,27 +1781,80 @@ Never include extra keys or text outside JSON.`;
};
}
function getDefaultDocumentSkillKey() {
if (!currentPubkey) return '';
return skillKey(currentPubkey, DEFAULT_DOCUMENT_SKILL_SLUG);
}
function isDefaultDocumentSkillKey(key) {
return String(key || '').trim() === getDefaultDocumentSkillKey();
}
function getConversationSkillKeys(convo) {
return Array.isArray(convo?.skillKeys)
? convo.skillKeys.map((x) => String(x || '').trim()).filter(Boolean)
: [];
}
function getConversationRemovedSkillKeys(convo) {
return Array.isArray(convo?.removedSkillKeys)
? convo.removedSkillKeys.map((x) => String(x || '').trim()).filter(Boolean)
: [];
}
function enforceDefaultSkillSelection() {
const convo = getCurrentConversation();
if (!convo) return;
const defaultKey = getDefaultDocumentSkillKey();
if (!defaultKey) return;
const hasDefaultSkill = skills.some((s) => s.key === defaultKey);
if (!hasDefaultSkill) return;
const removed = new Set(getConversationRemovedSkillKeys(convo));
const selected = new Set(getConversationSkillKeys(convo));
if (!removed.has(defaultKey) && !selected.has(defaultKey)) {
selected.add(defaultKey);
convo.skillKeys = Array.from(selected);
selectedSkillKeys = [...convo.skillKeys];
updateConversation({ skillKeys: [...convo.skillKeys] });
renderSkillsList();
renderSkillsEditor();
}
}
function syncSelectedSkillsFromConversation(convo) {
const target = getConversationSkillKeys(convo);
const curr = selectedSkillKeys.join('|');
const next = target.join('|');
if (curr === next) return;
selectedSkillKeys = target;
renderSkillsList();
renderSkillsEditor();
if (curr !== next) {
selectedSkillKeys = target;
renderSkillsList();
renderSkillsEditor();
}
enforceDefaultSkillSelection();
}
function persistSelectedSkillsToConversation() {
const convo = getCurrentConversation();
if (!convo) return;
convo.skillKeys = [...selectedSkillKeys];
updateConversation({ skillKeys: [...selectedSkillKeys] });
convo.removedSkillKeys = getConversationRemovedSkillKeys(convo);
updateConversation({
skillKeys: [...selectedSkillKeys],
removedSkillKeys: [...convo.removedSkillKeys]
});
}
function setDefaultSkillExplicitlyRemoved(removed) {
const convo = getCurrentConversation();
if (!convo) return;
const defaultKey = getDefaultDocumentSkillKey();
if (!defaultKey) return;
const set = new Set(getConversationRemovedSkillKeys(convo));
if (removed) set.add(defaultKey);
else set.delete(defaultKey);
convo.removedSkillKeys = Array.from(set);
updateConversation({ removedSkillKeys: [...convo.removedSkillKeys] });
}
function toggleSkillSelection(key, enabled) {
@@ -1791,6 +1866,9 @@ Never include extra keys or text outside JSON.`;
} else if (!enabled && exists) {
selectedSkillKeys = selectedSkillKeys.filter((k) => k !== skillKeyRaw);
}
if (isDefaultDocumentSkillKey(skillKeyRaw)) {
setDefaultSkillExplicitlyRemoved(!enabled);
}
persistSelectedSkillsToConversation();
renderSkillsList();
renderSkillsEditor();
@@ -2113,6 +2191,7 @@ Never include extra keys or text outside JSON.`;
}
renderSkillsList();
renderSkillsEditor();
enforceDefaultSkillSelection();
}
function clearSkillSubscription() {
@@ -2185,7 +2264,9 @@ Never include extra keys or text outside JSON.`;
for (const skill of selected) {
const edit = getSkillEditorValue(skill);
const parsed = parseSkillTemplate(edit.template, userInput);
const parsed = parseSkillTemplate(edit.template, userInput, {
document: String(taDocument?.value || '')
});
if (parsed.system) systemParts.push(parsed.system);
if (parsed.userTemplate) {
resolvedUser = parsed.user;
@@ -2223,6 +2304,7 @@ Never include extra keys or text outside JSON.`;
model_id: String(convo?.modelId || ''),
provider_name: String(aiConfig?.provider || ''),
skill_keys: Array.isArray(convo?.skillKeys) ? convo.skillKeys : [],
removed_skill_keys: getConversationRemovedSkillKeys(convo),
messages: deleted ? [] : safeMessages,
deleted: Boolean(deleted)
};
@@ -2327,6 +2409,9 @@ Never include extra keys or text outside JSON.`;
skillKeys: Array.isArray(parsed?.skill_keys)
? parsed.skill_keys.map((x) => String(x || '').trim()).filter(Boolean)
: [],
removedSkillKeys: Array.isArray(parsed?.removed_skill_keys)
? parsed.removed_skill_keys.map((x) => String(x || '').trim()).filter(Boolean)
: [],
messages,
createdAt: Number(Date.parse(d) || nowTs()),
updatedAt: Number(parsed?.updated_at_ms || nowTs())
@@ -3241,7 +3326,10 @@ Never include extra keys or text outside JSON.`;
try {
const apiPayload = conversationToApiMessages(convo, { currentPrompt: prompt });
const wrapperSystem = buildDocumentSystemPrompt(prompt);
const allMessages = [{ role: 'system', content: wrapperSystem }, ...apiPayload.messages];
const hasDefaultSkillSelected = getSelectedSkills().some((s) => normalizeSlug(s.slug) === DEFAULT_DOCUMENT_SKILL_SLUG);
const allMessages = hasDefaultSkillSelected
? [...apiPayload.messages]
: [{ role: 'system', content: wrapperSystem }, ...apiPayload.messages];
let rawContent = '';
let responseJson = null;
@@ -3465,6 +3553,7 @@ Never include extra keys or text outside JSON.`;
if (btnSkillClearAll) {
btnSkillClearAll.addEventListener('click', () => {
selectedSkillKeys = [];
setDefaultSkillExplicitlyRemoved(true);
persistSelectedSkillsToConversation();
renderSkillsList();
renderSkillsEditor();
@@ -3972,6 +4061,83 @@ Never include extra keys or text outside JSON.`;
});
}
async function ensureDefaultDocumentSkillExists() {
if (!currentPubkey) return;
const slug = DEFAULT_DOCUMENT_SKILL_SLUG;
const key = getDefaultDocumentSkillKey();
const existingInMemory = skills.find((s) => s.key === key);
if (existingInMemory) {
enforceDefaultSkillSelection();
return;
}
const filter = {
kinds: [31123],
authors: [currentPubkey],
'#d': [slug],
limit: 1
};
const cached = await queryCache(filter).catch(() => []);
const cachedEvt = Array.isArray(cached) && cached.length > 0
? [...cached].sort((a, b) => Number(b?.created_at || 0) - Number(a?.created_at || 0))[0]
: null;
if (cachedEvt) {
upsertSkillFromEvent(cachedEvt);
enforceDefaultSkillSelection();
return;
}
const relays = await ndkFetchEvents(filter).catch(() => []);
const relayEvt = Array.isArray(relays) && relays.length > 0
? [...relays].sort((a, b) => Number(b?.created_at || 0) - Number(a?.created_at || 0))[0]
: null;
if (relayEvt) {
upsertSkillFromEvent(relayEvt);
enforceDefaultSkillSelection();
return;
}
const draftSkill = {
key,
slug,
description: DEFAULT_DOCUMENT_SKILL_DESCRIPTION,
template: DEFAULT_DOCUMENT_SKILL_TEMPLATE,
temperature: Number(aiConfig.temperature ?? 0),
max_tokens: Math.max(1, Math.floor(Number(aiConfig.max_tokens ?? 10000))),
llm: String(aiConfig.model || 'default').trim() || 'default',
seed: null,
requires_tools: [],
optional_tools: [],
requires_skills: [],
unavailable_required_tools: [],
needsUnavailableTools: false,
author: currentPubkey,
created_at: Math.floor(Date.now() / 1000),
raw: null
};
skillEditorValues[draftSkill.key] = {
slug: draftSkill.slug,
description: draftSkill.description,
llm: draftSkill.llm,
temperature: String(draftSkill.temperature),
max_tokens: String(draftSkill.max_tokens),
seed: '',
template: draftSkill.template
};
const skillEvent = buildSkillEventFromEditor(draftSkill);
await publishEvent(skillEvent);
upsertSkillFromEvent({
...skillEvent,
pubkey: currentPubkey,
id: uid()
});
setStatus('Created default-document-skill.');
enforceDefaultSkillSelection();
}
(async function main() {
const versionInfo = await getVersion();
console.log(`[document.html ${versionInfo.VERSION}] Loading...`);
@@ -4104,6 +4270,7 @@ Never include extra keys or text outside JSON.`;
renderSkillsList();
renderSkillsEditor();
refreshSkills();
await ensureDefaultDocumentSkillExists();
bindRelayActivityListeners();
setStatus('Ready.');
getRoutstrBalance().catch((error) => {
+3 -3
View File
@@ -1,5 +1,5 @@
{
"VERSION": "v0.7.7",
"VERSION_NUMBER": "0.7.7",
"BUILD_DATE": "2026-04-28T14:00:02.302Z"
"VERSION": "v0.7.8",
"VERSION_NUMBER": "0.7.8",
"BUILD_DATE": "2026-04-28T14:39:51.733Z"
}