File size: 13,545 Bytes
b070b4a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4943545
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93329be
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import os
import re
import streamlit as st

# プロンプトディレクトリのパス
PROMPTS_DIR = "prompts"

# プロンプトディレクトリが存在しない場合は作成
def ensure_prompts_directory():
    if not os.path.exists(PROMPTS_DIR):
        os.makedirs(PROMPTS_DIR)

# モデル名とモードからプロンプトファイルのパスを取得
def get_prompt_path(model_name, mode=None):
    if mode:
        # モードが指定されている場合はモード別のプロンプトを返す
        return os.path.join(PROMPTS_DIR, f"{model_name}_{mode}.txt")
    else:
        # モードが指定されていない場合は基本プロンプトを返す
        return os.path.join(PROMPTS_DIR, f"{model_name}.txt")

# デフォルトのプロンプト内容 - モデル別(簡易版)
DEFAULT_MODEL_PROMPTS = {
    "mistral-saba-24b": """あなたは入力分析に特化したAIアシスタントです。ユーザーの質問や要求を深く分析し、重要な要素や課題を抽出してください。""",

    "qwen-2.5-coder-32b": """あなたはコーディングに特化した高性能AIアシスタントです。
プログラミング問題に対する効率的なソリューションを提供し、多様な言語に対応してください。""",

    "deepseek-r1-distill-llama-70b": """あなたは知識豊富なAIアシスタントです。
特に物理シミュレーションや複雑なアルゴリズムについての深い理解を持っています。"""
}

# デフォルトのプロンプト内容 - モード別(簡易版)
DEFAULT_MODE_PROMPTS = {
    "qwen-2.5-coder-32b_通常モード": """あなたはコーディングアシスタントとして、ユーザーの質問や課題に対して明確で実用的な回答を提供してください。""",

    "qwen-2.5-coder-32b_実装モード_設計": """ユーザーの要件に基づいて詳細な設計書を作成してください。
システム構造、コンポーネント責任、技術的仕様を明確に定義してください。""",

    "qwen-2.5-coder-32b_実装モード_実装": """提供された設計書に基づいて、高品質な実装コードを生成してください。
コードは実行可能で、適切なエラー処理とコメントを含むものにしてください。""",

    "qwen-2.5-coder-32b_エラー修正モード": """ユーザーが提示したコードのエラーを診断し修正してください。
エラーの根本原因を特定し、修正方法と再発防止策を提案してください。"""
}

# すべてのデフォルトプロンプトを含む辞書
ALL_DEFAULT_PROMPTS = {**DEFAULT_MODEL_PROMPTS, **DEFAULT_MODE_PROMPTS}

# プロンプトファイルが存在しない場合にデフォルトのプロンプトを作成
def create_default_prompt_files():
    ensure_prompts_directory()
    for prompt_key, prompt_text in ALL_DEFAULT_PROMPTS.items():
        prompt_path = os.path.join(PROMPTS_DIR, f"{prompt_key}.txt")
        if not os.path.exists(prompt_path):
            with open(prompt_path, "w", encoding="utf-8") as f:
                f.write(prompt_text)

# プロンプトをロード(ファイルが存在しない場合はデフォルトを使用)
def load_system_prompt(model_name, mode=None):
    ensure_prompts_directory()
    
    if mode:
        # モード指定がある場合、モード別プロンプトをロード
        prompt_key = f"{model_name}_{mode}"
        prompt_path = get_prompt_path(model_name, mode)
        
        # モード別プロンプトが存在しなければデフォルトを作成
        if not os.path.exists(prompt_path) and prompt_key in DEFAULT_MODE_PROMPTS:
            with open(prompt_path, "w", encoding="utf-8") as f:
                f.write(DEFAULT_MODE_PROMPTS[prompt_key])
                
        # ファイルが存在すればロード
        if os.path.exists(prompt_path):
            with open(prompt_path, "r", encoding="utf-8") as f:
                return f.read()
        
        # モード別プロンプトがない場合は基本プロンプトを使用
    
    # 基本プロンプトをロード
    base_prompt_path = os.path.join(PROMPTS_DIR, f"{model_name}.txt")
    if os.path.exists(base_prompt_path):
        with open(base_prompt_path, "r", encoding="utf-8") as f:
            return f.read()
    
    # どのファイルも存在しない場合、デフォルトを返す
    return DEFAULT_MODEL_PROMPTS.get(model_name, "あなたはコーディングの専門家です。ユーザーのプログラミングに関する質問に詳細かつ正確に回答してください。")

# テキストが物理学に関連しているかチェックする関数
def is_physics_related(text):
    """テキストが物理学に関連しているか判定する"""
    lower_text = text.lower()
    
    # 明確な物理関連キーワード
    explicit_physics_keywords = [
        "物理法則", "物理シミュレーション", "ニュートン力学", "電磁気学", "熱力学", "量子力学", "相対性理論", 
        "physics simulation", "newton's laws", "electromagnetism", "thermodynamics", 
        "quantum mechanics", "relativity theory"
    ]
    
    # 単語境界を考慮すべきキーワード
    word_boundary_keywords = [
        r"\b力学\b", r"\b重力\b", r"\b摩擦\b", r"\b衝突\b", r"\b運動方程式\b", r"\b加速度\b", r"\b速度\b", r"\b質量\b",
        r"\bgravity\b", r"\bfriction\b", r"\bcollision\b", r"\bvelocity\b", r"\bacceleration\b", r"\bmass\b"
    ]
    
    # 明確な物理用語が含まれているか
    explicit_match = any(keyword.lower() in lower_text for keyword in explicit_physics_keywords)
    
    # 境界を考慮すべき単語が含まれているか
    boundary_match = any(re.search(pattern, lower_text) for pattern in word_boundary_keywords)
    
    # 物理シミュレーション関連のフレーズパターン
    physics_phrases = [
        r"物理.*シミュレ(ーション|ート)",
        r"physics.*simulation",
        r"ボールの.*衝突",
        r"ball.*collision",
        r"重力.*計算",
        r"gravity.*calculation",
        r"運動方程式",
        r"equation.*motion",
        r"力学.*モデル",
        r"mechanics.*model"
    ]
    phrase_match = any(re.search(pattern, lower_text) for pattern in physics_phrases)
    
    # 誤検出しやすいコンテキスト
    non_physics_contexts = [
        "アプリの動作", "システムの動作", "プログラムの動作", "ソフトウェアの動作",
        "app behavior", "system behavior", "program behavior", "software behavior",
        "UIの動き", "インターフェースの動き", "ボタンの動き",
        "UI movement", "interface movement", "button movement"
    ]
    negative_context = any(context.lower() in lower_text for context in non_physics_contexts)
    
    return explicit_match or (boundary_match and not negative_context) or phrase_match

# テキストからコードブロックを抽出する関数
def extract_all_code_blocks(text):
    """テキストから全てのコードブロックを抽出する"""
    code_block_pattern = r"```(\w+)?\n([\s\S]*?)\n```"
    matches = re.findall(code_block_pattern, text)
    
    if not matches:
        return []
    
    code_blocks = []
    for lang, code in matches:
        lang = lang or "python"  # 言語指定がない場合はPythonと仮定
        code_blocks.append(f"```{lang}\n{code}\n```")
    
    return code_blocks

# AIが生成したコードを新しいファイルに保存する関数
def save_generated_code_to_new_file(code_text, suggested_name=None):
    """AIが生成したコードを新しいファイルに保存する"""
    # コードブロックのマークアップを削除
    code_pattern = r"```(?:\w+)?\n([\s\S]*?)\n```"
    match = re.search(code_pattern, code_text)
    clean_code = match.group(1) if match else code_text
    
    # 言語の推測
    language = "py"  # デフォルトはPython
    lang_pattern = r"```(\w+)\n"
    lang_match = re.search(lang_pattern, code_text)
    if lang_match:
        detected_lang = lang_match.group(1).lower()
        lang_mapping = {
            "python": "py",
            "javascript": "js",
            "html": "html",
            "css": "css",
            "json": "json",
            "java": "java",
            "cpp": "cpp",
            "c++": "cpp",
            "c": "c",
            "go": "go",
            "rust": "rs",
            "typescript": "ts"
        }
        language = lang_mapping.get(detected_lang, detected_lang)
    
    # ファイル名の生成
    if not suggested_name:
        import hashlib
        import time
        # タイムスタンプとコードの一部からハッシュを生成
        timestamp = str(int(time.time()))
        code_hash = hashlib.md5(clean_code[:100].encode()).hexdigest()[:6]
        suggested_name = f"generated_{timestamp}_{code_hash}"
    
    # 拡張子が含まれていなければ追加
    if "." not in suggested_name:
        suggested_name = f"{suggested_name}.{language}"
    
    # 同名ファイルが存在する場合は連番を付ける
    base_name, ext = os.path.splitext(suggested_name)
    counter = 1
    file_name = suggested_name
    while file_name in st.session_state.files:
        file_name = f"{base_name}_{counter}{ext}"
        counter += 1
    
    # ファイルを保存
    st.session_state.files[file_name] = clean_code
    st.session_state.current_file = file_name
    
    return file_name

# ファイル間の依存関係を分析する関数
def get_file_dependencies(files):
    """アップロードされたファイル群の依存関係を分析する"""
    dependencies = {}
    file_summary = {}
    
    # ファイル間の依存関係を検出
    for filename, content in files.items():
        dependencies[filename] = []
        file_summary[filename] = []
        
        # 主要な機能や役割を特定するためのキーワードパターン
        keyword_patterns = {
            "import": r"import\s+(\w+)",  # Pythonのimport文
            "from": r"from\s+(\w+(?:\.\w+)*)\s+import",  # fromを使ったimport文
            "require": r"require\(['\"](.+?)['\"]\)",  # Node.jsのrequire
            "include": r"include\(['\"](.+?)['\"]\)",  # PHPやC/C++のinclude
            "class": r"class\s+(\w+)",  # クラス定義
            "function": r"def\s+(\w+)|function\s+(\w+)",  # 関数定義
            "streamlit": r"st\.(\w+)",  # Streamlit関数
            "variable": r"(\w+)\s*=\s*"  # 変数定義
        }
        
        # 他のファイル名への参照を検索
        for other_filename in files.keys():
            if other_filename != filename and other_filename in content:
                # ファイル名への直接参照パターン
                direct_ref_patterns = [
                    rf"import\s+{os.path.splitext(other_filename)[0]}",  # Pythonのimport
                    rf"from\s+{os.path.splitext(other_filename)[0]}",  # Pythonのfrom import
                    rf"require\(['\"]\./{other_filename}['\"]\)",  # Node.jsのrequire
                    rf"include\(['\"]\./{other_filename}['\"]\)",  # includeでの参照
                    rf"open\(['\"]\./{other_filename}['\"]\)",  # ファイルを開く操作
                    rf"['\"]\./{other_filename}['\"]"  # ファイルパスの文字列
                ]
                
                # いずれかのパターンにマッチする場合、依存関係を追加
                if any(re.search(pattern, content) for pattern in direct_ref_patterns):
                    dependencies[filename].append(other_filename)
        
        # ファイルの主要なコンポーネントを抽出
        for keyword, pattern in keyword_patterns.items():
            matches = re.findall(pattern, content)
            if matches:
                flat_matches = []
                for match in matches:
                    if isinstance(match, tuple):
                        # タプルの場合(複数のキャプチャグループがある場合)
                        flat_matches.extend([m for m in match if m])
                    else:
                        flat_matches.append(match)
                
                # 重複を除去して最大10個までの項目を記録
                unique_matches = list(set(flat_matches))[:10]
                if unique_matches:
                    summary_item = f"{keyword}: {', '.join(unique_matches)}"
                    file_summary[filename].append(summary_item)
    
    # 依存関係を文字列として整形
    dependencies_str = "ファイル間の依存関係:\n"
    for filename, deps in dependencies.items():
        if deps:
            dependencies_str += f"- {filename} -> {', '.join(deps)}\n"
        else:
            dependencies_str += f"- {filename}: 依存ファイルなし\n"
    
    # ファイル概要を文字列として整形
    summary_str = "各ファイルの概要:\n"
    for filename, summary_items in file_summary.items():
        summary_str += f"## {filename}\n"
        if summary_items:
            for item in summary_items:
                summary_str += f"- {item}\n"
        else:
            summary_str += "- 主要コンポーネントを検出できませんでした\n"
        summary_str += "\n"
    
    return dependencies_str, summary_str