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