scitex 2.14.0__py3-none-any.whl → 2.15.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scitex/__init__.py +71 -17
- scitex/_env_loader.py +156 -0
- scitex/_mcp_resources/__init__.py +37 -0
- scitex/_mcp_resources/_cheatsheet.py +135 -0
- scitex/_mcp_resources/_figrecipe.py +138 -0
- scitex/_mcp_resources/_formats.py +102 -0
- scitex/_mcp_resources/_modules.py +337 -0
- scitex/_mcp_resources/_session.py +149 -0
- scitex/_mcp_tools/__init__.py +4 -0
- scitex/_mcp_tools/audio.py +66 -0
- scitex/_mcp_tools/diagram.py +11 -95
- scitex/_mcp_tools/introspect.py +210 -0
- scitex/_mcp_tools/plt.py +260 -305
- scitex/_mcp_tools/scholar.py +74 -0
- scitex/_mcp_tools/social.py +244 -0
- scitex/_mcp_tools/template.py +24 -0
- scitex/_mcp_tools/writer.py +21 -204
- scitex/ai/_gen_ai/_PARAMS.py +10 -7
- scitex/ai/classification/reporters/_SingleClassificationReporter.py +45 -1603
- scitex/ai/classification/reporters/_mixins/__init__.py +36 -0
- scitex/ai/classification/reporters/_mixins/_constants.py +67 -0
- scitex/ai/classification/reporters/_mixins/_cv_summary.py +387 -0
- scitex/ai/classification/reporters/_mixins/_feature_importance.py +119 -0
- scitex/ai/classification/reporters/_mixins/_metrics.py +275 -0
- scitex/ai/classification/reporters/_mixins/_plotting.py +179 -0
- scitex/ai/classification/reporters/_mixins/_reports.py +153 -0
- scitex/ai/classification/reporters/_mixins/_storage.py +160 -0
- scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +30 -1550
- scitex/ai/classification/timeseries/_sliding_window_core.py +467 -0
- scitex/ai/classification/timeseries/_sliding_window_plotting.py +369 -0
- scitex/audio/README.md +40 -36
- scitex/audio/__init__.py +129 -61
- scitex/audio/_branding.py +185 -0
- scitex/audio/_mcp/__init__.py +32 -0
- scitex/audio/_mcp/handlers.py +59 -6
- scitex/audio/_mcp/speak_handlers.py +238 -0
- scitex/audio/_relay.py +225 -0
- scitex/audio/_tts.py +18 -10
- scitex/audio/engines/base.py +17 -10
- scitex/audio/engines/elevenlabs_engine.py +7 -2
- scitex/audio/mcp_server.py +228 -75
- scitex/canvas/README.md +1 -1
- scitex/canvas/editor/_dearpygui/__init__.py +25 -0
- scitex/canvas/editor/_dearpygui/_editor.py +147 -0
- scitex/canvas/editor/_dearpygui/_handlers.py +476 -0
- scitex/canvas/editor/_dearpygui/_panels/__init__.py +17 -0
- scitex/canvas/editor/_dearpygui/_panels/_control.py +119 -0
- scitex/canvas/editor/_dearpygui/_panels/_element_controls.py +190 -0
- scitex/canvas/editor/_dearpygui/_panels/_preview.py +43 -0
- scitex/canvas/editor/_dearpygui/_panels/_sections.py +390 -0
- scitex/canvas/editor/_dearpygui/_plotting.py +187 -0
- scitex/canvas/editor/_dearpygui/_rendering.py +504 -0
- scitex/canvas/editor/_dearpygui/_selection.py +295 -0
- scitex/canvas/editor/_dearpygui/_state.py +93 -0
- scitex/canvas/editor/_dearpygui/_utils.py +61 -0
- scitex/canvas/editor/flask_editor/_core/__init__.py +27 -0
- scitex/canvas/editor/flask_editor/_core/_bbox_extraction.py +200 -0
- scitex/canvas/editor/flask_editor/_core/_editor.py +173 -0
- scitex/canvas/editor/flask_editor/_core/_export_helpers.py +353 -0
- scitex/canvas/editor/flask_editor/_core/_routes_basic.py +190 -0
- scitex/canvas/editor/flask_editor/_core/_routes_export.py +332 -0
- scitex/canvas/editor/flask_editor/_core/_routes_panels.py +252 -0
- scitex/canvas/editor/flask_editor/_core/_routes_save.py +218 -0
- scitex/canvas/editor/flask_editor/_core.py +25 -1684
- scitex/canvas/editor/flask_editor/templates/__init__.py +32 -70
- scitex/cli/__init__.py +38 -43
- scitex/cli/audio.py +76 -27
- scitex/cli/capture.py +13 -20
- scitex/cli/introspect.py +481 -0
- scitex/cli/main.py +200 -109
- scitex/cli/mcp.py +60 -34
- scitex/cli/plt.py +357 -0
- scitex/cli/repro.py +15 -8
- scitex/cli/resource.py +15 -8
- scitex/cli/scholar/__init__.py +23 -8
- scitex/cli/scholar/_crossref_scitex.py +296 -0
- scitex/cli/scholar/_fetch.py +25 -3
- scitex/cli/social.py +314 -0
- scitex/cli/stats.py +15 -8
- scitex/cli/template.py +129 -12
- scitex/cli/tex.py +15 -8
- scitex/cli/writer.py +132 -8
- scitex/cloud/__init__.py +41 -2
- scitex/config/README.md +1 -1
- scitex/config/__init__.py +16 -2
- scitex/config/_env_registry.py +256 -0
- scitex/context/__init__.py +22 -0
- scitex/dev/__init__.py +20 -1
- scitex/diagram/__init__.py +42 -19
- scitex/diagram/mcp_server.py +13 -125
- scitex/gen/__init__.py +50 -14
- scitex/gen/_list_packages.py +4 -4
- scitex/introspect/__init__.py +82 -0
- scitex/introspect/_call_graph.py +303 -0
- scitex/introspect/_class_hierarchy.py +163 -0
- scitex/introspect/_core.py +41 -0
- scitex/introspect/_docstring.py +131 -0
- scitex/introspect/_examples.py +113 -0
- scitex/introspect/_imports.py +271 -0
- scitex/{gen/_inspect_module.py → introspect/_list_api.py} +43 -54
- scitex/introspect/_mcp/__init__.py +41 -0
- scitex/introspect/_mcp/handlers.py +233 -0
- scitex/introspect/_members.py +155 -0
- scitex/introspect/_resolve.py +89 -0
- scitex/introspect/_signature.py +131 -0
- scitex/introspect/_source.py +80 -0
- scitex/introspect/_type_hints.py +172 -0
- scitex/io/_save.py +1 -2
- scitex/io/bundle/README.md +1 -1
- scitex/logging/_formatters.py +19 -9
- scitex/mcp_server.py +98 -5
- scitex/os/__init__.py +4 -0
- scitex/{gen → os}/_check_host.py +4 -5
- scitex/plt/__init__.py +245 -550
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +5 -10
- scitex/plt/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/plt/gallery/README.md +1 -1
- scitex/plt/utils/_hitmap/__init__.py +82 -0
- scitex/plt/utils/_hitmap/_artist_extraction.py +343 -0
- scitex/plt/utils/_hitmap/_color_application.py +346 -0
- scitex/plt/utils/_hitmap/_color_conversion.py +121 -0
- scitex/plt/utils/_hitmap/_constants.py +40 -0
- scitex/plt/utils/_hitmap/_hitmap_core.py +334 -0
- scitex/plt/utils/_hitmap/_path_extraction.py +357 -0
- scitex/plt/utils/_hitmap/_query.py +113 -0
- scitex/plt/utils/_hitmap.py +46 -1616
- scitex/plt/utils/_metadata/__init__.py +80 -0
- scitex/plt/utils/_metadata/_artists/__init__.py +25 -0
- scitex/plt/utils/_metadata/_artists/_base.py +195 -0
- scitex/plt/utils/_metadata/_artists/_collections.py +356 -0
- scitex/plt/utils/_metadata/_artists/_extract.py +57 -0
- scitex/plt/utils/_metadata/_artists/_images.py +80 -0
- scitex/plt/utils/_metadata/_artists/_lines.py +261 -0
- scitex/plt/utils/_metadata/_artists/_patches.py +247 -0
- scitex/plt/utils/_metadata/_artists/_text.py +106 -0
- scitex/plt/utils/_metadata/_csv.py +416 -0
- scitex/plt/utils/_metadata/_detect.py +225 -0
- scitex/plt/utils/_metadata/_legend.py +127 -0
- scitex/plt/utils/_metadata/_rounding.py +117 -0
- scitex/plt/utils/_metadata/_verification.py +202 -0
- scitex/schema/README.md +1 -1
- scitex/scholar/__init__.py +8 -0
- scitex/scholar/_mcp/crossref_handlers.py +265 -0
- scitex/scholar/core/Scholar.py +63 -1700
- scitex/scholar/core/_mixins/__init__.py +36 -0
- scitex/scholar/core/_mixins/_enrichers.py +270 -0
- scitex/scholar/core/_mixins/_library_handlers.py +100 -0
- scitex/scholar/core/_mixins/_loaders.py +103 -0
- scitex/scholar/core/_mixins/_pdf_download.py +375 -0
- scitex/scholar/core/_mixins/_pipeline.py +312 -0
- scitex/scholar/core/_mixins/_project_handlers.py +125 -0
- scitex/scholar/core/_mixins/_savers.py +69 -0
- scitex/scholar/core/_mixins/_search.py +103 -0
- scitex/scholar/core/_mixins/_services.py +88 -0
- scitex/scholar/core/_mixins/_url_finding.py +105 -0
- scitex/scholar/crossref_scitex.py +367 -0
- scitex/scholar/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/scholar/examples/00_run_all.sh +120 -0
- scitex/scholar/jobs/_executors.py +27 -3
- scitex/scholar/pdf_download/ScholarPDFDownloader.py +38 -416
- scitex/scholar/pdf_download/_cli.py +154 -0
- scitex/scholar/pdf_download/strategies/__init__.py +11 -8
- scitex/scholar/pdf_download/strategies/manual_download_fallback.py +80 -3
- scitex/scholar/pipelines/ScholarPipelineBibTeX.py +73 -121
- scitex/scholar/pipelines/ScholarPipelineParallel.py +80 -138
- scitex/scholar/pipelines/ScholarPipelineSingle.py +43 -63
- scitex/scholar/pipelines/_single_steps.py +71 -36
- scitex/scholar/storage/_LibraryManager.py +97 -1695
- scitex/scholar/storage/_mixins/__init__.py +30 -0
- scitex/scholar/storage/_mixins/_bibtex_handlers.py +128 -0
- scitex/scholar/storage/_mixins/_library_operations.py +218 -0
- scitex/scholar/storage/_mixins/_metadata_conversion.py +226 -0
- scitex/scholar/storage/_mixins/_paper_saving.py +456 -0
- scitex/scholar/storage/_mixins/_resolution.py +376 -0
- scitex/scholar/storage/_mixins/_storage_helpers.py +121 -0
- scitex/scholar/storage/_mixins/_symlink_handlers.py +226 -0
- scitex/scholar/url_finder/.tmp/open_url/KNOWN_RESOLVERS.py +462 -0
- scitex/scholar/url_finder/.tmp/open_url/README.md +223 -0
- scitex/scholar/url_finder/.tmp/open_url/_DOIToURLResolver.py +694 -0
- scitex/scholar/url_finder/.tmp/open_url/_OpenURLResolver.py +1160 -0
- scitex/scholar/url_finder/.tmp/open_url/_ResolverLinkFinder.py +344 -0
- scitex/scholar/url_finder/.tmp/open_url/__init__.py +24 -0
- scitex/security/README.md +3 -3
- scitex/session/README.md +1 -1
- scitex/session/__init__.py +26 -7
- scitex/session/_decorator.py +1 -1
- scitex/sh/README.md +1 -1
- scitex/sh/__init__.py +7 -4
- scitex/social/__init__.py +155 -0
- scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/stats/_mcp/_handlers/__init__.py +31 -0
- scitex/stats/_mcp/_handlers/_corrections.py +113 -0
- scitex/stats/_mcp/_handlers/_descriptive.py +78 -0
- scitex/stats/_mcp/_handlers/_effect_size.py +106 -0
- scitex/stats/_mcp/_handlers/_format.py +94 -0
- scitex/stats/_mcp/_handlers/_normality.py +110 -0
- scitex/stats/_mcp/_handlers/_posthoc.py +224 -0
- scitex/stats/_mcp/_handlers/_power.py +247 -0
- scitex/stats/_mcp/_handlers/_recommend.py +102 -0
- scitex/stats/_mcp/_handlers/_run_test.py +279 -0
- scitex/stats/_mcp/_handlers/_stars.py +48 -0
- scitex/stats/_mcp/handlers.py +19 -1171
- scitex/stats/auto/_stat_style.py +175 -0
- scitex/stats/auto/_style_definitions.py +411 -0
- scitex/stats/auto/_styles.py +22 -620
- scitex/stats/descriptive/__init__.py +11 -8
- scitex/stats/descriptive/_ci.py +39 -0
- scitex/stats/power/_power.py +15 -4
- scitex/str/__init__.py +2 -1
- scitex/str/_title_case.py +63 -0
- scitex/template/README.md +1 -1
- scitex/template/__init__.py +25 -10
- scitex/template/_code_templates.py +147 -0
- scitex/template/_mcp/handlers.py +81 -0
- scitex/template/_mcp/tool_schemas.py +55 -0
- scitex/template/_templates/__init__.py +51 -0
- scitex/template/_templates/audio.py +233 -0
- scitex/template/_templates/canvas.py +312 -0
- scitex/template/_templates/capture.py +268 -0
- scitex/template/_templates/config.py +43 -0
- scitex/template/_templates/diagram.py +294 -0
- scitex/template/_templates/io.py +107 -0
- scitex/template/_templates/module.py +53 -0
- scitex/template/_templates/plt.py +202 -0
- scitex/template/_templates/scholar.py +267 -0
- scitex/template/_templates/session.py +130 -0
- scitex/template/_templates/session_minimal.py +43 -0
- scitex/template/_templates/session_plot.py +67 -0
- scitex/template/_templates/session_stats.py +77 -0
- scitex/template/_templates/stats.py +323 -0
- scitex/template/_templates/writer.py +296 -0
- scitex/template/clone_writer_directory.py +5 -5
- scitex/ui/_backends/_email.py +10 -2
- scitex/ui/_backends/_webhook.py +5 -1
- scitex/web/_search_pubmed.py +10 -6
- scitex/writer/README.md +1 -1
- scitex/writer/_mcp/handlers.py +11 -744
- scitex/writer/_mcp/tool_schemas.py +5 -335
- scitex-2.15.2.dist-info/METADATA +648 -0
- {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/RECORD +246 -150
- scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
- scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
- scitex/dev/plt/data/mpl/PLOTTING_FUNCTIONS.yaml +0 -90
- scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES.yaml +0 -1571
- scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES_DETAILED.yaml +0 -6262
- scitex/dev/plt/data/mpl/SIGNATURES_FLATTENED.yaml +0 -1274
- scitex/dev/plt/data/mpl/dir_ax.txt +0 -459
- scitex/diagram/_compile.py +0 -312
- scitex/diagram/_diagram.py +0 -355
- scitex/diagram/_mcp/__init__.py +0 -4
- scitex/diagram/_mcp/handlers.py +0 -400
- scitex/diagram/_mcp/tool_schemas.py +0 -157
- scitex/diagram/_presets.py +0 -173
- scitex/diagram/_schema.py +0 -182
- scitex/diagram/_split.py +0 -278
- scitex/gen/_ci.py +0 -12
- scitex/gen/_title_case.py +0 -89
- scitex/plt/_mcp/__init__.py +0 -4
- scitex/plt/_mcp/_handlers_annotation.py +0 -102
- scitex/plt/_mcp/_handlers_figure.py +0 -195
- scitex/plt/_mcp/_handlers_plot.py +0 -252
- scitex/plt/_mcp/_handlers_style.py +0 -219
- scitex/plt/_mcp/handlers.py +0 -74
- scitex/plt/_mcp/tool_schemas.py +0 -497
- scitex/plt/mcp_server.py +0 -231
- scitex/scholar/data/.gitkeep +0 -0
- scitex/scholar/data/README.md +0 -44
- scitex/scholar/data/bib_files/bibliography.bib +0 -1952
- scitex/scholar/data/bib_files/neurovista.bib +0 -277
- scitex/scholar/data/bib_files/neurovista_enriched.bib +0 -441
- scitex/scholar/data/bib_files/neurovista_enriched_enriched.bib +0 -441
- scitex/scholar/data/bib_files/neurovista_processed.bib +0 -338
- scitex/scholar/data/bib_files/openaccess.bib +0 -89
- scitex/scholar/data/bib_files/pac-seizure_prediction_enriched.bib +0 -2178
- scitex/scholar/data/bib_files/pac.bib +0 -698
- scitex/scholar/data/bib_files/pac_enriched.bib +0 -1061
- scitex/scholar/data/bib_files/pac_processed.bib +0 -0
- scitex/scholar/data/bib_files/pac_titles.txt +0 -75
- scitex/scholar/data/bib_files/paywalled.bib +0 -98
- scitex/scholar/data/bib_files/related-papers-by-coauthors.bib +0 -58
- scitex/scholar/data/bib_files/related-papers-by-coauthors_enriched.bib +0 -87
- scitex/scholar/data/bib_files/seizure_prediction.bib +0 -694
- scitex/scholar/data/bib_files/seizure_prediction_processed.bib +0 -0
- scitex/scholar/data/bib_files/test_complete_enriched.bib +0 -437
- scitex/scholar/data/bib_files/test_final_enriched.bib +0 -437
- scitex/scholar/data/bib_files/test_seizure.bib +0 -46
- scitex/scholar/data/impact_factor/JCR_IF_2022.xlsx +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024.db +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024.xlsx +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024_v01.db +0 -0
- scitex/scholar/data/impact_factor.db +0 -0
- scitex/scholar/examples/SUGGESTIONS.md +0 -865
- scitex/scholar/examples/dev.py +0 -38
- scitex-2.14.0.dist-info/METADATA +0 -1238
- /scitex/{gen → context}/_detect_environment.py +0 -0
- /scitex/{gen → context}/_get_notebook_path.py +0 -0
- /scitex/{gen/_shell.py → sh/_shell_legacy.py} +0 -0
- {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/WHEEL +0 -0
- {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/entry_points.txt +0 -0
- {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/licenses/LICENSE +0 -0
scitex/audio/mcp_server.py
CHANGED
|
@@ -10,13 +10,13 @@ Enables remote agents to connect and play audio on local speakers.
|
|
|
10
10
|
|
|
11
11
|
Usage:
|
|
12
12
|
scitex audio serve # stdio (default)
|
|
13
|
-
scitex audio serve -t http --port
|
|
14
|
-
scitex audio serve -t sse --port
|
|
13
|
+
scitex audio serve -t http --port 31293 # HTTP transport
|
|
14
|
+
scitex audio serve -t sse --port 31293 # SSE transport
|
|
15
15
|
|
|
16
16
|
For remote audio:
|
|
17
|
-
1. Run locally: scitex audio serve -t http --port
|
|
18
|
-
2. SSH tunnel: ssh -R
|
|
19
|
-
3. Remote agent connects to http://localhost:
|
|
17
|
+
1. Run locally: scitex audio serve -t http --port 31293
|
|
18
|
+
2. SSH tunnel: ssh -R 31293:localhost:31293 remote-host
|
|
19
|
+
3. Remote agent connects to http://localhost:31293
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
22
|
from __future__ import annotations
|
|
@@ -35,16 +35,19 @@ except ImportError:
|
|
|
35
35
|
FASTMCP_AVAILABLE = False
|
|
36
36
|
FastMCP = None # type: ignore
|
|
37
37
|
|
|
38
|
-
__all__ = ["mcp", "run_server", "main", "FASTMCP_AVAILABLE"]
|
|
38
|
+
__all__ = ["mcp", "run_server", "run_relay_server", "main", "FASTMCP_AVAILABLE"]
|
|
39
|
+
|
|
40
|
+
# Import branding
|
|
41
|
+
from ._branding import (
|
|
42
|
+
get_mcp_instructions,
|
|
43
|
+
get_mcp_server_name,
|
|
44
|
+
)
|
|
39
45
|
|
|
40
46
|
# Initialize MCP server
|
|
41
47
|
if FASTMCP_AVAILABLE:
|
|
42
48
|
mcp = FastMCP(
|
|
43
|
-
name=
|
|
44
|
-
instructions=(
|
|
45
|
-
"Text-to-Speech server with multiple backends (pyttsx3, gtts, elevenlabs). "
|
|
46
|
-
"Use speak() to convert text to speech. Supports cross-process FIFO queuing."
|
|
47
|
-
),
|
|
49
|
+
name=get_mcp_server_name(),
|
|
50
|
+
instructions=get_mcp_instructions(),
|
|
48
51
|
)
|
|
49
52
|
else:
|
|
50
53
|
mcp = None
|
|
@@ -61,6 +64,16 @@ def _get_audio_dir() -> Path:
|
|
|
61
64
|
|
|
62
65
|
|
|
63
66
|
if FASTMCP_AVAILABLE:
|
|
67
|
+
import asyncio
|
|
68
|
+
|
|
69
|
+
def _run_async(coro):
|
|
70
|
+
"""Run async handler synchronously for FastMCP tools."""
|
|
71
|
+
try:
|
|
72
|
+
loop = asyncio.get_event_loop()
|
|
73
|
+
except RuntimeError:
|
|
74
|
+
loop = asyncio.new_event_loop()
|
|
75
|
+
asyncio.set_event_loop(loop)
|
|
76
|
+
return loop.run_until_complete(coro)
|
|
64
77
|
|
|
65
78
|
@mcp.tool()
|
|
66
79
|
def speak(
|
|
@@ -74,72 +87,92 @@ if FASTMCP_AVAILABLE:
|
|
|
74
87
|
fallback: bool = True,
|
|
75
88
|
agent_id: Optional[str] = None,
|
|
76
89
|
) -> str:
|
|
77
|
-
"""Convert text to speech with fallback (pyttsx3 -> gtts -> elevenlabs).
|
|
90
|
+
"""[audio] Convert text to speech with fallback (pyttsx3 -> gtts -> elevenlabs).
|
|
78
91
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
save: Save audio to file (default False)
|
|
87
|
-
fallback: Try next backend on failure (default True)
|
|
88
|
-
agent_id: Optional identifier for the agent making the request
|
|
92
|
+
NOTE: Plays on SERVER. For remote use, see speak_relay.
|
|
93
|
+
"""
|
|
94
|
+
return audio_speak_local(
|
|
95
|
+
text=text, backend=backend, voice=voice, rate=rate,
|
|
96
|
+
speed=speed, play=play, save=save, fallback=fallback,
|
|
97
|
+
agent_id=agent_id,
|
|
98
|
+
)
|
|
89
99
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
100
|
+
@mcp.tool()
|
|
101
|
+
def audio_speak_local(
|
|
102
|
+
text: str,
|
|
103
|
+
backend: Optional[str] = None,
|
|
104
|
+
voice: Optional[str] = None,
|
|
105
|
+
rate: int = 150,
|
|
106
|
+
speed: float = 1.5,
|
|
107
|
+
play: bool = True,
|
|
108
|
+
save: bool = False,
|
|
109
|
+
fallback: bool = True,
|
|
110
|
+
agent_id: Optional[str] = None,
|
|
111
|
+
) -> str:
|
|
112
|
+
"""[audio] Convert text to speech on the LOCAL/SERVER machine.
|
|
93
113
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
speak("Hello world")
|
|
97
|
-
speak("Bonjour", backend="gtts", voice="fr")
|
|
98
|
-
speak("Fast speech", rate=200)
|
|
99
|
-
"""
|
|
100
|
-
from . import speak as tts_speak
|
|
101
|
-
from ._cross_process_lock import AudioPlaybackLock
|
|
114
|
+
Use when running Claude Code directly on your local machine.
|
|
115
|
+
Audio plays where MCP server runs.
|
|
102
116
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
117
|
+
Args:
|
|
118
|
+
text: Text to convert to speech
|
|
119
|
+
backend: TTS backend (pyttsx3, gtts, elevenlabs)
|
|
120
|
+
voice: Voice/language
|
|
121
|
+
rate: Speech rate (pyttsx3 only)
|
|
122
|
+
speed: Speed multiplier (gtts)
|
|
123
|
+
play: Play audio (default True)
|
|
124
|
+
save: Save to file (default False)
|
|
125
|
+
fallback: Try next backend on failure
|
|
126
|
+
agent_id: Agent identifier
|
|
127
|
+
"""
|
|
128
|
+
from ._mcp.speak_handlers import speak_local_handler
|
|
107
129
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
text=text,
|
|
115
|
-
backend=backend,
|
|
116
|
-
voice=voice,
|
|
117
|
-
play=play,
|
|
118
|
-
output_path=output_path,
|
|
119
|
-
fallback=fallback,
|
|
120
|
-
rate=rate,
|
|
121
|
-
speed=speed,
|
|
122
|
-
)
|
|
123
|
-
finally:
|
|
124
|
-
lock.release()
|
|
130
|
+
result = _run_async(speak_local_handler(
|
|
131
|
+
text=text, backend=backend, voice=voice, rate=rate,
|
|
132
|
+
speed=speed, play=play, save=save, fallback=fallback,
|
|
133
|
+
agent_id=agent_id,
|
|
134
|
+
))
|
|
135
|
+
return json.dumps(result, indent=2)
|
|
125
136
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
137
|
+
@mcp.tool()
|
|
138
|
+
def audio_speak_relay(
|
|
139
|
+
text: str,
|
|
140
|
+
backend: Optional[str] = None,
|
|
141
|
+
voice: Optional[str] = None,
|
|
142
|
+
rate: int = 150,
|
|
143
|
+
speed: float = 1.5,
|
|
144
|
+
play: bool = True,
|
|
145
|
+
save: bool = False,
|
|
146
|
+
fallback: bool = True,
|
|
147
|
+
agent_id: Optional[str] = None,
|
|
148
|
+
) -> str:
|
|
149
|
+
"""[audio] Convert text to speech via RELAY server (remote playback).
|
|
135
150
|
|
|
136
|
-
|
|
137
|
-
|
|
151
|
+
Use when running on remote server (e.g., NAS) and want audio
|
|
152
|
+
on your local machine. REQUIRES relay server running locally.
|
|
138
153
|
|
|
139
|
-
|
|
154
|
+
Args:
|
|
155
|
+
text: Text to convert to speech
|
|
156
|
+
backend: TTS backend (pyttsx3, gtts, elevenlabs)
|
|
157
|
+
voice: Voice/language
|
|
158
|
+
rate: Speech rate (pyttsx3 only)
|
|
159
|
+
speed: Speed multiplier (gtts)
|
|
160
|
+
play: Play audio (default True)
|
|
161
|
+
save: Save to file (default False)
|
|
162
|
+
fallback: Try next backend on failure
|
|
163
|
+
agent_id: Agent identifier
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Success with playback info, or error with setup instructions
|
|
167
|
+
"""
|
|
168
|
+
from ._mcp.speak_handlers import speak_relay_handler
|
|
140
169
|
|
|
141
|
-
|
|
142
|
-
|
|
170
|
+
result = _run_async(speak_relay_handler(
|
|
171
|
+
text=text, backend=backend, voice=voice, rate=rate,
|
|
172
|
+
speed=speed, play=play, save=save, fallback=fallback,
|
|
173
|
+
agent_id=agent_id,
|
|
174
|
+
))
|
|
175
|
+
return json.dumps(result, indent=2)
|
|
143
176
|
|
|
144
177
|
@mcp.tool()
|
|
145
178
|
def list_backends() -> str:
|
|
@@ -244,21 +277,28 @@ if FASTMCP_AVAILABLE:
|
|
|
244
277
|
|
|
245
278
|
def run_server(
|
|
246
279
|
transport: str = "stdio",
|
|
247
|
-
host: str =
|
|
248
|
-
port: int =
|
|
280
|
+
host: Optional[str] = None,
|
|
281
|
+
port: Optional[int] = None,
|
|
249
282
|
) -> None:
|
|
250
283
|
"""Run the MCP server.
|
|
251
284
|
|
|
252
285
|
Args:
|
|
253
286
|
transport: Transport protocol ("stdio", "sse", or "http")
|
|
254
|
-
host: Host for HTTP/SSE transport
|
|
255
|
-
port: Port for HTTP/SSE transport
|
|
287
|
+
host: Host for HTTP/SSE transport (default from branding)
|
|
288
|
+
port: Port for HTTP/SSE transport (default from branding)
|
|
256
289
|
"""
|
|
290
|
+
from ._branding import DEFAULT_HOST, DEFAULT_PORT
|
|
291
|
+
|
|
292
|
+
host = host or DEFAULT_HOST
|
|
293
|
+
port = port or DEFAULT_PORT
|
|
294
|
+
|
|
257
295
|
if not FASTMCP_AVAILABLE:
|
|
258
296
|
import sys
|
|
259
297
|
|
|
298
|
+
from ._branding import BRAND_NAME
|
|
299
|
+
|
|
260
300
|
print("=" * 60)
|
|
261
|
-
print("MCP Server '
|
|
301
|
+
print(f"MCP Server '{BRAND_NAME}' requires the 'fastmcp' package.")
|
|
262
302
|
print()
|
|
263
303
|
print("Install with:")
|
|
264
304
|
print(" pip install fastmcp")
|
|
@@ -268,19 +308,132 @@ def run_server(
|
|
|
268
308
|
print("=" * 60)
|
|
269
309
|
sys.exit(1)
|
|
270
310
|
|
|
311
|
+
from ._branding import BRAND_NAME
|
|
312
|
+
|
|
271
313
|
if transport == "stdio":
|
|
272
314
|
mcp.run(transport="stdio")
|
|
273
315
|
elif transport == "sse":
|
|
274
|
-
print(f"Starting
|
|
316
|
+
print(f"Starting {BRAND_NAME} MCP server (SSE) on {host}:{port}")
|
|
275
317
|
mcp.run(transport="sse", host=host, port=port)
|
|
276
318
|
elif transport == "http":
|
|
277
|
-
print(f"Starting
|
|
319
|
+
print(f"Starting {BRAND_NAME} MCP server (HTTP) on {host}:{port}")
|
|
278
320
|
print(f"Connect via: http://{host}:{port}/mcp")
|
|
279
321
|
mcp.run(transport="streamable-http", host=host, port=port)
|
|
280
322
|
else:
|
|
281
323
|
raise ValueError(f"Unknown transport: {transport}")
|
|
282
324
|
|
|
283
325
|
|
|
326
|
+
def run_relay_server(host: Optional[str] = None, port: Optional[int] = None) -> None:
|
|
327
|
+
"""Run HTTP relay server for remote audio playback.
|
|
328
|
+
|
|
329
|
+
This exposes simple REST endpoints that remote agents can connect to.
|
|
330
|
+
Unlike the MCP server, this uses standard HTTP POST/GET.
|
|
331
|
+
|
|
332
|
+
Endpoints:
|
|
333
|
+
POST /speak - Speak text
|
|
334
|
+
GET /health - Health check
|
|
335
|
+
GET /list_backends - List available backends
|
|
336
|
+
"""
|
|
337
|
+
from ._branding import BRAND_NAME, DEFAULT_HOST, DEFAULT_PORT
|
|
338
|
+
|
|
339
|
+
host = host or DEFAULT_HOST
|
|
340
|
+
port = port or DEFAULT_PORT
|
|
341
|
+
|
|
342
|
+
try:
|
|
343
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
344
|
+
except ImportError as e:
|
|
345
|
+
raise RuntimeError(f"HTTP server not available: {e}") from e
|
|
346
|
+
|
|
347
|
+
class RelayHandler(BaseHTTPRequestHandler):
|
|
348
|
+
"""HTTP handler for audio relay requests."""
|
|
349
|
+
|
|
350
|
+
def _send_json(self, data: dict, status: int = 200) -> None:
|
|
351
|
+
"""Send JSON response."""
|
|
352
|
+
import json
|
|
353
|
+
|
|
354
|
+
body = json.dumps(data, indent=2).encode("utf-8")
|
|
355
|
+
self.send_response(status)
|
|
356
|
+
self.send_header("Content-Type", "application/json")
|
|
357
|
+
self.send_header("Content-Length", str(len(body)))
|
|
358
|
+
self.send_header("Access-Control-Allow-Origin", "*")
|
|
359
|
+
self.end_headers()
|
|
360
|
+
self.wfile.write(body)
|
|
361
|
+
|
|
362
|
+
def do_OPTIONS(self) -> None:
|
|
363
|
+
"""Handle CORS preflight."""
|
|
364
|
+
self.send_response(200)
|
|
365
|
+
self.send_header("Access-Control-Allow-Origin", "*")
|
|
366
|
+
self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
|
367
|
+
self.send_header("Access-Control-Allow-Headers", "Content-Type")
|
|
368
|
+
self.end_headers()
|
|
369
|
+
|
|
370
|
+
def do_GET(self) -> None:
|
|
371
|
+
"""Handle GET requests."""
|
|
372
|
+
import json
|
|
373
|
+
|
|
374
|
+
if self.path == "/health":
|
|
375
|
+
self._send_json({"status": "healthy", "server": BRAND_NAME})
|
|
376
|
+
elif self.path == "/list_backends":
|
|
377
|
+
result = list_backends()
|
|
378
|
+
self._send_json(json.loads(result))
|
|
379
|
+
else:
|
|
380
|
+
self._send_json({"error": "Not found"}, 404)
|
|
381
|
+
|
|
382
|
+
def do_POST(self) -> None:
|
|
383
|
+
"""Handle POST requests."""
|
|
384
|
+
import json
|
|
385
|
+
|
|
386
|
+
if self.path == "/speak":
|
|
387
|
+
content_length = int(self.headers.get("Content-Length", 0))
|
|
388
|
+
body = self.rfile.read(content_length)
|
|
389
|
+
try:
|
|
390
|
+
data = json.loads(body.decode("utf-8"))
|
|
391
|
+
# Import speak directly from audio module (not MCP tool)
|
|
392
|
+
from . import speak as tts_speak
|
|
393
|
+
from ._cross_process_lock import AudioPlaybackLock
|
|
394
|
+
|
|
395
|
+
# Acquire lock for FIFO
|
|
396
|
+
lock = AudioPlaybackLock()
|
|
397
|
+
lock.acquire(timeout=120.0)
|
|
398
|
+
try:
|
|
399
|
+
tts_speak(
|
|
400
|
+
text=data.get("text", ""),
|
|
401
|
+
backend=data.get("backend"),
|
|
402
|
+
voice=data.get("voice"),
|
|
403
|
+
rate=data.get("rate", 150),
|
|
404
|
+
speed=data.get("speed", 1.5),
|
|
405
|
+
play=data.get("play", True),
|
|
406
|
+
fallback=data.get("fallback", True),
|
|
407
|
+
mode="local",
|
|
408
|
+
)
|
|
409
|
+
finally:
|
|
410
|
+
lock.release()
|
|
411
|
+
|
|
412
|
+
self._send_json({
|
|
413
|
+
"success": True,
|
|
414
|
+
"text": data.get("text", ""),
|
|
415
|
+
"played": True,
|
|
416
|
+
"timestamp": datetime.now().isoformat(),
|
|
417
|
+
})
|
|
418
|
+
except Exception as e:
|
|
419
|
+
self._send_json({"success": False, "error": str(e)}, 500)
|
|
420
|
+
else:
|
|
421
|
+
self._send_json({"error": "Not found"}, 404)
|
|
422
|
+
|
|
423
|
+
def log_message(self, format: str, *args) -> None:
|
|
424
|
+
"""Suppress default logging."""
|
|
425
|
+
pass
|
|
426
|
+
|
|
427
|
+
print(f"Starting {BRAND_NAME} relay server on {host}:{port}")
|
|
428
|
+
print("Endpoints: POST /speak, GET /health, GET /list_backends")
|
|
429
|
+
server = HTTPServer((host, port), RelayHandler)
|
|
430
|
+
try:
|
|
431
|
+
server.serve_forever()
|
|
432
|
+
except KeyboardInterrupt:
|
|
433
|
+
print("\nShutting down relay server")
|
|
434
|
+
server.shutdown()
|
|
435
|
+
|
|
436
|
+
|
|
284
437
|
def main():
|
|
285
438
|
"""Entry point for scitex-audio-mcp command."""
|
|
286
439
|
run_server(transport="stdio")
|
scitex/canvas/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!-- ---
|
|
2
2
|
!-- Timestamp: 2025-12-08
|
|
3
3
|
!-- Author: ywatanabe
|
|
4
|
-
!-- File: /home/ywatanabe/proj/scitex-
|
|
4
|
+
!-- File: /home/ywatanabe/proj/scitex-python/src/scitex/vis/README.md
|
|
5
5
|
!-- --- -->
|
|
6
6
|
|
|
7
7
|
# scitex.vis - Canvas-Based Figure Composition
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: "2026-01-24 (ywatanabe)"
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-python/src/scitex/canvas/editor/_dearpygui/__init__.py
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
DearPyGui editor package.
|
|
7
|
+
|
|
8
|
+
Modular GPU-accelerated figure editor using DearPyGui.
|
|
9
|
+
|
|
10
|
+
Modules:
|
|
11
|
+
- _state: EditorState dataclass for all editor state
|
|
12
|
+
- _utils: Utility functions (checkerboard, constants)
|
|
13
|
+
- _rendering: Figure rendering and highlight drawing
|
|
14
|
+
- _plotting: CSV data plotting
|
|
15
|
+
- _selection: Element selection and hover detection
|
|
16
|
+
- _handlers: Event handlers and callbacks
|
|
17
|
+
- _panels: UI panel creation
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from ._editor import DearPyGuiEditor
|
|
21
|
+
|
|
22
|
+
__all__ = ["DearPyGuiEditor"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# EOF
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: "2026-01-24 (ywatanabe)"
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-python/src/scitex/canvas/editor/_dearpygui/_editor.py
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
DearPyGui-based figure editor with GPU-accelerated rendering.
|
|
7
|
+
|
|
8
|
+
Thin orchestrator class that delegates to modular components.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Dict, Optional
|
|
13
|
+
|
|
14
|
+
from ._state import EditorState
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DearPyGuiEditor:
|
|
18
|
+
"""
|
|
19
|
+
GPU-accelerated figure editor using DearPyGui.
|
|
20
|
+
|
|
21
|
+
Features:
|
|
22
|
+
- Modern immediate-mode GUI with GPU acceleration
|
|
23
|
+
- Real-time figure preview
|
|
24
|
+
- Property editors with sliders, color pickers, and input fields
|
|
25
|
+
- Click-to-select traces on preview
|
|
26
|
+
- Save to .manual.json
|
|
27
|
+
- SciTeX style defaults pre-filled
|
|
28
|
+
- Dark/light theme support
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
json_path: Path,
|
|
34
|
+
metadata: Dict[str, Any],
|
|
35
|
+
csv_data: Optional[Any] = None,
|
|
36
|
+
png_path: Optional[Path] = None,
|
|
37
|
+
manual_overrides: Optional[Dict[str, Any]] = None,
|
|
38
|
+
):
|
|
39
|
+
"""Initialize the DearPyGui editor.
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
json_path : Path
|
|
44
|
+
Path to the JSON metadata file
|
|
45
|
+
metadata : dict
|
|
46
|
+
Figure metadata dictionary
|
|
47
|
+
csv_data : pd.DataFrame, optional
|
|
48
|
+
CSV data for plotting
|
|
49
|
+
png_path : Path, optional
|
|
50
|
+
Path to the PNG file
|
|
51
|
+
manual_overrides : dict, optional
|
|
52
|
+
Manual override settings
|
|
53
|
+
"""
|
|
54
|
+
self.state = EditorState.create(
|
|
55
|
+
json_path=json_path,
|
|
56
|
+
metadata=metadata,
|
|
57
|
+
csv_data=csv_data,
|
|
58
|
+
png_path=png_path,
|
|
59
|
+
manual_overrides=manual_overrides,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Backward compatibility properties
|
|
63
|
+
self._texture_id = None
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def json_path(self) -> Path:
|
|
67
|
+
"""Get JSON path."""
|
|
68
|
+
return self.state.json_path
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def metadata(self) -> Dict[str, Any]:
|
|
72
|
+
"""Get metadata."""
|
|
73
|
+
return self.state.metadata
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def csv_data(self) -> Optional[Any]:
|
|
77
|
+
"""Get CSV data."""
|
|
78
|
+
return self.state.csv_data
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def current_overrides(self) -> Dict[str, Any]:
|
|
82
|
+
"""Get current overrides."""
|
|
83
|
+
return self.state.current_overrides
|
|
84
|
+
|
|
85
|
+
@current_overrides.setter
|
|
86
|
+
def current_overrides(self, value: Dict[str, Any]) -> None:
|
|
87
|
+
"""Set current overrides."""
|
|
88
|
+
self.state.current_overrides = value
|
|
89
|
+
|
|
90
|
+
def run(self):
|
|
91
|
+
"""Launch the DearPyGui editor."""
|
|
92
|
+
try:
|
|
93
|
+
import dearpygui.dearpygui as dpg
|
|
94
|
+
except ImportError:
|
|
95
|
+
raise ImportError(
|
|
96
|
+
"DearPyGui is required for this editor. "
|
|
97
|
+
"Install with: pip install dearpygui"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
from ._panels import create_control_panel, create_preview_panel
|
|
101
|
+
from ._rendering import update_preview
|
|
102
|
+
|
|
103
|
+
dpg.create_context()
|
|
104
|
+
|
|
105
|
+
# Configure viewport
|
|
106
|
+
dpg.create_viewport(
|
|
107
|
+
title=f"SciTeX Editor ({self.state.backend_name}) - {self.state.json_path.name}",
|
|
108
|
+
width=1400,
|
|
109
|
+
height=900,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Create texture registry for image preview
|
|
113
|
+
with dpg.texture_registry(show=False):
|
|
114
|
+
# Create initial texture with placeholder
|
|
115
|
+
width, height = 800, 600
|
|
116
|
+
texture_data = [0.2, 0.2, 0.2, 1.0] * (width * height)
|
|
117
|
+
self._texture_id = dpg.add_dynamic_texture(
|
|
118
|
+
width=width,
|
|
119
|
+
height=height,
|
|
120
|
+
default_value=texture_data,
|
|
121
|
+
tag="preview_texture",
|
|
122
|
+
)
|
|
123
|
+
self.state.texture_id = self._texture_id
|
|
124
|
+
|
|
125
|
+
# Create main window
|
|
126
|
+
with dpg.window(label="SciTeX Figure Editor", tag="main_window"):
|
|
127
|
+
with dpg.group(horizontal=True):
|
|
128
|
+
# Left panel: Preview
|
|
129
|
+
create_preview_panel(self.state, dpg)
|
|
130
|
+
|
|
131
|
+
# Right panel: Controls
|
|
132
|
+
create_control_panel(self.state, dpg)
|
|
133
|
+
|
|
134
|
+
# Set main window as primary
|
|
135
|
+
dpg.set_primary_window("main_window", True)
|
|
136
|
+
|
|
137
|
+
# Initial render
|
|
138
|
+
update_preview(self.state, dpg)
|
|
139
|
+
|
|
140
|
+
# Setup and show
|
|
141
|
+
dpg.setup_dearpygui()
|
|
142
|
+
dpg.show_viewport()
|
|
143
|
+
dpg.start_dearpygui()
|
|
144
|
+
dpg.destroy_context()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# EOF
|