Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- static/dashboard.html +66 -3
- supabase_user_notes.sql +46 -0
static/dashboard.html
CHANGED
|
@@ -64,9 +64,13 @@
|
|
| 64 |
|
| 65 |
/* Text utility overrides for light mode */
|
| 66 |
[data-theme="light"] .text-white { color: var(--app-fg) !important; }
|
| 67 |
-
[data-theme="light"] .text-gray-300 { color: rgba(
|
| 68 |
-
[data-theme="light"] .text-gray-400 { color: rgba(
|
| 69 |
-
[data-theme="light"] .text-gray-500 { color: rgba(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
|
| 71 |
/* Inputs in light mode */
|
| 72 |
[data-theme="light"] input,
|
|
@@ -642,6 +646,23 @@
|
|
| 642 |
</div>
|
| 643 |
<div id="settings-mcp-server-list" class="space-y-3"></div>
|
| 644 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 645 |
</div>
|
| 646 |
</div>
|
| 647 |
|
|
@@ -1198,6 +1219,7 @@
|
|
| 1198 |
}
|
| 1199 |
|
| 1200 |
loadProviders();
|
|
|
|
| 1201 |
await loadChatHistoryList();
|
| 1202 |
createTerminalTab(); // Init first tab
|
| 1203 |
applyTerminalLayout();
|
|
@@ -2578,6 +2600,47 @@
|
|
| 2578 |
}
|
| 2579 |
}
|
| 2580 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2581 |
init();
|
| 2582 |
</script>
|
| 2583 |
</body>
|
|
|
|
| 64 |
|
| 65 |
/* Text utility overrides for light mode */
|
| 66 |
[data-theme="light"] .text-white { color: var(--app-fg) !important; }
|
| 67 |
+
[data-theme="light"] .text-gray-300 { color: rgba(15, 23, 42, 0.92) !important; }
|
| 68 |
+
[data-theme="light"] .text-gray-400 { color: rgba(15, 23, 42, 0.78) !important; }
|
| 69 |
+
[data-theme="light"] .text-gray-500 { color: rgba(15, 23, 42, 0.66) !important; }
|
| 70 |
+
[data-theme="light"] .text-gray-200 { color: rgba(15, 23, 42, 0.96) !important; }
|
| 71 |
+
|
| 72 |
+
[data-theme="light"] a { color: #1d4ed8; }
|
| 73 |
+
[data-theme="light"] .prose a { color: #1d4ed8; }
|
| 74 |
|
| 75 |
/* Inputs in light mode */
|
| 76 |
[data-theme="light"] input,
|
|
|
|
| 646 |
</div>
|
| 647 |
<div id="settings-mcp-server-list" class="space-y-3"></div>
|
| 648 |
</div>
|
| 649 |
+
|
| 650 |
+
<div class="pt-4 border-t border-gray-700">
|
| 651 |
+
<div class="flex items-center justify-between gap-2 mb-2">
|
| 652 |
+
<div class="font-semibold text-gray-200">Notes</div>
|
| 653 |
+
<button onclick="saveUserNotes()"
|
| 654 |
+
class="bg-green-600 hover:bg-green-700 text-white px-2 py-1 rounded text-xs">Save</button>
|
| 655 |
+
</div>
|
| 656 |
+
<div class="text-xs text-gray-400 mb-2">Personal markdown notes (saved to Supabase).</div>
|
| 657 |
+
<div class="grid grid-cols-1 gap-3">
|
| 658 |
+
<textarea id="notes-editor" rows="8"
|
| 659 |
+
class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500 font-mono"
|
| 660 |
+
placeholder="# Notes..."></textarea>
|
| 661 |
+
<div class="text-xs text-gray-400">Preview</div>
|
| 662 |
+
<div id="notes-preview" class="prose prose-invert max-w-none bg-gray-900/40 border border-gray-700 rounded-lg p-3 overflow-auto"></div>
|
| 663 |
+
<div id="notes-status" class="text-xs text-gray-500"></div>
|
| 664 |
+
</div>
|
| 665 |
+
</div>
|
| 666 |
</div>
|
| 667 |
</div>
|
| 668 |
|
|
|
|
| 1219 |
}
|
| 1220 |
|
| 1221 |
loadProviders();
|
| 1222 |
+
await initNotes();
|
| 1223 |
await loadChatHistoryList();
|
| 1224 |
createTerminalTab(); // Init first tab
|
| 1225 |
applyTerminalLayout();
|
|
|
|
| 2600 |
}
|
| 2601 |
}
|
| 2602 |
|
| 2603 |
+
async function initNotes() {
|
| 2604 |
+
const editor = document.getElementById('notes-editor');
|
| 2605 |
+
const preview = document.getElementById('notes-preview');
|
| 2606 |
+
const status = document.getElementById('notes-status');
|
| 2607 |
+
if (!editor || !preview) return;
|
| 2608 |
+
|
| 2609 |
+
editor.addEventListener('input', () => {
|
| 2610 |
+
renderMarkdownInto(preview, editor.value || '');
|
| 2611 |
+
if (status) status.textContent = 'Not saved';
|
| 2612 |
+
});
|
| 2613 |
+
|
| 2614 |
+
try {
|
| 2615 |
+
const { data: { user } } = await supabase.auth.getUser();
|
| 2616 |
+
if (!user) return;
|
| 2617 |
+
const { data, error } = await supabase.from('user_notes').select('content,updated_at').eq('user_id', user.id).maybeSingle();
|
| 2618 |
+
if (error) throw error;
|
| 2619 |
+
editor.value = data?.content || '';
|
| 2620 |
+
renderMarkdownInto(preview, editor.value || '');
|
| 2621 |
+
if (status) status.textContent = data?.updated_at ? `Loaded (${new Date(data.updated_at).toLocaleString()})` : 'No notes yet';
|
| 2622 |
+
} catch (e) {
|
| 2623 |
+
if (status) status.textContent = 'Notes table not configured yet.';
|
| 2624 |
+
console.warn('Notes load failed', e);
|
| 2625 |
+
}
|
| 2626 |
+
}
|
| 2627 |
+
|
| 2628 |
+
async function saveUserNotes() {
|
| 2629 |
+
const editor = document.getElementById('notes-editor');
|
| 2630 |
+
const status = document.getElementById('notes-status');
|
| 2631 |
+
if (!editor) return;
|
| 2632 |
+
try {
|
| 2633 |
+
const { data: { user } } = await supabase.auth.getUser();
|
| 2634 |
+
if (!user) return;
|
| 2635 |
+
const content = editor.value || '';
|
| 2636 |
+
const { error } = await supabase.from('user_notes').upsert({ user_id: user.id, content }, { onConflict: 'user_id' });
|
| 2637 |
+
if (error) throw error;
|
| 2638 |
+
if (status) status.textContent = `Saved (${new Date().toLocaleString()})`;
|
| 2639 |
+
} catch (e) {
|
| 2640 |
+
if (status) status.textContent = `Save failed: ${e?.message || e}`;
|
| 2641 |
+
}
|
| 2642 |
+
}
|
| 2643 |
+
|
| 2644 |
init();
|
| 2645 |
</script>
|
| 2646 |
</body>
|
supabase_user_notes.sql
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
-- Simple per-user notes storage (single row per user).
|
| 2 |
+
-- Run this in Supabase SQL Editor.
|
| 3 |
+
|
| 4 |
+
create table if not exists public.user_notes (
|
| 5 |
+
user_id uuid primary key references auth.users (id) on delete cascade,
|
| 6 |
+
content text not null default '',
|
| 7 |
+
updated_at timestamptz not null default now()
|
| 8 |
+
);
|
| 9 |
+
|
| 10 |
+
create or replace function public.set_notes_updated_at()
|
| 11 |
+
returns trigger
|
| 12 |
+
language plpgsql
|
| 13 |
+
as $$
|
| 14 |
+
begin
|
| 15 |
+
new.updated_at = now();
|
| 16 |
+
return new;
|
| 17 |
+
end;
|
| 18 |
+
$$;
|
| 19 |
+
|
| 20 |
+
drop trigger if exists user_notes_set_updated_at on public.user_notes;
|
| 21 |
+
create trigger user_notes_set_updated_at
|
| 22 |
+
before update on public.user_notes
|
| 23 |
+
for each row
|
| 24 |
+
execute procedure public.set_notes_updated_at();
|
| 25 |
+
|
| 26 |
+
alter table public.user_notes enable row level security;
|
| 27 |
+
|
| 28 |
+
drop policy if exists "user_notes_select_own" on public.user_notes;
|
| 29 |
+
create policy "user_notes_select_own"
|
| 30 |
+
on public.user_notes
|
| 31 |
+
for select
|
| 32 |
+
using (auth.uid() = user_id);
|
| 33 |
+
|
| 34 |
+
drop policy if exists "user_notes_insert_own" on public.user_notes;
|
| 35 |
+
create policy "user_notes_insert_own"
|
| 36 |
+
on public.user_notes
|
| 37 |
+
for insert
|
| 38 |
+
with check (auth.uid() = user_id);
|
| 39 |
+
|
| 40 |
+
drop policy if exists "user_notes_update_own" on public.user_notes;
|
| 41 |
+
create policy "user_notes_update_own"
|
| 42 |
+
on public.user_notes
|
| 43 |
+
for update
|
| 44 |
+
using (auth.uid() = user_id)
|
| 45 |
+
with check (auth.uid() = user_id);
|
| 46 |
+
|