Yasu777 commited on
Commit
dde7f08
·
verified ·
1 Parent(s): a5e26cc
Files changed (1) hide show
  1. app.py +834 -533
app.py CHANGED
@@ -1,22 +1,29 @@
1
  import streamlit as st
2
  import os
3
  import re
 
 
 
4
  from groq import Groq
5
  from prompts import load_system_prompt, create_default_prompt_files
 
6
 
7
  # デフォルトのプロンプトファイルを作成
8
  create_default_prompt_files()
9
 
10
  # Streamlitアプリのセットアップ
11
  st.set_page_config(
12
- page_title="コーディングアシスタント",
13
  page_icon="💻",
14
  layout="wide"
15
  )
16
 
17
- # タイトルと説明
18
- st.title("コーディングアシスタント 💻")
19
- st.markdown("このアプリはGroq APIを使用したーディグ特化のAIチャットアシスタトです。")
 
 
 
20
 
21
  # APIキーの取得(Hugging Face Spaces環境変数から)
22
  api_key = os.getenv("GROQ_API_KEY", "")
@@ -31,587 +38,881 @@ MODELS = {
31
  # 動作モードの定義
32
  MODES = ["通常モード", "実装モード", "エラー修正モード"]
33
 
34
- # モード選択
35
- selected_mode = st.sidebar.radio(
36
- "ド選択",
37
- MODES
38
- )
39
-
40
- # モード説明の表示
41
- mode_descriptions = {
42
- "通常モード": "Mistral Sabaがユーザー入力を分析し、必要に応じてDeepSeekの知識も活用した上で、Qwen Coderが回答を生成します。",
43
- "実装モード": "ユーザー入力の分析に基づき、Qwen Coderが設計書を作成した後、実装コードを提供します。",
44
- "エラー修正モード": "提示されたコードのエラーを分析し、Qwen Coderが修正案を提供します。"
45
- }
46
-
47
- st.sidebar.markdown(f"**{selected_mode}**: {mode_descriptions[selected_mode]}")
48
-
49
- # セッション初期化
50
- if "messages" not in st.session_state:
51
- st.session_state.messages = []
52
 
53
- if "thinking_process" not in st.session_state:
54
- st.session_state.thinking_process = []
55
-
56
- if "current_mode" not in st.session_state:
57
- st.session_state.current_mode = selected_mode
58
 
59
- if "is_first_message" not in st.session_state:
60
- st.session_state.is_first_message = True
61
-
62
- # 会話状態初期化
63
- if "conversation_state" not in st.session_state:
64
- st.session_state.conversation_state = {
65
- "topic": None, # 会話の主要トピック
66
- "last_question_type": None, # 前回の質問のタイプ
67
- "mentioned_entities": set(), # 会話で言及されたエンティティ
68
- "follow_up_count": 0 # フォローアップ質問の数
69
  }
70
-
71
- # モード変更の検出
72
- if st.session_state.current_mode != selected_mode:
73
- st.session_state.current_mode = selected_mode
74
- st.session_state.is_first_message = True
75
 
76
- # モード変更時にこれまでのチャットを要約
77
- if len(st.session_state.messages) > 0:
78
- with st.spinner("前回の会話内容を要約しています..."):
79
- try:
80
- # チャット履歴を文字列にまとめる
81
- chat_history = ""
82
- for msg in st.session_state.messages:
83
- role = "ユーザー" if msg["role"] == "user" else "アシスタント"
84
- chat_history += f"{role}: {msg['content']}\n\n"
85
-
86
- # 要約を生成
87
- client = Groq(api_key=api_key)
88
- summarize_prompt = f"""
89
- 以下はユーザーとアシスタントの間の会話です。この会話を300単語以内で要約してください。
90
- 特に重要な内容、技術的な詳細、解決した問題に焦点を当ててください。
91
-
92
- {chat_history}
93
- """
94
-
95
- # Mistral Sabaモデルのシステムプロンプトをロード
96
- summary_system_prompt = load_system_prompt(MODELS["mistral_saba"])
97
-
98
- summary_response = client.chat.completions.create(
99
- model=MODELS["mistral_saba"],
100
- messages=[
101
- {"role": "system", "content": summary_system_prompt},
102
- {"role": "user", "content": summarize_prompt}
103
- ],
104
- temperature=0.3,
105
- max_tokens=1000
106
- )
107
-
108
- summary = summary_response.choices[0].message.content
109
- st.session_state.chat_summary = summary
110
-
111
- # 要約をthinking_processに追加
112
- st.session_state.thinking_process.append({
113
- "title": "前回の会話要約",
114
- "content": summary
115
- })
116
-
117
- # ユーザーに要約を通知
118
- st.info(f"モードが変更されました。前回の会話の要約:\n\n{summary}")
119
- except Exception as e:
120
- st.error(f"会話要約の生成中にエラーが発生しました: {str(e)}")
121
-
122
- # 物理学関連のキーワード
123
- PHYSICS_KEYWORDS = [
124
- "物理", "力学", "電磁気", "熱力学", "量子", "相対性", "波動", "力", "運動", "エネルギー",
125
- "physics", "mechanics", "electromagnetism", "thermodynamics", "quantum", "relativity",
126
- "wave", "force", "motion", "energy", "シミュレーション", "simulation", "重力", "gravity",
127
- "加速度", "acceleration", "速度", "velocity", "衝突", "collision", "反発", "rebound",
128
- "摩擦", "friction", "剛体", "rigid body", "粒子", "particle", "流体", "fluid",
129
- "弾性", "elastic", "軌道", "trajectory", "バネ", "spring", "圧力", "pressure"
130
- ]
131
-
132
- # テキストが物理学に関連しているかチェック
133
- def is_physics_related(text):
134
- lower_text = text.lower()
135
 
136
- # 明確な物関連キーワード(より具体的で誤検出が少ないもの)
137
- explicit_physics_keywords = [
138
- "物理法則", "物理シミュレーション", "ニュートン力学", "電磁気学", "熱力学", "量子力学", "相対性理論",
139
- "physics simulation", "newton's laws", "electromagnetism", "thermodynamics",
140
- "quantum mechanics", "relativity theory"
141
- ]
142
 
143
- # 単語境界を考慮すべきキーワード(一般的な単語だが、単独で使われた場合は物理関連の可能性が高い)
144
- word_boundary_keywords = [
145
- r"\b力学\b", r"\b重力\b", r"\b摩擦\b", r"\b衝突\b", r"\b運動方程式\b", r"\b加速度\b", r"\b速度\b", r"\b質量\b",
146
- r"\bgravity\b", r"\bfriction\b", r"\bcollision\b", r"\bvelocity\b", r"\bacceleration\b", r"\bmass\b"
147
- ]
148
 
149
- # 明確な物理用語が含まれているか
150
- explicit_match = any(keyword.lower() in lower_text for keyword in explicit_physics_keywords)
 
 
 
151
 
152
- # 境界を考慮すべき単語が含まれているか(単語として独立して存在する場合のみ)
153
- boundary_match = any(re.search(pattern, lower_text) for pattern in word_boundary_keywords)
 
 
 
 
 
 
 
 
 
 
154
 
155
- # 物理シミュレーション関連の特定のレーズパターン
156
- physics_phrases = [
157
- r"物理.*シミュレ(ーション|ート)",
158
- r"physics.*simulation",
159
- r"ボールの.*衝突",
160
- r"ball.*collision",
161
- r"重力.*計算",
162
- r"gravity.*calculation",
163
- r"運動方程式",
164
- r"equation.*motion",
165
- r"力学.*モデル",
166
- r"mechanics.*model"
167
- ]
168
- phrase_match = any(re.search(pattern, lower_text) for pattern in physics_phrases)
 
 
 
 
 
 
 
 
 
 
 
169
 
170
- # 誤検出しやすいコンテキストのック(これらが含まれる場合は物理関連ではない可性が高い)
171
- non_physics_contexts = [
172
- "アプリの動作", "システムの動作", "プログラムの動作", "ソフトウェアの動作",
173
- "app behavior", "system behavior", "program behavior", "software behavior",
174
- "UIの動き", "インターフェースの動き", "ボタンの動き",
175
- "UI movement", "interface movement", "button movement"
176
- ]
177
- negative_context = any(context.lower() in lower_text for context in non_physics_contexts)
 
 
 
 
178
 
179
- # 物理関連である判断するロジック:
180
- # 1. 明確な物理用語が含まれている、または
181
- # 2. 境界を考慮すべき単語が含まれてて、かつ誤検出コンテキストが含まれていない、または
182
- # 3. 物理シミュレーション関連特定のフレーズが含まれている
183
- return explicit_match or (boundary_match and not negative_context) or phrase_match
 
 
 
 
 
 
 
 
 
 
 
 
184
 
185
- # 過去メッセージを表示
186
- for message in st.session_state.messages:
187
- with st.chat_message(message["role"]):
188
- st.markdown(message["content"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
 
190
- # 思考プロセスの表示(開閉可能なエクスパダー)
191
- if st.session_state.thinking_process:
192
- with st.expander("思考プロセスを表示"):
193
- for i, thought in enumerate(st.session_state.thinking_process):
194
- st.subheader(thought["title"])
195
- st.markdown(thought["content"])
196
- st.divider()
197
 
198
- # 会話状態を更新する関数
199
- def update_conversation_state(user_input, ai_response):
200
- # 主要トピックの更新(最初のメッセジから)
201
- if st.session_state.conversation_state["topic"] is None and user_input:
202
- # 簡易的なトピック抽出(最初の文か最初���30単語)
203
- first_sentence = user_input.split('.')[0]
204
- topic = first_sentence[:100] + "..." if len(first_sentence) > 100 else first_sentence
205
- st.session_state.conversation_state["topic"] = topic
206
 
207
- # フォローアップカウントの更新
208
- if "?" in user_input or "?" in user_input:
209
- st.session_state.conversation_state["follow_up_count"] += 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
 
211
- # その他の状態更新ロジックをここに追加
212
- pass
213
 
214
- # APIを使用した思考プロセスと回答の生成
215
- def generate_response(user_input):
216
- client = Groq(api_key=api_key)
217
- thinking_results = []
218
 
219
- # ユーザー入力が物理関連かどうかをチェ
220
- is_physics = is_physics_related(user_input)
 
 
 
 
 
 
 
 
 
 
221
 
222
- # Step 1: Mistral Sabaによる入力分析
223
- mistral_system_prompt = load_system_prompt(MODELS["mistral_saba"])
224
- mistral_prompt = f"""
225
- 以下ユーザー入力を分析してください。ユーザーの質問や求の本質、重要なキーワード、技術的課題を抽出し、
226
- ようなアローチで回答すべきかを詳細に検討してください。
 
 
 
227
 
228
- ユーザー入力: {user_input}
229
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
 
231
- mistral_response = client.chat.completions.create(
232
- model=MODELS["mistral_saba"],
233
- messages=[
234
- {"role": "system", "content": mistral_system_prompt},
235
- {"role": "user", "content": mistral_prompt}
236
- ],
237
- temperature=0.3,
238
- max_tokens=2000
239
- )
240
 
241
- mistral_analysis = mistral_response.choices[0].message.content
242
- thinking_results.append({
243
- "title": "Mistral Sabaによる分析",
244
- "content": mistral_analysis
245
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
 
247
- # 物理関連の場合のみDeepSeekの意見取得
248
- deepseek_knowledge = ""
249
- if is_physics:
250
- deepseek_system_prompt = load_system_prompt(MODELS["deepseek"])
251
- deepseek_prompt = f"""
252
- 以下のユーザー入力は物理学に関連しています。物理学の観点から、この質問や課題に対する
253
- 科学的知識、法則、公式、アプローチを提供してください。特に関連する物理概念と実装方法の
254
- 関連性があれば説明してください。
255
-
256
- ユーザー入力: {user_input}
257
- """
258
-
259
- deepseek_response = client.chat.completions.create(
260
- model=MODELS["deepseek"],
261
- messages=[
262
- {"role": "system", "content": deepseek_system_prompt},
263
- {"role": "user", "content": deepseek_prompt}
264
- ],
265
- temperature=0.3,
266
- max_tokens=2000
267
- )
268
-
269
- deepseek_knowledge = deepseek_response.choices[0].message.content
270
- thinking_results.append({
271
- "title": "DeepSeekによる物理学的知見",
272
- "content": deepseek_knowledge
273
- })
274
 
275
- # Step 2: モード別の応答生成
276
- if st.session_state.current_mode == "通常モード":
277
- # 物理関連場合のみDeepSeekの情報含め
278
- deepseek_section = ""
279
- physics_consideration = ""
280
-
281
- if is_physics:
282
- physics_consideration = " DeepSeekの物理学的知見を考慮して"
283
- deepseek_section = f"DeepSeekの物理学的知見:\n{deepseek_knowledge}"
284
-
285
- # 通常モード用のシステムプロンプトをロード
286
- system_prompt = load_system_prompt(MODELS["qwen_coder"], "通常モード")
287
-
288
- qwen_prompt = f"""
289
- 以下のユーザー入力に対してコーディングアシスタントとして回答してください。
290
- Mistral Sabaの分析結果{physics_consideration}を考慮して、
291
- 最適なソリューションを提供してください。
292
-
293
- ユーザー入力: {user_input}
294
 
295
- Mistral Sabaの分析:
296
- {mistral_analysis}
297
- """
298
 
299
- # 物理関連の場合のみDeepSeekの情報を追加
300
- if is_physics:
301
- qwen_prompt += f"\n\n{deepseek_section}"
 
302
 
303
- qwen_response = client.chat.completions.create(
304
- model=MODELS["qwen_coder"],
305
- messages=[
306
- {"role": "system", "content": system_prompt},
307
- {"role": "user", "content": qwen_prompt}
308
- ],
309
- temperature=0.5,
310
- max_tokens=4000
311
- )
312
-
313
- final_response = qwen_response.choices[0].message.content
314
 
315
- elif st.session_state.current_mode == "実装モード":
316
- # 物理関連の条件分岐を変数で処理
317
- deepseek_section = ""
318
- physics_note = ""
319
-
320
- if is_physics:
321
- deepseek_section = f"DeepSeekの物理学的知見:\n{deepseek_knowledge}"
322
- physics_note = "こ実装には物理シミュレーションが含まれているため、物理法則の正確性と数値的安定性を特に重視してください。"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
 
324
- # Step 2a: 設計フェズ - 専用のプロンプト使用
325
- design_system_prompt = load_system_prompt(MODELS["qwen_coder"], "実装モード_設計")
326
 
327
- design_prompt = f"""
328
- 以下のユーザー入力と分析結果に基づいて、実装に必要な設計書を作成してください。
 
 
 
329
 
330
  ユーザー入力: {user_input}
331
-
332
- Mistral Sabaの分析:
333
- {mistral_analysis}
334
  """
335
 
336
- # 物理関連の場合のみDeepSeekの情報を追加
337
- if is_physics:
338
- design_prompt += f"\n\n{deepseek_section}"
339
-
340
- design_response = client.chat.completions.create(
341
- model=MODELS["qwen_coder"],
342
  messages=[
343
- {"role": "system", "content": design_system_prompt},
344
- {"role": "user", "content": design_prompt}
345
  ],
346
- temperature=0.4,
347
- max_tokens=3000
348
  )
349
 
350
- design_doc = design_response.choices[0].message.content
351
  thinking_results.append({
352
- "title": "Qwen Coderによる設計書",
353
- "content": design_doc
354
  })
355
 
356
- # Step 2b: 実装フェーズ - 専用プロンプト使用
357
- implementation_system_prompt = load_system_prompt(MODELS["qwen_coder"], "実装モード_実装")
358
-
359
- implementation_prompt = f"""
360
- 以下の設計書に基づいて、完全な実装コードを提供してください。
361
- コードは実行可能で、エラー処理が適切に行われているものにしてください。
362
- 各コンポーネントには十分なコメントを付け、使用方法の例も含めてください。
363
- {physics_note}
364
-
365
- 設計書:
366
- {design_doc}
367
-
368
- ユーザーの元々の要求:
369
- {user_input}
370
- """
371
-
372
- implementation_response = client.chat.completions.create(
373
- model=MODELS["qwen_coder"],
374
- messages=[
375
- {"role": "system", "content": implementation_system_prompt},
376
- {"role": "user", "content": implementation_prompt}
377
- ],
378
- temperature=0.5,
379
- max_tokens=4000
380
- )
381
-
382
- implementation_result = implementation_response.choices[0].message.content
383
-
384
- # 設計書と実装を組み合わせた最終結果
385
- final_response = f"# 設計書\n\n{design_doc}\n\n# 実装コード\n\n{implementation_result}"
386
-
387
- elif st.session_state.current_mode == "エラー修正モード":
388
- # 物理関連の条件分岐を変数で処理
389
- deepseek_section = ""
390
-
391
- if is_physics:
392
- deepseek_section = f"DeepSeekの物理学的知見:\n{deepseek_knowledge}"
393
-
394
- # エラー修正モード用のシス��ムプロンプトをロード
395
- error_system_prompt = load_system_prompt(MODELS["qwen_coder"], "エラー修正モード")
396
-
397
- error_prompt = f"""
398
- 以下のユーザー入力はコードのエラーに関するものです。
399
- エラーの原因を特定し、修正案を提供してください。
400
- 説明では:
401
- 1. エラーの根本原因
402
- 2. 問題箇所の特定
403
- 3. 修正方法の詳細な説明
404
- 4. 修正されたコード
405
- 5. 同様のエラーを防ぐためのベストプラクティス
406
- を含めてください。
407
-
408
- ユーザー入力: {user_input}
409
-
410
- Mistral Sabaの分析:
411
- {mistral_analysis}
412
- """
413
-
414
- # 物理関連の場合のみDeepSeekの情報を追加
415
  if is_physics:
416
- error_prompt += f"\n\n{deepseek_section}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417
 
418
- error_response = client.chat.completions.create(
419
- model=MODELS["qwen_coder"],
420
- messages=[
421
- {"role": "system", "content": error_system_prompt},
422
- {"role": "user", "content": error_prompt}
423
- ],
424
- temperature=0.4,
425
- max_tokens=4000
426
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
427
 
428
- final_response = error_response.choices[0].message.content
429
-
430
- return final_response, thinking_results
431
-
432
- # テキスト内にコードブロックが含まれているかを確認
433
- def contains_code_block(text):
434
- # マークダウンのコードブロックパターン
435
- code_block_pattern = r"```[\w]*\n[\s\S]*?\n```"
436
- return bool(re.search(code_block_pattern, text))
437
-
438
- # テキストからすべてのコードブロックを抽出する
439
- def extract_all_code_blocks(text):
440
- """テキストから全てのコードブロックを抽出する"""
441
- code_block_pattern = r"```(\w+)?\n([\s\S]*?)\n```"
442
- matches = re.findall(code_block_pattern, text)
443
 
444
- if not matches:
445
- return []
 
446
 
447
- code_blocks = []
448
- for lang, code in matches:
449
- lang = lang or "python" # 言語指定がない場合はPythonと仮定
450
- code_blocks.append(f"```{lang}\n{code}\n```")
 
 
 
451
 
452
- return code_blocks
453
-
454
- # ユーザー入力
455
- if prompt := st.chat_input("質問を入力してください"):
456
- # ユーザーメッセージを表示
457
- st.chat_message("user").markdown(prompt)
458
-
459
- # ユーザーメッセージをセッション状態に追加
460
- st.session_state.messages.append({"role": "user", "content": prompt})
461
-
462
- # モード切替コマンドを検出
463
- if prompt.strip().startswith("/mode"):
464
- parts = prompt.strip().split()
465
- if len(parts) < 2:
466
- response = "モード指定が不完全です。使用方法: /mode [通常|実装|エラー修正]"
467
- else:
468
- mode = parts[1].lower()
469
- valid_modes = {"通常": "通常モード", "実装": "実装モード", "エラー修正": "エラー修正モード"}
470
-
471
- if mode not in valid_modes and mode not in valid_modes.values():
472
- response = f"無効なモードです。使用可能なモード: {', '.join(valid_modes.keys())}"
473
  else:
474
- old_mode = st.session_state.current_mode
475
- if mode in valid_modes:
476
- st.session_state.current_mode = valid_modes[mode]
477
- else:
478
- st.session_state.current_mode = mode
479
-
480
- st.session_state.is_first_message = True
481
-
482
- # モード変更時の説明メッセージを用意
483
- mode_descriptions = {
484
- "通常モード": "通常の質問応答モードに切り替えました。",
485
- "実装モード": "アイデアから設計と実装を生成するモードに切り替えました。",
486
- "エラー修正モード": "エラーメッセージから問題を診断・修正するモードに切り替えました。"
487
- }
488
 
489
- instructions = {
490
- "通常モード": "質問や議論したいトピックを入力してください。",
491
- "実装モード": "実装したい機能やアイデアを入力してください。",
492
- "エラー修正モード": "発生したエラーメッセージとコードを入力してください。"
493
- }
494
-
495
- response = f"モードを「{old_mode}」から「{st.session_state.current_mode}」に変更しました。\n\n{mode_descriptions[st.session_state.current_mode]}\n\n{instructions[st.session_state.current_mode]}"
496
-
497
- # レスポンスを表示
498
- st.chat_message("assistant").markdown(response)
499
-
500
- # アシスタントメッセージをセッション状態に追加
501
- st.session_state.messages.append({"role": "assistant", "content": response})
502
-
503
- # 通常の質問処理
504
- else:
505
- # APIキーがある場合のみ実行
506
- if api_key:
507
- try:
508
- # 最初のメッセージはチェーンを使用、それ以降はQwen Coderのみで応答
509
- if st.session_state.is_first_message:
510
- with st.spinner("回答を生成中...(複数のモデルによる分析を行っています)"):
511
- response, thinking_results = generate_response(prompt)
512
- st.session_state.thinking_process = thinking_results
513
- st.session_state.is_first_message = False
514
  else:
515
- with st.spinner("回答を生成中..."):
516
- # 直接Qwen Coderで応答
517
- client = Groq(api_key=api_key)
518
-
519
- # 現在のモードに合わせたシステムプロンプトをロード
520
- current_system_prompt = load_system_prompt(MODELS["qwen_coder"], st.session_state.current_mode)
521
-
522
- # 会話の連続性のための追加指示
523
- current_system_prompt += """
524
-
525
- 重要: これは継続中会話です。前の質問や回を踏まて、会話の自然な流れを維持てください
526
- ザーの最新の質問だけに集中するのではなく、会話全体の文脈を考慮てください
527
- は避け、新しい情報や洞察を提供してください
528
- """
529
-
530
- # 過去の会話履歴を完全に構築
531
- context = ""
532
- if len(st.session_state.messages) > 2:
533
- # 最近の会話履歴(最大トクン制限考慮して適切な数に制限)
534
- recent_messages = st.session_state.messages[-10:] # 最近の5往復まで
535
- for msg in recent_messages:
536
- role = "ザー" if msg["role"] == "user" else "アシスタント"
537
- # 全文を含める
538
- context += f"{role}: {msg['content']}\n\n"
539
-
540
- qwen_prompt = f"""
541
- あなたはユーザーと継続的な会話を行っているアシスタントです。
542
- これまでの会話の流れを踏まえて、最新のユーザー入力に適切に応答してください。
543
-
544
- これまで会話:
545
- {context}
546
-
547
- 最新のユーザー入力: {prompt}
548
-
549
- 前の会話の文脈を考慮して、この最新の質問に直接回答してください。
550
- 前回までの内容を繰り返すのではなく、会話を自然に進めてください。
551
- """
552
-
553
- qwen_response = client.chat.completions.create(
554
- model=MODELS["qwen_coder"],
555
- messages=[
556
- {"role": "system", "content": current_system_prompt},
557
- {"role": "user", "content": qwen_prompt}
558
- ],
559
- temperature=0.5,
560
- max_tokens=4000
561
- )
562
-
563
- response = qwen_response.choices[0].message.content
564
-
565
- # レスポンスを表示
566
- st.chat_message("assistant").markdown(response)
567
-
568
- # アシスタントメッセージをセッション状態に追加
569
- st.session_state.messages.append({"role": "assistant", "content": response})
570
-
571
- # 会話状態を更新
572
- update_conversation_state(prompt, response)
573
-
574
- # モード切替提案を追加(UIには表示しない)
575
- mode_suggestions = {
576
- "通常モード": "\n\n---\n*実装を生成するには、「/mode 実装」と入力して実装モードに切り替えてください。*",
577
- "実装モード": "\n\n---\n*このコードを検証・修正するには、「/mode エラー修正」と入力して検証修正モードに切り替えてください。*",
578
- "エラー修正モード": "\n\n---\n*新しい実装を生成するには、「/mode 実装」と入力して実装モードに切り替えてください。*"
579
- }
580
-
581
- # 提案はセッションステートには保存するが、UIには表示しない(次回のcontext生成で使用)
582
- suggestion = mode_suggestions.get(st.session_state.current_mode, "")
583
- st.session_state.messages[-1]["content"] += suggestion
584
-
585
- except Exception as e:
586
- st.error(f"エラーが発生しました: {str(e)}")
587
  else:
588
- st.error("GROQ_API_KEYが設定されていません。Hugging Face Spaces環境変数で設定してください。")
589
-
590
- # チャット履歴のクリア機能
591
- if st.sidebar.button("会話履歴クリア"):
592
- st.session_state.messages = []
593
- st.session_state.thinking_process = []
594
- st.session_state.is_first_message = True
595
- st.session_state.conversation_state = {
596
- "topic": None,
597
- "last_question_type": None,
598
- "mentioned_entities": set(),
599
- "follow_up_count": 0
600
- }
601
- st.rerun()
602
-
603
- # README情報
604
- st.sidebar.markdown("---")
605
- st.sidebar.markdown("### 使い方")
606
- st.sidebar.markdown("""
607
- 1. Hugging Face Spacesの環境変数でGROQ_API_KEYを設定
608
- 2. 使用るモード選択
609
- 3. チャット入力欄に質問を入力
610
- 4. 必要に応じてモードを切替え(切り替え時に前の会話要約れます)
611
- 5. 「思考プロセスを表示」をクリックすると、AIの分析過程を確認できます
612
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
613
 
614
- # API Keyがない場合の警告表示
615
  if not api_key:
616
  st.warning("""
617
  ### ⚠️ GROQ_API_KEYが設定されていません
 
1
  import streamlit as st
2
  import os
3
  import re
4
+ import subprocess
5
+ import tempfile
6
+ import json
7
  from groq import Groq
8
  from prompts import load_system_prompt, create_default_prompt_files
9
+ from streamlit_ace import st_ace
10
 
11
  # デフォルトのプロンプトファイルを作成
12
  create_default_prompt_files()
13
 
14
  # Streamlitアプリのセットアップ
15
  st.set_page_config(
16
+ page_title="コーディングアシスタント & エディタ",
17
  page_icon="💻",
18
  layout="wide"
19
  )
20
 
21
+ # セッション状態の初期化
22
+ if "files" not in st.session_state:
23
+ st.session_state.files = {} # ファイル名: コン
24
+
25
+ if "current_file" not in st.session_state:
26
+ st.session_state.current_file = None
27
 
28
  # APIキーの取得(Hugging Face Spaces環境変数から)
29
  api_key = os.getenv("GROQ_API_KEY", "")
 
38
  # 動作モードの定義
39
  MODES = ["通常モード", "実装モード", "エラー修正モード"]
40
 
41
+ # サイドバー - モード選択とファイル管理
42
+ with st.sidebar:
43
+ st.title("ディングアシスタント & エディタ 💻")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
+ # モード選択タブ
46
+ selected_mode = st.radio(
47
+ "モード選択",
48
+ MODES
49
+ )
50
 
51
+ # モード説明の表示
52
+ mode_descriptions = {
53
+ "通常モード": "Mistral Sabaがユーザー入力を分析し、必要に応じてDeepSeekの知識も活用した上で、Qwen Coderが回答を生成します。",
54
+ "実装モード": "ユーザー入力分析に基づき、Qwen Coderが設計書を作成した後、実装コードを提供します。",
55
+ "エラー修正モード": "提示されたコードのエラーを分析し、Qwen Coderが修正案を提供します。"
 
 
 
 
 
56
  }
 
 
 
 
 
57
 
58
+ st.markdown(f"**{selected_mode}**: {mode_descriptions[selected_mode]}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
+ # ファイル管セクション
61
+ st.header("ファイル管理")
 
 
 
 
62
 
63
+ # 新規ファイル作成
64
+ new_file_name = st.text_input("新規ファイル名", value="", key="new_file")
65
+ new_file_col1, new_file_col2 = st.columns([3, 1])
 
 
66
 
67
+ with new_file_col1:
68
+ file_extension = st.selectbox(
69
+ "拡張子",
70
+ options=[".py", ".js", ".html", ".css", ".json", ".txt"]
71
+ )
72
 
73
+ with new_file_col2:
74
+ if st.button("作成", key="create_file_btn"):
75
+ full_filename = new_file_name + file_extension
76
+ if full_filename not in st.session_state.files and new_file_name:
77
+ st.session_state.files[full_filename] = ""
78
+ st.session_state.current_file = full_filename
79
+ st.success(f"ファイル '{full_filename}' を作成しました")
80
+ st.rerun()
81
+ elif not new_file_name:
82
+ st.error("ファイル名を入力してください")
83
+ else:
84
+ st.error(f"ファイル '{full_filename}' は既に存在します")
85
 
86
+ # 既存ァイル一覧
87
+ if st.session_state.files:
88
+ st.subheader("既存ファイル")
89
+ for filename in st.session_state.files.keys():
90
+ col1, col2, col3 = st.columns([3, 1, 1])
91
+ with col1:
92
+ if st.button(filename, key=f"open_{filename}"):
93
+ st.session_state.current_file = filename
94
+ st.rerun()
95
+ with col2:
96
+ if st.button("削除", key=f"delete_{filename}"):
97
+ del st.session_state.files[filename]
98
+ if st.session_state.current_file == filename:
99
+ st.session_state.current_file = None
100
+ st.rerun()
101
+ with col3:
102
+ if st.button("コピー", key=f"copy_{filename}"):
103
+ name, ext = os.path.splitext(filename)
104
+ new_name = f"{name}_copy{ext}"
105
+ counter = 1
106
+ while new_name in st.session_state.files:
107
+ new_name = f"{name}_copy_{counter}{ext}"
108
+ counter += 1
109
+ st.session_state.files[new_name] = st.session_state.files[filename]
110
+ st.rerun()
111
 
112
+ # チト履歴のリア機
113
+ if st.button("会話履歴をクリア"):
114
+ st.session_state.messages = []
115
+ st.session_state.thinking_process = []
116
+ st.session_state.is_first_message = True
117
+ st.session_state.conversation_state = {
118
+ "topic": None,
119
+ "last_question_type": None,
120
+ "mentioned_entities": set(),
121
+ "follow_up_count": 0
122
+ }
123
+ st.rerun()
124
 
125
+ # 接続情報使い方のガイド
126
+ st.sidebar.markdown("---")
127
+ st.subheader("使方ガイド")
128
+ with st.expander("アプリ機能", expanded=True):
129
+ st.markdown("""
130
+ ### 主な機能
131
+ 1. **コードエディタ** - 複数のプログラミング言語に対応したシンタックスハイライト付きエディタ
132
+ 2. **Pythonコード実行** - エディタ上のPythonコードをその場で実行
133
+ 3. **AIアシスタント** - コーディングに関する質問や実装の提案
134
+ 4. **ファイル管理** - 複数のファイルの作成・保存・編集
135
+ 5. **エラー修正** - コードのエラーを自動で診断・修正
136
+
137
+ ### 特殊コマンド
138
+ - `/mode 通常` - 通常の質問応答モードに切り替え
139
+ - `/mode 実装` - コード実装モードに切り替え
140
+ - `/mode エラー修正` - エラー修正モードに切り替え
141
+ """)
142
 
143
+ with st.expander("Hugging Face Spacesで設定方法"):
144
+ st.markdown("""
145
+ ### Hugging Face Spacesでの設定手順
146
+
147
+ 1. **APIキーの設定**:
148
+ - Spacesのダッシュボードに移動
149
+ - Settings > Repository secrets
150
+ - 新しいシークレットを追加: キー名 `GROQ_API_KEY`、値に自分のGroq APIキーを入力
151
+
152
+ 2. **依存関係の追加**:
153
+ - `requirements.txt` に以下を追加:
154
+ ```
155
+ streamlit
156
+ groq
157
+ streamlit-ace
158
+ ```
159
+
160
+ 3. **ファイル構造**:
161
+ - `app.py`: メインアプリケーションファイル
162
+ - `prompts.py`: プロンプト管理用ファイル
163
+ - `prompts/`: プロンプトを保存するディレクトリ
164
+ """)
165
 
166
+ # メイエリアを2つのカラムに分割
167
+ code_col, chat_col = st.columns([1, 1])
 
 
 
 
 
168
 
169
+ # コードエディタカラム
170
+ with code_col:
171
+ st.header("コドエディタ")
 
 
 
 
 
172
 
173
+ if st.session_state.current_file:
174
+ # ファイルの拡張子に基づいて言語を設定
175
+ file_ext = os.path.splitext(st.session_state.current_file)[1].lower()
176
+ lang_map = {
177
+ ".py": "python",
178
+ ".js": "javascript",
179
+ ".html": "html",
180
+ ".css": "css",
181
+ ".json": "json",
182
+ ".txt": "text"
183
+ }
184
+ language = lang_map.get(file_ext, "python")
185
+
186
+ # エディタの表示
187
+ st.subheader(f"編集中: {st.session_state.current_file}")
188
+
189
+ code = st_ace(
190
+ value=st.session_state.files[st.session_state.current_file],
191
+ language=language,
192
+ theme="monokai",
193
+ keybinding="vscode",
194
+ font_size=14,
195
+ min_lines=20,
196
+ key=f"ace_editor_{st.session_state.current_file}"
197
+ )
198
+
199
+ # コードを保存
200
+ st.session_state.files[st.session_state.current_file] = code
201
+
202
+ # Python codeの場合、実行ボタンを表示
203
+ if language == "python":
204
+ if st.button("コードを実行", key="run_code_btn"):
205
+ with st.spinner("コードを実行中..."):
206
+ # 一時ファイルを作成してコードを実行
207
+ with tempfile.NamedTemporaryFile(suffix='.py', delete=False) as tmp:
208
+ tmp.write(code.encode())
209
+ tmp_name = tmp.name
210
+
211
+ try:
212
+ # サブプロセスでPythonコードを実行
213
+ result = subprocess.run(
214
+ ["python", tmp_name],
215
+ capture_output=True,
216
+ text=True,
217
+ timeout=10 # 10秒のタイムアウト
218
+ )
219
+
220
+ # 実行結果を表示
221
+ if result.stdout:
222
+ st.code(result.stdout, language="text")
223
+
224
+ if result.stderr:
225
+ st.error(result.stderr)
226
+
227
+ # エラーが発生した場合、自動的にエラー修正モードに切り替える
228
+ if st.button("エラー修正AIに相談", key="ask_error_fix"):
229
+ st.session_state.current_mode = "エラー修正モード"
230
+ error_query = f"次のPythonコードにエラーがあります。修正方法を教えてください。\n\n```python\n{code}\n```\n\nエラーメッセージ:\n```\n{result.stderr}\n```"
231
+
232
+ # チャットにエラー修正用のメッセージを追加
233
+ if "messages" not in st.session_state:
234
+ st.session_state.messages = []
235
+
236
+ st.session_state.messages.append({"role": "user", "content": error_query})
237
+ st.rerun()
238
+
239
+ except subprocess.TimeoutExpired:
240
+ st.error("実行がタイムアウトしました(10秒以上かかりました)")
241
+
242
+ except Exception as e:
243
+ st.error(f"実行中にエラーが発生しました: {str(e)}")
244
+
245
+ finally:
246
+ # 一時ファイルを削除
247
+ if os.path.exists(tmp_name):
248
+ os.unlink(tmp_name)
249
+
250
+ # ファイルエクスポート機能
251
+ st.download_button(
252
+ label="ファイルをダウンロード",
253
+ data=code,
254
+ file_name=st.session_state.current_file,
255
+ key="download_file"
256
+ )
257
 
258
+ else:
259
+ st.info("左のサイドバーから既存のファイルを選択するか、新しいファイルを作成してください。")
260
 
261
+ # チャットカラム
262
+ with chat_col:
263
+ st.header("AIアシスタント")
 
264
 
265
+ # ション初期化
266
+ if "messages" not in st.session_state:
267
+ st.session_state.messages = []
268
+
269
+ if "thinking_process" not in st.session_state:
270
+ st.session_state.thinking_process = []
271
+
272
+ if "current_mode" not in st.session_state:
273
+ st.session_state.current_mode = selected_mode
274
+
275
+ if "is_first_message" not in st.session_state:
276
+ st.session_state.is_first_message = True
277
 
278
+ # 会話状態の初期化
279
+ if "conversation_state" not in st.session_state:
280
+ st.session_state.conversation_state = {
281
+ "topic": None, # 会話トピック
282
+ "last_question_type": None, # 前回質問のタイ
283
+ "mentioned_entities": set(), # 会話で言及されたエンティティ
284
+ "follow_up_count": 0 # フォローアップ質問の数
285
+ }
286
 
287
+ # モード変更の検出
288
+ if st.session_state.current_mode != selected_mode:
289
+ st.session_state.current_mode = selected_mode
290
+ st.session_state.is_first_message = True
291
+
292
+ # モード変更時にこれまでのチャットを要約
293
+ if len(st.session_state.messages) > 0:
294
+ with st.spinner("前回の会話内容を要約しています..."):
295
+ try:
296
+ # チャット履歴を文字列にまとめる
297
+ chat_history = ""
298
+ for msg in st.session_state.messages:
299
+ role = "ユーザー" if msg["role"] == "user" else "アシスタント"
300
+ chat_history += f"{role}: {msg['content']}\n\n"
301
+
302
+ # 要約を生成
303
+ client = Groq(api_key=api_key)
304
+ summarize_prompt = f"""
305
+ 以下はユーザーとアシスタントの間の会話です。この会話を300単語以内で要約してください。
306
+ 特に重要な内容、技術的な詳細、解決した問題に焦点を当ててください。
307
+
308
+ {chat_history}
309
+ """
310
+
311
+ # Mistral Sabaモデルのシステムプロンプトをロード
312
+ summary_system_prompt = load_system_prompt(MODELS["mistral_saba"])
313
+
314
+ summary_response = client.chat.completions.create(
315
+ model=MODELS["mistral_saba"],
316
+ messages=[
317
+ {"role": "system", "content": summary_system_prompt},
318
+ {"role": "user", "content": summarize_prompt}
319
+ ],
320
+ temperature=0.3,
321
+ max_tokens=1000
322
+ )
323
+
324
+ summary = summary_response.choices[0].message.content
325
+ st.session_state.chat_summary = summary
326
+
327
+ # 要約をthinking_processに追加
328
+ st.session_state.thinking_process.append({
329
+ "title": "前回の会話要約",
330
+ "content": summary
331
+ })
332
+
333
+ # ユーザーに要約を通知
334
+ st.info(f"モードが変更されました。前回の会話の要約:\n\n{summary}")
335
+ except Exception as e:
336
+ st.error(f"会話要約の生成中にエラーが発生しました: {str(e)}")
337
 
338
+ # 物理学関連のキーワード
339
+ PHYSICS_KEYWORDS = [
340
+ "物理", "力学", "電磁気", "熱力学", "量子", "相対性", "波動", "力", "運動", "エネルギー",
341
+ "physics", "mechanics", "electromagnetism", "thermodynamics", "quantum", "relativity",
342
+ "wave", "force", "motion", "energy", "シミュレーション", "simulation", "重力", "gravity",
343
+ "加速度", "acceleration", "速度", "velocity", "衝突", "collision", "反発", "rebound",
344
+ "摩擦", "friction", "剛体", "rigid body", "粒子", "particle", "流体", "fluid",
345
+ "弾性", "elastic", "軌道", "trajectory", "バネ", "spring", "圧力", "pressure"
346
+ ]
347
 
348
+ # テキストが物理学に関連しているかチェック
349
+ def is_physics_related(text):
350
+ lower_text = text.lower()
351
+
352
+ # 明確な物理関連キーワード(より具体的で誤検出が少ないもの)
353
+ explicit_physics_keywords = [
354
+ "物理法則", "物理シミュレーション", "ニュートン力学", "電磁気学", "熱力学", "量子力学", "相対性理論",
355
+ "physics simulation", "newton's laws", "electromagnetism", "thermodynamics",
356
+ "quantum mechanics", "relativity theory"
357
+ ]
358
+
359
+ # 単語境界を考慮すべきキーワード(一般的な単語だが、単独で使われた場合は物理関連の可能性が高い)
360
+ word_boundary_keywords = [
361
+ r"\b力学\b", r"\b重力\b", r"\b摩擦\b", r"\b衝突\b", r"\b運動方程式\b", r"\b加速度\b", r"\b速度\b", r"\b質量\b",
362
+ r"\bgravity\b", r"\bfriction\b", r"\bcollision\b", r"\bvelocity\b", r"\bacceleration\b", r"\bmass\b"
363
+ ]
364
+
365
+ # 明確な物理用語が含まれているか
366
+ explicit_match = any(keyword.lower() in lower_text for keyword in explicit_physics_keywords)
367
+
368
+ # 境界を考慮すべき単語が含まれているか(単語として独立して存在する場合のみ)
369
+ boundary_match = any(re.search(pattern, lower_text) for pattern in word_boundary_keywords)
370
+
371
+ # 物理シミュレーション関連の特定のフレーズパターン
372
+ physics_phrases = [
373
+ r"物理.*シミュレ(ーション|ート)",
374
+ r"physics.*simulation",
375
+ r"ボールの.*衝突",
376
+ r"ball.*collision",
377
+ r"重力.*計算",
378
+ r"gravity.*calculation",
379
+ r"運動方程式",
380
+ r"equation.*motion",
381
+ r"力学.*モデル",
382
+ r"mechanics.*model"
383
+ ]
384
+ phrase_match = any(re.search(pattern, lower_text) for pattern in physics_phrases)
385
+
386
+ # 誤検出しやすいコンテキストのチェック(これらが含まれる場合は物理関連ではない可能性が高い)
387
+ non_physics_contexts = [
388
+ "アプリの動作", "システムの動作", "プログラムの動作", "ソフトウェアの動作",
389
+ "app behavior", "system behavior", "program behavior", "software behavior",
390
+ "UIの動き", "インターフェースの動き", "ボタンの動き",
391
+ "UI movement", "interface movement", "button movement"
392
+ ]
393
+ negative_context = any(context.lower() in lower_text for context in non_physics_contexts)
394
+
395
+ # 物理関連であると判断するロジック:
396
+ # 1. 明確な物理用語が含まれている、または
397
+ # 2. 境界を考慮すべき単語が含まれていて、かつ誤検出コンテキストが含まれていない、または
398
+ # 3. 物理シミュレーション関連の特定のフレーズが含まれている
399
+ return explicit_match or (boundary_match and not negative_context) or phrase_match
400
 
401
+ # テキスト内にコードブロックが含まれているか確認
402
+ def contains_code_block(text):
403
+ # マークダウンのコードブロックパターン
404
+ code_block_pattern = r"```[\w]*\n[\s\S]*?\n```"
405
+ return bool(re.search(code_block_pattern, text))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
406
 
407
+ # テキストからすべてのコードブロックを抽出する
408
+ def extract_all_code_blocks(text):
409
+ """テキストから全てコードブロック抽出す"""
410
+ code_block_pattern = r"```(\w+)?\n([\s\S]*?)\n```"
411
+ matches = re.findall(code_block_pattern, text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412
 
413
+ if not matches:
414
+ return []
 
415
 
416
+ code_blocks = []
417
+ for lang, code in matches:
418
+ lang = lang or "python" # 言語指定がない場合はPythonと仮定
419
+ code_blocks.append(f"```{lang}\n{code}\n```")
420
 
421
+ return code_blocks
 
 
 
 
 
 
 
 
 
 
422
 
423
+ # 生成されたコードを新しいファイルに保存する関数
424
+ def save_generated_code_to_new_file(code_text, suggested_name=None):
425
+ # コードブロックのマークアップを削除
426
+ code_pattern = r"```(?:\w+)?\n([\s\S]*?)\n```"
427
+ match = re.search(code_pattern, code_text)
428
+ clean_code = match.group(1) if match else code_text
429
+
430
+ # 言語推測
431
+ language = "py" # デフォルトはPython
432
+ lang_pattern = r"```(\w+)\n"
433
+ lang_match = re.search(lang_pattern, code_text)
434
+ if lang_match:
435
+ detected_lang = lang_match.group(1).lower()
436
+ lang_mapping = {
437
+ "python": "py",
438
+ "javascript": "js",
439
+ "html": "html",
440
+ "css": "css",
441
+ "json": "json",
442
+ "java": "java",
443
+ "cpp": "cpp",
444
+ "c++": "cpp",
445
+ "c": "c",
446
+ "go": "go",
447
+ "rust": "rs",
448
+ "typescript": "ts"
449
+ }
450
+ language = lang_mapping.get(detected_lang, detected_lang)
451
+
452
+ # ファイル名の生成
453
+ if not suggested_name:
454
+ import hashlib
455
+ import time
456
+ # タイムスタンプとコードの一部からハッシュを生成
457
+ timestamp = str(int(time.time()))
458
+ code_hash = hashlib.md5(clean_code[:100].encode()).hexdigest()[:6]
459
+ suggested_name = f"generated_{timestamp}_{code_hash}"
460
+
461
+ # 拡張子が含まれていなければ追加
462
+ if "." not in suggested_name:
463
+ suggested_name = f"{suggested_name}.{language}"
464
+
465
+ # 同名ファイルが存在する場合は連番を付ける
466
+ base_name, ext = os.path.splitext(suggested_name)
467
+ counter = 1
468
+ file_name = suggested_name
469
+ while file_name in st.session_state.files:
470
+ file_name = f"{base_name}_{counter}{ext}"
471
+ counter += 1
472
+
473
+ # ファイルを保存
474
+ st.session_state.files[file_name] = clean_code
475
+ st.session_state.current_file = file_name
476
+
477
+ return file_name
478
+
479
+ # 過去のメッセージを表示
480
+ for message in st.session_state.messages:
481
+ with st.chat_message(message["role"]):
482
+ st.markdown(message["content"])
483
+
484
+ # 思考プロセスの表示(開閉可能なエクスパンダー)
485
+ if st.session_state.thinking_process:
486
+ with st.expander("思考プロセスを表示"):
487
+ for i, thought in enumerate(st.session_state.thinking_process):
488
+ st.subheader(thought["title"])
489
+ st.markdown(thought["content"])
490
+ st.divider()
491
+
492
+ # 会話状態を更新する関数
493
+ def update_conversation_state(user_input, ai_response):
494
+ # 主要トピックの更新(最初のメッセージから)
495
+ if st.session_state.conversation_state["topic"] is None and user_input:
496
+ # 簡易的なトピック抽出(最初の文か最初の30単語)
497
+ first_sentence = user_input.split('.')[0]
498
+ topic = first_sentence[:100] + "..." if len(first_sentence) > 100 else first_sentence
499
+ st.session_state.conversation_state["topic"] = topic
500
+
501
+ # フォローアップカウントの更新
502
+ if "?" in user_input or "?" in user_input:
503
+ st.session_state.conversation_state["follow_up_count"] += 1
504
+
505
+ # その他の状態更新ロジックをここに追加
506
+ pass
507
+
508
+ # APIを使用した思考プロセスと回答の生成
509
+ def generate_response(user_input):
510
+ client = Groq(api_key=api_key)
511
+ thinking_results = []
512
 
513
+ # ザー入力が物理関連かどうかチェック
514
+ is_physics = is_physics_related(user_input)
515
 
516
+ # Step 1: Mistral Sabaによる入力分析
517
+ mistral_system_prompt = load_system_prompt(MODELS["mistral_saba"])
518
+ mistral_prompt = f"""
519
+ 以下のユーザー入力を分析してください。ユーザーの質問や要求の本質、重要なキーワード、技術的課題を抽出し、
520
+ どのようなアプローチで回答すべきかを詳細に検討してください。
521
 
522
  ユーザー入力: {user_input}
 
 
 
523
  """
524
 
525
+ mistral_response = client.chat.completions.create(
526
+ model=MODELS["mistral_saba"],
 
 
 
 
527
  messages=[
528
+ {"role": "system", "content": mistral_system_prompt},
529
+ {"role": "user", "content": mistral_prompt}
530
  ],
531
+ temperature=0.3,
532
+ max_tokens=2000
533
  )
534
 
535
+ mistral_analysis = mistral_response.choices[0].message.content
536
  thinking_results.append({
537
+ "title": "Mistral Sabaによる分析",
538
+ "content": mistral_analysis
539
  })
540
 
541
+ # 物理関連場合のみDeepSeekの意見取得
542
+ deepseek_knowledge = ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
543
  if is_physics:
544
+ deepseek_system_prompt = load_system_prompt(MODELS["deepseek"])
545
+ deepseek_prompt = f"""
546
+ 以下のユーザー入力は物理学に関連しています。物理学の観点から、この質問や課題に対する
547
+ 科学的知識、法則、公式、アプローチを提供してください。特に関連する物理概念と実装方法の
548
+ 関連性があれば説明してください。
549
+
550
+ ユーザー入力: {user_input}
551
+ """
552
+
553
+ deepseek_response = client.chat.completions.create(
554
+ model=MODELS["deepseek"],
555
+ messages=[
556
+ {"role": "system", "content": deepseek_system_prompt},
557
+ {"role": "user", "content": deepseek_prompt}
558
+ ],
559
+ temperature=0.3,
560
+ max_tokens=2000
561
+ )
562
+
563
+ deepseek_knowledge = deepseek_response.choices[0].message.content
564
+ thinking_results.append({
565
+ "title": "DeepSeekによる物理学的知見",
566
+ "content": deepseek_knowledge
567
+ })
568
+
569
+ # Step 2: モード別の応答生成
570
+ if st.session_state.current_mode == "通常モード":
571
+ # 物理関連の場合のみDeepSeekの情報を含める
572
+ deepseek_section = ""
573
+ physics_consideration = ""
574
+
575
+ if is_physics:
576
+ physics_consideration = " DeepSeekの物理学的知見を考慮して"
577
+ deepseek_section = f"DeepSeekの物理学的知見:\n{deepseek_knowledge}"
578
+
579
+ # 現在のエディタのコードを取得(コードエディタで何かが開かれている場合)
580
+ editor_code = ""
581
+ editor_reference = ""
582
+ if st.session_state.current_file and st.session_state.current_file in st.session_state.files:
583
+ editor_code = st.session_state.files[st.session_state.current_file]
584
+ editor_reference = f"現在エディタで開かれているファイル '{st.session_state.current_file}':\n```\n{editor_code}\n```"
585
+
586
+ # 通常モード用のシステムプロンプトをロード
587
+ system_prompt = load_system_prompt(MODELS["qwen_coder"], "通常モード")
588
+
589
+ qwen_prompt = f"""
590
+ 以下のユーザー入力に対してコーディングアシスタントとして回答してください。
591
+ Mistral Sabaの分析結果{physics_consideration}を考慮して、
592
+ 最適なソリューションを提供してください。
593
+
594
+ ユーザー入力: {user_input}
595
+
596
+ Mistral Sabaの分析:
597
+ {mistral_analysis}
598
+ """
599
+
600
+ # 物理関連の場合のみDeepSeekの情報を追加
601
+ if is_physics:
602
+ qwen_prompt += f"\n\n{deepseek_section}"
603
+
604
+ # エディタのコードが存在する場合は参照として追加
605
+ if editor_code:
606
+ qwen_prompt += f"\n\n{editor_reference}\n\nエディタで開かれているコードに関して、ユーザーの質問に答えるか、必要に応じてコードの改善を提案してください。"
607
+
608
+ qwen_response = client.chat.completions.create(
609
+ model=MODELS["qwen_coder"],
610
+ messages=[
611
+ {"role": "system", "content": system_prompt},
612
+ {"role": "user", "content": qwen_prompt}
613
+ ],
614
+ temperature=0.5,
615
+ max_tokens=4000
616
+ )
617
+
618
+ final_response = qwen_response.choices[0].message.content
619
 
620
+ elif st.session_state.current_mode == "実装モード":
621
+ # 物理関連の条件分岐を変数で処理
622
+ deepseek_section = ""
623
+ physics_note = ""
624
+
625
+ if is_physics:
626
+ deepseek_section = f"DeepSeekの物理学的知見:\n{deepseek_knowledge}"
627
+ physics_note = "この実装には物理シミュレーションが含まれているため、物理法則の正確性と数値的安定性を特に重視してください。"
628
+
629
+ # Step 2a: 設計フェーズ - 専用のプロンプトを使用
630
+ design_system_prompt = load_system_prompt(MODELS["qwen_coder"], "実装モード_設計")
631
+
632
+ design_prompt = f"""
633
+ 以下のユーザー入力と分析結果に基づいて、実装に必要な設計書を作成してください。
634
+
635
+ ユーザー入力: {user_input}
636
+
637
+ Mistral Sabaの分析:
638
+ {mistral_analysis}
639
+ """
640
+
641
+ # 物理関連の場合のみDeepSeekの情報を追加
642
+ if is_physics:
643
+ design_prompt += f"\n\n{deepseek_section}"
644
+
645
+ design_response = client.chat.completions.create(
646
+ model=MODELS["qwen_coder"],
647
+ messages=[
648
+ {"role": "system", "content": design_system_prompt},
649
+ {"role": "user", "content": design_prompt}
650
+ ],
651
+ temperature=0.4,
652
+ max_tokens=3000
653
+ )
654
+
655
+ design_doc = design_response.choices[0].message.content
656
+ thinking_results.append({
657
+ "title": "Qwen Coderによる設計書",
658
+ "content": design_doc
659
+ })
660
+
661
+ # Step 2b: 実装フェーズ - 専用のプロンプトを使用
662
+ implementation_system_prompt = load_system_prompt(MODELS["qwen_coder"], "実装モード_実装")
663
+
664
+ implementation_prompt = f"""
665
+ 以下の設計書に基づいて、完全な実装コードを提供してください。
666
+ コードは実行可能で、エラー処理が適切に行われているものにしてください。
667
+ 各コンポーネントには十分なコメントを付け、使用方法の例も含めてください。
668
+ {physics_note}
669
+
670
+ 設計書:
671
+ {design_doc}
672
+
673
+ ユーザーの元々の要求:
674
+ {user_input}
675
+ """
676
+
677
+ implementation_response = client.chat.completions.create(
678
+ model=MODELS["qwen_coder"],
679
+ messages=[
680
+ {"role": "system", "content": implementation_system_prompt},
681
+ {"role": "user", "content": implementation_prompt}
682
+ ],
683
+ temperature=0.5,
684
+ max_tokens=4000
685
+ )
686
+
687
+ implementation_result = implementation_response.choices[0].message.content
688
+
689
+ # 設計書と実装を組み合わせた最終結果
690
+ final_response = f"# 設計書\n\n{design_doc}\n\n# 実装コード\n\n{implementation_result}"
691
+
692
+ # コードの自動抽出と保存機能
693
+ code_blocks = extract_all_code_blocks(implementation_result)
694
+ if code_blocks and len(code_blocks) > 0:
695
+ # 抽出したコードブロックに対してボタンを作成するための識別子を付加
696
+ final_response += "\n\n**実装されたコードをエディタに保存できます。チャット上の「コードを保存」ボタンをクリックしてください。**"
697
+ # 最初のコードブロックを抽出してセッション状態に保存
698
+ st.session_state.last_generated_code = code_blocks[0]
699
+
700
+ elif st.session_state.current_mode == "エラー修正モード":
701
+ # エディタのコードを取得(存在する場合)
702
+ editor_code = ""
703
+ editor_reference = ""
704
+ if st.session_state.current_file and st.session_state.current_file in st.session_state.files:
705
+ editor_code = st.session_state.files[st.session_state.current_file]
706
+ editor_reference = f"現在エディタで開かれているファイル '{st.session_state.current_file}':\n```\n{editor_code}\n```\n\nこのコードにエラーがある可能性があります。ユーザーからの情報と合わせて分析してください。"
707
+
708
+ # 物理関連の条件分岐を変数で処理
709
+ deepseek_section = ""
710
+
711
+ if is_physics:
712
+ deepseek_section = f"DeepSeekの物理学的知見:\n{deepseek_knowledge}"
713
+
714
+ # エラー修正モード用のシステムプロンプトをロード
715
+ error_system_prompt = load_system_prompt(MODELS["qwen_coder"], "エラー修正モード")
716
+
717
+ error_prompt = f"""
718
+ 以下のユーザー入力はコードのエラーに関するものです。
719
+ エラーの原因を特定し、修正案を提供してください。
720
+ 説明では:
721
+ 1. エラーの根本原因
722
+ 2. 問題箇所の特定
723
+ 3. 修正方法の詳細な説明
724
+ 4. 修正されたコード
725
+ 5. 同様のエラーを防ぐためのベストプラクティス
726
+ を含めてください。
727
+
728
+ ユーザー入力: {user_input}
729
+
730
+ Mistral Sabaの分析:
731
+ {mistral_analysis}
732
+ """
733
+
734
+ # エディタのコードを追加
735
+ if editor_code:
736
+ error_prompt += f"\n\n{editor_reference}"
737
+
738
+ # 物理関連の場合のみDeepSeekの情報を追加
739
+ if is_physics:
740
+ error_prompt += f"\n\n{deepseek_section}"
741
+
742
+ error_response = client.chat.completions.create(
743
+ model=MODELS["qwen_coder"],
744
+ messages=[
745
+ {"role": "system", "content": error_system_prompt},
746
+ {"role": "user", "content": error_prompt}
747
+ ],
748
+ temperature=0.4,
749
+ max_tokens=4000
750
+ )
751
+
752
+ final_response = error_response.choices[0].message.content
753
 
754
+ return final_response, thinking_results
 
 
 
 
 
 
 
 
 
 
 
 
 
 
755
 
756
+ # 最後に生成されたコードを保存するセッション変数の初期化
757
+ if "last_generated_code" not in st.session_state:
758
+ st.session_state.last_generated_code = None
759
 
760
+ # 「コードを保存」ボタンが押された場合の処理
761
+ if st.session_state.last_generated_code and st.button("AIが生成したコードをエディタに保存"):
762
+ with st.spinner("コードを保存中..."):
763
+ file_name = save_generated_code_to_new_file(st.session_state.last_generated_code)
764
+ st.success(f"コードを '{file_name}' として保存しました")
765
+ st.session_state.last_generated_code = None # 保存したのでクリア
766
+ st.rerun()
767
 
768
+ # ユーザー入力
769
+ if prompt := st.chat_input("質問を入力してください"):
770
+ # ユーザーメッセージを表示
771
+ st.chat_message("user").markdown(prompt)
772
+
773
+ # ユーザーメッセージをセッション状態に追加
774
+ st.session_state.messages.append({"role": "user", "content": prompt})
775
+
776
+ # モード切替コマンドを検出
777
+ if prompt.strip().startswith("/mode"):
778
+ parts = prompt.strip().split()
779
+ if len(parts) < 2:
780
+ response = "モード指定が不完全です。使用方法: /mode [通常|実装|エラー修正]"
 
 
 
 
 
 
 
 
781
  else:
782
+ mode = parts[1].lower()
783
+ valid_modes = {"通常": "通常モード", "実装": "実装モード", "エラー修正": "エラー修正モード"}
 
 
 
 
 
 
 
 
 
 
 
 
784
 
785
+ if mode not in valid_modes and mode not in valid_modes.values():
786
+ response = f"無効なモードです。使用可能なモード: {', '.join(valid_modes.keys())}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
787
  else:
788
+ old_mode = st.session_state.current_mode
789
+ if mode in valid_modes:
790
+ st.session_state.current_mode = valid_modes[mode]
791
+ else:
792
+ st.session_state.current_mode = mode
793
+
794
+ st.session_state.is_first_message = True
795
+
796
+ # モード変更時の説明メッセージを用意
797
+ mode_descriptions = {
798
+ "通常モード": "通常の質問モードに切り替",
799
+ "実装モド": "アイデアから設計と実装を生成するモードに切り替えま",
800
+ "エラー修正モード": "エラーメッセージから問題を診断・修正するモードに切替えま"
801
+ }
802
+
803
+ instructions = {
804
+ "通常モード": "質問や議論したいトピックを入力してください。",
805
+ "実装モード": "実装したい機能やアイデアを入力してください。",
806
+ "エラー修正モード": "発生したエラメッセージとコード入力してください。"
807
+ }
808
+
809
+ response = f"ドを「{old_mode}」から「{st.session_state.current_mode}」に変更しました。\n\n{mode_descriptions[st.session_state.current_mode]}\n\n{instructions[st.session_state.current_mode]}"
810
+
811
+ # レスポンスを表示
812
+ st.chat_message("assistant").markdown(response)
813
+
814
+ # アシスタントメッセージをセッション状態に追加
815
+ st.session_state.messages.append({"role": "assistant", "content": response})
816
+
817
+ # 通常質問処理
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
818
  else:
819
+ # APIキーがある場合み実行
820
+ if api_key:
821
+ try:
822
+ # 最初のメッセージはチェーン使用、それ以降はQwen Coderのみで応答
823
+ if st.session_state.is_first_message:
824
+ with st.spinner("回答を生成中...(複数のモデルによる分析を行っています)"):
825
+ response, thinking_results = generate_response(prompt)
826
+ st.session_state.thinking_process = thinking_results
827
+ st.session_state.is_first_message = False
828
+ else:
829
+ with st.spinner("回答を生成中..."):
830
+ # 直接Qwen Coderで応答
831
+ client = Groq(api_key=api_key)
832
+
833
+ # 現在のモードに合わせたシステムプロンプトをロード
834
+ current_system_prompt = load_system_prompt(MODELS["qwen_coder"], st.session_state.current_mode)
835
+
836
+ # 会話の連続性のための追加指示
837
+ current_system_prompt += """
838
+
839
+ 重要: これは継続中の会話で。前の質問や回答踏まえて、会話の自然な流れを維持してください。
840
+ ユーザーの最新の質問だけに集中するのではなく、会話全体の文脈考慮してください。
841
+ 返し避け、新しい情報や洞察を提供してくだい。
842
+ """
843
+
844
+ # 過去の会話履歴を完全に構築
845
+ context = ""
846
+ if len(st.session_state.messages) > 2:
847
+ # 最近の会話履歴(最大トークン制限を考慮して適切な数に制限)
848
+ recent_messages = st.session_state.messages[-10:] # 最近の10メッセージまで
849
+ for msg in recent_messages:
850
+ role = "ユーザー" if msg["role"] == "user" else "アシスタント"
851
+ # 全文を含める
852
+ context += f"{role}: {msg['content']}\n\n"
853
+
854
+ # 現在のエディタのコードを取得(存在する場合)
855
+ editor_code = ""
856
+ editor_reference = ""
857
+ if st.session_state.current_file and st.session_state.current_file in st.session_state.files:
858
+ editor_code = st.session_state.files[st.session_state.current_file]
859
+ editor_reference = f"\n\n現在エディタで開かれているファイル '{st.session_state.current_file}':\n```\n{editor_code}\n```\n\nエディタで開かれているコードに関して、ユーザーの質問に答えるか、必要に応じてコードの改善を提案してください。"
860
+
861
+ qwen_prompt = f"""
862
+ あなたはユーザーと継続的な会話を行っているアシスタントです。
863
+ これまでの会話の流れを踏まえて、最新のユーザー入力に適切に応答してください。
864
+
865
+ これまでの会話:
866
+ {context}
867
+
868
+ 最新のユーザー入力: {prompt}
869
+
870
+ 前の会話の文脈を考慮して、この最新の質問に直接回答してください。
871
+ 前回までの内容を繰り返すのではなく、会話を自然に進めてください。
872
+ """
873
+
874
+ # エディタのコードが存在する場合は参照として追加
875
+ if editor_code:
876
+ qwen_prompt += editor_reference
877
+
878
+ qwen_response = client.chat.completions.create(
879
+ model=MODELS["qwen_coder"],
880
+ messages=[
881
+ {"role": "system", "content": current_system_prompt},
882
+ {"role": "user", "content": qwen_prompt}
883
+ ],
884
+ temperature=0.5,
885
+ max_tokens=4000
886
+ )
887
+
888
+ response = qwen_response.choices[0].message.content
889
+
890
+ # レスポンスを表示
891
+ st.chat_message("assistant").markdown(response)
892
+
893
+ # アシスタントメッセージをセッション状態に追加
894
+ st.session_state.messages.append({"role": "assistant", "content": response})
895
+
896
+ # 会話状態を更新
897
+ update_conversation_state(prompt, response)
898
+
899
+ # モード切替提案を追加(UIには表示しない)
900
+ mode_suggestions = {
901
+ "通常モード": "\n\n---\n*実装を生成するには、「/mode 実装」と入力して実装モードに切り替えてください。*",
902
+ "実装モード": "\n\n---\n*このコードを検証・修正するには、「/mode エラー修正」と入力して検証修正モードに切り替えてください。*",
903
+ "エラー修正モード": "\n\n---\n*新しい実装を生成するには、「/mode 実装」と入力して実装モードに切り替えてください。*"
904
+ }
905
+
906
+ # 提案はセッションステートには保存するが、UIには表示しない(次回のcontext生成で使用)
907
+ suggestion = mode_suggestions.get(st.session_state.current_mode, "")
908
+ st.session_state.messages[-1]["content"] += suggestion
909
+
910
+ except Exception as e:
911
+ st.error(f"エラーが発生しました: {str(e)}")
912
+ else:
913
+ st.error("GROQ_API_KEYが設定されていません。Hugging Face Spacesの環境変数で設定してください。")
914
 
915
+ # APIキーがない場合の警告表示
916
  if not api_key:
917
  st.warning("""
918
  ### ⚠️ GROQ_API_KEYが設定されていません