kazutab commited on
Commit
2c475e2
·
verified ·
1 Parent(s): 0a9998a

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +114 -79
app.py CHANGED
@@ -1,11 +1,7 @@
1
- from fastapi import FastAPI, HTTPException
2
- from fastapi.middleware.cors import CORSMiddleware
3
- from pydantic import BaseModel
4
- from fastapi.responses import FileResponse
5
- from fastapi.staticfiles import StaticFiles
6
  import os
7
  import re
8
  import gradio as gr
 
9
 
10
  try:
11
  from llama_cpp import Llama
@@ -17,30 +13,10 @@ except ImportError:
17
  from transformers import AutoModelForCausalLM, AutoTokenizer
18
  print("llama-cpp-python is not installed. Falling back to transformers inference.")
19
 
20
- app = FastAPI()
21
-
22
- # CORS configuration to allow the frontend to communicate with this API
23
- app.add_middleware(
24
- CORSMiddleware,
25
- allow_origins=["*"], # Allows all origins for local development
26
- allow_credentials=True,
27
- allow_methods=["*"], # Allows all methods
28
- allow_headers=["*"], # Allows all headers
29
- )
30
-
31
- class TextRequest(BaseModel):
32
- text: str
33
-
34
- class MapResponse(BaseModel):
35
- markdown: str
36
-
37
  # ---------------------------------------------------------
38
- # AI Model Initialization (Phase 5)
39
  # ---------------------------------------------------------
40
-
41
  if USE_LLAMA_CPP:
42
- import os
43
-
44
  GGUF_FILENAME = "production_mindmap_model.gguf"
45
  if os.path.exists(f"./{GGUF_FILENAME}"):
46
  GGUF_PATH = f"./{GGUF_FILENAME}"
@@ -82,19 +58,14 @@ STRICT_SYSTEM_PROMPT = """あなたは極めて優秀で厳密な情報抽出ア
82
  2. ただし、情報を階層化して分かりやすく整理するための「一般的なカテゴリ名(例:背景、特徴、課題、概要など)」を親見出しとして補足することは許可します。
83
  3. あなたの推測や外部知識を一切混ぜず、入力文章内の事実のみを構造化してください。"""
84
 
85
- import spaces
86
-
87
- @app.post("/generate", response_model=MapResponse)
88
  @spaces.GPU(duration=120)
89
- def generate_mindmap(request: TextRequest):
90
- input_text = request.text.strip()
91
  if not input_text:
92
- raise HTTPException(status_code=400, detail="Text is empty")
93
 
94
- print(f"APIリクエスト受信しました(文字数: {len(input_text)}文字)")
95
- print("AIが推論(Markdown構造)を生成中...")
96
 
97
- # Qwen用のチャットプロンプト構築
98
  USER_PROMPT = f"""以下の文章から論理構造を抽出し、Markdown形式の目次(マインドマップ)を出力してください。
99
 
100
  【出力時の厳守ルール(違反厳禁)】
@@ -113,81 +84,145 @@ def generate_mindmap(request: TextRequest):
113
  response = model.create_chat_completion(
114
  messages=messages,
115
  max_tokens=1024,
116
- temperature=0.0, # Greedy Decoding (推測・創造を完全に排除)
117
  repeat_penalty=1.1
118
  )
119
  generated_markdown = response['choices'][0]['message']['content'].strip()
120
  else:
121
- # トークナイザーでプロンプト化
122
  prompt = tokenizer.apply_chat_template(
123
  messages,
124
  tokenize=False,
125
  add_generation_prompt=True
126
  )
127
-
128
  inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
129
-
130
  with torch.no_grad():
131
  outputs = model.generate(
132
  **inputs,
133
- max_new_tokens=1024, # 新しく生成するトークン数の上限
134
- do_sample=False, # Greedy Decoding を有効化
135
  repetition_penalty=1.1,
136
  pad_token_id=tokenizer.pad_token_id,
137
  eos_token_id=tokenizer.eos_token_id
138
  )
139
-
140
- # 4. 入力プロンプト部分を切り落として、新しく生成された部分だけを取り出す
141
  input_length = inputs["input_ids"].shape[1]
142
  generated_tokens = outputs[0][input_length:]
143
-
144
- # 5. 数値(トークン)を人間が読めるMarkdownテキストにデコード
145
  generated_markdown = tokenizer.decode(generated_tokens, skip_special_tokens=True).strip()
146
 
147
- # 【超重要】T5トークナイザーが改行(\n)を消してしまう問題の対策
148
- # 見出し記号(#)の前に強制的に改行を挿入して、Markmapが認識できるようにする
149
  generated_markdown = re.sub(r'\s*(#+ )', r'\n\1', generated_markdown).strip()
150
 
151
- # Markmapは必ず「#(大見出し)」から始まる必要があるため、存在しない場合は強制付与
152
  if not generated_markdown.startswith('#'):
153
  if '##' in generated_markdown or '###' in generated_markdown:
154
  generated_markdown = "# マインドマップ\n" + generated_markdown
155
  else:
156
  generated_markdown = "# マインドマップ\n## 抽出結果\n- " + generated_markdown.replace('\n', '\n- ')
157
 
158
- print("生成が完了しました! (コンソール出力時文字化けエラー防ぐため内容の表示を省略)")
159
-
160
- return MapResponse(markdown=generated_markdown)
161
-
162
- # フロントエンドの静的ファイルをマウント
163
- app.mount("/assets", StaticFiles(directory="frontend"), name="assets")
164
-
165
- @app.get("/")
166
- async def root():
167
- # ルートURLにアクセスされたらフロントエンドのindex.htmlを返す
168
- return FileResponse("frontend/index.html")
169
-
170
- @app.get("/{filename}")
171
- async def get_frontend_file(filename: str):
172
- # app.js や style.css などのファイルを返す
173
- file_path = os.path.join("frontend", filename)
174
- if os.path.exists(file_path):
175
- return FileResponse(file_path)
176
- raise HTTPException(status_code=404, detail="File not found")
177
-
178
- raise HTTPException(status_code=404, detail="File not found")
179
 
180
  # ---------------------------------------------------------
181
- # HF ZeroGPU Gradio Wrapper (The Final Hack)
182
  # ---------------------------------------------------------
183
- # ZeroGPU REQUIRES a Gradio app to exist, so we mount a dummy one.
184
- @spaces.GPU(duration=120)
185
- def dummy_gpu_task(*args, **kwargs):
186
- return "dummy"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
 
188
- demo = gr.Interface(fn=dummy_gpu_task, inputs="text", outputs="text")
189
- app = gr.mount_gradio_app(app, demo, path="/dummy_gradio")
 
 
 
 
190
 
191
- if __name__ == "__main__":
192
- import uvicorn
193
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
 
 
 
 
 
1
  import os
2
  import re
3
  import gradio as gr
4
+ import spaces
5
 
6
  try:
7
  from llama_cpp import Llama
 
13
  from transformers import AutoModelForCausalLM, AutoTokenizer
14
  print("llama-cpp-python is not installed. Falling back to transformers inference.")
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  # ---------------------------------------------------------
17
+ # AI Model Initialization
18
  # ---------------------------------------------------------
 
19
  if USE_LLAMA_CPP:
 
 
20
  GGUF_FILENAME = "production_mindmap_model.gguf"
21
  if os.path.exists(f"./{GGUF_FILENAME}"):
22
  GGUF_PATH = f"./{GGUF_FILENAME}"
 
58
  2. ただし、情報を階層化して分かりやすく整理するための「一般的なカテゴリ名(例:背景、特徴、課題、概要など)」を親見出しとして補足することは許可します。
59
  3. あなたの推測や外部知識を一切混ぜず、入力文章内の事実のみを構造化してください。"""
60
 
 
 
 
61
  @spaces.GPU(duration=120)
62
+ def generate_mindmap(input_text: str):
63
+ input_text = input_text.strip()
64
  if not input_text:
65
+ return "<div style='color: red; padding: 20px;'>文章を入力してください。</div>"
66
 
67
+ print(f"推論開始しま(文字数: {len(input_text)}文字)")
 
68
 
 
69
  USER_PROMPT = f"""以下の文章から論理構造を抽出し、Markdown形式の目次(マインドマップ)を出力してください。
70
 
71
  【出力時の厳守ルール(違反厳禁)】
 
84
  response = model.create_chat_completion(
85
  messages=messages,
86
  max_tokens=1024,
87
+ temperature=0.0,
88
  repeat_penalty=1.1
89
  )
90
  generated_markdown = response['choices'][0]['message']['content'].strip()
91
  else:
 
92
  prompt = tokenizer.apply_chat_template(
93
  messages,
94
  tokenize=False,
95
  add_generation_prompt=True
96
  )
 
97
  inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
 
98
  with torch.no_grad():
99
  outputs = model.generate(
100
  **inputs,
101
+ max_new_tokens=1024,
102
+ do_sample=False,
103
  repetition_penalty=1.1,
104
  pad_token_id=tokenizer.pad_token_id,
105
  eos_token_id=tokenizer.eos_token_id
106
  )
 
 
107
  input_length = inputs["input_ids"].shape[1]
108
  generated_tokens = outputs[0][input_length:]
 
 
109
  generated_markdown = tokenizer.decode(generated_tokens, skip_special_tokens=True).strip()
110
 
 
 
111
  generated_markdown = re.sub(r'\s*(#+ )', r'\n\1', generated_markdown).strip()
112
 
 
113
  if not generated_markdown.startswith('#'):
114
  if '##' in generated_markdown or '###' in generated_markdown:
115
  generated_markdown = "# マインドマップ\n" + generated_markdown
116
  else:
117
  generated_markdown = "# マインドマップ\n## 抽出結果\n- " + generated_markdown.replace('\n', '\n- ')
118
 
119
+ # Markmap 用HTML動的に構築て返
120
+ html_output = f"""
121
+ <div class="markmap" style="width: 100%; height: 100%; min-height: 500px;">
122
+ <script type="text/template">
123
+ {generated_markdown}
124
+ </script>
125
+ </div>
126
+ <script>
127
+ // GradioのHTML更新後にMarkmapを強制再レンダリングする
128
+ setTimeout(() => {{
129
+ if (window.markmap && window.markmap.autoLoader) {{
130
+ window.markmap.autoLoader.renderAll();
131
+ }}
132
+ }}, 100);
133
+ </script>
134
+ """
135
+ return html_output
 
 
 
 
136
 
137
  # ---------------------------------------------------------
138
+ # UI Construction (Native Gradio)
139
  # ---------------------------------------------------------
140
+ custom_css = """
141
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
142
+
143
+ body {
144
+ font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", "Roboto", "Helvetica Neue", sans-serif;
145
+ background-color: #fafafa !important;
146
+ }
147
+
148
+ /* GradioのデフォルトUIを上書きしてガラス風のプレミアムデザインにする */
149
+ .gradio-container {
150
+ max-width: 1200px !important;
151
+ margin: 0 auto !important;
152
+ padding: 24px !important;
153
+ }
154
+
155
+ #sidebar {
156
+ background-color: #ffffff;
157
+ border-radius: 12px;
158
+ padding: 24px;
159
+ box-shadow: 0 4px 20px rgba(0,0,0,0.04);
160
+ border: 1px solid #eaeaea;
161
+ }
162
+
163
+ #canvas {
164
+ background-color: #fafafa;
165
+ border-radius: 12px;
166
+ padding: 24px;
167
+ border: 1px solid #eaeaea;
168
+ background-image: radial-gradient(#e5e7eb 1px, transparent 1px);
169
+ background-size: 20px 20px;
170
+ min-height: 600px;
171
+ box-shadow: inset 0 2px 10px rgba(0,0,0,0.02);
172
+ }
173
+
174
+ .submit-btn {
175
+ background: #000000 !important;
176
+ color: #ffffff !important;
177
+ border-radius: 8px !important;
178
+ font-weight: 500 !important;
179
+ padding: 12px !important;
180
+ border: none !important;
181
+ transition: all 0.2s ease !important;
182
+ }
183
+
184
+ .submit-btn:hover {
185
+ background: #333333 !important;
186
+ transform: translateY(-1px);
187
+ }
188
+
189
+ textarea {
190
+ border-radius: 8px !important;
191
+ border: 1px solid #eaeaea !important;
192
+ box-shadow: 0 1px 2px rgba(0,0,0,0.02) !important;
193
+ }
194
+ """
195
+
196
+ head_scripts = """
197
+ <script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
198
+ <script src="https://cdn.jsdelivr.net/npm/markmap-view"></script>
199
+ <script src="https://cdn.jsdelivr.net/npm/markmap-autoloader"></script>
200
+ """
201
+
202
+ with gr.Blocks(css=custom_css, head=head_scripts, theme=gr.themes.Default(primary_hue="zinc")) as demo:
203
+ with gr.Row():
204
+ with gr.Column(scale=3, elem_id="sidebar"):
205
+ gr.Markdown("## 🧠 MindMap Studio")
206
+ gr.Markdown("AIが入力された文章を分析し、論理的なマインドマップを自動生成します。\n\n*Hugging Face ZeroGPU Native Version*")
207
+
208
+ text_input = gr.Textbox(
209
+ lines=12,
210
+ label="入力文章",
211
+ placeholder="ここに議事録やニュース記事を入力してください...",
212
+ elem_id="input-box"
213
+ )
214
+ submit_btn = gr.Button("マインドマップを生成", elem_classes="submit-btn")
215
+
216
+ with gr.Column(scale=7, elem_id="canvas"):
217
+ map_output = gr.HTML(
218
+ "<div style='text-align:center; padding-top:200px; color:#a1a1aa; font-weight: 500;'>ここにマインドマップが表示されます</div>"
219
+ )
220
 
221
+ submit_btn.click(
222
+ fn=generate_mindmap,
223
+ inputs=text_input,
224
+ outputs=map_output,
225
+ api_name="generate"
226
+ )
227
 
228
+ demo.launch()