Two additions to the shared notes, both about getting content in and out of the prompt. Per-chat pinning. Notes normally reach the model as a title list only, and the body arrives just if the model decides to call read_note. A 📌 on the tab now puts a page's full body in the system prompt for the chat that is open, every turn, no tool round trip. The selection is per session (new table session_note_pages, unique on session_id + page_id), so different conversations carry different pages. The title list marks pinned pages so the model does not read them twice, and bodies are capped per page by SESSION_NOTES_CONTENT_LIMIT. A new chat has no session row until its first message, so the front end cannot PUT the selection yet. POST /chat/stream therefore accepts note_page_ids and applies it before building the context, which makes the pins count from the very first turn. File import. Dropping .md/.txt on the editor - or picking them with the new ⬆ button - creates pages, and a .zip is unpacked into one page per text entry with the first path component as the section. That is exactly the shape the export writes, so a downloaded zip round-trips back in. Titles that collide get a " (2)" suffix rather than overwriting, since restoring a backup into a live workspace should not destroy anything. UploadFile needs python-multipart, added to the lockfile. Toasts grew a kind: the import success message was rendering in the red error toast, as was the existing session-import message. Both are green now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
58 lines
1.4 KiB
Vue
58 lines
1.4 KiB
Vue
<script setup lang="ts">
|
|
import { useChatStore } from '../stores/chat'
|
|
|
|
const store = useChatStore()
|
|
</script>
|
|
|
|
<template>
|
|
<div class="toast-list">
|
|
<div
|
|
v-for="toast in store.toasts"
|
|
:key="toast.id"
|
|
:class="['toast', toast.kind]"
|
|
@click="store.dismissToast(toast.id)"
|
|
>
|
|
<span class="icon">{{ toast.kind === 'success' ? '✓' : '⚠' }}</span>
|
|
<span class="msg">{{ toast.message }}</span>
|
|
<button class="close">✕</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.toast-list {
|
|
position: fixed;
|
|
bottom: 24px;
|
|
right: 24px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
z-index: 1000;
|
|
pointer-events: none;
|
|
}
|
|
.toast {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 10px 14px;
|
|
background: #1f0a0a;
|
|
border: 1px solid #7f1d1d;
|
|
border-radius: 8px;
|
|
color: #f87171;
|
|
font-size: 13px;
|
|
max-width: 360px;
|
|
cursor: pointer;
|
|
pointer-events: all;
|
|
animation: slide-in 0.2s ease;
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
|
|
}
|
|
.toast.success { background: #0a1f14; border-color: #166534; color: #6ee7b7; }
|
|
.icon { flex-shrink: 0; }
|
|
.msg { flex: 1; word-break: break-word; }
|
|
.close { background: none; border: none; color: inherit; cursor: pointer; font-size: 11px; padding: 0; }
|
|
@keyframes slide-in {
|
|
from { opacity: 0; transform: translateY(8px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
</style>
|