Pin notes to a chat, and import notes from files
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>
This commit is contained in:
@@ -3,12 +3,12 @@ import zipfile
|
||||
from datetime import datetime, timezone
|
||||
from urllib.parse import quote
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
|
||||
from fastapi.responses import Response
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.orm import Session as DBSession
|
||||
|
||||
from app.core.notes import next_position, reorder, rename_section, set_section
|
||||
from app.core.notes import import_files, next_position, reorder, rename_section, set_section
|
||||
from app.db.database import get_db
|
||||
from app.db.models import NotePage
|
||||
|
||||
@@ -129,6 +129,24 @@ def create_page(req: NotePageCreate, db: DBSession = Depends(get_db)):
|
||||
return page
|
||||
|
||||
|
||||
@router.post("/import", response_model=list[NotePageResponse], status_code=201)
|
||||
async def import_pages(files: list[UploadFile] = File(...), db: DBSession = Depends(get_db)):
|
||||
"""アップロードされたファイルをページとして取り込む。
|
||||
|
||||
.md / .txt はそのまま1ページ、.zip は中のテキストファイルを1つずつページにする
|
||||
(ZIP内のフォルダ名がセクションになるので、エクスポートしたZIPをそのまま戻せる)。
|
||||
対象外の拡張子や大きすぎるファイルは黙って飛ばすので、1つも取り込めなければ 400。
|
||||
"""
|
||||
payload = [(f.filename or "無題", await f.read()) for f in files]
|
||||
created = import_files(db, payload)
|
||||
if not created:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="取り込めるファイルがありませんでした(.md / .txt / .zip のみ)",
|
||||
)
|
||||
return created
|
||||
|
||||
|
||||
@router.post("/sections/rename")
|
||||
def rename_section_endpoint(req: SectionRenameRequest, db: DBSession = Depends(get_db)):
|
||||
"""old_name セクションに属する全ページを new_name セクションへ一括で付け替える。new_name が空ならグループ解除。"""
|
||||
|
||||
Reference in New Issue
Block a user