File size: 6,709 Bytes
e12c660
b4c7867
 
 
 
 
 
e12c660
b4c7867
 
 
 
e12c660
 
 
 
 
 
 
b4c7867
 
 
 
e12c660
 
 
 
 
 
b4c7867
 
 
 
 
e12c660
b4c7867
e12c660
b4c7867
 
 
e12c660
b4c7867
 
e12c660
b4c7867
 
 
 
 
 
e12c660
b4c7867
 
e12c660
 
 
 
b4c7867
 
e12c660
b4c7867
 
 
 
e12c660
b4c7867
e12c660
b4c7867
 
e12c660
b4c7867
 
e12c660
b4c7867
e12c660
 
 
 
b4c7867
 
 
e12c660
b4c7867
e12c660
b4c7867
e12c660
b4c7867
 
e12c660
b4c7867
e12c660
b4c7867
 
1bfb390
 
 
 
 
 
b4c7867
 
e12c660
b4c7867
e12c660
b4c7867
 
 
1bfb390
 
 
 
 
 
b4c7867
 
e12c660
b4c7867
e12c660
 
b4c7867
 
 
e12c660
b4c7867
e12c660
 
 
b4c7867
 
e12c660
 
 
 
 
 
 
 
 
 
 
 
b4c7867
 
 
e12c660
b4c7867
e12c660
 
b4c7867
 
 
e12c660
b4c7867
 
e12c660
 
b4c7867
 
e12c660
 
 
b4c7867
 
e12c660
b4c7867
e12c660
 
b4c7867
 
 
e12c660
b4c7867
 
e12c660
 
b4c7867
 
e12c660
 
 
b4c7867
 
e12c660
b4c7867
e12c660
 
b4c7867
 
 
e12c660
b4c7867
 
e12c660
 
b4c7867
e12c660
b4c7867
e12c660
 
b4c7867
 
 
 
 
 
e12c660
b4c7867
e12c660
 
b4c7867
 
e12c660
b4c7867
 
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
import os
import time
from datetime import datetime
import gradio as gr

from src.backend.notebooks import create_notebook, rename_notebook, delete_notebook
from src.storage.index_store import list_notebooks
from src.storage.paths import ensure_tree, nb_root
from src.storage.chat_store import append_chat, load_chat
from src.storage.artifact_store import list_artifacts as list_artifacts_store, next_artifact_path
from src.backend.ingest import ingest_files as ingest_files_backend, ingest_url as ingest_url_backend
from src.backend.rag import retrieve, rag_answer
from src.backend.artifacts import (
    generate_report,
    generate_quiz,
    generate_podcast_transcript,
    transcript_to_mp3,
)


def now_iso():
    return datetime.utcnow().isoformat() + "Z"


def _require_notebook(notebook_id: str):
    if not notebook_id:
        raise gr.Error("Please create/select a notebook first.")


def chat_pairs(history):
    pairs = []
    last_user = None
    for m in history:
        if m.get("role") == "user":
            last_user = m.get("content", "")
        elif m.get("role") == "assistant":
            pairs.append((last_user or "", m.get("content", "")))
            last_user = None
    return pairs


def ui_bootstrap(username: str):
    nbs = list_notebooks(username)

    if not nbs:
        nb_id = create_notebook(username, "My First Notebook")
        nbs = list_notebooks(username)
        current = nb_id
    else:
        current = nbs[0][1]

    ensure_tree(username, current)
    history = load_chat(username, current)
    artifacts = list_artifacts_store(username, current)

    return gr.Dropdown(choices=nbs, value=current), chat_pairs(history), artifacts


def on_switch_notebook(username: str, notebook_id: str):
    _require_notebook(notebook_id)
    ensure_tree(username, notebook_id)
    history = load_chat(username, notebook_id)
    return chat_pairs(history), list_artifacts_store(username, notebook_id)


def on_create_notebook(username: str, name: str):
    name = (name or "").strip() or "Untitled Notebook"
    nb_id = create_notebook(username, name)
    nbs = list_notebooks(username)
    ensure_tree(username, nb_id)
    return gr.Dropdown(choices=nbs, value=nb_id), [], list_artifacts_store(username, nb_id)


def on_rename_notebook(username: str, notebook_id: str, new_name: str):
    _require_notebook(notebook_id)
    new_name = (new_name or "").strip()
    if not new_name:
        raise gr.Error("Enter a new notebook name.")
    rename_notebook(username, notebook_id, new_name)
    return gr.Dropdown(choices=list_notebooks(username), value=notebook_id)


def on_delete_notebook(username: str, notebook_id: str):
    _require_notebook(notebook_id)
    delete_notebook(username, notebook_id)
    # Return the bootstrap tuple (dropdown, chat, artifacts)
    return ui_bootstrap(username)


def on_ingest_files(username: str, notebook_id: str, files):
    _require_notebook(notebook_id)
    if not files:
        raise gr.Error("Upload at least one file.")
    try:
        added = ingest_files_backend(username, notebook_id, files)
    except Exception as e:
        raise gr.Error(f"File ingest failed: {e}")
    if added == 0:
        raise gr.Error("No chunks were indexed. Use supported files (PDF/PPTX/TXT) with extractable text.")
    return f"Ingested files. Added {added} chunks."


def on_ingest_url(username: str, notebook_id: str, url: str):
    _require_notebook(notebook_id)
    url = (url or "").strip()
    if not url:
        raise gr.Error("Enter a URL.")
    try:
        added = ingest_url_backend(username, notebook_id, url)
    except Exception as e:
        raise gr.Error(f"URL ingest failed: {e}")
    if added == 0:
        raise gr.Error("No chunks were indexed from the URL.")
    return f"Ingested URL. Added {added} chunks."


def on_chat(username: str, notebook_id: str, chatbot, msg: str):
    _require_notebook(notebook_id)

    msg = (msg or "").strip()
    if not msg:
        return chatbot, ""

    t0 = time.time()

    append_chat(username, notebook_id, {"role": "user", "content": msg, "ts": now_iso()})

    hits = retrieve(username, notebook_id, msg, k=6)
    ans = rag_answer(msg, hits)

    append_chat(
        username,
        notebook_id,
        {
            "role": "assistant",
            "content": ans,
            "ts": now_iso(),
            "latency_ms": int((time.time() - t0) * 1000),
        },
    )

    chatbot = chatbot + [(msg, ans)]
    return chatbot, ""


def on_report(username: str, notebook_id: str, topic: str, extra: str):
    _require_notebook(notebook_id)

    topic = (topic or "").strip()
    if not topic:
        raise gr.Error("Enter a topic.")

    hits = retrieve(username, notebook_id, topic, k=6)
    if not hits:
        raise gr.Error("No sources yet. Ingest files/URL first.")

    md = generate_report(topic, hits, extra)
    out = next_artifact_path(username, notebook_id, "reports", ".md")
    with open(out, "w", encoding="utf-8") as f:
        f.write(md)

    return "Report generated.", list_artifacts_store(username, notebook_id), out


def on_quiz(username: str, notebook_id: str, topic: str, extra: str):
    _require_notebook(notebook_id)

    topic = (topic or "").strip()
    if not topic:
        raise gr.Error("Enter a topic.")

    hits = retrieve(username, notebook_id, topic, k=6)
    if not hits:
        raise gr.Error("No sources yet. Ingest files/URL first.")

    md = generate_quiz(topic, hits, extra)
    out = next_artifact_path(username, notebook_id, "quizzes", ".md")
    with open(out, "w", encoding="utf-8") as f:
        f.write(md)

    return "Quiz generated.", list_artifacts_store(username, notebook_id), out


def on_podcast(username: str, notebook_id: str, topic: str, extra: str):
    _require_notebook(notebook_id)

    topic = (topic or "").strip()
    if not topic:
        raise gr.Error("Enter a topic.")

    hits = retrieve(username, notebook_id, topic, k=6)
    if not hits:
        raise gr.Error("No sources yet. Ingest files/URL first.")

    md = generate_podcast_transcript(topic, hits, extra)

    md_path = next_artifact_path(username, notebook_id, "podcasts", ".md")
    with open(md_path, "w", encoding="utf-8") as f:
        f.write(md)

    mp3_path = next_artifact_path(username, notebook_id, "podcasts", ".mp3")
    transcript_to_mp3(md, mp3_path)

    return "Podcast generated.", list_artifacts_store(username, notebook_id), md_path, mp3_path


def on_download(username: str, notebook_id: str, selection: str):
    _require_notebook(notebook_id)

    if not selection:
        return None

    p = os.path.join(nb_root(username, notebook_id), "artifacts", selection)
    return p if os.path.exists(p) else None