Spaces:
Sleeping
Sleeping
File size: 38,919 Bytes
c9d618e 1c2624b c9d618e d0ad526 c9d618e a7e5883 c9d618e d0ad526 c9d618e d0ad526 c9d618e c7d80b8 c9d618e cd4878a c9d618e cd4878a c9d618e 650fdad c9d618e d0ad526 c9d618e d0ad526 c9d618e d0ad526 c9d618e d0ad526 c9d618e 650fdad c9d618e d0ad526 c9d618e d0ad526 c9d618e d0ad526 c9d618e 650fdad c9d618e d0ad526 c9d618e d0ad526 c9d618e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 | import streamlit as st
import re
import os
from groq import Groq
from utils import load_system_prompt, extract_all_code_blocks, is_physics_related, save_generated_code_to_new_file, get_file_dependencies # get_file_dependenciesをインポート
def setup_chat(api_key, mobile=False):
"""チャット機能とその関連機能をセットアップする"""
# ヘッダー
st.header("AIアシスタント" if not mobile else "チャット")
# 過去のメッセージを表示
display_message_history()
# 明示的に更新する
if st.session_state.get("ui_needs_update", False) or True: # Trueを追加して常に表示
# 思考プロセスの表示(開閉可能なエクスパンダー)
if st.session_state.thinking_process:
with st.expander("思考プロセスを表示"):
for i, thought in enumerate(st.session_state.thinking_process):
st.subheader(thought["title"])
st.markdown(thought["content"])
st.divider()
# 最後に生成されたコードを保存する機能
if st.session_state.last_generated_code:
if st.button("AIが生成したコードをエディタに保存", key="save_ai_code"):
with st.spinner("コードを保存中..."):
file_name = save_generated_code_to_new_file(st.session_state.last_generated_code)
st.success(f"コードを '{file_name}' として保存しました")
st.session_state.last_generated_code = None # 保存したのでクリア
# モバイルモードならエディタタブに切り替え
if mobile:
st.session_state.active_tab = "エディタ"
st.rerun()
# フラグをリセット
st.session_state.ui_needs_update = False
# チャット入力
if prompt := st.chat_input("質問を入力してください"):
process_chat_input(prompt, api_key)
def display_message_history():
"""会話履歴を表示する"""
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
def process_chat_input(prompt, api_key):
"""ユーザー入力を処理し、応答を生成する"""
# ユーザーメッセージを表示
st.chat_message("user").markdown(prompt)
# ユーザーメッセージをセッション状態に追加
st.session_state.messages.append({"role": "user", "content": prompt})
# 編集コマンドを検出
if prompt.strip().startswith("/edit"):
process_edit_command(prompt, api_key)
return
# モード切替コマンドを検出
if prompt.strip().startswith("/mode"):
process_mode_command(prompt)
return
# ファイル解析コマンドを検出
if prompt.strip().startswith("/analyse") or prompt.strip().startswith("/analyze"):
process_analyze_command(api_key)
return
# 通常の質問処理
process_normal_query(prompt, api_key)
def process_edit_command(prompt, api_key):
"""エディタのコード編集コマンドを処理する"""
# 現在開いているファイルがない場合
if not st.session_state.current_file:
response = "エディタに開いているファイルがありません。まずはファイルを作成するか開いてください。"
st.chat_message("assistant").markdown(response)
st.session_state.messages.append({"role": "assistant", "content": response})
return
# 編集指示を抽出
instructions = prompt.replace("/edit", "").strip()
if not instructions:
instructions = "コードを改善してください。可読性を高め、バグがあれば修正し、コメントを追加してください。"
current_code = st.session_state.files[st.session_state.current_file]
file_ext = os.path.splitext(st.session_state.current_file)[1].lower()
# 言語を特定
lang_map = {
".py": "Python",
".js": "JavaScript",
".html": "HTML",
".css": "CSS",
".json": "JSON",
".txt": "テキスト"
}
language = lang_map.get(file_ext, "コード")
# AIに編集を依頼
with st.spinner("AIによるコード編集中..."):
edited_code = request_ai_edit(
api_key,
current_code,
instructions,
language
)
if edited_code:
st.session_state.files[st.session_state.current_file] = edited_code
response = f"✅ {st.session_state.current_file} を編集しました。編集結果はエディタで確認できます。"
# モバイルモードならエディタタブに切り替え
if st.session_state.mobile_mode:
st.session_state.active_tab = "エディタ"
else:
response = "⚠️ コードの編集に失敗しました。もう一度お試しください。"
# 応答を表示して保存
st.chat_message("assistant").markdown(response)
st.session_state.messages.append({"role": "assistant", "content": response})
if st.session_state.mobile_mode and edited_code:
st.rerun()
def process_mode_command(prompt):
"""モード切り替えコマンドを処理する"""
parts = prompt.strip().split()
if len(parts) < 2:
response = "モード指定が不完全です。使用方法: /mode [通常|実装|エラー修正]"
else:
mode = parts[1].lower()
valid_modes = {"通常": "通常モード", "実装": "実装モード", "エラー修正": "エラー修正モード"}
if mode not in valid_modes and mode not in valid_modes.values():
response = f"無効なモードです。使用可能なモード: {', '.join(valid_modes.keys())}"
else:
old_mode = st.session_state.current_mode
if mode in valid_modes:
st.session_state.current_mode = valid_modes[mode]
else:
st.session_state.current_mode = mode
st.session_state.is_first_message = True
# モード変更時の説明メッセージを用意
mode_descriptions = {
"通常モード": "通常の質問応答モードに切り替えました。",
"実装モード": "アイデアから設計と実装を生成するモードに切り替えました。",
"エラー修正モード": "エラーメッセージから問題を診断・修正するモードに切り替えました。"
}
instructions = {
"通常モード": "質問や議論したいトピックを入力してください。",
"実装モード": "実装したい機能やアイデアを入力してください。",
"エラー修正モード": "発生したエラーメッセージとコードを入力してください。"
}
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]}"
# レスポンスを表示
st.chat_message("assistant").markdown(response)
# アシスタントメッセージをセッション状態に追加
st.session_state.messages.append({"role": "assistant", "content": response})
def process_analyze_command(api_key):
"""アップロードされたファイル群を解析するコマンドを処理する"""
# ファイルが存在するか確認
if not st.session_state.files:
response = "解析するファイルがありません。まずはファイルをアップロードするか作成してください。"
st.chat_message("assistant").markdown(response)
st.session_state.messages.append({"role": "assistant", "content": response})
return
with st.spinner("ファイル群を解析中..."):
# 依存関係の分析を実行
dependencies, file_summary = get_file_dependencies(st.session_state.files)
# 解析結果を生成
all_files_content = ""
for filename, content in st.session_state.files.items():
# ファイルサイズが大きい場合は先頭部分のみ
if len(content) > 1000:
all_files_content += f"\n\nファイル '{filename}'(先頭部分):\n```\n{content[:1000]}...\n```"
else:
all_files_content += f"\n\nファイル '{filename}':\n```\n{content}\n```"
# AIに解析を依頼
client = Groq(api_key=api_key)
# モデル名のマッピング
from config import MODELS
system_prompt = load_system_prompt(MODELS["qwen_coder"], "通常モード")
system_prompt += """
複数のファイルからなるプロジェクトを解析する際は、以下の点に注目してください:
1. ファイル間の依存関係と呼び出し構造
2. 主要なクラスやモジュールの責任範囲
3. データの流れと状態管理の方法
4. アプリケーションのアーキテクチャパターン
5. コードの再利用性と保守性
解析結果は以下の構造で提供してください:
1. プロジェクト概要 - 全体の目的と機能
2. 主要コンポーネント - 各ファイルの役割と責任
3. 依存関係図 - ファイル間の関係性
4. アーキテクチャ評価 - 設計パターンと原則の適用状況
5. 改善提案 - リファクタリングや拡張のための提案
"""
analyze_prompt = f"""
以下のファイル群からなるプロジェクトの構造と依存関係を分析してください。
各ファイルの役割、相互関係、データフローを明確にし、システム全体の理解を深めるための
包括的な分析を提供してください。
ファイル依存関係の分析結果:
{dependencies}
ファイル概要:
{file_summary}
アップロードされたファイル群の内容(一部省略あり):
{all_files_content}
"""
# 回答の生成
response = client.chat.completions.create(
model=MODELS["qwen_coder"],
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": analyze_prompt}
],
temperature=0.5,
max_tokens=4000
)
analysis_result = response.choices[0].message.content
# 分析結果を思考プロセスに追加
st.session_state.thinking_process.append({
"title": "プロジェクト構造解析",
"content": f"依存関係:\n{dependencies}\n\nファイル概要:\n{file_summary}"
})
# レスポンスを表示
st.chat_message("assistant").markdown(analysis_result)
# アシスタントメッセージをセッション状態に追加
st.session_state.messages.append({"role": "assistant", "content": analysis_result})
# 明示的に表示を更新するために変数を設定
st.session_state.ui_needs_update = True
st.rerun()
def process_normal_query(prompt, api_key):
"""通常の質問を処理し、AIからの応答を生成する"""
# APIキーがある場合のみ実行
if api_key:
try:
# 最初のメッセージはチェーンを使用、それ以降はQwen Coderのみで応答
if st.session_state.is_first_message:
with st.spinner("回答を生成中...(複数のモデルによる分析を行っています)"):
response, thinking_results = generate_chain_response(prompt, api_key)
st.session_state.thinking_process = thinking_results
st.session_state.is_first_message = False
else:
with st.spinner("回答を生成中..."):
response = generate_direct_response(prompt, api_key)
# レスポンスを表示
st.chat_message("assistant").markdown(response)
# アシスタントメッセージをセッション状態に追加
st.session_state.messages.append({"role": "assistant", "content": response})
# 会話状態を更新
update_conversation_state(prompt, response)
# モード切替提案を追加(UIには表示しない)
mode_suggestions = {
"通常モード": "\n\n---\n*実装を生成するには、「/mode 実装」と入力して実装モードに切り替えてください。*",
"実装モード": "\n\n---\n*このコードを検証・修正するには、「/mode エラー修正」と入力して検証修正モードに切り替えてください。*",
"エラー修正モード": "\n\n---\n*新しい実装を生成するには、「/mode 実装」と入力して実装モードに切り替えてください。*"
}
# 提案はセッションステートには保存するが、UIには表示しない(次回のcontext生成で使用)
suggestion = mode_suggestions.get(st.session_state.current_mode, "")
st.session_state.messages[-1]["content"] += suggestion
# コードブロックを抽出して保存機能を提供
code_blocks = extract_all_code_blocks(response)
if code_blocks and len(code_blocks) > 0:
st.session_state.last_generated_code = code_blocks[0]
# 明示的に表示を更新するために変数を設定
st.session_state.ui_needs_update = True
# モバイルでは自動的にUI更新のためのフラグを設定
if st.session_state.mobile_mode:
st.rerun() # モバイルでは即座に画面を更新
except Exception as e:
st.error(f"エラーが発生しました: {str(e)}")
else:
st.error("GROQ_API_KEYが設定されていません。")
def generate_chain_response(user_input, api_key):
"""複数のモデルを使ったチェーンベースの応答生成"""
client = Groq(api_key=api_key)
thinking_results = []
# モデル名のマッピング
from config import MODELS
# ユーザー入力が物理関連かどうかをチェック
is_physics = is_physics_related(user_input)
# Step 1: Mistral Sabaによる入力分析
mistral_system_prompt = load_system_prompt(MODELS["mistral_saba"])
mistral_prompt = f"""
分析以下用户输入。 提取用户问题或请求的性质、关键词和技术问题、
详细考虑应采用何种方法来回答这些问题。
ユーザー入力: {user_input}
"""
mistral_response = client.chat.completions.create(
model=MODELS["mistral_saba"],
messages=[
{"role": "system", "content": mistral_system_prompt},
{"role": "user", "content": mistral_prompt}
],
temperature=0.6,
max_tokens=3000
)
mistral_analysis = mistral_response.choices[0].message.content
thinking_results.append({
"title": "Mistral Sabaによる分析",
"content": mistral_analysis
})
# 物理関連の場合のみDeepSeekの意見を取得
deepseek_knowledge = ""
if is_physics:
deepseek_system_prompt = load_system_prompt(MODELS["deepseek"])
deepseek_prompt = f"""
以下のユーザー入力は物理学に関連しています。物理学の観点から、この質問や課題に対する
科学的知識、法則、公式、アプローチを提供してください。特に関連する物理概念と実装方法の
関連性があれば説明してください。
ユーザー入力: {user_input}
"""
deepseek_response = client.chat.completions.create(
model=MODELS["deepseek"],
messages=[
{"role": "system", "content": deepseek_system_prompt},
{"role": "user", "content": deepseek_prompt}
],
temperature=0.6,
max_tokens=3000
)
deepseek_knowledge = deepseek_response.choices[0].message.content
thinking_results.append({
"title": "DeepSeekによる物理学的知見",
"content": deepseek_knowledge
})
# Step 2: モード別の応答生成
if st.session_state.current_mode == "通常モード":
# 物理関連の場合のみDeepSeekの情報を含める
deepseek_section = ""
physics_consideration = ""
if is_physics:
physics_consideration = " DeepSeekの物理学的知見を考慮して"
deepseek_section = f"DeepSeekの物理学的知見:\n{deepseek_knowledge}"
# 現在のエディタのコードを取得(コードエディタで何かが開かれている場合)
editor_code = ""
editor_reference = ""
if st.session_state.current_file and st.session_state.current_file in st.session_state.files:
editor_code = st.session_state.files[st.session_state.current_file]
editor_reference = f"現在エディタで開かれているファイル '{st.session_state.current_file}':\n```\n{editor_code}\n```"
# アップロードされた全ファイルのリストを作成
all_files_context = ""
mentioned_files = []
# ユーザー入力で言及されたファイル名を抽出
for filename in st.session_state.files.keys():
if filename in user_input and filename != st.session_state.current_file:
mentioned_files.append(filename)
# ファイルコンテキストの生成
if mentioned_files:
all_files_context = "ユーザーが言及したファイル:\n"
for filename in mentioned_files:
file_content = st.session_state.files[filename]
# ファイルサイズが大きい場合は先頭部分のみ含める
if len(file_content) > 1500:
all_files_context += f"\nファイル '{filename}'(先頭部分):\n```\n{file_content[:1500]}...\n```"
else:
all_files_context += f"\nファイル '{filename}':\n```\n{file_content}\n```"
elif len(st.session_state.files) > 1: # 複数ファイルがある場合
all_files_context = "アップロードされたファイル一覧:\n"
for filename in st.session_state.files.keys():
if filename != st.session_state.current_file: # 現在開いているファイル以外
all_files_context += f"- {filename}\n"
# 通常モード用のシステムプロンプトをロード
system_prompt = load_system_prompt(MODELS["qwen_coder"], "通常モード")
qwen_prompt = f"""
以下のユーザー入力に対してコーディングアシスタントとして回答してください。
Mistral Sabaの分析結果{physics_consideration}を考慮して、
最適なソリューションを提供してください。
ユーザー入力: {user_input}
Mistral Sabaの分析:
{mistral_analysis}
"""
# 物理関連の場合のみDeepSeekの情報を追加
if is_physics:
qwen_prompt += f"\n\n{deepseek_section}"
# エディタのコードが存在する場合は参照として追加
if editor_code:
qwen_prompt += f"\n\n{editor_reference}\n\nエディタで開かれているコードに関して、ユーザーの質問に答えるか、必要に応じてコードの改善を提案してください。"
# アップロードされたファイル情報を追加
if all_files_context:
qwen_prompt += f"\n\n{all_files_context}"
qwen_response = client.chat.completions.create(
model=MODELS["qwen_coder"],
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": qwen_prompt}
],
temperature=0.5,
max_tokens=4000
)
final_response = qwen_response.choices[0].message.content
elif st.session_state.current_mode == "実装モード":
# 物理関連の条件分岐を変数で処理
deepseek_section = ""
physics_note = ""
if is_physics:
deepseek_section = f"DeepSeekの物理学的知見:\n{deepseek_knowledge}"
physics_note = "この実装には物理シミュレーションが含まれているため、物理法則の正確性と数値的安定性を特に重視してください。"
# アップロードされたファイル情報を取得
all_files_context = ""
mentioned_files = []
# ユーザー入力で言及されたファイル名を抽出
for filename in st.session_state.files.keys():
if filename in user_input:
mentioned_files.append(filename)
# ファイルコンテキストの生成
if mentioned_files:
all_files_context = "参照すべきファイル:\n"
for filename in mentioned_files:
file_content = st.session_state.files[filename]
# ファイルサイズが大きい場合は先頭部分のみ含める
if len(file_content) > 1000:
all_files_context += f"\nファイル '{filename}'(先頭部分):\n```\n{file_content[:1000]}...\n```"
else:
all_files_context += f"\nファイル '{filename}':\n```\n{file_content}\n```"
# Step 2a: 設計フェーズ - 専用のプロンプトを使用
design_system_prompt = load_system_prompt(MODELS["qwen_coder"], "実装モード_設計")
design_prompt = f"""
以下のユーザー入力と分析結果に基づいて、実装に必要な設計書を作成してください。
ユーザー入力: {user_input}
Mistral Sabaの分析:
{mistral_analysis}
"""
# 物理関連の場合のみDeepSeekの情報を追加
if is_physics:
design_prompt += f"\n\n{deepseek_section}"
# アップロードされたファイル情報を追加
if all_files_context:
design_prompt += f"\n\n{all_files_context}\n\n上記ファイルの構造や機能を考慮して設計を行ってください。"
design_response = client.chat.completions.create(
model=MODELS["qwen_coder"],
messages=[
{"role": "system", "content": design_system_prompt},
{"role": "user", "content": design_prompt}
],
temperature=0.6,
max_tokens=4000
)
design_doc = design_response.choices[0].message.content
thinking_results.append({
"title": "Qwen Coderによる設計書",
"content": design_doc
})
# Step 2b: 実装フェーズ - 専用のプロンプトを使用
implementation_system_prompt = load_system_prompt(MODELS["qwen_coder"], "実装モード_実装")
implementation_prompt = f"""
以下の設計書に基づいて、完全な実装コードを提供してください。
コードは実行可能で、エラー処理が適切に行われているものにしてください。
各コンポーネントには十分なコメントを付け、使用方法の例も含めてください。
{physics_note}
設計書:
{design_doc}
ユーザーの元々の要求:
{user_input}
"""
# アップロードされたファイル情報を追加(実装フェーズでも必要)
if all_files_context:
implementation_prompt += f"\n\n{all_files_context}\n\n上記の既存ファイルと互換性のあるコードを生成してください。"
implementation_response = client.chat.completions.create(
model=MODELS["qwen_coder"],
messages=[
{"role": "system", "content": implementation_system_prompt},
{"role": "user", "content": implementation_prompt}
],
temperature=0.5,
max_tokens=4000
)
implementation_result = implementation_response.choices[0].message.content
# 設計書と実装を組み合わせた最終結果
final_response = f"# 設計書\n\n{design_doc}\n\n# 実装コード\n\n{implementation_result}"
# コードの自動抽出と保存機能
code_blocks = extract_all_code_blocks(implementation_result)
if code_blocks and len(code_blocks) > 0:
# 抽出したコードブロックに対してボタンを作成するための識別子を付加
final_response += "\n\n**実装されたコードをエディタに保存できます。チャット上の「コードを保存」ボタンをクリックしてください。**"
# 最初のコードブロックを抽出してセッション状態に保存
st.session_state.last_generated_code = code_blocks[0]
elif st.session_state.current_mode == "エラー修正モード":
# エディタのコードを取得(存在する場合)
editor_code = ""
editor_reference = ""
if st.session_state.current_file and st.session_state.current_file in st.session_state.files:
editor_code = st.session_state.files[st.session_state.current_file]
editor_reference = f"現在エディタで開かれているファイル '{st.session_state.current_file}':\n```\n{editor_code}\n```\n\nこのコードにエラーがある可能性があります。ユーザーからの情報と合わせて分析してください。"
# 物理関連の条件分岐を変数で処理
deepseek_section = ""
if is_physics:
deepseek_section = f"DeepSeekの物理学的知見:\n{deepseek_knowledge}"
# アップロードされたファイル情報を取得
all_files_context = ""
mentioned_files = []
# ユーザー入力で言及されたファイル名を抽出
for filename in st.session_state.files.keys():
if filename in user_input and filename != st.session_state.current_file:
mentioned_files.append(filename)
# ファイルコンテキストの生成
if mentioned_files:
all_files_context = "関連するファイル:\n"
for filename in mentioned_files:
file_content = st.session_state.files[filename]
# ファイルサイズが大きい場合は先頭部分のみ含める
if len(file_content) > 1000:
all_files_context += f"\nファイル '{filename}'(先頭部分):\n```\n{file_content[:1000]}...\n```"
else:
all_files_context += f"\nファイル '{filename}':\n```\n{file_content}\n```"
# エラー修正モード用のシステムプロンプトをロード
error_system_prompt = load_system_prompt(MODELS["qwen_coder"], "エラー修正モード")
error_prompt = f"""
以下のユーザー入力はコードのエラーに関するものです。
エラーの原因を特定し、修正案を提供してください。
説明では:
1. エラーの根本原因
2. 問題箇所の特定
3. 修正方法の詳細な説明
4. 修正されたコード
5. 同様のエラーを防ぐためのベストプラクティス
を含めてください。
ユーザー入力: {user_input}
Mistral Sabaの分析:
{mistral_analysis}
"""
# エディタのコードを追加
if editor_code:
error_prompt += f"\n\n{editor_reference}"
# 物理関連の場合のみDeepSeekの情報を追加
if is_physics:
error_prompt += f"\n\n{deepseek_section}"
# 関連ファイル情報を追加
if all_files_context:
error_prompt += f"\n\n{all_files_context}"
error_response = client.chat.completions.create(
model=MODELS["qwen_coder"],
messages=[
{"role": "system", "content": error_system_prompt},
{"role": "user", "content": error_prompt}
],
temperature=0.6,
max_tokens=4000
)
final_response = error_response.choices[0].message.content
return final_response, thinking_results
def generate_direct_response(user_input, api_key):
"""会話の連続性を考慮した直接応答を生成する"""
# APIクライアントを初期化
client = Groq(api_key=api_key)
# モデル名のマッピング
from config import MODELS
# 現在のモードに合わせたシステムプロンプトをロード
current_system_prompt = load_system_prompt(MODELS["qwen_coder"], st.session_state.current_mode)
# 会話の連続性のための追加指示
current_system_prompt += """
重要: これは継続中の会話です。前の質問や回答を踏まえて、会話の自然な流れを維持してください。
ユーザーの最新の質問だけに集中するのではなく、会話全体の文脈を考慮してください。
繰り返しは避け、新しい情報や洞察を提供してください。
"""
# 過去の会話履歴を完全に構築
context = ""
if len(st.session_state.messages) > 2:
# 最近の会話履歴(最大トークン制限を考慮して適切な数に制限)
recent_messages = st.session_state.messages[-10:] # 最近の10メッセージまで
for msg in recent_messages:
role = "ユーザー" if msg["role"] == "user" else "アシスタント"
# 全文を含める
context += f"{role}: {msg['content']}\n\n"
# 現在のエディタのコードを取得(存在する場合)
editor_code = ""
editor_reference = ""
if st.session_state.current_file and st.session_state.current_file in st.session_state.files:
editor_code = st.session_state.files[st.session_state.current_file]
editor_reference = f"\n\n現在エディタで開かれているファイル '{st.session_state.current_file}':\n```\n{editor_code}\n```\n\nエディタで開かれているコードに関して、ユーザーの質問に答えるか、必要に応じてコードの改善を提案してください。"
# ユーザー入力で言及されたファイルに関する情報を追加
all_files_context = ""
mentioned_files = []
# ユーザー入力で言及されたファイル名を抽出
for filename in st.session_state.files.keys():
if filename in user_input and filename != st.session_state.current_file:
mentioned_files.append(filename)
# ファイルコンテキストの生成
if mentioned_files:
all_files_context = "\n\nユーザーが言及したファイル:\n"
for filename in mentioned_files:
file_content = st.session_state.files[filename]
# ファイルサイズが大きい場合は先頭部分のみ含める
if len(file_content) > 1000:
all_files_context += f"\nファイル '{filename}'(先頭部分):\n```\n{file_content[:1000]}...\n```"
else:
all_files_context += f"\nファイル '{filename}':\n```\n{file_content}\n```"
qwen_prompt = f"""
あなたはユーザーと継続的な会話を行っているアシスタントです。
これまでの会話の流れを踏まえて、最新のユーザー入力に適切に応答してください。
これまでの会話:
{context}
最新のユーザー入力: {user_input}
前の会話の文脈を考慮して、この最新の質問に直接回答してください。
前回までの内容を繰り返すのではなく、会話を自然に進めてください。
"""
# エディタのコードが存在する場合は参照として追加
if editor_code:
qwen_prompt += editor_reference
# 言及されたファイルがある場合はコンテキストに追加
if all_files_context:
qwen_prompt += all_files_context
qwen_response = client.chat.completions.create(
model=MODELS["qwen_coder"],
messages=[
{"role": "system", "content": current_system_prompt},
{"role": "user", "content": qwen_prompt}
],
temperature=0.5,
max_tokens=4000
)
return qwen_response.choices[0].message.content
def request_ai_edit(api_key, code, instructions, language):
"""AIにコードの編集を依頼する"""
client = Groq(api_key=api_key)
# モデル名のマッピング
from config import MODELS
# 編集用のシステムプロンプトをロード
edit_system_prompt = """あなたは優秀なコードエディタアシスタントです。
ユーザーから提供されたコードを指示に従って編集する役割を担っています。
以下のガイドラインに従ってください:
1. コード全体を提供してください(一部だけではなく)
2. 元のコードの機能を維持してください
3. コメントを追加して読みやすくします
4. ベストプラクティスに従ってコードを改善します
5. パフォーマンスやセキュリティの問題があれば修正します
最終的な編集済みコードのみを返してください。説明や解説は含めないでください。
"""
edit_prompt = f"""
以下の{language}コードを編集してください。
編集指示:
{instructions}
コード:
```
{code}
```
編集後のコード全体を返してください。解説は不要です。
"""
try:
response = client.chat.completions.create(
model=MODELS["qwen_coder"],
messages=[
{"role": "system", "content": edit_system_prompt},
{"role": "user", "content": edit_prompt}
],
temperature=0.3,
max_tokens=4000
)
# 回答からコードを抽出
edited_code = response.choices[0].message.content
# コードブロックから実際のコードを抽出
if "```" in edited_code:
# コードブロックのマークアップを削除
code_pattern = r"```(?:\w+)?\n([\s\S]*?)\n```"
match = re.search(code_pattern, edited_code)
if match:
edited_code = match.group(1)
return edited_code
except Exception as e:
st.error(f"AIによるコード編集中にエラーが発生しました: {str(e)}")
return None
def update_conversation_state(user_input, ai_response):
"""会話の状態を更新する"""
# 主要トピックの更新(最初のメッセージから)
if st.session_state.conversation_state["topic"] is None and user_input:
# 簡易的なトピック抽出(最初の文か最初の30単語)
first_sentence = user_input.split('.')[0]
topic = first_sentence[:100] + "..." if len(first_sentence) > 100 else first_sentence
st.session_state.conversation_state["topic"] = topic
# フォローアップカウントの更新
if "?" in user_input or "?" in user_input:
st.session_state.conversation_state["follow_up_count"] += 1
def summarize_chat():
"""チャット履歴を要約する"""
# APIキーの取得
api_key = load_api_key()
if not api_key:
return "APIキーが設定されていないため、要約を生成できません。"
client = Groq(api_key=api_key)
# チャット履歴を文字列にまとめる
chat_history = ""
for msg in st.session_state.messages:
role = "ユーザー" if msg["role"] == "user" else "アシスタント"
chat_history += f"{role}: {msg['content']}\n\n"
# 要約を生成
from config import MODELS
summarize_prompt = f"""
以下はユーザーとアシスタントの間の会話です。この会話を300単語以内で要約してください。
特に重要な内容、技術的な詳細、解決した問題に焦点を当ててください。
{chat_history}
"""
# Mistral Sabaモデルのシステムプロンプトをロード
summary_system_prompt = load_system_prompt(MODELS["mistral_saba"])
summary_response = client.chat.completions.create(
model=MODELS["mistral_saba"],
messages=[
{"role": "system", "content": summary_system_prompt},
{"role": "user", "content": summarize_prompt}
],
temperature=0.3,
max_tokens=1000
)
summary = summary_response.choices[0].message.content
st.session_state.chat_summary = summary
# 要約をthinking_processに追加
st.session_state.thinking_process.append({
"title": "前回の会話要約",
"content": summary
})
return summary |