DreamyDetective commited on
Commit
a1a5a09
·
verified ·
1 Parent(s): 3847a57

Upload folder using huggingface_hub

Browse files
Files changed (11) hide show
  1. .gitattributes +35 -35
  2. .gitignore +42 -0
  3. README.md +121 -2
  4. app.py +354 -0
  5. chat_logger.py +143 -0
  6. docs/IMPLEMENTATION_SUMMARY.md +89 -0
  7. docs/LOGGING.md +153 -0
  8. requirements.txt +1 -0
  9. run.ps1 +6 -0
  10. styles.css +179 -0
  11. theme.py +136 -0
.gitattributes CHANGED
@@ -1,35 +1,35 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Virtual environments
2
+ venv/
3
+ .venv/
4
+ env/
5
+
6
+ # Python
7
+ __pycache__/
8
+ *.py[cod]
9
+ *$py.class
10
+ *.so
11
+ .Python
12
+ *.egg
13
+ *.egg-info/
14
+ .eggs/
15
+ dist/
16
+ build/
17
+ pip-wheel-metadata/
18
+
19
+ # Testing / type check / lint caches
20
+ .pytest_cache/
21
+ .mypy_cache/
22
+ .ruff_cache/
23
+ .coverage
24
+ htmlcov/
25
+
26
+ # Gradio / local runs
27
+ .gradio/
28
+ flagged/
29
+
30
+ # Environment
31
+ .env
32
+ .env.*
33
+ !.env.example
34
+
35
+ # IDE / OS
36
+ .idea/
37
+ .vscode/
38
+ *.swp
39
+ .DS_Store
40
+ Thumbs.db
41
+
42
+ logs/
README.md CHANGED
@@ -7,7 +7,126 @@ sdk: gradio
7
  sdk_version: 6.11.0
8
  app_file: app.py
9
  pinned: false
10
- short_description: Sequential Thinking MCP Server
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  sdk_version: 6.11.0
8
  app_file: app.py
9
  pinned: false
10
+ short_description: Sequential Thinking MCP Server (Gradio + Python)
11
  ---
12
 
13
+ # Sequential Thinking Gradio MCP Server
14
+
15
+ Python [Gradio](https://www.gradio.app/) app that exposes **Sequential Thinking** as an MCP server over SSE, alongside a small UI for manual thought steps. It mirrors the behaviour of the reference [Sequential Thinking MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/sequentialthinking) (TypeScript) using Gradio’s built-in MCP support (`gradio[mcp]`).
16
+
17
+ ## What’s included
18
+
19
+ - **MCP over HTTP (SSE)** — connect any MCP client to the Gradio `/gradio_api/mcp/sse` endpoint
20
+ - **Web UI** — process thoughts, reset session, inspect history, and copy-paste MCP config
21
+ - **Structured thinking** — revisions, branches, adjustable totals, optional “needs more thoughts”
22
+ - **Logging** — every thought is appended to `logs/chat_logs.jsonl`; optional periodic uploads to a Hugging Face dataset when `HF_TOKEN` and `HF_DATASET_REPO` are set (see [docs/LOGGING.md](docs/LOGGING.md))
23
+
24
+ ## Requirements
25
+
26
+ - Python 3.10+ recommended
27
+ - Dependencies: see [requirements.txt](requirements.txt) (`gradio[mcp]==6.11.0`)
28
+
29
+ ## Run locally
30
+
31
+ ```bash
32
+ python -m venv .venv
33
+ # Windows: .venv\Scripts\activate
34
+ # macOS/Linux: source .venv/bin/activate
35
+ pip install -r requirements.txt
36
+ python app.py
37
+ ```
38
+
39
+ Gradio starts (default [http://localhost:7860](http://localhost:7860)). **MCP SSE URL:**
40
+
41
+ `http://localhost:7860/gradio_api/mcp/sse`
42
+
43
+ On Windows, if your default console encoding breaks Gradio’s startup output, use UTF-8, for example:
44
+
45
+ ```bash
46
+ python -X utf8 app.py
47
+ ```
48
+
49
+ **PowerShell helper:** [run.ps1](run.ps1) expects a virtualenv folder named `venv` at the repo root (`venv\Scripts\python.exe`). If you use `.venv` instead, activate it and run `python app.py` as above.
50
+
51
+ ## MCP tools
52
+
53
+ | Tool | Description |
54
+ | --------------------- | ---------------------------------------------------------------- |
55
+ | `sequential_thinking` | Record one thinking step and return progress metadata |
56
+ | `reset_session` | Clear in-memory history and branches; new session id for logging |
57
+ | `get_history` | Return the full thought list for the current session |
58
+
59
+ ### `sequential_thinking` parameters
60
+
61
+ Names follow the Python implementation (snake_case):
62
+
63
+ | Parameter | Type | Required | Description |
64
+ | --------------------- | ------- | -------- | ---------------------------------------- |
65
+ | `thought` | string | yes | Current thinking step |
66
+ | `thought_number` | integer | yes | Step index (starts at 1) |
67
+ | `total_thoughts` | integer | yes | Estimated total steps (can grow) |
68
+ | `next_thought_needed` | boolean | yes | Whether another step is expected |
69
+ | `is_revision` | boolean | no | This step revises earlier thinking |
70
+ | `revises_thought` | integer | no | Which thought number is revised |
71
+ | `branch_from_thought` | integer | no | Branch from this thought number |
72
+ | `branch_id` | string | no | Label for the branch |
73
+ | `needs_more_thoughts` | boolean | no | Extend beyond the current total estimate |
74
+
75
+ ## Configure your MCP client
76
+
77
+ Point the client at the SSE URL (adjust host/port if you change Gradio’s `server_name` / `server_port`).
78
+
79
+ ### Claude Desktop (`claude_desktop_config.json`)
80
+
81
+ ```json
82
+ {
83
+ "mcpServers": {
84
+ "sequential-thinking": {
85
+ "url": "http://localhost:7860/gradio_api/mcp/sse"
86
+ }
87
+ }
88
+ }
89
+ ```
90
+
91
+ ### VS Code — user or workspace `mcp.json`
92
+
93
+ ```json
94
+ {
95
+ "servers": {
96
+ "sequential-thinking": {
97
+ "url": "http://localhost:7860/gradio_api/mcp/sse"
98
+ }
99
+ }
100
+ }
101
+ ```
102
+
103
+ See [VS Code MCP documentation](https://code.visualstudio.com/docs/copilot/customization/mcp-servers) for file locations.
104
+
105
+ ### Cursor
106
+
107
+ Add an MCP server entry with the same `url` as above (for example in your Cursor MCP configuration), with the app running locally.
108
+
109
+ ## Environment variables
110
+
111
+ | Variable | Effect |
112
+ | ------------------------- | ------------------------------------------------------------------------------------------- |
113
+ | `DISABLE_THOUGHT_LOGGING` | Set to `true` to suppress **console** banners for each thought (JSONL logging is unchanged) |
114
+ | `HF_TOKEN` | Optional — Hugging Face token for dataset upload |
115
+ | `HF_DATASET_REPO` | Optional — dataset id (e.g. `username/my-logs`) used with `HF_TOKEN` |
116
+
117
+ Details: [docs/LOGGING.md](docs/LOGGING.md).
118
+
119
+ ## Hugging Face Spaces
120
+
121
+ The YAML front matter at the top of this file is for Space deployment. After deploy, use your Space URL for MCP, for example:
122
+
123
+ `https://<your-space-name>.hf.space/gradio_api/mcp/sse`
124
+
125
+ ## Relation to other packages
126
+
127
+ The npm package `@modelcontextprotocol/server-sequential-thinking` and the `mcp/sequentialthinking` Docker image are **separate** official distributions. **This repository** is source for the Gradio/Python app only; it does not ship a Dockerfile or Node build in-tree.
128
+
129
+ ## Docs
130
+
131
+ - [docs/LOGGING.md](docs/LOGGING.md) — log format, Hugging Face upload, troubleshooting
132
+ - [docs/IMPLEMENTATION_SUMMARY.md](docs/IMPLEMENTATION_SUMMARY.md) — logging implementation notes
app.py ADDED
@@ -0,0 +1,354 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Sequential Thinking — Gradio MCP App
3
+
4
+ Ports the Sequential Thinking MCP Server (TypeScript) to a Python Gradio app
5
+ that exposes the same tool via Gradio's built-in MCP server support.
6
+
7
+ Launch: python app.py
8
+ MCP SSE endpoint: http://localhost:7860/gradio_api/mcp/sse
9
+ """
10
+
11
+ import json
12
+ import os
13
+ import sys
14
+ from typing import Optional
15
+ from uuid import uuid4
16
+
17
+ # On Windows the default console encoding (cp1252) cannot represent the emoji
18
+ # characters that Gradio prints on startup. Reconfigure stdout/stderr to
19
+ # UTF-8 early so those prints succeed. When running via `run.ps1` (or with
20
+ # `python -X utf8 app.py`) the streams are already UTF-8 and this is a no-op.
21
+ if sys.platform == "win32":
22
+ for _stream in (sys.stdout, sys.stderr):
23
+ try:
24
+ if hasattr(_stream, "reconfigure") and _stream.encoding.lower() != "utf-8":
25
+ _stream.reconfigure(encoding="utf-8", errors="replace")
26
+ except Exception:
27
+ pass
28
+
29
+ import gradio as gr
30
+ from theme import soft_professional_theme
31
+ from chat_logger import log_chat, shutdown_logger
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # Core state (mirrors SequentialThinkingServer in lib.ts)
35
+ # ---------------------------------------------------------------------------
36
+
37
+ _thought_history: list[dict] = []
38
+ _branches: dict[str, list[dict]] = {}
39
+ _session_id: str = uuid4().hex # Session identifier for logging
40
+
41
+
42
+ # ---------------------------------------------------------------------------
43
+ # Tool logic
44
+ # ---------------------------------------------------------------------------
45
+
46
+ def sequential_thinking(
47
+ thought: str,
48
+ thought_number: int,
49
+ total_thoughts: int,
50
+ next_thought_needed: bool,
51
+ is_revision: bool = False,
52
+ revises_thought: Optional[int] = None,
53
+ branch_from_thought: Optional[int] = None,
54
+ branch_id: Optional[str] = None,
55
+ needs_more_thoughts: bool = False,
56
+ ) -> dict:
57
+ """
58
+ A detailed tool for dynamic and reflective problem-solving through thoughts.
59
+ This tool helps analyze problems through a flexible thinking process that
60
+ can adapt and evolve. Each thought can build on, question, or revise
61
+ previous insights as understanding deepens.
62
+
63
+ Use this tool when:
64
+ - Breaking down complex problems into steps
65
+ - Planning and design with room for revision
66
+ - Analysis that might need course correction
67
+ - Problems where the full scope is not clear initially
68
+ - Tasks that need context maintained over multiple steps
69
+
70
+ Args:
71
+ thought: Your current thinking step (analytical step, revision, question, hypothesis, etc.)
72
+ thought_number: Current thought number in sequence (starts at 1)
73
+ total_thoughts: Estimated total thoughts needed — can be adjusted up or down
74
+ next_thought_needed: True if more thinking is required, even if at what seemed like the end
75
+ is_revision: True if this thought revises or corrects a previous thought
76
+ revises_thought: Which thought number is being reconsidered (required when is_revision=True)
77
+ branch_from_thought: Thought number this branch splits off from (for alternative paths)
78
+ branch_id: Unique identifier for this branch (e.g. "alternative-approach")
79
+ needs_more_thoughts: True if you realise more thoughts are needed beyond totalThoughts
80
+
81
+ Returns:
82
+ dict with thoughtNumber, totalThoughts, nextThoughtNeeded, branches, thoughtHistoryLength
83
+ """
84
+ global _thought_history, _branches
85
+
86
+ # Sanitise optional int inputs that arrive as 0 from Gradio Number widgets
87
+ if revises_thought == 0:
88
+ revises_thought = None
89
+ if branch_from_thought == 0:
90
+ branch_from_thought = None
91
+
92
+ # Mirror TypeScript behaviour: expand totalThoughts if thoughtNumber exceeds it
93
+ if thought_number > total_thoughts:
94
+ total_thoughts = thought_number
95
+
96
+ thought_data: dict = {
97
+ "thought": thought,
98
+ "thoughtNumber": thought_number,
99
+ "totalThoughts": total_thoughts,
100
+ "isRevision": is_revision,
101
+ "revisesThought": revises_thought,
102
+ "branchFromThought": branch_from_thought,
103
+ "branchId": branch_id if branch_id and branch_id.strip() else None,
104
+ "needsMoreThoughts": needs_more_thoughts,
105
+ "nextThoughtNeeded": next_thought_needed,
106
+ }
107
+
108
+ _thought_history.append(thought_data)
109
+
110
+ if branch_from_thought and branch_id and branch_id.strip():
111
+ _branches.setdefault(branch_id, []).append(thought_data)
112
+
113
+ _log_thought(thought_data)
114
+
115
+ # Log to local storage
116
+ log_chat(
117
+ session_id=_session_id,
118
+ model_name="sequential-thinking",
119
+ thought=thought,
120
+ thought_number=thought_number,
121
+ total_thoughts=total_thoughts,
122
+ metadata={
123
+ "is_revision": is_revision,
124
+ "revises_thought": revises_thought,
125
+ "branch_from_thought": branch_from_thought,
126
+ "branch_id": branch_id,
127
+ "needs_more_thoughts": needs_more_thoughts,
128
+ "next_thought_needed": next_thought_needed,
129
+ }
130
+ )
131
+
132
+ return {
133
+ "thoughtNumber": thought_number,
134
+ "totalThoughts": total_thoughts,
135
+ "nextThoughtNeeded": next_thought_needed,
136
+ "branches": list(_branches.keys()),
137
+ "thoughtHistoryLength": len(_thought_history),
138
+ }
139
+
140
+
141
+ def reset_session() -> str:
142
+ """
143
+ Reset the thought history and all branches, starting a fresh problem-solving session.
144
+
145
+ Returns:
146
+ Confirmation message string.
147
+ """
148
+ global _thought_history, _branches, _session_id
149
+ _thought_history = []
150
+ _branches = {}
151
+ _session_id = uuid4().hex # Generate new session ID
152
+ return "Session reset. Ready for a new problem-solving session."
153
+
154
+
155
+ def get_history() -> list:
156
+ """
157
+ Return the full thought history for the current session.
158
+
159
+ Returns:
160
+ List of all recorded thought objects.
161
+ """
162
+ return _thought_history
163
+
164
+
165
+ # ---------------------------------------------------------------------------
166
+ # Logging helper (mirrors lib.ts formatThought, without chalk colours)
167
+ # ---------------------------------------------------------------------------
168
+
169
+ def _log_thought(data: dict) -> None:
170
+ if os.environ.get("DISABLE_THOUGHT_LOGGING", "").lower() == "true":
171
+ return
172
+
173
+ t_num = data["thoughtNumber"]
174
+ t_total = data["totalThoughts"]
175
+ thought = data["thought"]
176
+
177
+ if data.get("isRevision"):
178
+ label = f"[Revision] {t_num}/{t_total} (revising thought {data.get('revisesThought')})"
179
+ elif data.get("branchFromThought"):
180
+ label = (
181
+ f"[Branch] {t_num}/{t_total} "
182
+ f"(from thought {data['branchFromThought']}, ID: {data.get('branchId')})"
183
+ )
184
+ else:
185
+ label = f"[Thought] {t_num}/{t_total}"
186
+
187
+ width = max(len(label), len(thought)) + 4
188
+ border = "-" * width
189
+ try:
190
+ print(f"\n+{border}+")
191
+ print(f"| {label.ljust(width - 2)} |")
192
+ print(f"+{border}+")
193
+ print(f"| {thought.ljust(width - 2)} |")
194
+ print(f"+{border}+")
195
+ except UnicodeEncodeError:
196
+ print(f"\n{label}\n {thought}")
197
+
198
+
199
+ # ---------------------------------------------------------------------------
200
+ # Gradio UI
201
+ # ---------------------------------------------------------------------------
202
+
203
+ DESCRIPTION = """
204
+ # 🧠 Sequential Thinking MCP Server
205
+
206
+ Dynamic, reflective problem-solving through structured thoughts.
207
+ This Gradio app exposes the **Sequential Thinking** tool as an MCP server.
208
+
209
+ **MCP endpoint (SSE):** `http://localhost:7860/gradio_api/mcp/sse`
210
+ """
211
+
212
+ # Load custom CSS
213
+ with open('styles.css', 'r') as f:
214
+ custom_css = f.read()
215
+
216
+ with gr.Blocks(title="Sequential Thinking MCP", theme=soft_professional_theme, css=custom_css) as demo:
217
+ gr.Markdown(DESCRIPTION)
218
+
219
+ with gr.Tabs():
220
+
221
+ # ------------------------------------------------------------------
222
+ # Tab 1 — process a thought
223
+ # ------------------------------------------------------------------
224
+ with gr.Tab("Process Thought"):
225
+ with gr.Row(equal_height=False):
226
+ with gr.Column(scale=1):
227
+ thought_input = gr.Textbox(
228
+ label="Thought",
229
+ placeholder="Enter your current thinking step…",
230
+ lines=5,
231
+ )
232
+ with gr.Row():
233
+ thought_number_input = gr.Number(
234
+ label="Thought Number", value=1, minimum=1, precision=0
235
+ )
236
+ total_thoughts_input = gr.Number(
237
+ label="Total Thoughts (estimate)", value=5, minimum=1, precision=0
238
+ )
239
+ next_thought_needed_input = gr.Checkbox(
240
+ label="Next Thought Needed", value=True
241
+ )
242
+
243
+ with gr.Accordion("Advanced options", open=False):
244
+ is_revision_input = gr.Checkbox(label="Is Revision", value=False)
245
+ revises_thought_input = gr.Number(
246
+ label="Revises Thought #",
247
+ minimum=0,
248
+ precision=0,
249
+ value=0,
250
+ info="0 = not applicable",
251
+ )
252
+ branch_from_thought_input = gr.Number(
253
+ label="Branch From Thought #",
254
+ minimum=0,
255
+ precision=0,
256
+ value=0,
257
+ info="0 = not applicable",
258
+ )
259
+ branch_id_input = gr.Textbox(
260
+ label="Branch ID",
261
+ placeholder="e.g. alternative-approach",
262
+ )
263
+ needs_more_thoughts_input = gr.Checkbox(
264
+ label="Needs More Thoughts", value=False
265
+ )
266
+
267
+ with gr.Row():
268
+ submit_btn = gr.Button("Process Thought", variant="primary")
269
+ reset_btn = gr.Button("Reset Session", variant="secondary")
270
+
271
+ with gr.Column(scale=1):
272
+ output_json = gr.JSON(label="Result")
273
+ status_box = gr.Textbox(label="Status", interactive=False, visible=True)
274
+
275
+ submit_btn.click(
276
+ fn=sequential_thinking,
277
+ inputs=[
278
+ thought_input,
279
+ thought_number_input,
280
+ total_thoughts_input,
281
+ next_thought_needed_input,
282
+ is_revision_input,
283
+ revises_thought_input,
284
+ branch_from_thought_input,
285
+ branch_id_input,
286
+ needs_more_thoughts_input,
287
+ ],
288
+ outputs=output_json,
289
+ )
290
+
291
+ reset_btn.click(fn=reset_session, inputs=[], outputs=status_box)
292
+
293
+ # ------------------------------------------------------------------
294
+ # Tab 2 — inspect history
295
+ # ------------------------------------------------------------------
296
+ with gr.Tab("Thought History"):
297
+ refresh_btn = gr.Button("Refresh")
298
+ history_output = gr.JSON(label="All Thoughts")
299
+ refresh_btn.click(fn=get_history, inputs=[], outputs=history_output)
300
+
301
+ # ------------------------------------------------------------------
302
+ # Tab 3 — quick reference
303
+ # ------------------------------------------------------------------
304
+ with gr.Tab("MCP Reference"):
305
+ gr.Markdown("""
306
+ ## Connecting via MCP
307
+
308
+ Add this to your MCP client configuration:
309
+
310
+ ```json
311
+ {
312
+ "mcpServers": {
313
+ "sequential-thinking": {
314
+ "url": "http://localhost:7860/gradio_api/mcp/sse"
315
+ }
316
+ }
317
+ }
318
+ ```
319
+
320
+ ## Exposed MCP Tools
321
+
322
+ | Tool | Description |
323
+ |------|-------------|
324
+ | `sequential_thinking` | Process a single thought step |
325
+ | `reset_session` | Clear history and branches |
326
+ | `get_history` | Return the full thought history |
327
+
328
+ ## Parameters for `sequential_thinking`
329
+
330
+ | Parameter | Type | Required | Description |
331
+ |-----------|------|----------|-------------|
332
+ | `thought` | string | ✅ | Current thinking step |
333
+ | `thought_number` | int | ✅ | Step index (starts at 1) |
334
+ | `total_thoughts` | int | ✅ | Estimated total steps |
335
+ | `next_thought_needed` | bool | ✅ | More steps required? |
336
+ | `is_revision` | bool | | Revises an earlier thought |
337
+ | `revises_thought` | int | | Which thought is revised |
338
+ | `branch_from_thought` | int | | Branch origin thought |
339
+ | `branch_id` | string | | Branch label |
340
+ | `needs_more_thoughts` | bool | | Extend beyond estimate |
341
+
342
+ ## Environment Variables
343
+
344
+ | Variable | Default | Description |
345
+ |----------|---------|-------------|
346
+ | `DISABLE_THOUGHT_LOGGING` | `false` | Set to `true` to suppress console output |
347
+ """)
348
+
349
+
350
+ if __name__ == "__main__":
351
+ try:
352
+ demo.launch(mcp_server=True)
353
+ finally:
354
+ shutdown_logger()
chat_logger.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Chat logging module with local JSONL storage and optional HuggingFace upload."""
2
+
3
+ import os
4
+ import json
5
+ import threading
6
+ import atexit
7
+ from queue import Queue
8
+ from datetime import datetime
9
+ from pathlib import Path
10
+ from typing import Any, Optional
11
+
12
+ try:
13
+ from huggingface_hub import HfApi
14
+ HF_AVAILABLE = True
15
+ except ImportError:
16
+ HF_AVAILABLE = False
17
+
18
+ # Configuration
19
+ HF_TOKEN = os.environ.get("HF_TOKEN")
20
+ DATASET_REPO_ID = os.environ.get("HF_DATASET_REPO")
21
+ LOCAL_LOG_DIR = Path("logs")
22
+ LOCAL_LOG_FILE = LOCAL_LOG_DIR / "chat_logs.jsonl"
23
+ UPLOAD_INTERVAL_SECONDS = 300 # 5 minutes
24
+
25
+ # Ensure log directory exists
26
+ LOCAL_LOG_DIR.mkdir(exist_ok=True)
27
+
28
+ # Thread-safe queue for async logging
29
+ _log_queue: Queue = Queue()
30
+ _shutdown_event = threading.Event()
31
+ _upload_lock = threading.Lock()
32
+ _worker_thread_started = False
33
+
34
+
35
+ def log_chat(
36
+ session_id: str,
37
+ model_name: str,
38
+ thought: str,
39
+ thought_number: int,
40
+ total_thoughts: int,
41
+ metadata: Optional[dict] = None
42
+ ) -> None:
43
+ """Queue a thought log entry for async processing."""
44
+ _log_queue.put({
45
+ "session_id": session_id,
46
+ "model_name": model_name,
47
+ "thought": thought,
48
+ "thought_number": thought_number,
49
+ "total_thoughts": total_thoughts,
50
+ "metadata": metadata or {},
51
+ "timestamp": datetime.now().isoformat()
52
+ })
53
+
54
+
55
+ def _write_to_jsonl(entry: dict) -> None:
56
+ """Append a log entry to the local JSONL file."""
57
+ try:
58
+ with open(LOCAL_LOG_FILE, "a", encoding="utf-8") as f:
59
+ f.write(json.dumps(entry, ensure_ascii=False) + "\n")
60
+ except Exception as e:
61
+ print(f"Error writing to JSONL: {e}")
62
+
63
+
64
+ def _worker_thread():
65
+ """Background worker that processes the log queue."""
66
+ while not _shutdown_event.is_set():
67
+ try:
68
+ entry = _log_queue.get(timeout=1.0)
69
+ _write_to_jsonl(entry)
70
+ _log_queue.task_done()
71
+ except Exception:
72
+ continue
73
+
74
+
75
+ def _upload_to_huggingface() -> bool:
76
+ """Upload local JSONL to HuggingFace dataset (if configured)."""
77
+ if not HF_AVAILABLE or not HF_TOKEN or not DATASET_REPO_ID:
78
+ return False
79
+
80
+ with _upload_lock:
81
+ if not LOCAL_LOG_FILE.exists():
82
+ return False
83
+
84
+ try:
85
+ api = HfApi(token=HF_TOKEN)
86
+
87
+ # Ensure repo exists
88
+ try:
89
+ api.repo_info(repo_id=DATASET_REPO_ID, repo_type="dataset")
90
+ except Exception:
91
+ api.create_repo(
92
+ repo_id=DATASET_REPO_ID,
93
+ repo_type="dataset",
94
+ private=True
95
+ )
96
+
97
+ # Upload with timestamp
98
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
99
+ api.upload_file(
100
+ path_or_fileobj=str(LOCAL_LOG_FILE),
101
+ path_in_repo=f"logs/chat_logs_{timestamp}.jsonl",
102
+ repo_id=DATASET_REPO_ID,
103
+ repo_type="dataset",
104
+ commit_message=f"Chat logs upload {timestamp}"
105
+ )
106
+
107
+ # Clear local file after successful upload
108
+ LOCAL_LOG_FILE.unlink()
109
+ LOCAL_LOG_FILE.touch()
110
+ return True
111
+ except Exception as e:
112
+ print(f"HuggingFace upload failed: {e}")
113
+ return False
114
+
115
+
116
+ def _upload_timer_thread():
117
+ """Periodic upload thread."""
118
+ while not _shutdown_event.is_set():
119
+ _shutdown_event.wait(timeout=UPLOAD_INTERVAL_SECONDS)
120
+ if not _shutdown_event.is_set():
121
+ _upload_to_huggingface()
122
+
123
+
124
+ def shutdown_logger():
125
+ """Gracefully shutdown logger and upload remaining logs."""
126
+ _shutdown_event.set()
127
+ _log_queue.join() # Wait for queue to empty
128
+ _upload_to_huggingface() # Final upload
129
+
130
+
131
+ def _ensure_threads_started():
132
+ """Start background threads if not already started."""
133
+ global _worker_thread_started
134
+ if not _worker_thread_started:
135
+ _worker_thread_started = True
136
+ threading.Thread(target=_worker_thread, daemon=True).start()
137
+ if HF_AVAILABLE and HF_TOKEN and DATASET_REPO_ID:
138
+ threading.Thread(target=_upload_timer_thread, daemon=True).start()
139
+ atexit.register(shutdown_logger)
140
+
141
+
142
+ # Ensure threads start when module is imported
143
+ _ensure_threads_started()
docs/IMPLEMENTATION_SUMMARY.md ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Chat Logging Implementation Summary
2
+
3
+ ## Changes Made
4
+
5
+ ### 1. **New File: `chat_logger.py`**
6
+ - Implements local JSONL chat logging with thread-safe async processing
7
+ - **Local logging**: Always enabled, writes to `logs/chat_logs.jsonl`
8
+ - **Optional HuggingFace upload**: Only if `HF_TOKEN` and `HF_DATASET_REPO` environment variables are set
9
+ - **Features**:
10
+ - Queue-based async processing (non-blocking)
11
+ - Automatic background worker thread
12
+ - Periodic HuggingFace uploads (every 5 minutes)
13
+ - Graceful shutdown with final flush
14
+ - Automatic dataset creation if needed
15
+
16
+ ### 2. **Updated `app.py`**
17
+ - Added imports: `uuid4`, `log_chat`, `shutdown_logger`
18
+ - Added `_session_id` state variable (unique per session)
19
+ - Updated `sequential_thinking()`: Logs each thought with metadata
20
+ - Updated `reset_session()`: Generates new session ID when resetting
21
+ - Updated `__main__`: Calls `shutdown_logger()` on app exit for graceful cleanup
22
+
23
+ ### 3. **New File: `LOGGING.md`**
24
+ - Complete documentation of logging features
25
+ - Configuration instructions
26
+ - Environment variables reference
27
+ - Data format examples
28
+ - Troubleshooting guide
29
+
30
+ ## Usage
31
+
32
+ ### No Configuration (Local Logging Only)
33
+ ```bash
34
+ python app.py
35
+ ```
36
+ - Logs all thoughts to `logs/chat_logs.jsonl`
37
+ - No external dependencies required
38
+
39
+ ### With HuggingFace Upload
40
+ ```bash
41
+ export HF_TOKEN=hf_xxxxxxxxxxxxx
42
+ export HF_DATASET_REPO=username/my-logs
43
+ python app.py
44
+ ```
45
+ - Logs locally AND uploads to HuggingFace every 5 minutes
46
+ - Automatic dataset creation
47
+
48
+ ## What Gets Logged
49
+
50
+ Each thought entry includes:
51
+ - **session_id**: Unique session identifier
52
+ - **model_name**: `"sequential-thinking"`
53
+ - **thought**: The actual thought text
54
+ - **thought_number**: Step number in sequence
55
+ - **total_thoughts**: Estimated total steps
56
+ - **metadata**: Revision/branch information
57
+ - **timestamp**: ISO 8601 timestamp
58
+
59
+ Example:
60
+ ```json
61
+ {
62
+ "session_id": "abc123xyz...",
63
+ "model_name": "sequential-thinking",
64
+ "thought": "First, let me understand the problem...",
65
+ "thought_number": 1,
66
+ "total_thoughts": 5,
67
+ "metadata": {
68
+ "is_revision": false,
69
+ "branch_id": null,
70
+ "next_thought_needed": true
71
+ },
72
+ "timestamp": "2024-01-15T10:30:00.123456"
73
+ }
74
+ ```
75
+
76
+ ## Key Features
77
+
78
+ ✅ **Always on** - Local logging works without any configuration
79
+ ✅ **Optional upload** - HuggingFace integration only if credentials set
80
+ ✅ **Async processing** - Non-blocking, runs in background threads
81
+ ✅ **Graceful shutdown** - Flushes all pending logs before exit
82
+ ✅ **Session tracking** - Groups related thoughts by session ID
83
+ ✅ **Error handling** - Continues working even if upload fails
84
+
85
+ ## Files Modified/Created
86
+
87
+ - ✅ Created: `chat_logger.py` (140 lines)
88
+ - ✅ Created: `LOGGING.md` (documentation)
89
+ - ✅ Updated: `app.py` (added logging integration)
docs/LOGGING.md ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Sequential Thinking with Chat Logging
2
+
3
+ This Gradio application now includes automatic chat logging for all thoughts and thinking processes.
4
+
5
+ ## Chat Logging Features
6
+
7
+ ### Local Logging (Always Enabled)
8
+
9
+ All thought records are automatically logged to a local JSONL file at `logs/chat_logs.jsonl`:
10
+
11
+ ```json
12
+ {
13
+ "session_id": "abc123xyz789...",
14
+ "model_name": "sequential-thinking",
15
+ "thought": "Let me break down this problem step by step...",
16
+ "thought_number": 1,
17
+ "total_thoughts": 5,
18
+ "metadata": {
19
+ "is_revision": false,
20
+ "revises_thought": null,
21
+ "branch_from_thought": null,
22
+ "branch_id": null,
23
+ "needs_more_thoughts": false,
24
+ "next_thought_needed": true
25
+ },
26
+ "timestamp": "2024-01-15T10:30:00.123456"
27
+ }
28
+ ```
29
+
30
+ #### Accessing Local Logs
31
+
32
+ Local logs are written asynchronously to `logs/chat_logs.jsonl`. Each line is a complete JSON object representing one thought entry.
33
+
34
+ ### HuggingFace Dataset Upload (Optional)
35
+
36
+ If you have HuggingFace Hub credentials configured, logs are automatically uploaded periodically:
37
+
38
+ #### Configuration
39
+
40
+ Set these environment variables:
41
+
42
+ ```bash
43
+ export HF_TOKEN=hf_xxxxxxxxxxxxx
44
+ export HF_DATASET_REPO=username/my-sequential-thinking-logs
45
+ ```
46
+
47
+ #### Upload Behavior
48
+
49
+ - **Interval**: Every 5 minutes (configurable in `chat_logger.py`)
50
+ - **Format**: Timestamped files named `logs/chat_logs_YYYYMMDD_HHMMSS.jsonl`
51
+ - **Cleanup**: Local file is cleared after successful upload
52
+ - **Fallback**: If upload fails, logs remain locally and retry on next interval
53
+
54
+ #### Creating a HuggingFace Dataset
55
+
56
+ If the dataset doesn't exist, the logger creates it automatically on first upload (must be private).
57
+
58
+ ## Running the App
59
+
60
+ ### Standard Launch
61
+
62
+ ```bash
63
+ python app.py
64
+ ```
65
+
66
+ The app will:
67
+ 1. Start the Gradio interface at `http://localhost:7860`
68
+ 2. Begin logging all thoughts to `logs/chat_logs.jsonl`
69
+ 3. Upload to HuggingFace (if configured) every 5 minutes
70
+ 4. Flush remaining logs on shutdown
71
+
72
+ ### Environment Variables
73
+
74
+ | Variable | Default | Description |
75
+ |----------|---------|-------------|
76
+ | `HF_TOKEN` | - | HuggingFace API token (optional) |
77
+ | `HF_DATASET_REPO` | - | HuggingFace dataset repo ID (optional) |
78
+ | `DISABLE_THOUGHT_LOGGING` | `false` | Suppress console output of thoughts |
79
+
80
+ ### Disabling Console Output
81
+
82
+ ```bash
83
+ DISABLE_THOUGHT_LOGGING=true python app.py
84
+ ```
85
+
86
+ Logs will still be saved locally; only console printing is suppressed.
87
+
88
+ ## MCP Server
89
+
90
+ The app also exposes an MCP (Model Context Protocol) endpoint:
91
+
92
+ **SSE Endpoint**: `http://localhost:7860/gradio_api/mcp/sse`
93
+
94
+ Configure in your MCP client:
95
+
96
+ ```json
97
+ {
98
+ "mcpServers": {
99
+ "sequential-thinking": {
100
+ "url": "http://localhost:7860/gradio_api/mcp/sse"
101
+ }
102
+ }
103
+ }
104
+ ```
105
+
106
+ ## Log Data Structure
107
+
108
+ Each logged thought contains:
109
+
110
+ - **session_id**: Unique identifier for the current thinking session
111
+ - **model_name**: Always `"sequential-thinking"` for this app
112
+ - **thought**: The actual thought text
113
+ - **thought_number**: Current step number
114
+ - **total_thoughts**: Estimated total steps
115
+ - **metadata**: Additional context including revision/branch info
116
+ - **timestamp**: ISO 8601 timestamp
117
+
118
+ ## Sessions
119
+
120
+ A new session ID is generated:
121
+ - When the app starts
122
+ - When "Reset Session" is clicked in the UI
123
+
124
+ All thoughts in a session share the same `session_id`, making it easy to group related thinking processes.
125
+
126
+ ## Data Privacy
127
+
128
+ - Local logs are stored in `logs/chat_logs.jsonl` on your machine
129
+ - Only uploaded to HuggingFace if credentials are configured
130
+ - Uploaded datasets are set to private by default
131
+ - Logs are cleared from local storage after successful HuggingFace upload
132
+
133
+ ## Troubleshooting
134
+
135
+ ### Logs Not Appearing
136
+
137
+ 1. Check that `logs/` directory exists: `ls logs/`
138
+ 2. Verify JSONL file: `tail -f logs/chat_logs.jsonl`
139
+ 3. Check console for any error messages
140
+
141
+ ### HuggingFace Upload Issues
142
+
143
+ 1. Verify `HF_TOKEN` is valid: `huggingface-cli whoami`
144
+ 2. Ensure `HF_DATASET_REPO` follows format: `username/repo-name`
145
+ 3. Check internet connection
146
+ 4. Logs will retry on next interval; check for error messages in console
147
+
148
+ ### Performance
149
+
150
+ Logging runs in background threads and has minimal impact on performance:
151
+ - Queue-based async processing
152
+ - Non-blocking JSONL writes
153
+ - Periodic batch uploads to HuggingFace
requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ gradio[mcp]==6.11.0
run.ps1 ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ # Activate the virtual environment and launch the Gradio MCP app.
2
+ # The -X utf8 flag forces UTF-8 I/O so Gradio's emoji startup messages
3
+ # render correctly on Windows terminals.
4
+
5
+ $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
6
+ & "$scriptDir\venv\Scripts\python.exe" -X utf8 -u "$scriptDir\app.py" @args
styles.css ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --color-grey-50: #f9fafb;
3
+ --banner-background: var(--secondary-400);
4
+ --banner-text-color: var(--primary-200);
5
+ --banner-background-dark: var(--secondary-800);
6
+ --banner-text-color-dark: var(--primary-100);
7
+ --banner-text-a-color: var(--primary-100);
8
+ --banner-text-a-color-dark: var(--secondary-100);
9
+ --banner-chrome-height: calc(16px + 43px);
10
+
11
+ /* Chrome height with no banner */
12
+ --chat-chrome-height-wide-no-banner: 320px;
13
+ --chat-chrome-height-narrow-no-banner: 450px;
14
+
15
+ /* Use these when we are not using a banner */
16
+ /*--chat-chrome-height-wide: var(--chat-chrome-height-wide-no-banner);*/
17
+ /*--chat-chrome-height-narrow: var(--chat-chrome-height-narrow-no-banner);*/
18
+
19
+ /* When we are using a banner, add the banner height to the chat chrome height */
20
+ --chat-chrome-height-wide: calc(var(--chat-chrome-height-wide-no-banner) + var(--banner-chrome-height));
21
+ --chat-chrome-height-narrow: calc(var(--chat-chrome-height-narrow-no-banner) + var(--banner-chrome-height));
22
+ }
23
+
24
+ .banner-message {
25
+ background-color: var(--banner-background);
26
+ padding: 5px;
27
+ margin: 0;
28
+ border-radius: 5px;
29
+ border: none;
30
+ }
31
+
32
+ .banner-message-text {
33
+ font-size: 14px;
34
+ font-weight: bolder;
35
+ color: var(--banner-text-color) !important;
36
+ }
37
+
38
+ .banner-message-emoji {
39
+ margin-right: 3px;
40
+ font-size: 16px;
41
+ }
42
+
43
+ .banner-message-text a {
44
+ color: var(--banner-text-a-color) !important;
45
+ }
46
+
47
+ body.dark .banner-message {
48
+ background-color: var(--banner-background-dark) !important;
49
+ }
50
+ body.dark .gradio-container .contain .banner-message .banner-message-text {
51
+ color: var(--banner-text-color-dark) !important;
52
+ }
53
+
54
+ body.dark .gradio-container .contain .banner-message .banner-message-text a {
55
+ color: var(--banner-text-a-color-dark) !important;
56
+ }
57
+
58
+ .toast-body {
59
+ background-color: var(--color-grey-50);
60
+ }
61
+
62
+ .html-container:has(.css-styles) {
63
+ padding: 0;
64
+ margin: 0;
65
+ }
66
+
67
+ .css-styles {
68
+ height: 0;
69
+ }
70
+
71
+ .model-message {
72
+ text-align: end;
73
+ }
74
+
75
+ .model-dropdown-container {
76
+ display: flex;
77
+ align-items: center;
78
+ gap: 10px;
79
+ padding: 0;
80
+ }
81
+
82
+ .user-input-container .multimodal-textbox{
83
+ border: none !important;
84
+ }
85
+
86
+ /* Match the height of the modified multimodal input box on the same row */
87
+ .control-button {
88
+ height: 51px;
89
+ }
90
+
91
+ button.cancel {
92
+ border: var(--button-border-width) solid var(--button-cancel-border-color);
93
+ background: var(--button-cancel-background-fill);
94
+ color: var(--button-cancel-text-color);
95
+ box-shadow: var(--button-cancel-shadow);
96
+ }
97
+
98
+ button.cancel:hover, .cancel[disabled] {
99
+ background: var(--button-cancel-background-fill-hover);
100
+ color: var(--button-cancel-text-color-hover);
101
+ }
102
+
103
+ .opt-out-message {
104
+ top: 8px;
105
+ }
106
+
107
+ .opt-out-message .html-container, .opt-out-checkbox label {
108
+ font-size: 14px !important;
109
+ padding: 0 !important;
110
+ margin: 0 !important;
111
+ color: var(--neutral-400) !important;
112
+ }
113
+
114
+ div.block.chatbot {
115
+ height: calc(100svh - var(--chat-chrome-height-wide)) !important;
116
+ max-height: 900px !important;
117
+ }
118
+
119
+ div.no-padding {
120
+ padding: 0 !important;
121
+ }
122
+
123
+ @media (max-width: 1280px) {
124
+ div.block.chatbot {
125
+ height: calc(100svh - var(--chat-chrome-height-wide)) !important;
126
+ }
127
+ }
128
+
129
+ @media (max-width: 1024px) {
130
+ .responsive-row {
131
+ flex-direction: column;
132
+ }
133
+
134
+ .model-message {
135
+ text-align: start;
136
+ font-size: 10px !important;
137
+ }
138
+
139
+ .model-dropdown-container {
140
+ flex-direction: column;
141
+ align-items: flex-start;
142
+ }
143
+
144
+ div.block.chatbot {
145
+ height: calc(100svh - var(--chat-chrome-height-narrow)) !important;
146
+ }
147
+ }
148
+
149
+ @media (max-width: 400px) {
150
+ .responsive-row {
151
+ flex-direction: column;
152
+ }
153
+
154
+ .model-message {
155
+ text-align: start;
156
+ font-size: 10px !important;
157
+ }
158
+
159
+ .model-dropdown-container {
160
+ flex-direction: column;
161
+ align-items: flex-start;
162
+ }
163
+
164
+ div.block.chatbot {
165
+ max-height: 360px !important;
166
+ }
167
+ }
168
+
169
+ @media (max-height: 932px) {
170
+ .chatbot {
171
+ max-height: 500px !important;
172
+ }
173
+ }
174
+
175
+ @media (max-height: 1280px) {
176
+ div.block.chatbot {
177
+ max-height: 800px !important;
178
+ }
179
+ }
theme.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Soft Professional Tech Theme for Sequential Thinking MCP App
3
+ Clean, modern aesthetic with teal-gray accents, soft gradients, and professional typography.
4
+ """
5
+
6
+ from typing import Iterable
7
+ from gradio.themes import Soft
8
+ from gradio.themes.utils import colors, fonts, sizes
9
+
10
+ # Primary accent color - Teal Gray
11
+ # A sophisticated blue-teal that works well in both light and dark modes
12
+ colors.teal_gray = colors.Color(
13
+ name="teal_gray",
14
+ c50="#e8f1f4", # Lightest - backgrounds
15
+ c100="#cddde3", # Light accents
16
+ c200="#a8c3cf", # Subtle highlights
17
+ c300="#7da6b8", # Secondary text
18
+ c400="#588aa2", # Primary buttons
19
+ c500="#3d6e87", # Base accent color
20
+ c600="#335b70", # Hover states
21
+ c700="#2b495a", # Dark mode accents
22
+ c800="#2c5364", # Dark backgrounds
23
+ c900="#233f4b", # Darker backgrounds
24
+ c950="#1b323c", # Darkest
25
+ )
26
+
27
+ # Cancel/destructive action color - Muted Red Gray
28
+ # Less aggressive than pure red, better for professional UIs
29
+ colors.red_gray = colors.Color(
30
+ name="red_gray",
31
+ c50="#f7eded",
32
+ c100="#f5dcdc",
33
+ c200="#efb4b4",
34
+ c300="#e78f8f",
35
+ c400="#d96a6a", # Cancel button background
36
+ c500="#c65353", # Cancel button hover
37
+ c600="#b24444",
38
+ c700="#8f3434", # Dark mode cancel
39
+ c800="#732d2d",
40
+ c900="#5f2626",
41
+ c950="#4d2020",
42
+ )
43
+
44
+
45
+ class SoftProfessionalTheme(Soft):
46
+ """
47
+ Soft Professional Tech Theme
48
+
49
+ A clean, modern theme with teal-gray accents, soft gradients,
50
+ and professional typography. Suitable for chat apps, dashboards,
51
+ and any professional-grade Gradio application.
52
+ """
53
+ def __init__(
54
+ self,
55
+ *,
56
+ primary_hue: colors.Color | str = colors.gray,
57
+ secondary_hue: colors.Color | str = colors.teal_gray,
58
+ neutral_hue: colors.Color | str = colors.slate,
59
+ text_size: sizes.Size | str = sizes.text_md,
60
+ font: fonts.Font | str | Iterable[fonts.Font | str] = (
61
+ fonts.GoogleFont("Inconsolata"),
62
+ "Arial",
63
+ "sans-serif",
64
+ ),
65
+ font_mono: fonts.Font | str | Iterable[fonts.Font | str] = (
66
+ fonts.GoogleFont("IBM Plex Mono"),
67
+ "ui-monospace",
68
+ "monospace",
69
+ ),
70
+ ):
71
+ super().__init__(
72
+ primary_hue=primary_hue,
73
+ secondary_hue=secondary_hue,
74
+ neutral_hue=neutral_hue,
75
+ text_size=text_size,
76
+ font=font,
77
+ font_mono=font_mono,
78
+ )
79
+ super().set(
80
+ # === BACKGROUNDS ===
81
+ # Soft diagonal gradient for body - creates depth without distraction
82
+ body_background_fill="linear-gradient(135deg, *primary_200, *primary_100)",
83
+ body_background_fill_dark="linear-gradient(135deg, *primary_900, *primary_800)",
84
+ background_fill_primary="*primary_50",
85
+ background_fill_primary_dark="*primary_900",
86
+
87
+ # === PRIMARY BUTTONS ===
88
+ # Teal-gray gradient buttons with proper hover states
89
+ button_primary_text_color="white",
90
+ button_primary_text_color_hover="black",
91
+ button_primary_background_fill="linear-gradient(90deg, *secondary_400, *secondary_400)",
92
+ button_primary_background_fill_hover="linear-gradient(90deg, *secondary_300, *secondary_300)",
93
+ button_primary_background_fill_dark="linear-gradient(90deg, *secondary_600, *secondary_800)",
94
+ button_primary_background_fill_hover_dark="linear-gradient(90deg, *secondary_500, *secondary_500)",
95
+ button_primary_shadow="*shadow_drop_lg",
96
+
97
+ # === SECONDARY BUTTONS ===
98
+ # Subtle gray gradient for secondary actions
99
+ button_secondary_text_color="black",
100
+ button_secondary_text_color_hover="white",
101
+ button_secondary_background_fill="linear-gradient(90deg, *primary_300, *primary_300)",
102
+ button_secondary_background_fill_hover="linear-gradient(90deg, *primary_400, *primary_400)",
103
+ button_secondary_background_fill_dark="linear-gradient(90deg, *primary_500, *primary_600)",
104
+ button_secondary_background_fill_hover_dark="linear-gradient(90deg, *primary_500, *primary_500)",
105
+
106
+ # === CANCEL/STOP BUTTONS ===
107
+ # Muted red-gray for less aggressive cancel actions
108
+ button_cancel_background_fill=f"linear-gradient(90deg, {colors.red_gray.c400}, {colors.red_gray.c500})",
109
+ button_cancel_background_fill_dark=f"linear-gradient(90deg, {colors.red_gray.c700}, {colors.red_gray.c800})",
110
+ button_cancel_background_fill_hover=f"linear-gradient(90deg, {colors.red_gray.c500}, {colors.red_gray.c600})",
111
+ button_cancel_background_fill_hover_dark=f"linear-gradient(90deg, {colors.red_gray.c800}, {colors.red_gray.c900})",
112
+ button_cancel_text_color="white",
113
+ button_cancel_text_color_dark="white",
114
+ button_cancel_text_color_hover="white",
115
+ button_cancel_text_color_hover_dark="white",
116
+
117
+ # === SLIDERS ===
118
+ slider_color="*secondary_300",
119
+ slider_color_dark="*secondary_600",
120
+
121
+ # === BLOCKS & CONTAINERS ===
122
+ block_title_text_weight="600",
123
+ block_border_width="3px",
124
+ block_shadow="*shadow_drop_lg",
125
+ block_label_background_fill="*primary_200",
126
+
127
+ # === BUTTONS GENERAL ===
128
+ button_large_padding="11px",
129
+
130
+ # === ACCENTS ===
131
+ color_accent_soft="*primary_100",
132
+ )
133
+
134
+
135
+ # Export theme instance for easy import
136
+ soft_professional_theme = SoftProfessionalTheme()