Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/notebooklm/_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -988,6 +988,8 @@ async def generate_mind_map(
self,
notebook_id: str,
source_ids: builtins.list[str] | None = None,
language: str = "en",
instructions: str | None = None,
) -> dict[str, Any]:
"""Generate an interactive mind map.

Expand All @@ -997,6 +999,8 @@ async def generate_mind_map(
Args:
notebook_id: The notebook ID.
source_ids: Source IDs to include. If None, uses all sources.
language: Language code (default: "en").
instructions: Custom instructions for the generated mind map.

Returns:
Dictionary with 'mind_map' (JSON data) and 'note_id'.
Expand All @@ -1014,7 +1018,7 @@ async def generate_mind_map(
None,
None,
None,
["interactive_mindmap", [["[CONTEXT]", ""]], ""],
["interactive_mindmap", [["[CONTEXT]", instructions or ""]], language],
None,
[2, None, [1]],
]
Expand Down
19 changes: 16 additions & 3 deletions src/notebooklm/cli/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -994,14 +994,21 @@ async def _generate():
default=None,
help="Notebook ID (uses current if not set)",
)
@click.argument("description", default="", required=False)
@click.option("--language", default=None, help="Output language (default: from config or 'en')")
Comment on lines +997 to +998
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add an explicit --instructions option for mind-map generation.

The implementation currently supports instructions only via positional description, but the stated objective requires a dedicated --instructions flag.

Proposed fix (keep positional text for backward compatibility)
 `@generate.command`("mind-map")
 `@click.option`(
@@
 )
 `@click.argument`("description", default="", required=False)
+@click.option(
+    "--instructions",
+    default=None,
+    help="Custom instructions for mind map generation",
+)
 `@click.option`("--language", default=None, help="Output language (default: from config or 'en')")
 `@click.option`("--source", "-s", "source_ids", multiple=True, help="Limit to specific source IDs")
 `@json_option`
 `@with_client`
-def generate_mind_map(ctx, notebook_id, description, language, source_ids, json_output, client_auth):
+def generate_mind_map(
+    ctx, notebook_id, description, instructions, language, source_ids, json_output, client_auth
+):
@@
     async def _run():
         async with NotebookLMClient(client_auth) as client:
             nb_id_resolved = await resolve_notebook_id(client, nb_id)
             sources = await resolve_source_ids(client, nb_id_resolved, source_ids)
+            resolved_instructions = instructions if instructions is not None else (description or None)
@@
                 result = await client.artifacts.generate_mind_map(
                     nb_id_resolved,
                     source_ids=sources,
                     language=resolve_language(language),
-                    instructions=description or None,
+                    instructions=resolved_instructions,
                 )
             else:
                 with console.status("Generating mind map..."):
                     result = await client.artifacts.generate_mind_map(
                         nb_id_resolved,
                         source_ids=sources,
                         language=resolve_language(language),
-                        instructions=description or None,
+                        instructions=resolved_instructions,
                     )

Also applies to: 1002-1002, 1023-1026, 1031-1034

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notebooklm/cli/generate.py` around lines 997 - 998, Add a new Click
option --instructions (e.g., `@click.option`("--instructions", default=None,
help="Mind-map instructions; overrides positional description")) alongside the
existing positional argument "description" and "--language"; then update the
command handler to prefer the --instructions value when present and fall back to
the positional description for backward compatibility (replace usages that
currently read the positional "description" at the call sites referenced around
lines 1002, 1023-1026, and 1031-1034 so they use instructions = instructions if
instructions is not None else description). Ensure the option help text clearly
states it overrides the positional description.

@click.option("--source", "-s", "source_ids", multiple=True, help="Limit to specific source IDs")
@json_option
@with_client
def generate_mind_map(ctx, notebook_id, source_ids, json_output, client_auth):
def generate_mind_map(ctx, notebook_id, description, language, source_ids, json_output, client_auth):
"""Generate mind map.

\b
Use --json for machine-readable output.

\b
Example:
notebooklm generate mind-map "focus on causal relationships"
notebooklm generate mind-map --language zh-CN
"""
nb_id = require_notebook(notebook_id)

Expand All @@ -1013,12 +1020,18 @@ async def _run():
# Show status spinner only for console output
if json_output:
result = await client.artifacts.generate_mind_map(
nb_id_resolved, source_ids=sources
nb_id_resolved,
source_ids=sources,
language=resolve_language(language),
instructions=description or None,
)
else:
with console.status("Generating mind map..."):
result = await client.artifacts.generate_mind_map(
nb_id_resolved, source_ids=sources
nb_id_resolved,
source_ids=sources,
language=resolve_language(language),
instructions=description or None,
)
Comment on lines 1021 to 1035
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for resolving the language and instructions is duplicated across the if/else branches. Additionally, calling resolve_language multiple times within the same function can be inefficient if it involves file I/O. It's better to resolve these values once and reuse them to improve efficiency and clarity.

Suggested change
if json_output:
result = await client.artifacts.generate_mind_map(
nb_id_resolved, source_ids=sources
nb_id_resolved,
source_ids=sources,
language=resolve_language(language),
instructions=description or None,
)
else:
with console.status("Generating mind map..."):
result = await client.artifacts.generate_mind_map(
nb_id_resolved, source_ids=sources
nb_id_resolved,
source_ids=sources,
language=resolve_language(language),
instructions=description or None,
)
lang = resolve_language(language)
instr = description or None
if json_output:
result = await client.artifacts.generate_mind_map(
nb_id_resolved,
source_ids=sources,
language=lang,
instructions=instr,
)
else:
with console.status("Generating mind map..."):
result = await client.artifacts.generate_mind_map(
nb_id_resolved,
source_ids=sources,
language=lang,
instructions=instr,
)
References
  1. To improve efficiency and clarity, avoid reading the same file multiple times within a single function. Instead, read the file once, store its contents in a variable, and reuse the variable.


_output_mind_map_result(result, json_output)
Expand Down
29 changes: 29 additions & 0 deletions tests/unit/cli/test_generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,35 @@ def test_generate_mind_map(self, runner, mock_auth):

assert result.exit_code == 0

def test_generate_mind_map_with_language_and_instructions(self, runner, mock_auth):
with patch_client_for_module("generate") as mock_client_cls:
mock_client = create_mock_client()
mock_client.artifacts.generate_mind_map = AsyncMock(
return_value={"mind_map": {"name": "Root", "children": []}, "note_id": "n1"}
)
mock_client_cls.return_value = mock_client

with patch("notebooklm.cli.helpers.fetch_tokens", new_callable=AsyncMock) as mock_fetch:
mock_fetch.return_value = ("csrf", "session")
result = runner.invoke(
cli,
[
"generate",
"mind-map",
"--language",
"ja",
"-n",
"nb_123",
"focus on causal chains",
],
)

assert result.exit_code == 0
mock_client.artifacts.generate_mind_map.assert_awaited_once()
kwargs = mock_client.artifacts.generate_mind_map.await_args.kwargs
assert kwargs["language"] == "ja"
assert kwargs["instructions"] == "focus on causal chains"


# =============================================================================
# GENERATE REPORT TESTS
Expand Down
23 changes: 23 additions & 0 deletions tests/unit/test_source_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,29 @@ async def test_generate_mind_map_source_encoding(self, mock_core, mock_notes_api

assert source_ids_nested == [[["src_mm_1"]], [["src_mm_2"]]]

@pytest.mark.asyncio
async def test_generate_mind_map_language_and_instructions(self, mock_core, mock_notes_api):
"""Test generate_mind_map forwards language and instructions into the RPC payload."""
api = ArtifactsAPI(mock_core, mock_notes_api)

mock_core.rpc_call.return_value = [['{"name": "Mind Map", "children": []}']]

await api.generate_mind_map(
notebook_id="nb_123",
source_ids=["src_mm_1"],
language="zh-CN",
instructions="Focus on decision tradeoffs",
)

params = mock_core.rpc_call.call_args.args[1]
config = params[5]

assert config == [
"interactive_mindmap",
[["[CONTEXT]", "Focus on decision tradeoffs"]],
"zh-CN",
]

@pytest.mark.asyncio
async def test_suggest_reports_uses_get_suggested_reports(self, mock_core, mock_notes_api):
"""Test suggest_reports uses GET_SUGGESTED_REPORTS RPC."""
Expand Down
Loading