zubo 0.1.21 → 0.1.23
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/.playwright-mcp/console-2026-02-16T19-06-21-167Z.log +31 -0
- package/README.md +2 -1
- package/dashboard-chat.png +0 -0
- package/dashboard-followups.png +0 -0
- package/dashboard-history.png +0 -0
- package/dashboard-integrations.png +0 -0
- package/dashboard-knowledge-ok.png +0 -0
- package/dashboard-knowledge.png +0 -0
- package/dashboard-notes-add.png +0 -0
- package/dashboard-notes-improved.png +0 -0
- package/dashboard-notes.png +0 -0
- package/dashboard-overview.png +0 -0
- package/dashboard-preferences.png +0 -0
- package/dashboard-settings-fixed.png +0 -0
- package/dashboard-settings.png +0 -0
- package/dashboard-skills-ok.png +0 -0
- package/dashboard-skills.png +0 -0
- package/dashboard-todos-add.png +0 -0
- package/dashboard-todos-improved.png +0 -0
- package/dashboard-todos-item.png +0 -0
- package/dashboard-todos-priority-badge.png +0 -0
- package/dashboard-todos.png +0 -0
- package/dashboard-topics.png +0 -0
- package/docs/ROADMAP.md +12 -49
- package/migrations/024_personal_features.sql +96 -0
- package/package.json +1 -1
- package/site/docs/index.html +11 -0
- package/site/docs/skills.html +107 -0
- package/site/index.html +9 -1
- package/src/agent/context.ts +3 -3
- package/src/agent/delegate.ts +7 -2
- package/src/agent/loop.ts +6 -6
- package/src/agent/prompts.ts +49 -1
- package/src/agent/workflow-executor.ts +5 -1
- package/src/channels/dashboard.html.ts +558 -6
- package/src/channels/webchat.ts +305 -27
- package/src/llm/claude-code.ts +58 -17
- package/src/llm/codex.ts +59 -18
- package/src/start.ts +12 -0
- package/src/tools/builtin/diagnose.ts +19 -5
- package/src/tools/builtin/follow-ups.ts +189 -0
- package/src/tools/builtin/notes.ts +207 -0
- package/src/tools/builtin/preferences.ts +173 -0
- package/src/tools/builtin/todos.ts +270 -0
- package/src/tools/builtin/topics.ts +166 -0
- package/src/tools/mcp-client.ts +8 -0
- package/src/tools/permissions.ts +7 -0
- package/tests/agent/session.test.ts +43 -45
- package/tests/mcp-registry.test.ts +32 -35
- package/tests/personal-features.test.ts +1251 -0
- package/tests/skill-registry.test.ts +1 -7
- package/tests/db/export.test.ts +0 -219
- package/tests/session.test.ts +0 -58
- package/tests/tools/executor.test.ts +0 -150
|
@@ -659,7 +659,7 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
659
659
|
/* ===== TAB BAR ===== */
|
|
660
660
|
.tab-bar {
|
|
661
661
|
display: flex; gap: 2px; padding: 0 0 16px; border-bottom: 1px solid var(--border);
|
|
662
|
-
margin-bottom: 20px; overflow-x: auto; flex-shrink: 0;
|
|
662
|
+
margin-bottom: 20px; overflow-x: auto; flex-shrink: 0; flex-wrap: wrap; row-gap: 6px;
|
|
663
663
|
}
|
|
664
664
|
.tab {
|
|
665
665
|
padding: 8px 16px; font-size: 13px; font-weight: 500; color: var(--text-muted);
|
|
@@ -943,6 +943,23 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
943
943
|
<span class="nav-svg-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="20" height="12" rx="2"/><path d="M6 12h4"/><path d="M14 12h4"/><circle cx="8" cy="12" r="1"/><circle cx="16" cy="12" r="1"/></svg></span> Extensions
|
|
944
944
|
</a>
|
|
945
945
|
<div class="sidebar-divider"></div>
|
|
946
|
+
<div class="sidebar-section">Personal</div>
|
|
947
|
+
<a href="#todos" onclick="showPanel('todos')">
|
|
948
|
+
<span class="nav-svg-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg></span> Todos
|
|
949
|
+
</a>
|
|
950
|
+
<a href="#notes" onclick="showPanel('notes')">
|
|
951
|
+
<span class="nav-svg-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg></span> Notes
|
|
952
|
+
</a>
|
|
953
|
+
<a href="#preferences" onclick="showPanel('preferences')">
|
|
954
|
+
<span class="nav-svg-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></span> Preferences
|
|
955
|
+
</a>
|
|
956
|
+
<a href="#topics" onclick="showPanel('topics')">
|
|
957
|
+
<span class="nav-svg-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></span> Topics
|
|
958
|
+
</a>
|
|
959
|
+
<a href="#followups" onclick="showPanel('followups')">
|
|
960
|
+
<span class="nav-svg-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg></span> Follow-ups
|
|
961
|
+
</a>
|
|
962
|
+
<div class="sidebar-divider"></div>
|
|
946
963
|
<div class="sidebar-section">Settings</div>
|
|
947
964
|
<a href="#integrations" onclick="showPanel('integrations')">
|
|
948
965
|
<span class="nav-svg-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v6m0 8v6"/><path d="M6 12H2"/><path d="M22 12h-4"/><circle cx="12" cy="12" r="4"/><path d="M12 8V2"/></svg></span> Integrations
|
|
@@ -1833,6 +1850,119 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
1833
1850
|
</div>
|
|
1834
1851
|
</div>
|
|
1835
1852
|
|
|
1853
|
+
<!-- TODOS PANEL -->
|
|
1854
|
+
<div id="panel-todos" class="panel">
|
|
1855
|
+
<div class="panel-body">
|
|
1856
|
+
<p class="settings-desc" style="margin-bottom:16px;">Your tasks and to-dos. Add items, set priorities and due dates.</p>
|
|
1857
|
+
<div style="display:flex;gap:8px;margin-bottom:16px;align-items:center;flex-wrap:wrap;">
|
|
1858
|
+
<select id="todo-filter" class="settings-select" style="width:auto;" onchange="loadTodos()">
|
|
1859
|
+
<option value="pending">Active</option>
|
|
1860
|
+
<option value="done">Done</option>
|
|
1861
|
+
<option value="all">All</option>
|
|
1862
|
+
</select>
|
|
1863
|
+
<button class="btn btn-primary btn-sm" onclick="toggleTodoForm()">+ Add</button>
|
|
1864
|
+
</div>
|
|
1865
|
+
<div id="todo-add-form" style="display:none;background:var(--bg-surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:16px;">
|
|
1866
|
+
<input id="todo-title" type="text" placeholder="What needs to be done?" style="width:100%;margin-bottom:8px;padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;">
|
|
1867
|
+
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
|
|
1868
|
+
<select id="todo-priority" class="settings-select" style="width:auto;font-size:12px;">
|
|
1869
|
+
<option value="low">Low</option>
|
|
1870
|
+
<option value="medium" selected>Medium</option>
|
|
1871
|
+
<option value="high">High</option>
|
|
1872
|
+
<option value="urgent">Urgent</option>
|
|
1873
|
+
</select>
|
|
1874
|
+
<input id="todo-due" type="date" style="padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:12px;">
|
|
1875
|
+
<button class="btn btn-primary btn-sm" onclick="addTodo()">Save</button>
|
|
1876
|
+
<button class="btn btn-ghost btn-sm" onclick="toggleTodoForm()">Cancel</button>
|
|
1877
|
+
</div>
|
|
1878
|
+
</div>
|
|
1879
|
+
<div id="todos-list"></div>
|
|
1880
|
+
</div>
|
|
1881
|
+
</div>
|
|
1882
|
+
|
|
1883
|
+
<!-- NOTES PANEL -->
|
|
1884
|
+
<div id="panel-notes" class="panel">
|
|
1885
|
+
<div class="panel-body">
|
|
1886
|
+
<p class="settings-desc" style="margin-bottom:16px;">Your saved notes. Search, pin, and organize information.</p>
|
|
1887
|
+
<div style="display:flex;gap:8px;margin-bottom:16px;align-items:center;flex-wrap:wrap;">
|
|
1888
|
+
<input id="notes-search" type="text" placeholder="Search notes..." style="flex:1;min-width:150px;padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;" onkeydown="if(event.key==='Enter')loadNotes()">
|
|
1889
|
+
<button class="btn btn-ghost btn-sm" onclick="loadNotes()">Search</button>
|
|
1890
|
+
<button class="btn btn-primary btn-sm" onclick="toggleNoteForm()">+ Add</button>
|
|
1891
|
+
</div>
|
|
1892
|
+
<div id="note-add-form" style="display:none;background:var(--bg-surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:16px;">
|
|
1893
|
+
<input id="note-title" type="text" placeholder="Title" style="width:100%;margin-bottom:8px;padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;">
|
|
1894
|
+
<textarea id="note-content" placeholder="Content..." rows="4" style="width:100%;margin-bottom:8px;padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;resize:vertical;"></textarea>
|
|
1895
|
+
<input id="note-tags" type="text" placeholder="Tags (comma-separated)" style="width:100%;margin-bottom:8px;padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:12px;">
|
|
1896
|
+
<div style="display:flex;gap:8px;">
|
|
1897
|
+
<button class="btn btn-primary btn-sm" onclick="addNote()">Save</button>
|
|
1898
|
+
<button class="btn btn-ghost btn-sm" onclick="toggleNoteForm()">Cancel</button>
|
|
1899
|
+
</div>
|
|
1900
|
+
</div>
|
|
1901
|
+
<div id="notes-list"></div>
|
|
1902
|
+
</div>
|
|
1903
|
+
</div>
|
|
1904
|
+
|
|
1905
|
+
<!-- PREFERENCES PANEL -->
|
|
1906
|
+
<div id="panel-preferences" class="panel">
|
|
1907
|
+
<div class="panel-body">
|
|
1908
|
+
<p class="settings-desc" style="margin-bottom:16px;">Your preferences. These are used by the agent to personalize responses.</p>
|
|
1909
|
+
<div style="display:flex;gap:8px;margin-bottom:16px;align-items:center;">
|
|
1910
|
+
<button class="btn btn-primary btn-sm" onclick="togglePrefForm()">+ Add</button>
|
|
1911
|
+
</div>
|
|
1912
|
+
<div id="pref-add-form" style="display:none;background:var(--bg-surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:16px;">
|
|
1913
|
+
<div style="display:flex;gap:8px;margin-bottom:8px;flex-wrap:wrap;">
|
|
1914
|
+
<input id="pref-category" type="text" placeholder="Category (e.g. work)" style="flex:1;min-width:100px;padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;" value="general">
|
|
1915
|
+
<input id="pref-key" type="text" placeholder="Key (e.g. language)" style="flex:1;min-width:100px;padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;">
|
|
1916
|
+
</div>
|
|
1917
|
+
<input id="pref-value" type="text" placeholder="Value (e.g. TypeScript)" style="width:100%;margin-bottom:8px;padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;">
|
|
1918
|
+
<div style="display:flex;gap:8px;">
|
|
1919
|
+
<button class="btn btn-primary btn-sm" onclick="addPref()">Save</button>
|
|
1920
|
+
<button class="btn btn-ghost btn-sm" onclick="togglePrefForm()">Cancel</button>
|
|
1921
|
+
</div>
|
|
1922
|
+
</div>
|
|
1923
|
+
<div id="prefs-list"></div>
|
|
1924
|
+
</div>
|
|
1925
|
+
</div>
|
|
1926
|
+
|
|
1927
|
+
<!-- TOPICS PANEL -->
|
|
1928
|
+
<div id="panel-topics" class="panel">
|
|
1929
|
+
<div class="panel-body">
|
|
1930
|
+
<p class="settings-desc" style="margin-bottom:16px;">Conversation topics. Each topic keeps a separate thread of context.</p>
|
|
1931
|
+
<div style="display:flex;gap:8px;margin-bottom:16px;align-items:center;">
|
|
1932
|
+
<button class="btn btn-primary btn-sm" onclick="toggleTopicForm()">+ Add</button>
|
|
1933
|
+
</div>
|
|
1934
|
+
<div id="topic-add-form" style="display:none;background:var(--bg-surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:16px;">
|
|
1935
|
+
<input id="topic-name" type="text" placeholder="Topic name" style="width:100%;margin-bottom:8px;padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;">
|
|
1936
|
+
<input id="topic-desc" type="text" placeholder="Description (optional)" style="width:100%;margin-bottom:8px;padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:12px;">
|
|
1937
|
+
<div style="display:flex;gap:8px;">
|
|
1938
|
+
<button class="btn btn-primary btn-sm" onclick="addTopic()">Save</button>
|
|
1939
|
+
<button class="btn btn-ghost btn-sm" onclick="toggleTopicForm()">Cancel</button>
|
|
1940
|
+
</div>
|
|
1941
|
+
</div>
|
|
1942
|
+
<div id="topics-list"></div>
|
|
1943
|
+
</div>
|
|
1944
|
+
</div>
|
|
1945
|
+
|
|
1946
|
+
<!-- FOLLOW-UPS PANEL -->
|
|
1947
|
+
<div id="panel-followups" class="panel">
|
|
1948
|
+
<div class="panel-body">
|
|
1949
|
+
<p class="settings-desc" style="margin-bottom:16px;">Scheduled follow-ups. The agent will proactively remind you at the set time.</p>
|
|
1950
|
+
<div style="display:flex;gap:8px;margin-bottom:16px;align-items:center;">
|
|
1951
|
+
<button class="btn btn-primary btn-sm" onclick="toggleFollowupForm()">+ Add</button>
|
|
1952
|
+
</div>
|
|
1953
|
+
<div id="followup-add-form" style="display:none;background:var(--bg-surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:16px;">
|
|
1954
|
+
<input id="followup-context" type="text" placeholder="About what? (e.g. dentist appointment)" style="width:100%;margin-bottom:8px;padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;">
|
|
1955
|
+
<input id="followup-message" type="text" placeholder="Reminder message" style="width:100%;margin-bottom:8px;padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;">
|
|
1956
|
+
<input id="followup-at" type="datetime-local" style="width:100%;margin-bottom:8px;padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:12px;">
|
|
1957
|
+
<div style="display:flex;gap:8px;">
|
|
1958
|
+
<button class="btn btn-primary btn-sm" onclick="addFollowup()">Save</button>
|
|
1959
|
+
<button class="btn btn-ghost btn-sm" onclick="toggleFollowupForm()">Cancel</button>
|
|
1960
|
+
</div>
|
|
1961
|
+
</div>
|
|
1962
|
+
<div id="followups-list"></div>
|
|
1963
|
+
</div>
|
|
1964
|
+
</div>
|
|
1965
|
+
|
|
1836
1966
|
</div>
|
|
1837
1967
|
</div>
|
|
1838
1968
|
|
|
@@ -1920,8 +2050,8 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
1920
2050
|
|
|
1921
2051
|
<script>
|
|
1922
2052
|
// --- Panel routing ---
|
|
1923
|
-
var panelNames = ['agent','history','dashboard','memory','skills','workflows','webhooks','mcp','integrations','settings'];
|
|
1924
|
-
var panelTitles = { agent:'Chat', history:'History', dashboard:'Dashboard', memory:'Knowledge', skills:'Skills', workflows:'Workflows', webhooks:'Webhooks', mcp:'Extensions', integrations:'Integrations', settings:'Settings' };
|
|
2053
|
+
var panelNames = ['agent','history','dashboard','memory','skills','workflows','webhooks','mcp','todos','notes','preferences','topics','followups','integrations','settings'];
|
|
2054
|
+
var panelTitles = { agent:'Chat', history:'History', dashboard:'Dashboard', memory:'Knowledge', skills:'Skills', workflows:'Workflows', webhooks:'Webhooks', mcp:'Extensions', todos:'Todos', notes:'Notes', preferences:'Preferences', topics:'Topics', followups:'Follow-ups', integrations:'Integrations', settings:'Settings' };
|
|
1925
2055
|
|
|
1926
2056
|
// Legacy panel name mapping (old names -> new names + tab)
|
|
1927
2057
|
var legacyPanelMap = {
|
|
@@ -1963,6 +2093,11 @@ function showPanel(name) {
|
|
|
1963
2093
|
if (name === 'integrations') loadIntegrations();
|
|
1964
2094
|
if (name === 'webhooks') loadWebhooks();
|
|
1965
2095
|
if (name === 'mcp') loadMcpPanel();
|
|
2096
|
+
if (name === 'todos') loadTodos();
|
|
2097
|
+
if (name === 'notes') loadNotes();
|
|
2098
|
+
if (name === 'preferences') loadPrefs();
|
|
2099
|
+
if (name === 'topics') loadTopics();
|
|
2100
|
+
if (name === 'followups') loadFollowups();
|
|
1966
2101
|
if (name === 'settings') loadSettingsPanel();
|
|
1967
2102
|
closeMobileMenu();
|
|
1968
2103
|
}
|
|
@@ -3544,7 +3679,7 @@ function loadChannelStatus() {
|
|
|
3544
3679
|
var badge = document.getElementById('channel-count-badge');
|
|
3545
3680
|
if (badge) badge.textContent = connCount + ' active';
|
|
3546
3681
|
var sidebarBadge = document.getElementById('sidebar-conn-badge');
|
|
3547
|
-
if (sidebarBadge) sidebarBadge.textContent = connCount + ' channels';
|
|
3682
|
+
if (sidebarBadge) sidebarBadge.textContent = connCount + (connCount === 1 ? ' channel' : ' channels');
|
|
3548
3683
|
}).catch(function(err) { console.warn('Channel config load failed', err); });
|
|
3549
3684
|
}
|
|
3550
3685
|
|
|
@@ -4477,7 +4612,7 @@ function createThread() {
|
|
|
4477
4612
|
function switchThread(id, title) {
|
|
4478
4613
|
activeThreadId = id;
|
|
4479
4614
|
loadThreads();
|
|
4480
|
-
api('/threads/' + id + '/messages').then(function(data) {
|
|
4615
|
+
api('/threads/' + encodeURIComponent(id) + '/messages').then(function(data) {
|
|
4481
4616
|
var msgs = data.messages || [];
|
|
4482
4617
|
clearChatMessages();
|
|
4483
4618
|
if (msgs.length === 0) return;
|
|
@@ -4491,7 +4626,7 @@ function switchThread(id, title) {
|
|
|
4491
4626
|
}
|
|
4492
4627
|
|
|
4493
4628
|
function deleteThread(id) {
|
|
4494
|
-
api('/threads/' + id, { method: 'DELETE' }).then(function() {
|
|
4629
|
+
api('/threads/' + encodeURIComponent(id), { method: 'DELETE' }).then(function() {
|
|
4495
4630
|
if (activeThreadId === id) {
|
|
4496
4631
|
activeThreadId = null;
|
|
4497
4632
|
clearChatMessages();
|
|
@@ -5533,6 +5668,423 @@ function sendTestDigest() {
|
|
|
5533
5668
|
.catch(function(e) { toast('Send failed: ' + (e.message || '')); });
|
|
5534
5669
|
}
|
|
5535
5670
|
|
|
5671
|
+
// ── TODOS ──
|
|
5672
|
+
|
|
5673
|
+
function toggleTodoForm() {
|
|
5674
|
+
var f = document.getElementById('todo-add-form');
|
|
5675
|
+
f.style.display = f.style.display === 'none' ? 'block' : 'none';
|
|
5676
|
+
if (f.style.display === 'block') document.getElementById('todo-title').focus();
|
|
5677
|
+
}
|
|
5678
|
+
|
|
5679
|
+
function loadTodos() {
|
|
5680
|
+
var filter = document.getElementById('todo-filter').value;
|
|
5681
|
+
api('/todos?filter=' + filter).then(function(data) { renderTodos(data.todos || []); }).catch(function(e) { toast('Failed to load todos'); });
|
|
5682
|
+
}
|
|
5683
|
+
|
|
5684
|
+
function addTodo() {
|
|
5685
|
+
var title = document.getElementById('todo-title').value.trim();
|
|
5686
|
+
if (!title) return;
|
|
5687
|
+
var body = { title: title, priority: document.getElementById('todo-priority').value, due_date: document.getElementById('todo-due').value || null };
|
|
5688
|
+
api('/todos', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body) })
|
|
5689
|
+
.then(function() { document.getElementById('todo-title').value=''; toggleTodoForm(); loadTodos(); toast('Todo added'); })
|
|
5690
|
+
.catch(function(e) { toast('Failed to add todo'); });
|
|
5691
|
+
}
|
|
5692
|
+
|
|
5693
|
+
function completeTodo(id) {
|
|
5694
|
+
api('/todos/' + id, { method:'PUT', headers:{'Content-Type':'application/json'}, body:JSON.stringify({status:'done'}) })
|
|
5695
|
+
.then(function() { loadTodos(); }).catch(function(e) { toast('Failed to update todo'); loadTodos(); });
|
|
5696
|
+
}
|
|
5697
|
+
|
|
5698
|
+
function uncompleteTodo(id) {
|
|
5699
|
+
api('/todos/' + id, { method:'PUT', headers:{'Content-Type':'application/json'}, body:JSON.stringify({status:'pending'}) })
|
|
5700
|
+
.then(function() { loadTodos(); }).catch(function(e) { toast('Failed to update todo'); loadTodos(); });
|
|
5701
|
+
}
|
|
5702
|
+
|
|
5703
|
+
function deleteTodo(id) {
|
|
5704
|
+
api('/todos/' + id, { method:'DELETE' }).then(function() { loadTodos(); toast('Todo removed'); }).catch(function(e) { toast('Failed to delete todo'); });
|
|
5705
|
+
}
|
|
5706
|
+
|
|
5707
|
+
function renderTodos(todos) {
|
|
5708
|
+
var c = document.getElementById('todos-list');
|
|
5709
|
+
c.replaceChildren();
|
|
5710
|
+
if (!todos.length) { c.textContent = ''; var p = document.createElement('div'); p.className='empty-state-card'; p.style.textAlign='center'; var t=document.createElement('p'); t.style.cssText='color:var(--text-muted);margin-bottom:4px;'; t.textContent='No todos yet'; p.appendChild(t); var h=document.createElement('p'); h.style.cssText='color:var(--text-faint);font-size:12px;'; h.textContent='Click + Add above, or ask Zubo in chat to create one.'; p.appendChild(h); c.appendChild(p); return; }
|
|
5711
|
+
todos.forEach(function(t) {
|
|
5712
|
+
var item = document.createElement('div');
|
|
5713
|
+
item.className = 'memory-item';
|
|
5714
|
+
item.style.display = 'flex';
|
|
5715
|
+
item.style.alignItems = 'flex-start';
|
|
5716
|
+
item.style.gap = '12px';
|
|
5717
|
+
var isDone = t.status === 'done';
|
|
5718
|
+
|
|
5719
|
+
var check = document.createElement('input');
|
|
5720
|
+
check.type = 'checkbox';
|
|
5721
|
+
check.checked = isDone;
|
|
5722
|
+
check.style.cssText = 'margin-top:3px;cursor:pointer;accent-color:var(--accent);';
|
|
5723
|
+
check.onchange = function() { check.checked ? completeTodo(t.id) : uncompleteTodo(t.id); };
|
|
5724
|
+
|
|
5725
|
+
var body = document.createElement('div');
|
|
5726
|
+
body.style.flex = '1';
|
|
5727
|
+
var titleEl = document.createElement('div');
|
|
5728
|
+
titleEl.style.cssText = isDone ? 'text-decoration:line-through;color:var(--text-muted);' : 'color:var(--text);';
|
|
5729
|
+
titleEl.textContent = t.title;
|
|
5730
|
+
body.appendChild(titleEl);
|
|
5731
|
+
|
|
5732
|
+
var meta = document.createElement('div');
|
|
5733
|
+
meta.style.cssText = 'font-size:11px;color:var(--text-faint);margin-top:4px;display:flex;gap:8px;flex-wrap:wrap;';
|
|
5734
|
+
var prBg = { urgent:'rgba(239,68,68,0.15)', high:'rgba(245,158,11,0.15)', medium:'rgba(139,92,246,0.15)', low:'rgba(100,116,139,0.1)' };
|
|
5735
|
+
var prFg = { urgent:'#ef4444', high:'#f59e0b', medium:'#8b5cf6', low:'var(--text-faint)' };
|
|
5736
|
+
var prSpan = document.createElement('span');
|
|
5737
|
+
prSpan.style.cssText = 'color:' + (prFg[t.priority]||'var(--text-faint)') + ';background:' + (prBg[t.priority]||'transparent') + ';padding:1px 8px;border-radius:10px;font-size:10px;font-weight:500;';
|
|
5738
|
+
prSpan.textContent = t.priority;
|
|
5739
|
+
meta.appendChild(prSpan);
|
|
5740
|
+
if (t.due_date) { var ds = document.createElement('span'); ds.style.cssText = 'display:inline-flex;align-items:center;gap:3px;'; ds.textContent = '\u{1F4C5} ' + t.due_date; meta.appendChild(ds); }
|
|
5741
|
+
if (t.tags && t.tags !== '[]') {
|
|
5742
|
+
try { var tags = JSON.parse(t.tags); if (tags.length) { var ts = document.createElement('span'); ts.textContent = tags.join(', '); meta.appendChild(ts); } } catch(e){}
|
|
5743
|
+
}
|
|
5744
|
+
body.appendChild(meta);
|
|
5745
|
+
|
|
5746
|
+
var del = document.createElement('button');
|
|
5747
|
+
del.className = 'btn btn-ghost btn-sm';
|
|
5748
|
+
del.textContent = 'Delete';
|
|
5749
|
+
del.style.cssText = 'font-size:11px;padding:2px 8px;color:var(--text-faint);';
|
|
5750
|
+
del.onclick = function() { deleteTodo(t.id); };
|
|
5751
|
+
|
|
5752
|
+
item.appendChild(check);
|
|
5753
|
+
item.appendChild(body);
|
|
5754
|
+
item.appendChild(del);
|
|
5755
|
+
c.appendChild(item);
|
|
5756
|
+
});
|
|
5757
|
+
}
|
|
5758
|
+
|
|
5759
|
+
// ── NOTES ──
|
|
5760
|
+
|
|
5761
|
+
function toggleNoteForm() {
|
|
5762
|
+
var f = document.getElementById('note-add-form');
|
|
5763
|
+
f.style.display = f.style.display === 'none' ? 'block' : 'none';
|
|
5764
|
+
if (f.style.display === 'block') document.getElementById('note-title').focus();
|
|
5765
|
+
}
|
|
5766
|
+
|
|
5767
|
+
function loadNotes() {
|
|
5768
|
+
var q = document.getElementById('notes-search').value.trim();
|
|
5769
|
+
var url = '/notes' + (q ? '?q=' + encodeURIComponent(q) : '');
|
|
5770
|
+
api(url).then(function(data) { renderNotes(data.notes || []); }).catch(function(e) { toast('Failed to load notes'); });
|
|
5771
|
+
}
|
|
5772
|
+
|
|
5773
|
+
function addNote() {
|
|
5774
|
+
var title = document.getElementById('note-title').value.trim();
|
|
5775
|
+
var content = document.getElementById('note-content').value.trim();
|
|
5776
|
+
if (!title || !content) return;
|
|
5777
|
+
var body = { title: title, content: content, tags: document.getElementById('note-tags').value };
|
|
5778
|
+
api('/notes', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body) })
|
|
5779
|
+
.then(function() {
|
|
5780
|
+
document.getElementById('note-title').value='';
|
|
5781
|
+
document.getElementById('note-content').value='';
|
|
5782
|
+
document.getElementById('note-tags').value='';
|
|
5783
|
+
toggleNoteForm(); loadNotes(); toast('Note saved');
|
|
5784
|
+
}).catch(function(e) { toast('Failed to save note'); });
|
|
5785
|
+
}
|
|
5786
|
+
|
|
5787
|
+
function togglePin(id, pinned) {
|
|
5788
|
+
api('/notes/' + id, { method:'PUT', headers:{'Content-Type':'application/json'}, body:JSON.stringify({pinned:!pinned}) })
|
|
5789
|
+
.then(function() { loadNotes(); }).catch(function(e) { toast('Failed to update note'); });
|
|
5790
|
+
}
|
|
5791
|
+
|
|
5792
|
+
function deleteNote(id) {
|
|
5793
|
+
api('/notes/' + id, { method:'DELETE' }).then(function() { loadNotes(); toast('Note deleted'); }).catch(function(e) { toast('Failed to delete note'); });
|
|
5794
|
+
}
|
|
5795
|
+
|
|
5796
|
+
function renderNotes(notes) {
|
|
5797
|
+
var c = document.getElementById('notes-list');
|
|
5798
|
+
c.replaceChildren();
|
|
5799
|
+
if (!notes.length) { var p = document.createElement('div'); p.className='empty-state-card'; p.style.textAlign='center'; var t=document.createElement('p'); t.style.cssText='color:var(--text-muted);margin-bottom:4px;'; t.textContent='No notes yet'; p.appendChild(t); var h=document.createElement('p'); h.style.cssText='color:var(--text-faint);font-size:12px;'; h.textContent='Save notes here or tell Zubo to remember something in chat.'; p.appendChild(h); c.appendChild(p); return; }
|
|
5800
|
+
notes.forEach(function(n) {
|
|
5801
|
+
var item = document.createElement('div');
|
|
5802
|
+
item.className = 'memory-item';
|
|
5803
|
+
|
|
5804
|
+
var header = document.createElement('div');
|
|
5805
|
+
header.style.cssText = 'display:flex;justify-content:space-between;align-items:flex-start;gap:8px;';
|
|
5806
|
+
var titleEl = document.createElement('div');
|
|
5807
|
+
titleEl.style.cssText = 'font-weight:500;color:var(--text);';
|
|
5808
|
+
titleEl.textContent = (n.pinned ? '\u{1F4CC} ' : '') + n.title;
|
|
5809
|
+
header.appendChild(titleEl);
|
|
5810
|
+
|
|
5811
|
+
var actions = document.createElement('div');
|
|
5812
|
+
actions.style.cssText = 'display:flex;gap:4px;flex-shrink:0;';
|
|
5813
|
+
var pinBtn = document.createElement('button');
|
|
5814
|
+
pinBtn.className = 'btn btn-ghost btn-sm';
|
|
5815
|
+
pinBtn.style.cssText = 'font-size:11px;padding:2px 8px;color:var(--text-faint);';
|
|
5816
|
+
pinBtn.textContent = n.pinned ? 'Unpin' : 'Pin';
|
|
5817
|
+
pinBtn.onclick = function() { togglePin(n.id, n.pinned); };
|
|
5818
|
+
var delBtn = document.createElement('button');
|
|
5819
|
+
delBtn.className = 'btn btn-ghost btn-sm';
|
|
5820
|
+
delBtn.style.cssText = 'font-size:11px;padding:2px 8px;color:var(--text-faint);';
|
|
5821
|
+
delBtn.textContent = 'Delete';
|
|
5822
|
+
delBtn.onclick = function() { deleteNote(n.id); };
|
|
5823
|
+
actions.appendChild(pinBtn);
|
|
5824
|
+
actions.appendChild(delBtn);
|
|
5825
|
+
header.appendChild(actions);
|
|
5826
|
+
item.appendChild(header);
|
|
5827
|
+
|
|
5828
|
+
var content = document.createElement('div');
|
|
5829
|
+
content.style.cssText = 'font-size:13px;color:var(--text-secondary);margin-top:8px;white-space:pre-wrap;max-height:120px;overflow:hidden;';
|
|
5830
|
+
content.textContent = n.content;
|
|
5831
|
+
item.appendChild(content);
|
|
5832
|
+
|
|
5833
|
+
var meta = document.createElement('div');
|
|
5834
|
+
meta.style.cssText = 'font-size:11px;color:var(--text-faint);margin-top:8px;';
|
|
5835
|
+
var parts = [n.updated_at ? n.updated_at.split('T')[0] : ''];
|
|
5836
|
+
if (n.tags && n.tags !== '[]') { try { var tags = JSON.parse(n.tags); if (tags.length) parts.push(tags.join(', ')); } catch(e){} }
|
|
5837
|
+
meta.textContent = parts.filter(Boolean).join(' \u00B7 ');
|
|
5838
|
+
item.appendChild(meta);
|
|
5839
|
+
c.appendChild(item);
|
|
5840
|
+
});
|
|
5841
|
+
}
|
|
5842
|
+
|
|
5843
|
+
// ── PREFERENCES ──
|
|
5844
|
+
|
|
5845
|
+
function togglePrefForm() {
|
|
5846
|
+
var f = document.getElementById('pref-add-form');
|
|
5847
|
+
f.style.display = f.style.display === 'none' ? 'block' : 'none';
|
|
5848
|
+
if (f.style.display === 'block') document.getElementById('pref-key').focus();
|
|
5849
|
+
}
|
|
5850
|
+
|
|
5851
|
+
function loadPrefs() {
|
|
5852
|
+
api('/preferences').then(function(data) { renderPrefs(data.preferences || []); }).catch(function(e) { toast('Failed to load preferences'); });
|
|
5853
|
+
}
|
|
5854
|
+
|
|
5855
|
+
function addPref() {
|
|
5856
|
+
var key = document.getElementById('pref-key').value.trim();
|
|
5857
|
+
var value = document.getElementById('pref-value').value.trim();
|
|
5858
|
+
if (!key || !value) return;
|
|
5859
|
+
var body = { category: document.getElementById('pref-category').value.trim() || 'general', key: key, value: value };
|
|
5860
|
+
api('/preferences', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body) })
|
|
5861
|
+
.then(function() {
|
|
5862
|
+
document.getElementById('pref-key').value='';
|
|
5863
|
+
document.getElementById('pref-value').value='';
|
|
5864
|
+
togglePrefForm(); loadPrefs(); toast('Preference saved');
|
|
5865
|
+
}).catch(function(e) { toast('Failed to save preference'); });
|
|
5866
|
+
}
|
|
5867
|
+
|
|
5868
|
+
function deletePref(id) {
|
|
5869
|
+
api('/preferences/' + id, { method:'DELETE' }).then(function() { loadPrefs(); toast('Preference removed'); }).catch(function(e) { toast('Failed to delete preference'); });
|
|
5870
|
+
}
|
|
5871
|
+
|
|
5872
|
+
function renderPrefs(prefs) {
|
|
5873
|
+
var c = document.getElementById('prefs-list');
|
|
5874
|
+
c.replaceChildren();
|
|
5875
|
+
if (!prefs.length) { var p = document.createElement('div'); p.className='empty-state-card'; p.style.textAlign='center'; var t=document.createElement('p'); t.style.cssText='color:var(--text-muted);margin-bottom:4px;'; t.textContent='No preferences yet'; p.appendChild(t); var h=document.createElement('p'); h.style.cssText='color:var(--text-faint);font-size:12px;'; h.textContent='Tell Zubo your preferences in chat, e.g. "I prefer TypeScript" or add them here.'; p.appendChild(h); c.appendChild(p); return; }
|
|
5876
|
+
|
|
5877
|
+
// Group by category
|
|
5878
|
+
var groups = {};
|
|
5879
|
+
prefs.forEach(function(p) {
|
|
5880
|
+
if (!groups[p.category]) groups[p.category] = [];
|
|
5881
|
+
groups[p.category].push(p);
|
|
5882
|
+
});
|
|
5883
|
+
|
|
5884
|
+
Object.keys(groups).sort().forEach(function(cat) {
|
|
5885
|
+
var section = document.createElement('div');
|
|
5886
|
+
section.style.cssText = 'margin-bottom:16px;';
|
|
5887
|
+
var heading = document.createElement('div');
|
|
5888
|
+
heading.style.cssText = 'font-size:12px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.5px;margin-bottom:8px;';
|
|
5889
|
+
heading.textContent = cat;
|
|
5890
|
+
section.appendChild(heading);
|
|
5891
|
+
|
|
5892
|
+
groups[cat].forEach(function(p) {
|
|
5893
|
+
var item = document.createElement('div');
|
|
5894
|
+
item.className = 'memory-item';
|
|
5895
|
+
item.style.cssText = 'display:flex;justify-content:space-between;align-items:center;gap:8px;';
|
|
5896
|
+
var left = document.createElement('span');
|
|
5897
|
+
var keySpan = document.createElement('span'); keySpan.style.color='var(--text)'; keySpan.textContent = p.key;
|
|
5898
|
+
var eqSpan = document.createElement('span'); eqSpan.style.cssText='color:var(--text-faint);margin:0 6px'; eqSpan.textContent = '=';
|
|
5899
|
+
var valSpan = document.createElement('span'); valSpan.style.color='var(--accent)'; valSpan.textContent = p.value;
|
|
5900
|
+
left.appendChild(keySpan); left.appendChild(eqSpan); left.appendChild(valSpan);
|
|
5901
|
+
if (p.source === 'inferred') { var src = document.createElement('span'); src.style.cssText='font-size:10px;color:var(--text-faint);margin-left:8px'; src.textContent='(inferred)'; left.appendChild(src); }
|
|
5902
|
+
var del = document.createElement('button');
|
|
5903
|
+
del.className = 'btn btn-ghost btn-sm';
|
|
5904
|
+
del.style.cssText = 'font-size:11px;padding:2px 8px;color:var(--text-faint);';
|
|
5905
|
+
del.textContent = 'Delete';
|
|
5906
|
+
del.onclick = function() { deletePref(p.id); };
|
|
5907
|
+
item.appendChild(left);
|
|
5908
|
+
item.appendChild(del);
|
|
5909
|
+
section.appendChild(item);
|
|
5910
|
+
});
|
|
5911
|
+
c.appendChild(section);
|
|
5912
|
+
});
|
|
5913
|
+
}
|
|
5914
|
+
|
|
5915
|
+
// ── TOPICS ──
|
|
5916
|
+
|
|
5917
|
+
function toggleTopicForm() {
|
|
5918
|
+
var f = document.getElementById('topic-add-form');
|
|
5919
|
+
f.style.display = f.style.display === 'none' ? 'block' : 'none';
|
|
5920
|
+
if (f.style.display === 'block') document.getElementById('topic-name').focus();
|
|
5921
|
+
}
|
|
5922
|
+
|
|
5923
|
+
function loadTopics() {
|
|
5924
|
+
api('/topics').then(function(data) { renderTopics(data.topics || []); }).catch(function(e) { toast('Failed to load topics'); });
|
|
5925
|
+
}
|
|
5926
|
+
|
|
5927
|
+
function addTopic() {
|
|
5928
|
+
var name = document.getElementById('topic-name').value.trim();
|
|
5929
|
+
if (!name) return;
|
|
5930
|
+
var body = { name: name, description: document.getElementById('topic-desc').value.trim() || null };
|
|
5931
|
+
api('/topics', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body) })
|
|
5932
|
+
.then(function() {
|
|
5933
|
+
document.getElementById('topic-name').value='';
|
|
5934
|
+
document.getElementById('topic-desc').value='';
|
|
5935
|
+
toggleTopicForm(); loadTopics(); toast('Topic created');
|
|
5936
|
+
}).catch(function(e) { toast('Failed to create topic'); });
|
|
5937
|
+
}
|
|
5938
|
+
|
|
5939
|
+
function toggleTopicStatus(id, currentStatus) {
|
|
5940
|
+
var newStatus = currentStatus === 'active' ? 'archived' : 'active';
|
|
5941
|
+
api('/topics/' + id, { method:'PUT', headers:{'Content-Type':'application/json'}, body:JSON.stringify({status:newStatus}) })
|
|
5942
|
+
.then(function() { loadTopics(); }).catch(function(e) { toast('Failed to update topic'); });
|
|
5943
|
+
}
|
|
5944
|
+
|
|
5945
|
+
function deleteTopic(id) {
|
|
5946
|
+
api('/topics/' + id, { method:'DELETE' }).then(function() { loadTopics(); toast('Topic deleted'); }).catch(function(e) { toast('Failed to delete topic'); });
|
|
5947
|
+
}
|
|
5948
|
+
|
|
5949
|
+
function renderTopics(topics) {
|
|
5950
|
+
var c = document.getElementById('topics-list');
|
|
5951
|
+
c.replaceChildren();
|
|
5952
|
+
if (!topics.length) { var p = document.createElement('div'); p.className='empty-state-card'; p.style.textAlign='center'; var t=document.createElement('p'); t.style.cssText='color:var(--text-muted);margin-bottom:4px;'; t.textContent='No topics yet'; p.appendChild(t); var h=document.createElement('p'); h.style.cssText='color:var(--text-faint);font-size:12px;'; h.textContent='Topics organize conversations into threads. Create one to keep context separate.'; p.appendChild(h); c.appendChild(p); return; }
|
|
5953
|
+
topics.forEach(function(t) {
|
|
5954
|
+
var item = document.createElement('div');
|
|
5955
|
+
item.className = 'memory-item';
|
|
5956
|
+
item.style.cssText = 'display:flex;justify-content:space-between;align-items:flex-start;gap:8px;';
|
|
5957
|
+
|
|
5958
|
+
var left = document.createElement('div');
|
|
5959
|
+
left.style.flex = '1';
|
|
5960
|
+
var nameEl = document.createElement('div');
|
|
5961
|
+
nameEl.style.cssText = 'color:var(--text);font-weight:500;';
|
|
5962
|
+
var dot = document.createElement('span');
|
|
5963
|
+
dot.style.cssText = 'display:inline-block;width:6px;height:6px;border-radius:50%;margin-right:6px;background:' + (t.status === 'active' ? 'var(--green)' : 'var(--text-faint)');
|
|
5964
|
+
nameEl.appendChild(dot);
|
|
5965
|
+
nameEl.appendChild(document.createTextNode(t.name));
|
|
5966
|
+
left.appendChild(nameEl);
|
|
5967
|
+
if (t.description) {
|
|
5968
|
+
var desc = document.createElement('div');
|
|
5969
|
+
desc.style.cssText = 'font-size:12px;color:var(--text-muted);margin-top:4px;';
|
|
5970
|
+
desc.textContent = t.description;
|
|
5971
|
+
left.appendChild(desc);
|
|
5972
|
+
}
|
|
5973
|
+
var meta = document.createElement('div');
|
|
5974
|
+
meta.style.cssText = 'font-size:11px;color:var(--text-faint);margin-top:4px;';
|
|
5975
|
+
meta.textContent = 'Last active: ' + (t.last_message_at ? t.last_message_at.split('T')[0] : 'never');
|
|
5976
|
+
left.appendChild(meta);
|
|
5977
|
+
|
|
5978
|
+
var actions = document.createElement('div');
|
|
5979
|
+
actions.style.cssText = 'display:flex;gap:4px;flex-shrink:0;';
|
|
5980
|
+
var archBtn = document.createElement('button');
|
|
5981
|
+
archBtn.className = 'btn btn-ghost btn-sm';
|
|
5982
|
+
archBtn.style.cssText = 'font-size:11px;padding:2px 8px;color:var(--text-faint);';
|
|
5983
|
+
archBtn.textContent = t.status === 'active' ? 'Archive' : 'Activate';
|
|
5984
|
+
archBtn.onclick = function() { toggleTopicStatus(t.id, t.status); };
|
|
5985
|
+
var delBtn = document.createElement('button');
|
|
5986
|
+
delBtn.className = 'btn btn-ghost btn-sm';
|
|
5987
|
+
delBtn.style.cssText = 'font-size:11px;padding:2px 8px;color:var(--text-faint);';
|
|
5988
|
+
delBtn.textContent = 'Delete';
|
|
5989
|
+
delBtn.onclick = function() { deleteTopic(t.id); };
|
|
5990
|
+
actions.appendChild(archBtn);
|
|
5991
|
+
actions.appendChild(delBtn);
|
|
5992
|
+
|
|
5993
|
+
item.appendChild(left);
|
|
5994
|
+
item.appendChild(actions);
|
|
5995
|
+
c.appendChild(item);
|
|
5996
|
+
});
|
|
5997
|
+
}
|
|
5998
|
+
|
|
5999
|
+
// ── FOLLOW-UPS ──
|
|
6000
|
+
|
|
6001
|
+
function toggleFollowupForm() {
|
|
6002
|
+
var f = document.getElementById('followup-add-form');
|
|
6003
|
+
f.style.display = f.style.display === 'none' ? 'block' : 'none';
|
|
6004
|
+
if (f.style.display === 'block') document.getElementById('followup-context').focus();
|
|
6005
|
+
}
|
|
6006
|
+
|
|
6007
|
+
function loadFollowups() {
|
|
6008
|
+
api('/followups').then(function(data) { renderFollowups(data.followups || []); }).catch(function(e) { toast('Failed to load follow-ups'); });
|
|
6009
|
+
}
|
|
6010
|
+
|
|
6011
|
+
function addFollowup() {
|
|
6012
|
+
var context = document.getElementById('followup-context').value.trim();
|
|
6013
|
+
var message = document.getElementById('followup-message').value.trim();
|
|
6014
|
+
var at = document.getElementById('followup-at').value;
|
|
6015
|
+
if (!context || !message || !at) return;
|
|
6016
|
+
var body = { context: context, message: message, follow_up_at: new Date(at).toISOString() };
|
|
6017
|
+
api('/followups', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body) })
|
|
6018
|
+
.then(function() {
|
|
6019
|
+
document.getElementById('followup-context').value='';
|
|
6020
|
+
document.getElementById('followup-message').value='';
|
|
6021
|
+
document.getElementById('followup-at').value='';
|
|
6022
|
+
toggleFollowupForm(); loadFollowups(); toast('Follow-up scheduled');
|
|
6023
|
+
}).catch(function(e) { toast('Failed to schedule follow-up'); });
|
|
6024
|
+
}
|
|
6025
|
+
|
|
6026
|
+
function cancelFollowup(id) {
|
|
6027
|
+
api('/followups/' + id, { method:'PUT', headers:{'Content-Type':'application/json'}, body:JSON.stringify({status:'cancelled'}) })
|
|
6028
|
+
.then(function() { loadFollowups(); toast('Follow-up cancelled'); }).catch(function(e) { toast('Failed to cancel follow-up'); });
|
|
6029
|
+
}
|
|
6030
|
+
|
|
6031
|
+
function deleteFollowup(id) {
|
|
6032
|
+
api('/followups/' + id, { method:'DELETE' }).then(function() { loadFollowups(); toast('Follow-up removed'); }).catch(function(e) { toast('Failed to delete follow-up'); });
|
|
6033
|
+
}
|
|
6034
|
+
|
|
6035
|
+
function renderFollowups(followups) {
|
|
6036
|
+
var c = document.getElementById('followups-list');
|
|
6037
|
+
c.replaceChildren();
|
|
6038
|
+
if (!followups.length) { var p = document.createElement('div'); p.className='empty-state-card'; p.style.textAlign='center'; var t=document.createElement('p'); t.style.cssText='color:var(--text-muted);margin-bottom:4px;'; t.textContent='No follow-ups scheduled'; p.appendChild(t); var h=document.createElement('p'); h.style.cssText='color:var(--text-faint);font-size:12px;'; h.textContent='Zubo will proactively remind you when a follow-up is due.'; p.appendChild(h); c.appendChild(p); return; }
|
|
6039
|
+
followups.forEach(function(f) {
|
|
6040
|
+
var item = document.createElement('div');
|
|
6041
|
+
item.className = 'memory-item';
|
|
6042
|
+
item.style.cssText = 'display:flex;justify-content:space-between;align-items:flex-start;gap:8px;';
|
|
6043
|
+
|
|
6044
|
+
var left = document.createElement('div');
|
|
6045
|
+
left.style.flex = '1';
|
|
6046
|
+
var contextEl = document.createElement('div');
|
|
6047
|
+
contextEl.style.cssText = 'color:var(--text);font-weight:500;';
|
|
6048
|
+
contextEl.textContent = f.context;
|
|
6049
|
+
left.appendChild(contextEl);
|
|
6050
|
+
var msgEl = document.createElement('div');
|
|
6051
|
+
msgEl.style.cssText = 'font-size:13px;color:var(--text-secondary);margin-top:4px;';
|
|
6052
|
+
msgEl.textContent = f.message;
|
|
6053
|
+
left.appendChild(msgEl);
|
|
6054
|
+
|
|
6055
|
+
var meta = document.createElement('div');
|
|
6056
|
+
meta.style.cssText = 'font-size:11px;color:var(--text-faint);margin-top:4px;display:flex;gap:8px;';
|
|
6057
|
+
var statusColors = { pending:'var(--yellow)', sent:'var(--green)', cancelled:'var(--text-faint)' };
|
|
6058
|
+
var statusSpan = document.createElement('span');
|
|
6059
|
+
statusSpan.style.color = statusColors[f.status] || 'var(--text-faint)';
|
|
6060
|
+
statusSpan.textContent = f.status;
|
|
6061
|
+
meta.appendChild(statusSpan);
|
|
6062
|
+
if (f.follow_up_at) { var whenSpan = document.createElement('span'); whenSpan.textContent = new Date(f.follow_up_at).toLocaleString(); meta.appendChild(whenSpan); }
|
|
6063
|
+
left.appendChild(meta);
|
|
6064
|
+
|
|
6065
|
+
var actions = document.createElement('div');
|
|
6066
|
+
actions.style.cssText = 'display:flex;gap:4px;flex-shrink:0;';
|
|
6067
|
+
if (f.status === 'pending') {
|
|
6068
|
+
var cancelBtn = document.createElement('button');
|
|
6069
|
+
cancelBtn.className = 'btn btn-ghost btn-sm';
|
|
6070
|
+
cancelBtn.style.cssText = 'font-size:11px;padding:2px 8px;color:var(--text-faint);';
|
|
6071
|
+
cancelBtn.textContent = 'Cancel';
|
|
6072
|
+
cancelBtn.onclick = function() { cancelFollowup(f.id); };
|
|
6073
|
+
actions.appendChild(cancelBtn);
|
|
6074
|
+
}
|
|
6075
|
+
var delBtn = document.createElement('button');
|
|
6076
|
+
delBtn.className = 'btn btn-ghost btn-sm';
|
|
6077
|
+
delBtn.style.cssText = 'font-size:11px;padding:2px 8px;color:var(--text-faint);';
|
|
6078
|
+
delBtn.textContent = 'Delete';
|
|
6079
|
+
delBtn.onclick = function() { deleteFollowup(f.id); };
|
|
6080
|
+
actions.appendChild(delBtn);
|
|
6081
|
+
|
|
6082
|
+
item.appendChild(left);
|
|
6083
|
+
item.appendChild(actions);
|
|
6084
|
+
c.appendChild(item);
|
|
6085
|
+
});
|
|
6086
|
+
}
|
|
6087
|
+
|
|
5536
6088
|
// Init
|
|
5537
6089
|
routeFromHash();
|
|
5538
6090
|
checkOnboarding();
|