Ludek Matyska commited on
Commit
17aca7c
Β·
1 Parent(s): 7d9e03e

feat: add json tools output

Browse files
Files changed (2) hide show
  1. app.py +8 -6
  2. theory_tools.py +86 -46
app.py CHANGED
@@ -5,25 +5,27 @@ from theory_tools import get_theory, get_theory_topics
5
  try:
6
  topics = get_theory_topics()
7
  topic_choices = list(topics.keys())
8
- except Exception as e:
9
  topic_choices = ["Error loading topics"]
10
 
11
  # Create the Gradio interface with tabs
12
  with gr.Blocks(title="Quantum Circuits Learning Programme") as demo:
13
  gr.Markdown("# Quantum Circuits Learning Programme")
14
- gr.Markdown("Learn about quantum circuits through interactive content from the Qiskit textbook.")
15
-
 
 
16
  with gr.Tabs():
17
  with gr.TabItem("Browse Topics"):
18
  topics_output = gr.JSON(label="Available Topics")
19
  topics_btn = gr.Button("Load Available Topics")
20
  topics_btn.click(fn=get_theory_topics, outputs=topics_output)
21
-
22
  with gr.TabItem("Learn Theory"):
23
  topic_input = gr.Textbox(
24
  label="Enter a Quantum Topic",
25
  placeholder="e.g., teleportation, superdense coding, what is quantum",
26
- info="Enter the name of a quantum topic to learn about"
27
  )
28
  theory_output = gr.Markdown(label="Theory Content")
29
  theory_btn = gr.Button("Get Theory Content")
@@ -31,4 +33,4 @@ with gr.Blocks(title="Quantum Circuits Learning Programme") as demo:
31
 
32
  # Launch the interface
33
  if __name__ == "__main__":
34
- demo.launch(mcp_server=True)
 
5
  try:
6
  topics = get_theory_topics()
7
  topic_choices = list(topics.keys())
8
+ except Exception:
9
  topic_choices = ["Error loading topics"]
10
 
11
  # Create the Gradio interface with tabs
12
  with gr.Blocks(title="Quantum Circuits Learning Programme") as demo:
13
  gr.Markdown("# Quantum Circuits Learning Programme")
14
+ gr.Markdown(
15
+ "Learn about quantum circuits through interactive content from the Qiskit textbook."
16
+ )
17
+
18
  with gr.Tabs():
19
  with gr.TabItem("Browse Topics"):
20
  topics_output = gr.JSON(label="Available Topics")
21
  topics_btn = gr.Button("Load Available Topics")
22
  topics_btn.click(fn=get_theory_topics, outputs=topics_output)
23
+
24
  with gr.TabItem("Learn Theory"):
25
  topic_input = gr.Textbox(
26
  label="Enter a Quantum Topic",
27
  placeholder="e.g., teleportation, superdense coding, what is quantum",
28
+ info="Enter the name of a quantum topic to learn about",
29
  )
30
  theory_output = gr.Markdown(label="Theory Content")
31
  theory_btn = gr.Button("Get Theory Content")
 
33
 
34
  # Launch the interface
35
  if __name__ == "__main__":
36
+ demo.launch(mcp_server=True)
theory_tools.py CHANGED
@@ -1,17 +1,19 @@
1
  import re
2
  import requests
3
  import nbformat
 
4
 
5
 
6
  RAW_ROOT = "https://raw.githubusercontent.com/Qiskit/textbook/main/notebooks/"
7
  # README locations we now support
8
  _SECTIONS: dict[str, str] = {
9
- "intro": "intro/README.md",
10
- "ch-states": "ch-states/README.md",
11
- "ch-gates": "ch-gates/README.md",
12
- "ch-algorithms":"ch-algorithms/README.md",
13
  }
14
 
 
15
  # ───────────────────────────────────────────────────────────────────
16
  # internals
17
  # ───────────────────────────────────────────────────────────────────
@@ -33,9 +35,7 @@ def _discover_files() -> list[str]:
33
  for dir_key, readme in _SECTIONS.items():
34
  found = _scrape_readme(readme)
35
  # Prepend the directory path if the README gives bare filenames
36
- prefixed = [
37
- name if "/" in name else f"{dir_key}/{name}" for name in found
38
- ]
39
  files.extend(prefixed)
40
  return files
41
 
@@ -49,31 +49,45 @@ def _pretty(path: str) -> str:
49
  # ───────────────────────────────────────────────────────────────────
50
  # public tools
51
  # ───────────────────────────────────────────────────────────────────
52
- def get_theory_topics() -> dict[str, str]:
53
- """Return a mapping of friendly topic names to notebook file paths.
54
-
55
  Discovers available Jupyter notebooks from the Qiskit textbook across all
56
  four main chapters (intro, ch-states, ch-gates, ch-algorithms) by scraping
57
  their respective README files.
58
-
59
  Returns:
60
- dict[str, str]: A dictionary mapping human-readable topic names to their
61
- corresponding notebook file paths. For example:
62
- {'What Is Quantum': 'intro/what-is-quantum.ipynb',
63
- 'Bloch Sphere': 'ch-states/bloch_sphere.ipynb'}
64
- Returns an empty dictionary if network requests fail.
65
-
 
 
 
 
 
 
 
66
  Note:
67
- If network requests fail, returns an empty dictionary instead of
68
  falling back to hardcoded content.
69
  """
70
  try:
71
  discovered_files = _discover_files()
72
  if not discovered_files:
73
- return {}
74
- return {_pretty(p): p for p in discovered_files}
 
 
 
 
 
 
 
75
  except Exception:
76
- return {}
77
 
78
 
79
  def get_theory(
@@ -82,62 +96,87 @@ def get_theory(
82
  include_headers: bool = True,
83
  ) -> str:
84
  """Download and parse a Qiskit textbook notebook, returning its content as text.
85
-
86
- Accepts flexible topic identification: pretty names ("Teleportation"),
87
  slugs ("teleportation"), or full paths ("intro/teleportation.ipynb").
88
  Downloads the notebook from GitHub and extracts its content.
89
-
90
  Args:
91
  topic (str): The quantum topic to fetch. Can be:
92
  - Pretty name: "Teleportation", "What Is Quantum"
93
- - Slug: "teleportation", "what-is-quantum"
94
  - Full path: "intro/teleportation.ipynb"
95
  markdown_only (bool, optional): If True, include only markdown cells.
96
  If False, also include code cells wrapped in ```python blocks.
97
  Defaults to True.
98
  include_headers (bool, optional): If True, prepend an H1 header with
99
  the topic name for better readability. Defaults to True.
100
-
101
  Returns:
102
- str: The concatenated content of the notebook as formatted text,
103
- with cells separated by double newlines. Returns error messages
104
- if the topic is not found or if network requests fail.
105
-
 
 
 
 
 
106
  Example:
107
- >>> content = get_theory("teleportation")
108
- >>> print(content[:100])
109
- # Teleportation
110
-
111
- Quantum teleportation is a process by which quantum information...
112
  """
113
- topics = get_theory_topics()
 
 
114
 
115
  # Build lenient lookup table
116
  lookup: dict[str, str] = {}
117
- for nice, path in topics.items():
118
- slug = path.rsplit("/", 1)[-1].removesuffix(".ipynb")
119
- lookup[nice.lower()] = path
 
 
 
120
  lookup[slug.lower()] = path
121
  lookup[path.lower()] = path
122
 
123
  key = topic.lower()
124
  if key not in lookup:
125
  if not topics:
126
- return "Unable to get theory - no topics available (network may be down)"
127
- available_topics = ', '.join(topics.keys())
128
- return f"Topic unknown: '{topic}'. Available topics: {available_topics}"
 
 
 
 
 
 
 
 
 
 
129
 
130
  path = lookup[key]
131
-
 
132
  try:
133
  raw_json = requests.get(f"{RAW_ROOT}{path}", timeout=20).text
134
  nb = nbformat.reads(raw_json, as_version=4)
135
  except Exception:
136
- return "Unable to get theory - failed to download or parse notebook content"
 
 
 
 
 
137
 
138
  chunks: list[str] = []
139
  if include_headers:
140
- chunks.append(f"# {_pretty(path)}\n")
141
 
142
  for cell in nb.cells:
143
  if cell.cell_type == "markdown":
@@ -145,4 +184,5 @@ def get_theory(
145
  elif cell.cell_type == "code" and not markdown_only:
146
  chunks.append(f"```python\n{cell.source}\n```")
147
 
148
- return "\n\n".join(chunks)
 
 
1
  import re
2
  import requests
3
  import nbformat
4
+ import json
5
 
6
 
7
  RAW_ROOT = "https://raw.githubusercontent.com/Qiskit/textbook/main/notebooks/"
8
  # README locations we now support
9
  _SECTIONS: dict[str, str] = {
10
+ "intro": "intro/README.md",
11
+ "ch-states": "ch-states/README.md",
12
+ "ch-gates": "ch-gates/README.md",
13
+ "ch-algorithms": "ch-algorithms/README.md",
14
  }
15
 
16
+
17
  # ───────────────────────────────────────────────────────────────────
18
  # internals
19
  # ───────────────────────────────────────────────────────────────────
 
35
  for dir_key, readme in _SECTIONS.items():
36
  found = _scrape_readme(readme)
37
  # Prepend the directory path if the README gives bare filenames
38
+ prefixed = [name if "/" in name else f"{dir_key}/{name}" for name in found]
 
 
39
  files.extend(prefixed)
40
  return files
41
 
 
49
  # ───────────────────────────────────────────────────────────────────
50
  # public tools
51
  # ───────────────────────────────────────────────────────────────────
52
+ def get_theory_topics() -> str:
53
+ """Return a structured list of available quantum theory topics.
54
+
55
  Discovers available Jupyter notebooks from the Qiskit textbook across all
56
  four main chapters (intro, ch-states, ch-gates, ch-algorithms) by scraping
57
  their respective README files.
58
+
59
  Returns:
60
+ str: JSON string containing a structured list of topics with title, slug, and path.
61
+ For example:
62
+ '{
63
+ "topics": [
64
+ {
65
+ "title": "What Is Quantum?",
66
+ "slug": "what-is-quantum",
67
+ "path": "intro/what-is-quantum.ipynb"
68
+ }
69
+ ]
70
+ }'
71
+ Returns JSON with empty topics array if network requests fail.
72
+
73
  Note:
74
+ If network requests fail, returns JSON with empty topics array instead of
75
  falling back to hardcoded content.
76
  """
77
  try:
78
  discovered_files = _discover_files()
79
  if not discovered_files:
80
+ return json.dumps({"topics": []})
81
+
82
+ topics = []
83
+ for path in discovered_files:
84
+ title = _pretty(path)
85
+ slug = path.rsplit("/", 1)[-1].removesuffix(".ipynb")
86
+ topics.append({"title": title, "slug": slug, "path": path})
87
+
88
+ return json.dumps({"topics": topics}, indent=2)
89
  except Exception:
90
+ return json.dumps({"topics": []})
91
 
92
 
93
  def get_theory(
 
96
  include_headers: bool = True,
97
  ) -> str:
98
  """Download and parse a Qiskit textbook notebook, returning its content as text.
99
+
100
+ Accepts flexible topic identification: pretty names ("Teleportation"),
101
  slugs ("teleportation"), or full paths ("intro/teleportation.ipynb").
102
  Downloads the notebook from GitHub and extracts its content.
103
+
104
  Args:
105
  topic (str): The quantum topic to fetch. Can be:
106
  - Pretty name: "Teleportation", "What Is Quantum"
107
+ - Slug: "teleportation", "what-is-quantum"
108
  - Full path: "intro/teleportation.ipynb"
109
  markdown_only (bool, optional): If True, include only markdown cells.
110
  If False, also include code cells wrapped in ```python blocks.
111
  Defaults to True.
112
  include_headers (bool, optional): If True, prepend an H1 header with
113
  the topic name for better readability. Defaults to True.
114
+
115
  Returns:
116
+ str: JSON string containing the topic name and notebook content.
117
+ For example:
118
+ '{
119
+ "topic": "Teleportation",
120
+ "content": "# Teleportation\\n\\nQuantum teleportation is a process..."
121
+ }'
122
+ Returns JSON with error message in content field if topic not found
123
+ or network requests fail.
124
+
125
  Example:
126
+ >>> result = get_theory("teleportation")
127
+ >>> data = json.loads(result)
128
+ >>> print(data["topic"])
129
+ Teleportation
 
130
  """
131
+ topics_json = get_theory_topics()
132
+ topics_data = json.loads(topics_json)
133
+ topics = topics_data.get("topics", [])
134
 
135
  # Build lenient lookup table
136
  lookup: dict[str, str] = {}
137
+ for topic_info in topics:
138
+ title = topic_info["title"]
139
+ slug = topic_info["slug"]
140
+ path = topic_info["path"]
141
+
142
+ lookup[title.lower()] = path
143
  lookup[slug.lower()] = path
144
  lookup[path.lower()] = path
145
 
146
  key = topic.lower()
147
  if key not in lookup:
148
  if not topics:
149
+ return json.dumps(
150
+ {
151
+ "topic": topic,
152
+ "content": "Unable to get theory - no topics available (network may be down)",
153
+ }
154
+ )
155
+ available_topics = ", ".join([t["title"] for t in topics])
156
+ return json.dumps(
157
+ {
158
+ "topic": topic,
159
+ "content": f"Topic unknown: '{topic}'. Available topics: {available_topics}",
160
+ }
161
+ )
162
 
163
  path = lookup[key]
164
+ topic_title = _pretty(path)
165
+
166
  try:
167
  raw_json = requests.get(f"{RAW_ROOT}{path}", timeout=20).text
168
  nb = nbformat.reads(raw_json, as_version=4)
169
  except Exception:
170
+ return json.dumps(
171
+ {
172
+ "topic": topic_title,
173
+ "content": "Unable to get theory - failed to download or parse notebook content",
174
+ }
175
+ )
176
 
177
  chunks: list[str] = []
178
  if include_headers:
179
+ chunks.append(f"# {topic_title}\n")
180
 
181
  for cell in nb.cells:
182
  if cell.cell_type == "markdown":
 
184
  elif cell.cell_type == "code" and not markdown_only:
185
  chunks.append(f"```python\n{cell.source}\n```")
186
 
187
+ content = "\n\n".join(chunks)
188
+ return json.dumps({"topic": topic_title, "content": content}, indent=2)