New "Notes" tab under Clinical Tools — a per-user scratchpad that's
explicitly NOT fed into AI prompts (distinct from user_memories).
Two-pane layout: searchable list on the left, rich-text editor with
title + save/edit/delete on the right.
Backend:
migrations/…_add-personal-notes.js — personal_notes table
(id, user_id → users ON DELETE CASCADE, title, body, created_at,
updated_at) with indexes on user_id + (user_id, updated_at).
src/routes/notes.js — CRUD + one AI endpoint:
GET /api/notes list, newest-updated first
GET /api/notes/:id fetch one
POST /api/notes create (title + body required)
PUT /api/notes/:id update
DELETE /api/notes/:id remove
POST /api/notes/from-voice transcript → { title, body }
via callAI (admin-controlled
provider — never selectable by
the clinician).
Body + title encrypted at rest via the same cryptoUtil used for
user_memories; 500-note per-user cap; 200-char title / 50 KB
body limits.
Frontend:
public/components/notes.html — empty-state card ("Hello 👋"),
sidebar list with search, editor head with voice-bar (Dictate /
Pause / Resume / Stop + live timer + pulse indicator), Tiptap
body, metadata footer. Uses existing .tp-* toolbar classes.
public/js/notes.js — lazy-init on first tab activation; Tiptap
editor built from window.Tiptap (same bundle the Content
Manager uses); delegated list clicks; Ctrl/Cmd+S to save;
uses app.js's AudioRecorder + transcribeAudio so the STT
pipeline is shared. On stop → transcribe → /api/notes/from-
voice → drop the AI-structured title + body into the editor;
clinician reviews then saves.
public/css/styles.css — 70 lines of .notes-* styles (card
layout, warm empty-state with gradient icon + tips, pulse
animation for the recording indicator, focus states, hover
nudges).
public/sw.js — bump cache from pedscribe-v12 → pedscribe-v12-
notes1 so clients pick up the new module/component.
Admin-controlled STT provider: the recorder posts its audio blob
to the existing /api/transcribe (Google / LiteLLM / ElevenLabs /
Browser Whisper — whatever admin wired up in Settings). Users
cannot pick the provider from this UI.
87 lines
4.7 KiB
HTML
87 lines
4.7 KiB
HTML
<div class="module-header">
|
|
<h2><i class="fas fa-note-sticky" style="color:#f59e0b;"></i> My Notes</h2>
|
|
<p>A quiet place for your thoughts — jot ideas, paste references, or dictate and let AI tidy it up. Only you can see these.</p>
|
|
</div>
|
|
|
|
<div class="notes-layout">
|
|
|
|
<!-- Left pane: list + new-note button + search -->
|
|
<aside class="notes-sidebar">
|
|
<div class="notes-sidebar-head">
|
|
<button id="btn-notes-new" class="btn-primary notes-new-btn" type="button">
|
|
<i class="fas fa-plus"></i> New note
|
|
</button>
|
|
<div class="notes-search">
|
|
<i class="fas fa-search"></i>
|
|
<input type="text" id="notes-search" placeholder="Search notes…" autocomplete="off">
|
|
</div>
|
|
</div>
|
|
<div id="notes-list" class="notes-list">
|
|
<div class="notes-empty">Loading…</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- Right pane: editor (hidden until a note is picked / created) -->
|
|
<section id="notes-editor" class="notes-editor hidden">
|
|
<div class="notes-editor-head">
|
|
<input type="text" id="note-title"
|
|
class="notes-title-input"
|
|
placeholder="Note title…"
|
|
maxlength="200"
|
|
autocomplete="off">
|
|
<div class="notes-editor-actions">
|
|
<!-- Voice → AI note: transcribes via the admin-configured
|
|
STT provider, then asks the AI to produce a clean note.
|
|
Pause/Resume available natively through MediaRecorder. -->
|
|
<div class="notes-voice-bar" id="notes-voice-bar">
|
|
<button id="btn-note-rec-start" class="btn-sm btn-ghost" type="button" title="Record voice → AI note">
|
|
<i class="fas fa-microphone" style="color:var(--red);"></i> Dictate
|
|
</button>
|
|
<button id="btn-note-rec-pause" class="btn-sm btn-ghost hidden" type="button">
|
|
<i class="fas fa-pause"></i> Pause
|
|
</button>
|
|
<button id="btn-note-rec-stop" class="btn-sm hidden" type="button" style="background:var(--red);color:white;border:none;">
|
|
<i class="fas fa-stop"></i> Stop
|
|
</button>
|
|
<span id="notes-rec-indicator" class="notes-rec-indicator hidden">
|
|
<span class="pulse-dot"></span>
|
|
<span id="notes-rec-state">Recording</span>
|
|
<span class="notes-rec-timer" id="notes-rec-timer">00:00</span>
|
|
</span>
|
|
</div>
|
|
<span id="notes-status" class="notes-status"></span>
|
|
<button id="btn-note-save" class="btn-sm btn-primary" type="button">
|
|
<i class="fas fa-floppy-disk"></i> Save
|
|
</button>
|
|
<button id="btn-note-delete" class="btn-sm" type="button" style="background:var(--red-light);color:var(--red);border:1px solid var(--red);">
|
|
<i class="fas fa-trash"></i> Delete
|
|
</button>
|
|
<button id="btn-note-close" class="btn-sm btn-ghost" type="button" title="Close editor">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div id="note-body-editor" class="notes-body-editor"></div>
|
|
<div class="notes-editor-foot">
|
|
<span id="note-meta" class="notes-meta"></span>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Empty placeholder — shown when no note is picked -->
|
|
<section id="notes-empty-state" class="notes-empty-state">
|
|
<div class="notes-empty-card">
|
|
<div class="notes-empty-icon"><i class="fas fa-feather"></i></div>
|
|
<h3>Hello 👋</h3>
|
|
<p>Pick a note on the left, or start a fresh one. You can type, paste, or tap <strong>Dictate</strong> to let AI clean up your voice into a polished note.</p>
|
|
<button id="btn-notes-new-empty" type="button" class="btn-primary notes-empty-cta">
|
|
<i class="fas fa-plus"></i> Create your first note
|
|
</button>
|
|
<div class="notes-empty-tips">
|
|
<div><i class="fas fa-bolt"></i> <span>Press <kbd>Ctrl</kbd>+<kbd>S</kbd> to save</span></div>
|
|
<div><i class="fas fa-microphone"></i> <span>Dictate works even offline with Browser Whisper</span></div>
|
|
<div><i class="fas fa-lock"></i> <span>Encrypted at rest — only you can read them</span></div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
</div>
|