summaryrefslogtreecommitdiff
path: root/src/ollama_web/routes/sessions.py
blob: 8980960b5a1e8b7effe449774212442c7047af18 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
"""Session management API routes."""

from __future__ import annotations

import logging
from typing import Any

from starlette.requests import Request
from starlette.responses import JSONResponse

from ..sessions import SessionStore
from ..tools.pdf import extract_pdf_text

logger = logging.getLogger("ollama_web.sessions")


def _get_store(request: Request) -> SessionStore:
    return request.app.state.session_store


async def list_sessions(request: Request) -> JSONResponse:
    store = _get_store(request)
    return JSONResponse({"sessions": store.list_sessions()})


async def create_session(request: Request) -> JSONResponse:
    store = _get_store(request)
    body = await request.json() if await request.body() else {}
    title = body.get("title")
    session = store.create(title=title)
    return JSONResponse(session)


async def get_session(request: Request) -> JSONResponse:
    store = _get_store(request)
    session_id = request.path_params["session_id"]
    session = store.get(session_id)
    if session is None:
        return JSONResponse({"error": "Session not found"}, status_code=404)
    return JSONResponse(session)


async def delete_session(request: Request) -> JSONResponse:
    store = _get_store(request)
    session_id = request.path_params["session_id"]
    ok = store.delete(session_id)
    if not ok:
        return JSONResponse({"error": "Session not found"}, status_code=404)
    return JSONResponse({"deleted": True})


async def upload_file(request: Request) -> JSONResponse:
    store = _get_store(request)
    session_id = request.path_params["session_id"]
    session = store.get(session_id)
    if session is None:
        return JSONResponse({"error": "Session not found"}, status_code=404)

    max_size = request.app.state.settings.max_upload_mb * 1024 * 1024
    try:
        form = await request.form()
    except Exception as exc:  # noqa: BLE001
        logger.error("failed to parse multipart form: %s", exc)
        return JSONResponse({"error": f"Invalid form data: {exc}"}, status_code=400)

    files = form.getlist("files") if hasattr(form, "getlist") else [form.get("files")]
    results: list[dict[str, Any]] = []
    for upload in files:
        if upload is None:
            continue
        try:
            data = await upload.read()
        except Exception as exc:  # noqa: BLE001
            results.append({"name": getattr(upload, "filename", "unknown"), "error": str(exc)})
            continue
        if len(data) > max_size:
            results.append(
                {
                    "name": getattr(upload, "filename", "unknown"),
                    "error": f"File exceeds {request.app.state.settings.max_upload_mb} MB limit",
                }
            )
            continue

        name = getattr(upload, "filename", "uploaded-file") or "uploaded-file"
        content_type = getattr(upload, "content_type", None) or "application/octet-stream"
        text = ""
        if content_type == "application/pdf" or name.lower().endswith(".pdf"):
            text = extract_pdf_text(data)
        else:
            # Try decode as text for text-like uploads.
            try:
                text = data.decode("utf-8")
            except UnicodeDecodeError:
                try:
                    text = data.decode("cp932")
                except UnicodeDecodeError:
                    text = data.decode("utf-8", errors="replace")

        chat_file = store.add_file(
            session_id=session_id,
            name=name,
            data=data,
            mime=content_type,
            text=text,
            source="upload",
        )
        results.append(chat_file.to_dict())

    return JSONResponse({"files": results})


async def delete_file(request: Request) -> JSONResponse:
    store = _get_store(request)
    session_id = request.path_params["session_id"]
    file_id = request.path_params["file_id"]
    ok = store.remove_file(session_id, file_id)
    if not ok:
        return JSONResponse({"error": "File not found"}, status_code=404)
    return JSONResponse({"deleted": True})