Export or import your user data (memories, presets, settings, skills, preferences) as a JSON file.
diff --git a/static/js/models.js b/static/js/models.js
index 1049a2c..3daf4bd 100644
--- a/static/js/models.js
+++ b/static/js/models.js
@@ -553,7 +553,7 @@ export async function refreshModels(force = false) {
box.appendChild(noModels);
// No endpoints yet: keep the welcome screen focused on first setup.
const welcomeSub = document.getElementById('welcome-sub');
- if (welcomeSub) welcomeSub.innerHTML = 'Type
to get started.';
const welcomeTip = document.getElementById('welcome-tip');
if (welcomeTip) welcomeTip.textContent = 'Type /setup, then choose Local models or API.';
} else {
diff --git a/static/js/slashCommands.js b/static/js/slashCommands.js
index 6485c29..099a42f 100644
--- a/static/js/slashCommands.js
+++ b/static/js/slashCommands.js
@@ -152,8 +152,8 @@ function _setupReply(text, remember = true) {
function _showSetupEndpointChoices() {
const providers = SETUP_PROVIDER_NAMES.map(name =>
- '
' +
'
' +
@@ -162,14 +162,14 @@ function _showSetupEndpointChoices() {
'
' +
'
' + SETUP_LOCAL_ICON + 'Local setup
' +
'
Paste endpoint URL in chat (example):
' +
- '
http://localhost:11434/v1
' +
+ '
http://localhost:11434/v1
' +
'
or
' +
- '
http://llm-host.local:8000/v1
' +
+ '
http://llm-host.local:8000/v1
' +
'
' +
'
' +
'
' + SETUP_API_ICON + 'API setup
' +
'
Paste provider name then API key (example):
' +
- '
deepseek sk-...
' +
+ '
deepseek sk-...
' +
'
Supported providers:
' + providers + '
' +
'
' +
'
'
@@ -201,7 +201,9 @@ function _showSetupEndpointChoicesStreamed(options = {}) {
text: 'deepseek sk-...',
copyText: 'deepseek sk-...',
},
- { kind: 'p', html: '
Supported providers:' + SETUP_PROVIDER_NAMES.join(', ') },
+ { kind: 'p', html: '
Supported providers:' + SETUP_PROVIDER_NAMES.map(name =>
+ '
' + name + ''
+ ).join(' ') },
];
return typewriterBlocksReply(blocks, { gap: '4px', bodyClass: 'setup-guide-no-censor', interval: 3 });
}
@@ -388,10 +390,36 @@ function typewriterBlocksReply(blocks, options = {}) {
pre.style.margin = '0';
const code = document.createElement('code');
pre.appendChild(code);
+ const useBtn = document.createElement('button');
+ useBtn.type = 'button';
+ useBtn.className = 'use-code';
+ useBtn.title = 'Use in Chat';
+ useBtn.innerHTML = '
';
+ const copyText = block.copyText || block.text || '';
+ const useNow = (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ e.stopImmediatePropagation();
+ let text = copyText;
+ if (text.includes('sk-...')) {
+ text = text.replace('sk-...', 'sk-');
+ }
+ const messageInput = document.getElementById('message');
+ if (messageInput) {
+ messageInput.value = text;
+ messageInput.dispatchEvent(new Event('input', { bubbles: true }));
+ messageInput.focus();
+ messageInput.setSelectionRange(text.length, text.length);
+ }
+ useBtn.classList.add('used');
+ setTimeout(() => useBtn.classList.remove('used'), 1200);
+ };
+ useBtn.addEventListener('pointerdown', useNow);
+ useBtn.addEventListener('click', useNow);
+ pre.appendChild(useBtn);
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'copy-code';
- const copyText = block.copyText || block.text || '';
btn.setAttribute('data-code', copyText);
btn.title = 'Copy';
btn.innerHTML = '
';
@@ -5907,6 +5935,60 @@ async function handleSlashCommand(input) {
export function initSlashCommands(deps) {
API_BASE = deps.apiBase || '';
if (deps.isStreaming) _isStreamingFn = deps.isStreaming;
+
+ // Global delegation for onboarding and setup clicks
+ document.addEventListener('click', (e) => {
+ // 1. Check for clicking the "/setup" trigger link on the welcome screen
+ const trigger = e.target.closest('.setup-trigger-link');
+ if (trigger) {
+ e.preventDefault();
+ const messageInput = document.getElementById('message');
+ if (messageInput) {
+ messageInput.value = '/setup';
+ messageInput.dispatchEvent(new Event('input', { bubbles: true }));
+ messageInput.focus();
+ const chatForm = document.getElementById('chat-form');
+ if (chatForm) {
+ chatForm.dispatchEvent(new Event('submit', { cancelable: true, bubbles: true }));
+ }
+ }
+ return;
+ }
+
+ // 2. Check for clicking a clickable provider inside the setup guide
+ const providerEl = e.target.closest('.setup-clickable-provider');
+ if (providerEl) {
+ e.preventDefault();
+ const providerName = providerEl.textContent.trim();
+ const messageInput = document.getElementById('message');
+ if (messageInput) {
+ const text = providerName + ' sk-';
+ messageInput.value = text;
+ messageInput.dispatchEvent(new Event('input', { bubbles: true }));
+ messageInput.focus();
+ messageInput.setSelectionRange(text.length, text.length);
+ }
+ return;
+ }
+
+ // 3. Check for clicking a clickable code block inside the setup guide
+ const codeEl = e.target.closest('.setup-clickable-code');
+ if (codeEl) {
+ e.preventDefault();
+ let text = codeEl.textContent.trim();
+ if (text.includes('sk-...')) {
+ text = text.replace('sk-...', 'sk-');
+ }
+ const messageInput = document.getElementById('message');
+ if (messageInput) {
+ messageInput.value = text;
+ messageInput.dispatchEvent(new Event('input', { bubbles: true }));
+ messageInput.focus();
+ messageInput.setSelectionRange(text.length, text.length);
+ }
+ return;
+ }
+ });
}
/**
diff --git a/static/style.css b/static/style.css
index dafeebd..c5d93ba 100644
--- a/static/style.css
+++ b/static/style.css
@@ -3367,6 +3367,33 @@ body.bg-pattern-sparkles {
border-color: var(--accent-primary, var(--red));
background: color-mix(in srgb, var(--accent-primary, var(--red)) 12%, var(--bg));
}
+ pre .use-code {
+ position:absolute; right:42px; top:6px;
+ background:var(--bg); color:var(--fg);
+ border:1px solid var(--border); border-radius:6px;
+ width:28px; height:28px; padding:0; cursor:pointer;
+ opacity:0; transition: opacity .15s, color .15s, border-color .15s;
+ display:flex; align-items:center; justify-content:center;
+ }
+ pre .use-code.bottom { top:auto; bottom:6px; }
+ pre:hover .use-code { opacity:0.7; }
+ pre .use-code:hover { opacity:1; }
+ pre .use-code.used {
+ opacity: 1;
+ color: var(--color-save-green, #4caf50);
+ border-color: var(--color-save-green, #4caf50);
+ background: color-mix(in srgb, var(--color-save-green, #4caf50) 18%, var(--bg));
+ animation: code-copy-pulse 0.36s cubic-bezier(0.34, 1.56, 0.64, 1);
+ }
+ .setup-trigger-link, .setup-clickable-provider, .setup-clickable-code {
+ transition: color 0.15s ease, opacity 0.15s ease;
+ }
+ .setup-trigger-link:hover,
+ .setup-clickable-provider:hover,
+ .setup-clickable-code:hover {
+ color: var(--accent, var(--red)) !important;
+ opacity: 0.9;
+ }
/* Tapping the code body (not a button) toggles the overlay buttons off so
they stop covering the text on touch screens. Tap again to bring back. */