Spaces:
Sleeping
Sleeping
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,285 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import math
|
3 |
+
import PIL.Image
|
4 |
+
import PIL.ImageFilter
|
5 |
+
import PIL.ImageEnhance
|
6 |
+
import numpy as np
|
7 |
+
from typing import Optional
|
8 |
+
|
9 |
+
def calculator(operation: str, a: float, b: float) -> str:
|
10 |
+
"""
|
11 |
+
基本的な計算機能を提供します。
|
12 |
+
|
13 |
+
Args:
|
14 |
+
operation: 実行する演算 (add, subtract, multiply, divide, power, sqrt)
|
15 |
+
a: 最初の数値
|
16 |
+
b: 二番目の数値(sqrt以外で必要)
|
17 |
+
|
18 |
+
Returns:
|
19 |
+
str: 計算結果
|
20 |
+
"""
|
21 |
+
try:
|
22 |
+
if operation == "add":
|
23 |
+
result = a + b
|
24 |
+
elif operation == "subtract":
|
25 |
+
result = a - b
|
26 |
+
elif operation == "multiply":
|
27 |
+
result = a * b
|
28 |
+
elif operation == "divide":
|
29 |
+
if b == 0:
|
30 |
+
return "エラー: ゼロで割ることはできません"
|
31 |
+
result = a / b
|
32 |
+
elif operation == "power":
|
33 |
+
result = a ** b
|
34 |
+
elif operation == "sqrt":
|
35 |
+
if a < 0:
|
36 |
+
return "エラー: 負の数の平方根は計算できません"
|
37 |
+
result = math.sqrt(a)
|
38 |
+
else:
|
39 |
+
return f"エラー: 未対応の演算 '{operation}'"
|
40 |
+
|
41 |
+
return f"結果: {result}"
|
42 |
+
except Exception as e:
|
43 |
+
return f"計算エラー: {str(e)}"
|
44 |
+
|
45 |
+
def image_processor(image: PIL.Image.Image,
|
46 |
+
effect: str = "blur",
|
47 |
+
intensity: float = 1.0) -> PIL.Image.Image:
|
48 |
+
"""
|
49 |
+
画像にエフェクトを適用します。
|
50 |
+
|
51 |
+
Args:
|
52 |
+
image: 入力画像
|
53 |
+
effect: 適用するエフェクト (blur, sharpen, brightness, contrast)
|
54 |
+
intensity: エフェクトの強度 (0.1-3.0)
|
55 |
+
|
56 |
+
Returns:
|
57 |
+
PIL.Image.Image: 処理済み画像
|
58 |
+
"""
|
59 |
+
if image is None:
|
60 |
+
return None
|
61 |
+
|
62 |
+
try:
|
63 |
+
# intensityを適切な範囲に制限
|
64 |
+
intensity = max(0.1, min(3.0, intensity))
|
65 |
+
|
66 |
+
if effect == "blur":
|
67 |
+
return image.filter(PIL.ImageFilter.GaussianBlur(radius=intensity))
|
68 |
+
elif effect == "sharpen":
|
69 |
+
return image.filter(PIL.ImageFilter.UnsharpMask(radius=intensity))
|
70 |
+
elif effect == "brightness":
|
71 |
+
enhancer = PIL.ImageEnhance.Brightness(image)
|
72 |
+
return enhancer.enhance(intensity)
|
73 |
+
elif effect == "contrast":
|
74 |
+
enhancer = PIL.ImageEnhance.Contrast(image)
|
75 |
+
return enhancer.enhance(intensity)
|
76 |
+
else:
|
77 |
+
return image
|
78 |
+
except Exception as e:
|
79 |
+
print(f"画像処理エラー: {e}")
|
80 |
+
return image
|
81 |
+
|
82 |
+
def text_analyzer(text: str) -> str:
|
83 |
+
"""
|
84 |
+
テキストを分析して統計情報を返します。
|
85 |
+
|
86 |
+
Args:
|
87 |
+
text: 分析するテキスト
|
88 |
+
|
89 |
+
Returns:
|
90 |
+
str: テキスト分析結果
|
91 |
+
"""
|
92 |
+
if not text:
|
93 |
+
return "テキストが入力されていません。"
|
94 |
+
|
95 |
+
try:
|
96 |
+
# 基本統計
|
97 |
+
char_count = len(text)
|
98 |
+
word_count = len(text.split())
|
99 |
+
line_count = len(text.split('\n'))
|
100 |
+
|
101 |
+
# 文字種別カウント
|
102 |
+
alpha_count = sum(1 for c in text if c.isalpha())
|
103 |
+
digit_count = sum(1 for c in text if c.isdigit())
|
104 |
+
space_count = sum(1 for c in text if c.isspace())
|
105 |
+
|
106 |
+
analysis = f"""📊 テキスト分析結果:
|
107 |
+
|
108 |
+
📏 基本統計:
|
109 |
+
• 文字数: {char_count}
|
110 |
+
• 単語数: {word_count}
|
111 |
+
• 行数: {line_count}
|
112 |
+
|
113 |
+
🔤 文字種別:
|
114 |
+
• 英字: {alpha_count}
|
115 |
+
• 数字: {digit_count}
|
116 |
+
• 空白: {space_count}
|
117 |
+
|
118 |
+
📈 密度:
|
119 |
+
• 平均単語長: {char_count/word_count:.1f}文字 (単語数 > 0の場合)
|
120 |
+
• 英字比率: {alpha_count/char_count*100:.1f}%
|
121 |
+
"""
|
122 |
+
return analysis
|
123 |
+
except Exception as e:
|
124 |
+
return f"分析エラー: {str(e)}"
|
125 |
+
|
126 |
+
# メインアプリケーションの作成
|
127 |
+
with gr.Blocks(
|
128 |
+
title="Gradio Streamable HTTP MCP Demo",
|
129 |
+
theme=gr.themes.Soft(),
|
130 |
+
css="""
|
131 |
+
.gradio-container {
|
132 |
+
max-width: 1200px !important;
|
133 |
+
}
|
134 |
+
.tab-nav button {
|
135 |
+
font-size: 16px !important;
|
136 |
+
}
|
137 |
+
"""
|
138 |
+
) as demo:
|
139 |
+
|
140 |
+
gr.Markdown("""
|
141 |
+
# 🚀 Gradio Streamable HTTP MCP デモアプリ
|
142 |
+
|
143 |
+
このアプリは、Gradio の新しい **Streamable HTTP Transport for MCP server** 機能のデモです。
|
144 |
+
|
145 |
+
## 🔗 MCP エンドポイント
|
146 |
+
アプリ起動後、以下のエンドポイントでMCPツールにアクセスできます:
|
147 |
+
- **HTTP Transport**: `{APP_URL}/gradio_api/mcp/http/`
|
148 |
+
- **SSE Transport**: `{APP_URL}/gradio_api/mcp/sse`
|
149 |
+
|
150 |
+
## 🛠️ 利用可能なツール
|
151 |
+
""")
|
152 |
+
|
153 |
+
with gr.Tabs():
|
154 |
+
|
155 |
+
# Calculator Tab
|
156 |
+
with gr.Tab("🧮 Calculator", id="calculator"):
|
157 |
+
gr.Markdown("### 数学計算ツール")
|
158 |
+
|
159 |
+
with gr.Row():
|
160 |
+
with gr.Column():
|
161 |
+
calc_operation = gr.Dropdown(
|
162 |
+
choices=["add", "subtract", "multiply", "divide", "power", "sqrt"],
|
163 |
+
value="add",
|
164 |
+
label="演算"
|
165 |
+
)
|
166 |
+
calc_a = gr.Number(value=10, label="数値 A")
|
167 |
+
calc_b = gr.Number(value=5, label="数値 B (sqrt以外で使用)")
|
168 |
+
|
169 |
+
with gr.Column():
|
170 |
+
calc_result = gr.Textbox(label="計算結果", interactive=False)
|
171 |
+
calc_btn = gr.Button("計算実行", variant="primary")
|
172 |
+
|
173 |
+
calc_btn.click(
|
174 |
+
fn=calculator,
|
175 |
+
inputs=[calc_operation, calc_a, calc_b],
|
176 |
+
outputs=calc_result
|
177 |
+
)
|
178 |
+
|
179 |
+
# Image Processor Tab
|
180 |
+
with gr.Tab("🖼️ Image Processor", id="image_processor"):
|
181 |
+
gr.Markdown("### 画像処理ツール")
|
182 |
+
|
183 |
+
with gr.Row():
|
184 |
+
with gr.Column():
|
185 |
+
input_image = gr.Image(
|
186 |
+
label="入力画像",
|
187 |
+
type="pil",
|
188 |
+
height=300
|
189 |
+
)
|
190 |
+
effect_type = gr.Dropdown(
|
191 |
+
choices=["blur", "sharpen", "brightness", "contrast"],
|
192 |
+
value="blur",
|
193 |
+
label="エフェクト"
|
194 |
+
)
|
195 |
+
effect_intensity = gr.Slider(
|
196 |
+
minimum=0.1,
|
197 |
+
maximum=3.0,
|
198 |
+
value=1.0,
|
199 |
+
step=0.1,
|
200 |
+
label="強度"
|
201 |
+
)
|
202 |
+
|
203 |
+
with gr.Column():
|
204 |
+
output_image = gr.Image(
|
205 |
+
label="処理済み画像",
|
206 |
+
height=300
|
207 |
+
)
|
208 |
+
process_btn = gr.Button("画像処理実行", variant="primary")
|
209 |
+
|
210 |
+
process_btn.click(
|
211 |
+
fn=image_processor,
|
212 |
+
inputs=[input_image, effect_type, effect_intensity],
|
213 |
+
outputs=output_image
|
214 |
+
)
|
215 |
+
|
216 |
+
# Text Analyzer Tab
|
217 |
+
with gr.Tab("📝 Text Analyzer", id="text_analyzer"):
|
218 |
+
gr.Markdown("### テキスト分析ツール")
|
219 |
+
|
220 |
+
with gr.Row():
|
221 |
+
with gr.Column():
|
222 |
+
input_text = gr.Textbox(
|
223 |
+
label="分析対象テキスト",
|
224 |
+
placeholder="ここにテキストを入力してください...",
|
225 |
+
lines=8,
|
226 |
+
value="Hello World! これはサンプルテキストです。\n日本語と英語が混在しています。"
|
227 |
+
)
|
228 |
+
|
229 |
+
with gr.Column():
|
230 |
+
analysis_result = gr.Textbox(
|
231 |
+
label="分析結果",
|
232 |
+
lines=15,
|
233 |
+
interactive=False
|
234 |
+
)
|
235 |
+
analyze_btn = gr.Button("テキスト分析実行", variant="primary")
|
236 |
+
|
237 |
+
analyze_btn.click(
|
238 |
+
fn=text_analyzer,
|
239 |
+
inputs=input_text,
|
240 |
+
outputs=analysis_result
|
241 |
+
)
|
242 |
+
|
243 |
+
gr.Markdown("""
|
244 |
+
## 📋 MCP クライアントでの使用方法
|
245 |
+
|
246 |
+
1. **MCP Inspector** でテスト:
|
247 |
+
```
|
248 |
+
https://your-app-url/gradio_api/mcp/http/
|
249 |
+
```
|
250 |
+
|
251 |
+
2. **Claude Desktop** での設定例:
|
252 |
+
```json
|
253 |
+
{
|
254 |
+
"mcpServers": {
|
255 |
+
"gradio-demo": {
|
256 |
+
"command": "curl",
|
257 |
+
"args": ["-X", "POST", "https://your-app-url/gradio_api/mcp/http/"]
|
258 |
+
}
|
259 |
+
}
|
260 |
+
}
|
261 |
+
```
|
262 |
+
|
263 |
+
3. **利用可能なツール**:
|
264 |
+
- `calculator`: 数学計算
|
265 |
+
- `image_processor`: 画像処理
|
266 |
+
- `text_analyzer`: テキスト分析
|
267 |
+
|
268 |
+
## 🔧 技術詳細
|
269 |
+
|
270 |
+
- **Transport**: Streamable HTTP
|
271 |
+
- **Protocol**: MCP (Model Context Protocol)
|
272 |
+
- **Endpoints**: `/gradio_api/mcp/http/` (HTTP), `/gradio_api/mcp/sse` (SSE)
|
273 |
+
- **Features**: ファイル入出力、状態管理、リアルタイム処理
|
274 |
+
""")
|
275 |
+
|
276 |
+
# アプリケーションの設定
|
277 |
+
if __name__ == "__main__":
|
278 |
+
demo.launch(
|
279 |
+
server_name="0.0.0.0",
|
280 |
+
server_port=7860,
|
281 |
+
share=False,
|
282 |
+
show_error=True,
|
283 |
+
mcp_server=True, # MCP server を有効化
|
284 |
+
inbrowser=False
|
285 |
+
)
|