Justxd22 commited on
Commit
804fa02
Β·
1 Parent(s): 1a74768

Implement alibi ID handling and update tool interactions for alibi verification

Browse files
TODO.md CHANGED
@@ -6,3 +6,4 @@
6
  - players can't know there's more suspects down the list as we removed the scroll bar, can you scroll a bit down by default to show there's more cards down
7
  - sound/music
8
  - eleven labs tts/voice
 
 
6
  - players can't know there's more suspects down the list as we removed the scroll bar, can you scroll a bit down by default to show there's more cards down
7
  - sound/music
8
  - eleven labs tts/voice
9
+ - typing effect
app.py CHANGED
@@ -92,13 +92,21 @@ class GameSession:
92
 
93
  if action == "use_tool":
94
  tool_name = payload.get("tool")
95
- arg = payload.get("input")
96
 
97
  kwargs = {}
98
  if tool_name == "get_location":
99
  kwargs = {"phone_number": arg}
100
  elif tool_name == "call_alibi":
101
- kwargs = {"phone_number": arg}
 
 
 
 
 
 
 
 
102
  elif tool_name == "get_dna_test":
103
  kwargs = {"evidence_id": arg}
104
  elif tool_name == "get_footage":
@@ -144,6 +152,12 @@ def format_tool_response(tool_name, arg, result, scenario):
144
  return s
145
  return None
146
 
 
 
 
 
 
 
147
  # Logic per tool
148
  if tool_name == "get_location":
149
  suspect = find_by_phone(arg)
@@ -166,7 +180,10 @@ def format_tool_response(tool_name, arg, result, scenario):
166
  html += str(result)
167
 
168
  elif tool_name == "call_alibi":
169
- suspect = find_by_phone(arg)
 
 
 
170
  if suspect:
171
  suspect_id = suspect["id"]
172
  suspect_name = suspect["name"]
 
92
 
93
  if action == "use_tool":
94
  tool_name = payload.get("tool")
95
+ arg = payload.get("input") # Default for single-input tools
96
 
97
  kwargs = {}
98
  if tool_name == "get_location":
99
  kwargs = {"phone_number": arg}
100
  elif tool_name == "call_alibi":
101
+ # Support both simple string (old) and structured (new)
102
+ if "alibi_id" in payload:
103
+ arg = payload.get("alibi_id") # Update arg for formatter
104
+ kwargs = {
105
+ "alibi_id": arg,
106
+ "question": payload.get("question")
107
+ }
108
+ else:
109
+ kwargs = {"phone_number": arg} # Fallback
110
  elif tool_name == "get_dna_test":
111
  kwargs = {"evidence_id": arg}
112
  elif tool_name == "get_footage":
 
152
  return s
153
  return None
154
 
155
+ def find_by_alibi_id(aid):
156
+ for s in scenario["suspects"]:
157
+ if s.get("alibi_id") == aid:
158
+ return s
159
+ return None
160
+
161
  # Logic per tool
162
  if tool_name == "get_location":
163
  suspect = find_by_phone(arg)
 
180
  html += str(result)
181
 
182
  elif tool_name == "call_alibi":
183
+ suspect = find_by_phone(arg) # Try phone first
184
+ if not suspect:
185
+ suspect = find_by_alibi_id(arg) # Try ID
186
+
187
  if suspect:
188
  suspect_id = suspect["id"]
189
  suspect_name = suspect["name"]
game/game_engine.py CHANGED
@@ -30,9 +30,14 @@ class GameInstance:
30
  self.llm_manager.create_agent("detective", "detective", detective_context)
31
 
32
  # 2. Suspects
33
- for suspect in self.scenario["suspects"]:
34
  role = "murderer" if suspect["is_murderer"] else "witness"
35
 
 
 
 
 
 
36
  # Context for prompt
37
  context = {
38
  "name": suspect["name"],
@@ -40,11 +45,12 @@ class GameInstance:
40
  "alibi_story": suspect["alibi_story"],
41
  "bio": suspect["bio"],
42
  "true_location": suspect["true_location"],
43
- "phone_number": suspect.get("phone_number", "Unknown"), # Add phone number to context
 
44
 
45
  # Murderer specific
46
- "method": self.scenario["title"], # Placeholder, scenario doesn't explicitly list 'method' field in plan, using title/weapon logic implied
47
- "location": self.scenario["victim"].get("location", "the scene"), # Using generic
48
  "motive": suspect["motive"]
49
  }
50
 
@@ -87,7 +93,7 @@ class GameInstance:
87
  result = tools.get_dna_test(self.scenario, kwargs.get("evidence_id"))
88
  elif tool_name == "call_alibi":
89
  cost = 1
90
- result = tools.call_alibi(self.scenario, kwargs.get("phone_number"))
91
  else:
92
  return {"error": f"Unknown tool: {tool_name}"}
93
 
 
30
  self.llm_manager.create_agent("detective", "detective", detective_context)
31
 
32
  # 2. Suspects
33
+ for i, suspect in enumerate(self.scenario["suspects"]):
34
  role = "murderer" if suspect["is_murderer"] else "witness"
35
 
36
+ # Generate or retrieve Alibi ID
37
+ # In a real app, this should be in the JSON. For now, we synth it.
38
+ alibi_id = suspect.get("alibi_id", f"ALIBI-{100+i}")
39
+ suspect["alibi_id"] = alibi_id # Save back to scenario for tools lookup
40
+
41
  # Context for prompt
42
  context = {
43
  "name": suspect["name"],
 
45
  "alibi_story": suspect["alibi_story"],
46
  "bio": suspect["bio"],
47
  "true_location": suspect["true_location"],
48
+ "phone_number": suspect.get("phone_number", "Unknown"),
49
+ "alibi_id": alibi_id, # Inject Alibi ID
50
 
51
  # Murderer specific
52
+ "method": self.scenario["title"],
53
+ "location": self.scenario["victim"].get("location", "the scene"),
54
  "motive": suspect["motive"]
55
  }
56
 
 
93
  result = tools.get_dna_test(self.scenario, kwargs.get("evidence_id"))
94
  elif tool_name == "call_alibi":
95
  cost = 1
96
+ result = tools.call_alibi(self.scenario, **kwargs)
97
  else:
98
  return {"error": f"Unknown tool: {tool_name}"}
99
 
game/llm_manager.py CHANGED
@@ -44,7 +44,7 @@ class LLMManager:
44
  def _load_prompts(self):
45
  prompts = {}
46
  prompt_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "prompts")
47
- for filename in ["murderer.txt", "witness.txt", "detective.txt"]:
48
  key = filename.replace(".txt", "")
49
  try:
50
  with open(os.path.join(prompt_dir, filename), "r") as f:
@@ -59,7 +59,7 @@ class LLMManager:
59
  Creates a new GeminiAgent for a specific character.
60
 
61
  agent_id: Unique ID (e.g., 'suspect_1', 'detective')
62
- role: 'murderer', 'witness', 'detective'
63
  context_data: Dict to fill in the prompt templates (name, victim_name, etc.)
64
  """
65
 
@@ -68,6 +68,8 @@ class LLMManager:
68
  base_prompt = self.prompts.get("murderer", "")
69
  elif role == "detective":
70
  base_prompt = self.prompts.get("detective", "")
 
 
71
  else:
72
  base_prompt = self.prompts.get("witness", "")
73
 
 
44
  def _load_prompts(self):
45
  prompts = {}
46
  prompt_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "prompts")
47
+ for filename in ["murderer.txt", "witness.txt", "detective.txt", "alibi_agent.txt"]:
48
  key = filename.replace(".txt", "")
49
  try:
50
  with open(os.path.join(prompt_dir, filename), "r") as f:
 
59
  Creates a new GeminiAgent for a specific character.
60
 
61
  agent_id: Unique ID (e.g., 'suspect_1', 'detective')
62
+ role: 'murderer', 'witness', 'detective', 'alibi_agent'
63
  context_data: Dict to fill in the prompt templates (name, victim_name, etc.)
64
  """
65
 
 
68
  base_prompt = self.prompts.get("murderer", "")
69
  elif role == "detective":
70
  base_prompt = self.prompts.get("detective", "")
71
+ elif role == "alibi_agent":
72
+ base_prompt = self.prompts.get("alibi_agent", "")
73
  else:
74
  base_prompt = self.prompts.get("witness", "")
75
 
mcp/tools.py CHANGED
@@ -1,5 +1,6 @@
1
  import time
2
  import re
 
3
 
4
  def normalize_phone(phone):
5
  """Strips non-digit characters from phone number for comparison."""
@@ -124,35 +125,57 @@ def get_dna_test(case_data, evidence_id: str) -> dict:
124
  "notes": dna["notes"]
125
  }
126
 
127
- def call_alibi(case_data, phone_number: str) -> dict:
128
- """Call an alibi witness."""
129
-
130
- # Find alibi in database - Fuzzy Match
131
- alibi = None
132
- target_digits = normalize_phone(phone_number)
133
-
134
- if not target_digits:
135
- return {"error": "Invalid phone number."}
 
 
 
 
 
 
 
 
 
 
 
136
 
137
- for suspect_alibi in case_data.get("evidence", {}).get("alibis", {}).values():
138
- contact_digits = normalize_phone(suspect_alibi.get("contact"))
139
- if contact_digits and (target_digits.endswith(contact_digits) or contact_digits.endswith(target_digits)):
140
- alibi = suspect_alibi
141
- break
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
- if not alibi:
144
- return {"error": "Number not found or no alibi associated."}
 
 
145
 
146
- # If alibi is truthful, confirm story
147
- if alibi["truth"].startswith("Telling truth"):
148
- response = f"Yes, I can confirm they were with me at that time."
149
- else:
150
- # Lying/Uncertain
151
- response = f"Uh... yes, they were with me. Definitely."
152
 
153
  return {
154
- "contact_name": alibi.get("contact_name", "Unknown"),
155
  "response": response,
156
- "confidence": "High" if alibi["verifiable"] else "Uncertain",
157
- "red_flags": [] if alibi["verifiable"] else ["Hesitant response", "No details provided"]
158
  }
 
1
  import time
2
  import re
3
+ from game.llm_manager import LLMManager
4
 
5
  def normalize_phone(phone):
6
  """Strips non-digit characters from phone number for comparison."""
 
125
  "notes": dna["notes"]
126
  }
127
 
128
+ def call_alibi(case_data, alibi_id: str = None, question: str = None, phone_number: str = None) -> dict:
129
+ """
130
+ Call an alibi witness using an LLM agent.
131
+ Requires `alibi_id` and `question`.
132
+ """
133
+ print(f"Calling alibi with alibi_id={alibi_id}, phone_number={phone_number}, question={question}")
134
+ # 1. Find suspect with this alibi_id
135
+ target_suspect = None
136
+ if alibi_id:
137
+ for s in case_data["suspects"]:
138
+ if s.get("alibi_id") == alibi_id:
139
+ target_suspect = s
140
+ break
141
+
142
+ if not target_suspect:
143
+ # Fallback: Try phone number for legacy support or wrong input
144
+ if phone_number:
145
+ # (Legacy logic skipped for brevity, encourage Alibi ID)
146
+ return {"error": "Please use the unique Alibi ID provided by the suspect."}
147
+ return {"error": "Invalid Alibi ID."}
148
 
149
+ if not question:
150
+ return {"error": "You must ask a question."}
151
+
152
+ # 2. Get alibi details
153
+ alibi_key = f"{target_suspect['id']}_alibi"
154
+ alibi_data = case_data.get("evidence", {}).get("alibis", {}).get(alibi_key)
155
+
156
+ if not alibi_data:
157
+ return {"error": "No alibi contact record found for this suspect."}
158
+
159
+ # 3. Create LLM Agent for Alibi
160
+ llm = LLMManager()
161
+
162
+ context = {
163
+ "suspect_name": target_suspect["name"],
164
+ "truth_context": alibi_data.get("truth", "Unknown"),
165
+ "suspect_story": target_suspect.get("alibi_story", "Unknown"),
166
+ "relationship": alibi_data.get("contact_name", "Acquaintance"),
167
+ "question": question
168
+ }
169
 
170
+ # Create a temporary agent
171
+ agent_id = f"alibi_{alibi_id}"
172
+ # Ensure LLMManager supports 'alibi_agent' role (update _load_prompts if needed)
173
+ llm.create_agent(agent_id, "alibi_agent", context)
174
 
175
+ response = llm.get_response(agent_id, question)
 
 
 
 
 
176
 
177
  return {
178
+ "contact_name": alibi_data.get("contact_name", "Unknown"),
179
  "response": response,
180
+ "confidence": "High" if alibi_data.get("verifiable") else "Uncertain"
 
181
  }
prompts/alibi_agent.txt ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ You are an ALIBI WITNESS in a murder investigation.
2
+
3
+ CONTEXT:
4
+ You are the alibi for {suspect_name}.
5
+ The detective is calling you to verify their story.
6
+
7
+ THE TRUTH (What actually happened):
8
+ {truth_context}
9
+
10
+ THE SUSPECT'S STORY (What they claim):
11
+ {suspect_story}
12
+
13
+ YOUR RELATIONSHIP:
14
+ {relationship}
15
+
16
+ YOUR INSTRUCTIONS:
17
+ 1. If the suspect's story matches the truth, CONFIRM it clearly.
18
+ 2. If the suspect is LYING but you are covering for them (e.g. you are a partner in crime or loyal friend), LIE to match their story.
19
+ 3. If the suspect is LYING and you don't know why (or you are honest), TELL THE TRUTH, which will contradict them.
20
+ 4. Answer the detective's specific question: "{question}"
21
+
22
+ Be conversational but direct. You are on the phone.
prompts/murderer.txt CHANGED
@@ -12,6 +12,7 @@ CASE DETAILS:
12
 
13
  YOUR DETAILS:
14
  - Phone Number: {phone_number}
 
15
 
16
  YOUR COVER STORY:
17
  - You were "{alibi_story}"
 
12
 
13
  YOUR DETAILS:
14
  - Phone Number: {phone_number}
15
+ - Alibi Contact ID: {alibi_id} (If asked for alibi contact, give this ID)
16
 
17
  YOUR COVER STORY:
18
  - You were "{alibi_story}"
prompts/witness.txt CHANGED
@@ -7,6 +7,7 @@ CASE DETAILS:
7
 
8
  YOUR DETAILS:
9
  - Phone Number: {phone_number}
 
10
 
11
  YOUR ALIBI:
12
  - You were "{alibi_story}"
 
7
 
8
  YOUR DETAILS:
9
  - Phone Number: {phone_number}
10
+ - Alibi Contact ID: {alibi_id} (If asked for alibi, give this ID)
11
 
12
  YOUR ALIBI:
13
  - You were "{alibi_story}"
scenarios/art_gallery.json CHANGED
@@ -17,6 +17,7 @@
17
  "motive": "Theft of 'The Red Sunset'",
18
  "true_location": "Gallery Wine Cellar",
19
  "alibi_story": "Stocktaking in basement",
 
20
  "phone_number": "+1-555-2101",
21
  "bio": "Sophisticated but in debt. Has been eyeing the collection."
22
  },
@@ -28,6 +29,7 @@
28
  "motive": "Contract dispute",
29
  "true_location": "Main Hall",
30
  "alibi_story": "Talking to guests",
 
31
  "phone_number": "+1-555-2102",
32
  "bio": "Passionate and volatile. Felt Vincent was cheating her."
33
  },
@@ -39,6 +41,7 @@
39
  "motive": "Blackmail",
40
  "true_location": "Sculpture Garden",
41
  "alibi_story": "Smoking cigar",
 
42
  "phone_number": "+1-555-2103",
43
  "bio": "Wealthy and secretive. Had a secret history with Vincent."
44
  },
@@ -50,6 +53,7 @@
50
  "motive": "None",
51
  "true_location": "Kitchen",
52
  "alibi_story": "Preparing hors d'oeuvres",
 
53
  "phone_number": "+1-555-2104",
54
  "bio": "Hardworking. Just there to do a job."
55
  }
 
17
  "motive": "Theft of 'The Red Sunset'",
18
  "true_location": "Gallery Wine Cellar",
19
  "alibi_story": "Stocktaking in basement",
20
+ "alibi_id": "ALIBI-301",
21
  "phone_number": "+1-555-2101",
22
  "bio": "Sophisticated but in debt. Has been eyeing the collection."
23
  },
 
29
  "motive": "Contract dispute",
30
  "true_location": "Main Hall",
31
  "alibi_story": "Talking to guests",
32
+ "alibi_id": "ALIBI-302",
33
  "phone_number": "+1-555-2102",
34
  "bio": "Passionate and volatile. Felt Vincent was cheating her."
35
  },
 
41
  "motive": "Blackmail",
42
  "true_location": "Sculpture Garden",
43
  "alibi_story": "Smoking cigar",
44
+ "alibi_id": "ALIBI-303",
45
  "phone_number": "+1-555-2103",
46
  "bio": "Wealthy and secretive. Had a secret history with Vincent."
47
  },
 
53
  "motive": "None",
54
  "true_location": "Kitchen",
55
  "alibi_story": "Preparing hors d'oeuvres",
56
+ "alibi_id": "ALIBI-304",
57
  "phone_number": "+1-555-2104",
58
  "bio": "Hardworking. Just there to do a job."
59
  }
scenarios/coffee_shop.json CHANGED
@@ -17,6 +17,7 @@
17
  "motive": "Jealousy",
18
  "true_location": "Coffee Shop Back Door",
19
  "alibi_story": "Gaming at home",
 
20
  "phone_number": "+1-555-1101",
21
  "bio": "Possessive and recently dumped. Has been sending angry texts."
22
  },
@@ -28,6 +29,7 @@
28
  "motive": "Financial dispute",
29
  "true_location": "Bank deposit drop",
30
  "alibi_story": "Depositing cash",
 
31
  "phone_number": "+1-555-1102",
32
  "bio": "Stressed about money. Owed Emma back pay."
33
  },
@@ -39,6 +41,7 @@
39
  "motive": "Promotion rivalry",
40
  "true_location": "Gym",
41
  "alibi_story": "Late night workout",
 
42
  "phone_number": "+1-555-1103",
43
  "bio": "Competitive and ambitious. Wanted the shift lead position."
44
  },
@@ -50,6 +53,7 @@
50
  "motive": "Obsession",
51
  "true_location": "Bus stop across street",
52
  "alibi_story": "Waiting for bus",
 
53
  "phone_number": "+1-555-1104",
54
  "bio": "Always there. Kind of creepy but harmless."
55
  }
 
17
  "motive": "Jealousy",
18
  "true_location": "Coffee Shop Back Door",
19
  "alibi_story": "Gaming at home",
20
+ "alibi_id": "ALIBI-201",
21
  "phone_number": "+1-555-1101",
22
  "bio": "Possessive and recently dumped. Has been sending angry texts."
23
  },
 
29
  "motive": "Financial dispute",
30
  "true_location": "Bank deposit drop",
31
  "alibi_story": "Depositing cash",
32
+ "alibi_id": "ALIBI-202",
33
  "phone_number": "+1-555-1102",
34
  "bio": "Stressed about money. Owed Emma back pay."
35
  },
 
41
  "motive": "Promotion rivalry",
42
  "true_location": "Gym",
43
  "alibi_story": "Late night workout",
44
+ "alibi_id": "ALIBI-203",
45
  "phone_number": "+1-555-1103",
46
  "bio": "Competitive and ambitious. Wanted the shift lead position."
47
  },
 
53
  "motive": "Obsession",
54
  "true_location": "Bus stop across street",
55
  "alibi_story": "Waiting for bus",
56
+ "alibi_id": "ALIBI-204",
57
  "phone_number": "+1-555-1104",
58
  "bio": "Always there. Kind of creepy but harmless."
59
  }
scenarios/silicon_valley.json CHANGED
@@ -17,6 +17,7 @@
17
  "motive": "Embezzlement discovered",
18
  "true_location": "Crime scene",
19
  "alibi_story": "At home watching Netflix",
 
20
  "phone_number": "+1-555-0101",
21
  "bio": "Detail-oriented and ambitious. Has been with the company since day one."
22
  },
@@ -28,6 +29,7 @@
28
  "motive": "None",
29
  "true_location": "2 blocks away",
30
  "alibi_story": "Working late on 5th floor",
 
31
  "phone_number": "+1-555-0102",
32
  "bio": "Quiet and keeps to himself. Has a record for petty theft from 10 years ago."
33
  },
@@ -39,6 +41,7 @@
39
  "motive": "Inheritance dispute",
40
  "true_location": "Restaurant downtown",
41
  "alibi_story": "Dinner with friends",
 
42
  "phone_number": "+1-555-0103",
43
  "bio": "Emotional and protective of the family legacy. Argued with Marcus recently."
44
  },
@@ -50,6 +53,7 @@
50
  "motive": "Corporate rivalry",
51
  "true_location": "Airport lounge",
52
  "alibi_story": "Catching a flight to NY",
 
53
  "phone_number": "+1-555-0104",
54
  "bio": "Ruthless businessman. Recently lost a major contract to Marcus's firm."
55
  }
 
17
  "motive": "Embezzlement discovered",
18
  "true_location": "Crime scene",
19
  "alibi_story": "At home watching Netflix",
20
+ "alibi_id": "ALIBI-101",
21
  "phone_number": "+1-555-0101",
22
  "bio": "Detail-oriented and ambitious. Has been with the company since day one."
23
  },
 
29
  "motive": "None",
30
  "true_location": "2 blocks away",
31
  "alibi_story": "Working late on 5th floor",
32
+ "alibi_id": "ALIBI-102",
33
  "phone_number": "+1-555-0102",
34
  "bio": "Quiet and keeps to himself. Has a record for petty theft from 10 years ago."
35
  },
 
41
  "motive": "Inheritance dispute",
42
  "true_location": "Restaurant downtown",
43
  "alibi_story": "Dinner with friends",
44
+ "alibi_id": "ALIBI-103",
45
  "phone_number": "+1-555-0103",
46
  "bio": "Emotional and protective of the family legacy. Argued with Marcus recently."
47
  },
 
53
  "motive": "Corporate rivalry",
54
  "true_location": "Airport lounge",
55
  "alibi_story": "Catching a flight to NY",
56
+ "alibi_id": "ALIBI-104",
57
  "phone_number": "+1-555-0104",
58
  "bio": "Ruthless businessman. Recently lost a major contract to Marcus's firm."
59
  }
ui/static/js/game_logic.js CHANGED
@@ -31,10 +31,16 @@ async function sendAction(action, data) {
31
  // console.log("πŸ“₯ API Response:", result);
32
 
33
  if (result && result.action) {
 
 
 
 
34
  handleServerMessage(result);
 
35
  }
36
  } catch (e) {
37
  console.error("Bridge Error:", e);
 
38
  }
39
  }
40
 
@@ -349,13 +355,28 @@ function useTool(toolName) {
349
  const input = document.getElementById('modal-input');
350
  const promptText = document.getElementById('modal-prompt-text');
351
 
352
- // Custom prompts
353
- if (toolName === 'get_location') promptText.innerText = "Enter Target Phone Number:";
354
- if (toolName === 'call_alibi') promptText.innerText = "Enter Witness Phone Number:";
355
- if (toolName === 'get_dna_test') promptText.innerText = "Enter Evidence ID:";
356
- if (toolName === 'get_footage') promptText.innerText = "Enter Camera Location:";
357
 
 
 
358
  input.value = '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
359
  modal.classList.add('active');
360
  input.focus();
361
  }
@@ -363,16 +384,69 @@ function useTool(toolName) {
363
  function submitTool() {
364
  const input = document.getElementById('modal-input');
365
  const value = input.value.trim();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
366
 
367
- if (value && pendingTool) {
368
- sendAction('use_tool', { tool: pendingTool, input: value });
369
- closeModal();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
370
  }
 
371
  }
372
 
373
  function closeModal() {
374
  document.getElementById('tool-modal').classList.remove('active');
375
  pendingTool = null;
 
 
376
  }
377
 
378
  // --- Listeners ---
 
31
  // console.log("πŸ“₯ API Response:", result);
32
 
33
  if (result && result.action) {
34
+ // Return result for local handling if needed
35
+ if (result.action === 'tool_error') {
36
+ return result;
37
+ }
38
  handleServerMessage(result);
39
+ return result;
40
  }
41
  } catch (e) {
42
  console.error("Bridge Error:", e);
43
+ return { action: 'error', message: e.message };
44
  }
45
  }
46
 
 
355
  const input = document.getElementById('modal-input');
356
  const promptText = document.getElementById('modal-prompt-text');
357
 
358
+ const section2 = document.getElementById('modal-section-2');
359
+ const input2 = document.getElementById('modal-input-2');
360
+ const promptText2 = document.getElementById('modal-prompt-text-2');
 
 
361
 
362
+ // Reset
363
+ section2.style.display = 'none';
364
  input.value = '';
365
+ input2.value = '';
366
+
367
+ // Custom prompts
368
+ if (toolName === 'get_location') {
369
+ promptText.innerText = "Enter Target Phone Number:";
370
+ } else if (toolName === 'call_alibi') {
371
+ promptText.innerText = "Enter Alibi ID (Ask suspect):";
372
+ section2.style.display = 'block';
373
+ promptText2.innerText = "Question for Alibi:";
374
+ } else if (toolName === 'get_dna_test') {
375
+ promptText.innerText = "Enter Evidence ID:";
376
+ } else if (toolName === 'get_footage') {
377
+ promptText.innerText = "Enter Camera Location:";
378
+ }
379
+
380
  modal.classList.add('active');
381
  input.focus();
382
  }
 
384
  function submitTool() {
385
  const input = document.getElementById('modal-input');
386
  const value = input.value.trim();
387
+ const confirmBtn = document.getElementById('modal-confirm');
388
+
389
+ if (!pendingTool) return;
390
+
391
+ let payload = null;
392
+
393
+ if (pendingTool === 'call_alibi') {
394
+ const input2 = document.getElementById('modal-input-2');
395
+ const value2 = input2.value.trim();
396
+
397
+ if (value && value2) {
398
+ payload = {
399
+ tool: pendingTool,
400
+ alibi_id: value,
401
+ question: value2
402
+ };
403
+ } else {
404
+ showModalError("Both fields are required.");
405
+ return;
406
+ }
407
+ } else {
408
+ if (value) {
409
+ payload = { tool: pendingTool, input: value };
410
+ }
411
+ }
412
 
413
+ if (payload) {
414
+ // Loading state
415
+ confirmBtn.innerText = "PROCESSING...";
416
+ confirmBtn.disabled = true;
417
+
418
+ sendAction('use_tool', payload).then(result => {
419
+ confirmBtn.innerText = "SUBMIT";
420
+ confirmBtn.disabled = false;
421
+
422
+ if (result && result.action === 'tool_error') {
423
+ showModalError(result.data.message);
424
+ } else {
425
+ closeModal();
426
+ }
427
+ });
428
+ }
429
+ }
430
+
431
+ function showModalError(msg) {
432
+ let errDiv = document.getElementById('modal-error');
433
+ if (!errDiv) {
434
+ errDiv = document.createElement('div');
435
+ errDiv.id = 'modal-error';
436
+ errDiv.style.color = 'red';
437
+ errDiv.style.marginTop = '10px';
438
+ errDiv.style.textAlign = 'right';
439
+ errDiv.style.fontWeight = 'bold';
440
+ document.querySelector('.modal-content').appendChild(errDiv);
441
  }
442
+ errDiv.innerText = msg;
443
  }
444
 
445
  function closeModal() {
446
  document.getElementById('tool-modal').classList.remove('active');
447
  pendingTool = null;
448
+ const errDiv = document.getElementById('modal-error');
449
+ if (errDiv) errDiv.innerText = '';
450
  }
451
 
452
  // --- Listeners ---
ui/templates/game_interface.html CHANGED
@@ -80,6 +80,12 @@
80
  <div class="modal-header">INVESTIGATION TOOL</div>
81
  <div id="modal-prompt-text">Enter details:</div>
82
  <input type="text" id="modal-input" class="modal-input" placeholder="...">
 
 
 
 
 
 
83
  <div class="modal-actions">
84
  <button id="modal-cancel" class="modal-btn cancel">CANCEL</button>
85
  <button id="modal-confirm" class="modal-btn confirm">SUBMIT</button>
 
80
  <div class="modal-header">INVESTIGATION TOOL</div>
81
  <div id="modal-prompt-text">Enter details:</div>
82
  <input type="text" id="modal-input" class="modal-input" placeholder="...">
83
+
84
+ <div id="modal-section-2" style="display:none">
85
+ <div id="modal-prompt-text-2">Question:</div>
86
+ <input type="text" id="modal-input-2" class="modal-input" placeholder="What do you want to ask?">
87
+ </div>
88
+
89
  <div class="modal-actions">
90
  <button id="modal-cancel" class="modal-btn cancel">CANCEL</button>
91
  <button id="modal-confirm" class="modal-btn confirm">SUBMIT</button>