File size: 7,210 Bytes
052521c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""

NotebookLM-Py FastAPI REST API

提供 RESTful 接口供 n8n 等工具调用

"""

from fastapi import FastAPI, HTTPException, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import Optional, List
from contextlib import asynccontextmanager
from notebooklm import NotebookLMClient
import os

# ==================== 数据模型 ====================

class CreateNotebookRequest(BaseModel):
    title: str

class AddSourceRequest(BaseModel):
    url: str
    wait: bool = True

class AskRequest(BaseModel):
    question: str

class GenerateAudioRequest(BaseModel):
    instructions: Optional[str] = None

class GenerateQuizRequest(BaseModel):
    difficulty: Optional[str] = "medium"

class NotebookResponse(BaseModel):
    id: str
    title: str

class SourceResponse(BaseModel):
    id: str
    title: str
    source_type: str

class ChatResponse(BaseModel):
    answer: str
    citations: Optional[List[str]] = None

class TaskResponse(BaseModel):
    task_id: str
    status: str

# ==================== 客户端管理 ====================

@asynccontextmanager
async def get_client():
    """获取 NotebookLM 客户端"""
    async with await NotebookLMClient.from_storage() as client:
        yield client

# ==================== FastAPI 应用 ====================

app = FastAPI(
    title="NotebookLM-Py API",
    description="非官方 Google NotebookLM REST API",
    version="1.0.0",
    docs_url="/api/docs",
    redoc_url="/api/redoc",
)

# CORS 配置
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# ==================== 笔记本 API ====================

@app.get("/api/notebooks", response_model=List[NotebookResponse], tags=["Notebooks"])
async def list_notebooks():
    """列出所有笔记本"""
    try:
        async with get_client() as client:
            notebooks = await client.notebooks.list()
            return [NotebookResponse(id=nb.id, title=nb.title) for nb in notebooks]
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.post("/api/notebooks", response_model=NotebookResponse, tags=["Notebooks"])
async def create_notebook(request: CreateNotebookRequest):
    """创建新笔记本"""
    try:
        async with get_client() as client:
            nb = await client.notebooks.create(request.title)
            return NotebookResponse(id=nb.id, title=nb.title)
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.get("/api/notebooks/{notebook_id}", response_model=NotebookResponse, tags=["Notebooks"])
async def get_notebook(notebook_id: str):
    """获取笔记本详情"""
    try:
        async with get_client() as client:
            nb = await client.notebooks.get(notebook_id)
            return NotebookResponse(id=nb.id, title=nb.title)
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.delete("/api/notebooks/{notebook_id}", tags=["Notebooks"])
async def delete_notebook(notebook_id: str):
    """删除笔记本"""
    try:
        async with get_client() as client:
            await client.notebooks.delete(notebook_id)
            return {"message": "Notebook deleted successfully"}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


# ==================== 资源 API ====================

@app.get("/api/notebooks/{notebook_id}/sources", response_model=List[SourceResponse], tags=["Sources"])
async def list_sources(notebook_id: str):
    """列出笔记本的所有资源"""
    try:
        async with get_client() as client:
            sources = await client.sources.list(notebook_id)
            return [SourceResponse(id=s.id, title=s.title, source_type=s.source_type) for s in sources]
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.post("/api/notebooks/{notebook_id}/sources/url", response_model=SourceResponse, tags=["Sources"])
async def add_url_source(notebook_id: str, request: AddSourceRequest):
    """添加 URL 资源"""
    try:
        async with get_client() as client:
            source = await client.sources.add_url(notebook_id, request.url, wait=request.wait)
            return SourceResponse(id=source.id, title=source.title, source_type=source.source_type)
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


# ==================== 对话 API ====================

@app.post("/api/notebooks/{notebook_id}/ask", response_model=ChatResponse, tags=["Chat"])
async def ask_question(notebook_id: str, request: AskRequest):
    """向笔记本提问"""
    try:
        async with get_client() as client:
            result = await client.chat.ask(notebook_id, request.question)
            return ChatResponse(answer=result.answer)
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


# ==================== 生成内容 API ====================

@app.post("/api/notebooks/{notebook_id}/generate/audio", response_model=TaskResponse, tags=["Generate"])
async def generate_audio(notebook_id: str, request: GenerateAudioRequest = None):
    """生成音频播客"""
    try:
        async with get_client() as client:
            instructions = request.instructions if request else None
            status = await client.artifacts.generate_audio(notebook_id, instructions=instructions)
            return TaskResponse(task_id=status.task_id, status="started")
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.post("/api/notebooks/{notebook_id}/generate/quiz", response_model=TaskResponse, tags=["Generate"])
async def generate_quiz(notebook_id: str, request: GenerateQuizRequest = None):
    """生成测验"""
    try:
        async with get_client() as client:
            difficulty = request.difficulty if request else "medium"
            status = await client.artifacts.generate_quiz(notebook_id, difficulty=difficulty)
            return TaskResponse(task_id=status.task_id, status="started")
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.post("/api/notebooks/{notebook_id}/generate/flashcards", response_model=TaskResponse, tags=["Generate"])
async def generate_flashcards(notebook_id: str):
    """生成闪卡"""
    try:
        async with get_client() as client:
            status = await client.artifacts.generate_flashcards(notebook_id)
            return TaskResponse(task_id=status.task_id, status="started")
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


# ==================== 健康检查 ====================

@app.get("/api/health", tags=["System"])
async def health_check():
    """健康检查"""
    return {"status": "ok"}


if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)