Tonic commited on
Commit
76435b8
·
unverified ·
2 Parent(s): 2276f30 f80ff23

add imagen lora server

Browse files
.gitignore CHANGED
@@ -0,0 +1 @@
 
 
1
+ .env
__pycache__/app.cpython-313.pyc ADDED
Binary file (2.3 kB). View file
 
app.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException
2
+ from pydantic import BaseModel
3
+ from typing import List
4
+ import os
5
+ from mistralai import Mistral
6
+ from dotenv import load_dotenv
7
+
8
+ # Load environment variables
9
+ load_dotenv()
10
+ api_key = os.getenv("MISTRAL_API_KEY")
11
+
12
+ # Initialize FastAPI app
13
+ app = FastAPI()
14
+
15
+ # Initialize Mistral client
16
+ client = Mistral(api_key=api_key)
17
+
18
+ class Message(BaseModel):
19
+ role: str
20
+ content: str
21
+
22
+ class ChatRequest(BaseModel):
23
+ model: str
24
+ messages: List[Message]
25
+
26
+ @app.post("/chat/complete")
27
+ async def chat_complete(request: ChatRequest):
28
+ try:
29
+ response = client.chat.complete(
30
+ model=request.model,
31
+ messages=[{"role": msg.role, "content": msg.content} for msg in request.messages]
32
+ )
33
+ return {
34
+ "content": response.choices[0].message.content,
35
+ "finish_reason": response.choices[0].finish_reason
36
+ }
37
+ except Exception as e:
38
+ raise HTTPException(status_code=500, detail=str(e))
39
+
40
+
41
+ if __name__ == "__main__":
42
+ import uvicorn
43
+ uvicorn.run(app, host="0.0.0.0", port=8000)
helpers/image-gen/flux-cctv.py CHANGED
@@ -1,7 +1,7 @@
1
  import gradio as gr
2
  import numpy as np
3
  import random
4
- # import spaces
5
  import torch
6
  from diffusers import DiffusionPipeline, FlowMatchEulerDiscreteScheduler, AutoencoderTiny, AutoencoderKL
7
  from transformers import CLIPTextModel, CLIPTokenizer, T5EncoderModel, T5TokenizerFast
@@ -16,7 +16,7 @@ good_vae = AutoencoderKL.from_pretrained("black-forest-labs/FLUX.1-dev", subfold
16
  pipe = DiffusionPipeline.from_pretrained("black-forest-labs/FLUX.1-dev", torch_dtype=dtype, vae=taef1).to(device)
17
 
18
  # Load the CCTV Horror LoRA
19
- pipe.load_lora_weights("Alfred126/lora-horror-cctv")
20
 
21
  torch.cuda.empty_cache()
22
 
@@ -25,13 +25,15 @@ MAX_IMAGE_SIZE = 2048
25
 
26
  pipe.flux_pipe_call_that_returns_an_iterable_of_images = flux_pipe_call_that_returns_an_iterable_of_images.__get__(pipe)
27
 
28
- # @spaces.GPU(duration=75)
29
  def infer(prompt, seed=42, randomize_seed=False, width=1024, height=1024, guidance_scale=3.5, num_inference_steps=28, lora_scale=0.7, progress=gr.Progress(track_tqdm=True)):
30
  if randomize_seed:
31
  seed = random.randint(0, MAX_SEED)
32
  generator = torch.Generator().manual_seed(seed)
33
 
34
- # Add cross attention scale for LoRA
 
 
35
  for img in pipe.flux_pipe_call_that_returns_an_iterable_of_images(
36
  prompt=prompt,
37
  guidance_scale=guidance_scale,
@@ -41,7 +43,6 @@ def infer(prompt, seed=42, randomize_seed=False, width=1024, height=1024, guidan
41
  generator=generator,
42
  output_type="pil",
43
  good_vae=good_vae,
44
- cross_attention_kwargs={"scale": lora_scale},
45
  ):
46
  yield img, seed
47
 
@@ -145,4 +146,4 @@ Create horror-style CCTV footage images using FLUX.1 and the CCTV Horror LoRA
145
  outputs=[result, seed]
146
  )
147
 
148
- demo.launch(share=True, ssr_mode=False)
 
1
  import gradio as gr
2
  import numpy as np
3
  import random
4
+ import spaces
5
  import torch
6
  from diffusers import DiffusionPipeline, FlowMatchEulerDiscreteScheduler, AutoencoderTiny, AutoencoderKL
7
  from transformers import CLIPTextModel, CLIPTokenizer, T5EncoderModel, T5TokenizerFast
 
16
  pipe = DiffusionPipeline.from_pretrained("black-forest-labs/FLUX.1-dev", torch_dtype=dtype, vae=taef1).to(device)
17
 
18
  # Load the CCTV Horror LoRA
19
+ pipe.load_lora_weights("Alfred126/lora-horror-cctv", adapter_name="horror")
20
 
21
  torch.cuda.empty_cache()
22
 
 
25
 
26
  pipe.flux_pipe_call_that_returns_an_iterable_of_images = flux_pipe_call_that_returns_an_iterable_of_images.__get__(pipe)
27
 
28
+ @spaces.GPU(duration=75)
29
  def infer(prompt, seed=42, randomize_seed=False, width=1024, height=1024, guidance_scale=3.5, num_inference_steps=28, lora_scale=0.7, progress=gr.Progress(track_tqdm=True)):
30
  if randomize_seed:
31
  seed = random.randint(0, MAX_SEED)
32
  generator = torch.Generator().manual_seed(seed)
33
 
34
+ # Set the LoRA scale through the pipeline parameters
35
+ pipe.set_adapters_scale(lora_scale)
36
+
37
  for img in pipe.flux_pipe_call_that_returns_an_iterable_of_images(
38
  prompt=prompt,
39
  guidance_scale=guidance_scale,
 
43
  generator=generator,
44
  output_type="pil",
45
  good_vae=good_vae,
 
46
  ):
47
  yield img, seed
48
 
 
146
  outputs=[result, seed]
147
  )
148
 
149
+ demo.launch()
static/assets/css/style.css ADDED
@@ -0,0 +1,424 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @font-face {
2
+ font-family: "HorrorBrush";
3
+ src: url("/static/assets/fonts/horrorbrush.ttf") format("truetype");
4
+ }
5
+
6
+ * {
7
+ margin: 0;
8
+ padding: 0;
9
+ box-sizing: border-box;
10
+ }
11
+
12
+ body {
13
+ background-color: #000000;
14
+ min-height: 100vh;
15
+ display: flex;
16
+ flex-direction: column;
17
+ align-items: center;
18
+ position: relative;
19
+ overflow: hidden;
20
+ }
21
+
22
+ .background-elements {
23
+ position: absolute;
24
+ width: 100%;
25
+ height: 100%;
26
+ pointer-events: none;
27
+ }
28
+
29
+ .blood {
30
+ position: absolute;
31
+ }
32
+
33
+ .blood-top-left {
34
+ top: 0;
35
+ left: 0;
36
+ width: 350px;
37
+ opacity: 50%;
38
+ }
39
+
40
+ .blood-bottom-right {
41
+ bottom: 0;
42
+ right: 0;
43
+ width: 250px;
44
+ position: absolute;
45
+ bottom: -120px;
46
+ right: -50px;
47
+ opacity: 40%;
48
+ }
49
+
50
+ .blood-top-right {
51
+ position: absolute;
52
+ top: 0;
53
+ right: 0;
54
+ width: 250px;
55
+ margin: 20px 20px 0px 0px;
56
+ transform: rotate(30deg);
57
+ }
58
+
59
+ .splatter {
60
+ position: absolute;
61
+ opacity: 60%;
62
+ bottom: 240px;
63
+ left: 30%;
64
+ transform: translateX(-50%);
65
+ transform: rotate(20deg);
66
+ width: 400px;
67
+ }
68
+
69
+ main {
70
+ display: flex;
71
+ flex-direction: column;
72
+ align-items: center;
73
+ gap: 100px;
74
+ z-index: 1;
75
+ margin-top: 200px;
76
+ }
77
+
78
+ .logo {
79
+ position: absolute;
80
+ top: 40px;
81
+ left: 50%;
82
+ transform: translateX(-50%);
83
+ animation: flicker 4s linear infinite;
84
+ }
85
+
86
+ .logo img {
87
+ width: 230px;
88
+ height: auto;
89
+ }
90
+
91
+ .menu {
92
+ display: flex;
93
+ flex-direction: column;
94
+ gap: 30px;
95
+ align-items: center;
96
+ position: relative;
97
+ }
98
+
99
+ .menu::after {
100
+ content: "";
101
+ position: absolute;
102
+ width: 800px;
103
+ height: 800px;
104
+ background: radial-gradient(
105
+ circle,
106
+ rgba(155, 0, 0, 0.35) 0%,
107
+ rgba(0, 0, 0, 0) 70%
108
+ );
109
+ z-index: -1;
110
+ top: 50%;
111
+ left: 50%;
112
+ transform: translate(-50%, -50%);
113
+ }
114
+
115
+ .menu-item {
116
+ font-family: "HorrorBrush", cursive;
117
+ font-size: 48px;
118
+ color: #fff;
119
+ text-decoration: none;
120
+ transition: color 0.3s ease;
121
+ letter-spacing: 4px;
122
+ text-transform: uppercase;
123
+ }
124
+
125
+ .new-game {
126
+ color: #9b0000;
127
+ }
128
+
129
+ .menu-item:hover {
130
+ color: #9b0000;
131
+ }
132
+
133
+ .how-to-play:hover {
134
+ color: #9b0000;
135
+ }
136
+
137
+ .level-maker:hover {
138
+ color: #9b0000;
139
+ }
140
+
141
+ @keyframes flicker {
142
+ 0% {
143
+ opacity: 1;
144
+ }
145
+ 5% {
146
+ opacity: 0.9;
147
+ }
148
+ 10% {
149
+ opacity: 1;
150
+ }
151
+ 15% {
152
+ opacity: 0.4;
153
+ }
154
+ 16% {
155
+ opacity: 1;
156
+ }
157
+ 17% {
158
+ opacity: 0.4;
159
+ }
160
+ 18% {
161
+ opacity: 1;
162
+ }
163
+ 35% {
164
+ opacity: 1;
165
+ }
166
+ 36% {
167
+ opacity: 0.3;
168
+ }
169
+ 37% {
170
+ opacity: 1;
171
+ }
172
+ 38% {
173
+ opacity: 0.5;
174
+ }
175
+ 39% {
176
+ opacity: 1;
177
+ }
178
+ 50% {
179
+ opacity: 1;
180
+ }
181
+ 51% {
182
+ opacity: 0.7;
183
+ }
184
+ 52% {
185
+ opacity: 1;
186
+ }
187
+ 53% {
188
+ opacity: 0.4;
189
+ }
190
+ 54% {
191
+ opacity: 1;
192
+ }
193
+ 85% {
194
+ opacity: 1;
195
+ }
196
+ 86% {
197
+ opacity: 0.6;
198
+ }
199
+ 87% {
200
+ opacity: 1;
201
+ }
202
+ 88% {
203
+ opacity: 0.4;
204
+ }
205
+ 89% {
206
+ opacity: 1;
207
+ }
208
+ 100% {
209
+ opacity: 1;
210
+ }
211
+ }
212
+
213
+ .led-bar {
214
+ position: fixed;
215
+ top: 0;
216
+ left: 0;
217
+ width: 100%;
218
+ height: 2px;
219
+ background: linear-gradient(
220
+ 90deg,
221
+ transparent 0%,
222
+ rgba(255, 0, 0, 0.4) 20%,
223
+ rgba(255, 0, 0, 0.8) 35%,
224
+ rgba(255, 50, 50, 1) 50%,
225
+ rgba(255, 0, 0, 0.8) 65%,
226
+ rgba(255, 0, 0, 0.4) 80%,
227
+ transparent 100%
228
+ );
229
+ z-index: 100;
230
+ animation: ledFlicker 4s infinite, ledPulse 10s infinite;
231
+ box-shadow: 0 0 20px rgba(255, 0, 0, 0.7), 0 0 35px rgba(255, 0, 0, 0.5),
232
+ 0 0 50px rgba(255, 0, 0, 0.4), 0 0 70px rgba(155, 0, 0, 0.3);
233
+ filter: blur(0.6px);
234
+ }
235
+
236
+ .light-beam {
237
+ position: absolute;
238
+ top: 0;
239
+ left: 0;
240
+ width: 100%;
241
+ height: 250px;
242
+ background: linear-gradient(
243
+ 180deg,
244
+ rgba(255, 0, 0, 0.3) 0%,
245
+ rgba(255, 0, 0, 0.2) 20%,
246
+ rgba(255, 0, 0, 0.15) 30%,
247
+ rgba(155, 0, 0, 0.08) 60%,
248
+ transparent 100%
249
+ );
250
+ animation: beamFlicker 4s infinite;
251
+ pointer-events: none;
252
+ filter: blur(2px);
253
+ }
254
+
255
+ @keyframes ledFlicker {
256
+ 0% {
257
+ opacity: 1;
258
+ }
259
+ 95% {
260
+ opacity: 1;
261
+ }
262
+ 96% {
263
+ opacity: 0.3;
264
+ }
265
+ 97% {
266
+ opacity: 1;
267
+ }
268
+ 98% {
269
+ opacity: 0.2;
270
+ }
271
+ 99% {
272
+ opacity: 0.9;
273
+ }
274
+ 100% {
275
+ opacity: 1;
276
+ }
277
+ }
278
+
279
+ @keyframes ledPulse {
280
+ 0% {
281
+ filter: brightness(1) blur(0.6px);
282
+ }
283
+ 50% {
284
+ filter: brightness(1.3) blur(0.4px);
285
+ }
286
+ 100% {
287
+ filter: brightness(1) blur(0.6px);
288
+ }
289
+ }
290
+
291
+ @keyframes beamFlicker {
292
+ 0% {
293
+ opacity: 0.7;
294
+ }
295
+ 95% {
296
+ opacity: 0.7;
297
+ }
298
+ 96% {
299
+ opacity: 0.2;
300
+ }
301
+ 97% {
302
+ opacity: 0.7;
303
+ }
304
+ 98% {
305
+ opacity: 0.1;
306
+ }
307
+ 99% {
308
+ opacity: 0.6;
309
+ }
310
+ 100% {
311
+ opacity: 0.7;
312
+ }
313
+ }
314
+
315
+ /* How to Play Page Styles */
316
+ .content {
317
+ color: #fff;
318
+ text-align: center;
319
+ max-width: 800px;
320
+ margin: 40px auto;
321
+ padding: 20px;
322
+ }
323
+
324
+ .content h1 {
325
+ font-family: "HorrorBrush", cursive;
326
+ font-size: 64px;
327
+ color: #9b0000;
328
+ position: absolute;
329
+ top: 40px;
330
+ left: 50%;
331
+ transform: translateX(-50%);
332
+ letter-spacing: 4px;
333
+ }
334
+
335
+ .back-button {
336
+ font-family: "HorrorBrush", cursive;
337
+ font-size: 36px;
338
+ color: #fff;
339
+ text-decoration: none;
340
+ transition: color 0.3s ease;
341
+ letter-spacing: 4px;
342
+ text-transform: uppercase;
343
+ position: absolute;
344
+ bottom: 40px;
345
+ left: 50%;
346
+ transform: translateX(-50%);
347
+ }
348
+
349
+ .back-button:hover {
350
+ color: #9b0000;
351
+ }
352
+
353
+ .character {
354
+ position: absolute;
355
+ bottom: -20px;
356
+ left: 50%;
357
+ transform: translateX(-50%);
358
+ z-index: 2;
359
+ animation: characterFlicker 6s infinite;
360
+ }
361
+
362
+ .character img {
363
+ width: 450px;
364
+ height: auto;
365
+ }
366
+
367
+ @keyframes characterFlicker {
368
+ 0% {
369
+ opacity: 1;
370
+ filter: brightness(1);
371
+ }
372
+ 42% {
373
+ opacity: 1;
374
+ filter: brightness(1);
375
+ }
376
+ 43% {
377
+ opacity: 0.8;
378
+ filter: brightness(1.2);
379
+ }
380
+ 44% {
381
+ opacity: 1;
382
+ filter: brightness(1);
383
+ }
384
+ 45% {
385
+ opacity: 0.6;
386
+ filter: brightness(1.3);
387
+ }
388
+ 46% {
389
+ opacity: 1;
390
+ filter: brightness(1);
391
+ }
392
+ 47% {
393
+ opacity: 0.2;
394
+ filter: brightness(1.5);
395
+ }
396
+ 48% {
397
+ opacity: 1;
398
+ filter: brightness(1);
399
+ }
400
+ 49% {
401
+ opacity: 0.4;
402
+ filter: brightness(1.2);
403
+ }
404
+ 50% {
405
+ opacity: 1;
406
+ filter: brightness(1);
407
+ }
408
+ 80% {
409
+ opacity: 1;
410
+ filter: brightness(1);
411
+ }
412
+ 81% {
413
+ opacity: 0.5;
414
+ filter: brightness(1.3);
415
+ }
416
+ 82% {
417
+ opacity: 1;
418
+ filter: brightness(1);
419
+ }
420
+ 100% {
421
+ opacity: 1;
422
+ filter: brightness(1);
423
+ }
424
+ }
static/assets/fonts/horrorbrush.ttf ADDED
Binary file (286 kB). View file
 
static/assets/img/blood-2.png ADDED
static/assets/img/hand.png ADDED
static/assets/img/help.png ADDED
static/assets/img/logo.png ADDED
static/assets/img/perso.png ADDED
static/assets/img/splatter.png ADDED
static/assets/img/vite.svg ADDED
static/game/apt.json CHANGED
@@ -3243,7 +3243,7 @@
3243
  ],
3244
  "rooms": [
3245
  [
3246
- "Kichen",
3247
  [
3248
  {
3249
  "x": 28,
@@ -3362,8 +3362,8 @@
3362
  ]
3363
  ],
3364
  "characterPos": {
3365
- "x": 28,
3366
- "y": 15
3367
  },
3368
  "gridCols": 32,
3369
  "gridRows": 20
 
3243
  ],
3244
  "rooms": [
3245
  [
3246
+ "Kitchen",
3247
  [
3248
  {
3249
  "x": 28,
 
3362
  ]
3363
  ],
3364
  "characterPos": {
3365
+ "x": 4,
3366
+ "y": 16
3367
  },
3368
  "gridCols": 32,
3369
  "gridRows": 20
static/game/game-with-chat.html DELETED
@@ -1,537 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <title>Get Me Out! - Apartment Escape Game with Chat</title>
5
- <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/p5.min.js"></script>
6
- <style>
7
- /* Layout */
8
- body {
9
- margin: 0;
10
- font-family: Arial, sans-serif;
11
- display: flex;
12
- height: 100vh;
13
- }
14
- #gameContainer {
15
- display: flex;
16
- width: 100%;
17
- height: 100%;
18
- }
19
- #mapSection {
20
- flex: 1;
21
- position: relative;
22
- background: #f5f5f5;
23
- display: flex;
24
- flex-direction: column;
25
- align-items: center;
26
- }
27
- /* We'll place the P5 canvas in here */
28
- .map-wrapper {
29
- margin: 20px;
30
- transform-origin: top left;
31
- }
32
-
33
- #chatSection {
34
- width: 400px;
35
- border-left: 1px solid #ddd;
36
- display: flex;
37
- flex-direction: column;
38
- background: white;
39
- }
40
- #chatHistory {
41
- flex: 1;
42
- overflow-y: auto;
43
- padding: 20px;
44
- background: #f8f9fa;
45
- }
46
- #chatControls {
47
- padding: 20px;
48
- border-top: 1px solid #ddd;
49
- background: white;
50
- }
51
- .chat-message {
52
- padding: 10px;
53
- margin: 5px 0;
54
- border-radius: 16px;
55
- max-width: 80%;
56
- word-wrap: break-word;
57
- }
58
- .user-message {
59
- background: #007AFF;
60
- color: white;
61
- margin-left: auto;
62
- border-radius: 16px 16px 4px 16px;
63
- }
64
- .assistant-message {
65
- background: #E9E9EB;
66
- color: black;
67
- margin-right: auto;
68
- border-radius: 16px 16px 16px 4px;
69
- }
70
- textarea {
71
- width: 100%;
72
- padding: 10px;
73
- border: 1px solid #ddd;
74
- border-radius: 4px;
75
- margin-bottom: 10px;
76
- resize: none;
77
- font-family: inherit;
78
- }
79
- button {
80
- padding: 8px 16px;
81
- background: #007AFF;
82
- color: white;
83
- border: none;
84
- border-radius: 4px;
85
- cursor: pointer;
86
- }
87
- button:hover {
88
- background: #0056b3;
89
- }
90
- .loading-dots {
91
- display: inline-flex;
92
- gap: 4px;
93
- padding: 5px;
94
- margin: 5px 0;
95
- }
96
- .dot {
97
- width: 8px;
98
- height: 8px;
99
- background: #6c757d;
100
- border-radius: 50%;
101
- animation: wave 1.3s linear infinite;
102
- }
103
- .dot:nth-child(2) { animation-delay: -1.1s; }
104
- .dot:nth-child(3) { animation-delay: -0.9s; }
105
- @keyframes wave {
106
- 0%, 60%, 100% { transform: translateY(0); }
107
- 30% { transform: translateY(-4px); }
108
- }
109
-
110
- /* Modal for API key (if needed) */
111
- #apiKeyModal {
112
- display: none;
113
- position: fixed;
114
- top: 0; left: 0;
115
- width: 100%; height: 100%;
116
- background: rgba(0,0,0,0.5);
117
- justify-content: center;
118
- align-items: center;
119
- }
120
- .modal-content {
121
- background: white;
122
- padding: 20px;
123
- border-radius: 8px;
124
- width: 300px;
125
- }
126
- #apiKey {
127
- width: 100%;
128
- margin: 10px 0;
129
- padding: 8px;
130
- }
131
- </style>
132
- </head>
133
- <body>
134
- <div id="gameContainer">
135
- <div id="mapSection">
136
- <div id="mapWrapper" class="map-wrapper">
137
- <!-- P5.js canvas goes here -->
138
- </div>
139
- </div>
140
- <div id="chatSection">
141
- <div id="chatHistory"></div>
142
- <div id="chatControls">
143
- <textarea id="prompt" placeholder="Type your message..." rows="3"></textarea>
144
- <button onclick="sendMessage()">Send</button>
145
- </div>
146
- </div>
147
- </div>
148
- <div id="apiKeyModal">
149
- <div class="modal-content">
150
- <h3>Enter Mistral API Key</h3>
151
- <input type="password" id="apiKey" placeholder="Enter your API key">
152
- <button onclick="saveApiKey()">Save</button>
153
- </div>
154
- </div>
155
-
156
- <script>
157
- /*
158
- * This script loads ./apt.json to set up the map,
159
- * draws it in p5.js, and provides a chat interface on the right.
160
- */
161
-
162
- // Constants and variables for the map
163
- const CELL_SIZE = 30;
164
- let GRID_COLS = 0;
165
- let GRID_ROWS = 0;
166
- let grid = [];
167
- let rooms = new Map();
168
- let characterPos = null;
169
- let path = [];
170
- let isMoving = false;
171
- let moveInterval = null;
172
-
173
- // Chat variables
174
- let apiKey = localStorage.getItem('mistralApiKey');
175
- const systemPrompt = `You are the game master of "Get Me Out!", a single-player text-based survival game. The scenario: a player must guide their girlfriend to safety via text messages while she's being hunted by a murderous clown in their apartment. The player has access to security cameras and can send instructions through text.
176
-
177
- RESPONSE FORMAT:
178
- You must ALWAYS respond with a JSON object. The response should reflect the girlfriend's reaction to the player's message.
179
-
180
- 1. For movement instructions ("go" action):
181
- {
182
- "action": "go",
183
- "to": "[room name]",
184
- "textMessage": "[girlfriend's response]"
185
- }
186
-
187
- 2. For any other input or unclear instructions:
188
- {
189
- "textMessage": "[girlfriend's response]"
190
- }
191
-
192
- VALID ROOMS:
193
- Only these rooms are recognized for movement:
194
- - Main Bathroom
195
- - Guest Toilet
196
- - Dining Room
197
- - Kitchen
198
- - TV Room
199
- - Living Room
200
- - Hallway
201
- - Office
202
- - Bedroom
203
-
204
- CHARACTER BEHAVIOR:
205
- The girlfriend is aware of the danger and extremely distressed. Her text responses should be:
206
- - Brief and urgent
207
- - Reflect genuine fear and panic
208
- - Written like real text messages (short, quick responses)
209
- - No time for pleasantries or long explanations
210
- - May include typos or rushed writing due to stress
211
- `;
212
-
213
- // Load apt.json upon page start
214
- async function loadAptJson() {
215
- try {
216
- const response = await fetch('./apt.json');
217
- if (!response.ok) {
218
- alert("Failed to load apt.json!");
219
- return;
220
- }
221
- const data = await response.json();
222
- // Update local variables from apt.json
223
- GRID_COLS = data.gridCols || 40;
224
- GRID_ROWS = data.gridRows || 20;
225
- grid = data.grid || [];
226
- rooms = new Map(data.rooms);
227
- characterPos = data.characterPos || { x: 0, y: 0 };
228
-
229
- // Once loaded, initialize the P5 canvas with correct dims
230
- let canvas = createCanvas(GRID_COLS * CELL_SIZE, GRID_ROWS * CELL_SIZE);
231
- canvas.parent('mapWrapper');
232
- adjustScale();
233
-
234
- } catch (e) {
235
- console.error("Error loading apt.json:", e);
236
- }
237
- }
238
-
239
- function adjustScale() {
240
- const availableWidth = window.innerWidth - 400 - 40; // space for chat + margins
241
- const actualCanvasWidth = GRID_COLS * CELL_SIZE;
242
- const scale = availableWidth / actualCanvasWidth;
243
- const mapWrapper = document.querySelector('#mapSection .map-wrapper');
244
- if (mapWrapper) {
245
- // use CSS zoom or transform
246
- mapWrapper.style.zoom = scale;
247
- }
248
- }
249
-
250
- window.addEventListener('resize', adjustScale);
251
-
252
- // p5.js setup
253
- function setup() {
254
- // We'll wait to createCanvas until apt.json is loaded
255
- loadAptJson();
256
- }
257
-
258
- // p5.js draw loop
259
- function draw() {
260
- if (!grid || grid.length === 0) {
261
- // No grid loaded yet or apt.json not ready
262
- return;
263
- }
264
- background(255);
265
- drawGrid();
266
- drawRooms();
267
- drawWallsAndDoors();
268
- drawPath();
269
- drawCharacter();
270
- }
271
-
272
- function drawGrid() {
273
- stroke(200);
274
- for (let x = 0; x <= GRID_COLS; x++) {
275
- line(x * CELL_SIZE, 0, x * CELL_SIZE, GRID_ROWS*CELL_SIZE);
276
- }
277
- for (let y = 0; y <= GRID_ROWS; y++) {
278
- line(0, y * CELL_SIZE, GRID_COLS*CELL_SIZE, y * CELL_SIZE);
279
- }
280
- }
281
-
282
- function drawRooms() {
283
- for (let [roomName, cells] of rooms) {
284
- const hue = stringToHue(roomName);
285
- fill(hue, 30, 95, 0.3);
286
- noStroke();
287
- for (let cell of cells) {
288
- rect(cell.x * CELL_SIZE, cell.y * CELL_SIZE, CELL_SIZE, CELL_SIZE);
289
- }
290
- if (cells.length > 0) {
291
- fill(0);
292
- textAlign(CENTER, CENTER);
293
- textSize(10);
294
- text(roomName,
295
- cells[0].x * CELL_SIZE + CELL_SIZE/2,
296
- cells[0].y * CELL_SIZE + CELL_SIZE/2
297
- );
298
- }
299
- }
300
- }
301
-
302
- function drawWallsAndDoors() {
303
- for (let y = 0; y < GRID_ROWS; y++) {
304
- for (let x = 0; x < GRID_COLS; x++) {
305
- const cell = grid[y][x];
306
- if (cell.type === 'wall') {
307
- fill(0);
308
- noStroke();
309
- rect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE);
310
- } else if (cell.type === 'door') {
311
- fill(255, 0, 0);
312
- noStroke();
313
- rect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE);
314
- }
315
- }
316
- }
317
- }
318
-
319
- function drawPath() {
320
- if (path.length > 0 && isMoving) {
321
- noFill();
322
- stroke(0, 255, 0);
323
- strokeWeight(2);
324
- line(
325
- characterPos.x * CELL_SIZE + CELL_SIZE/2,
326
- characterPos.y * CELL_SIZE + CELL_SIZE/2,
327
- path[0].x * CELL_SIZE + CELL_SIZE/2,
328
- path[0].y * CELL_SIZE + CELL_SIZE/2
329
- );
330
- for (let i = 0; i < path.length - 1; i++) {
331
- line(
332
- path[i].x * CELL_SIZE + CELL_SIZE/2,
333
- path[i].y * CELL_SIZE + CELL_SIZE/2,
334
- path[i + 1].x * CELL_SIZE + CELL_SIZE/2,
335
- path[i + 1].y * CELL_SIZE + CELL_SIZE/2
336
- );
337
- }
338
- strokeWeight(1);
339
- }
340
- }
341
-
342
- function drawCharacter() {
343
- if (characterPos) {
344
- textSize(CELL_SIZE * 0.8);
345
- textAlign(CENTER, CENTER);
346
- text('👧',
347
- characterPos.x * CELL_SIZE + CELL_SIZE/2,
348
- characterPos.y * CELL_SIZE + CELL_SIZE/2
349
- );
350
- }
351
- }
352
-
353
- function stringToHue(str) {
354
- let hash = 0;
355
- for (let i = 0; i < str.length; i++) {
356
- hash = str.charCodeAt(i) + ((hash << 5) - hash);
357
- }
358
- return hash % 360;
359
- }
360
-
361
- // Simple BFS for pathfinding
362
- function findPath(start, end) {
363
- const queue = [[start]];
364
- const visited = new Set();
365
- const key = pos => `${pos.x},${pos.y}`;
366
- visited.add(key(start));
367
-
368
- while (queue.length > 0) {
369
- const currentPath = queue.shift();
370
- const current = currentPath[currentPath.length - 1];
371
-
372
- if (current.x === end.x && current.y === end.y) {
373
- return currentPath;
374
- }
375
- const neighbors = [
376
- { x: current.x, y: current.y - 1 },
377
- { x: current.x+1, y: current.y },
378
- { x: current.x, y: current.y+1 },
379
- { x: current.x-1, y: current.y }
380
- ];
381
- for (const next of neighbors) {
382
- if (next.x < 0 || next.x >= GRID_COLS || next.y < 0 || next.y >= GRID_ROWS) continue;
383
- if (visited.has(key(next))) continue;
384
-
385
- const cell = grid[next.y][next.x];
386
- if (cell.type === 'wall') continue;
387
-
388
- visited.add(key(next));
389
- queue.push([...currentPath, next]);
390
- }
391
- }
392
- return [];
393
- }
394
-
395
- function moveToRoom(roomName) {
396
- const targetRoom = Array.from(rooms.entries())
397
- .find(([name]) => name.toLowerCase() === roomName.toLowerCase());
398
- if (!targetRoom || !characterPos) return false;
399
-
400
- const [_, cells] = targetRoom;
401
- if (cells.length === 0) return false;
402
- path = findPath(characterPos, cells[0]);
403
- if (path.length > 0) {
404
- isMoving = true;
405
- moveCharacterAlongPath();
406
- return true;
407
- }
408
- return false;
409
- }
410
-
411
- function moveCharacterAlongPath() {
412
- if (moveInterval) clearInterval(moveInterval);
413
- moveInterval = setInterval(() => {
414
- if (path.length === 0) {
415
- isMoving = false;
416
- clearInterval(moveInterval);
417
- return;
418
- }
419
- const nextPos = path.shift();
420
- characterPos = nextPos;
421
- }, 200);
422
- }
423
-
424
- /* Chat / Mistral-related code */
425
- function createLoadingIndicator() {
426
- const loadingDiv = document.createElement('div');
427
- loadingDiv.className = 'chat-message assistant-message';
428
- loadingDiv.innerHTML = `
429
- <div class="loading-dots">
430
- <div class="dot"></div>
431
- <div class="dot"></div>
432
- <div class="dot"></div>
433
- </div>
434
- `;
435
- return loadingDiv;
436
- }
437
-
438
- function addMessageToChat(role, content) {
439
- const chatHistory = document.getElementById('chatHistory');
440
- const messageDiv = document.createElement('div');
441
- messageDiv.className = `chat-message ${role}-message`;
442
- messageDiv.textContent = content;
443
- chatHistory.appendChild(messageDiv);
444
- chatHistory.scrollTop = chatHistory.scrollHeight;
445
- }
446
-
447
- async function sendMessage() {
448
- const prompt = document.getElementById('prompt').value.trim();
449
- if (!prompt) return;
450
-
451
- if (!apiKey) {
452
- alert('Please enter your Mistral API key first');
453
- document.getElementById('apiKeyModal').style.display = 'flex';
454
- return;
455
- }
456
-
457
- // Add user message to the chat
458
- addMessageToChat('user', prompt);
459
- document.getElementById('prompt').value = '';
460
-
461
- // Add loading indicator
462
- const chatHistory = document.getElementById('chatHistory');
463
- const loadingIndicator = createLoadingIndicator();
464
- chatHistory.appendChild(loadingIndicator);
465
-
466
- try {
467
- // Make call to Mistral or any AI endpoint
468
- const response = await fetch('https://api.mistral.ai/v1/chat/completions', {
469
- method: 'POST',
470
- headers: {
471
- 'Content-Type': 'application/json',
472
- 'Authorization': `Bearer ${apiKey}`
473
- },
474
- body: JSON.stringify({
475
- model: 'mistral-large-latest',
476
- messages: [
477
- { role: 'system', content: systemPrompt },
478
- { role: 'user', content: prompt }
479
- ]
480
- })
481
- });
482
-
483
- if (!response.ok) {
484
- throw new Error(`HTTP error! status: ${response.status}`);
485
- }
486
-
487
- const data = await response.json();
488
- const assistantResponse = data.choices[0].message.content || "";
489
-
490
- try {
491
- const jsonStart = assistantResponse.indexOf('{');
492
- const jsonEnd = assistantResponse.lastIndexOf('}') + 1;
493
- const jsonContent = assistantResponse.substring(jsonStart, jsonEnd);
494
- const jsonResponse = JSON.parse(jsonContent);
495
-
496
- if (jsonResponse.textMessage) {
497
- addMessageToChat('assistant', jsonResponse.textMessage);
498
- }
499
- if (jsonResponse.action === 'go' && jsonResponse.to) {
500
- moveToRoom(jsonResponse.to);
501
- }
502
- } catch (e) {
503
- console.error('Error parsing JSON from response:', e);
504
- addMessageToChat('assistant', 'Sorry, I had trouble understanding that response.');
505
- }
506
- } catch (error) {
507
- addMessageToChat('assistant', `Error: ${error.message}`);
508
- } finally {
509
- loadingIndicator.remove();
510
- }
511
- }
512
-
513
- function saveApiKey() {
514
- const key = document.getElementById('apiKey').value.trim();
515
- if (key) {
516
- apiKey = key;
517
- localStorage.setItem('mistralApiKey', key);
518
- document.getElementById('apiKeyModal').style.display = 'none';
519
- }
520
- }
521
-
522
- // Allow sending message with Enter
523
- document.addEventListener('DOMContentLoaded', () => {
524
- document.getElementById('prompt').addEventListener('keypress', function(e) {
525
- if (e.key === 'Enter' && !e.shiftKey) {
526
- e.preventDefault();
527
- sendMessage();
528
- }
529
- });
530
-
531
- if (!apiKey) {
532
- document.getElementById('apiKeyModal').style.display = 'flex';
533
- }
534
- });
535
- </script>
536
- </body>
537
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/game/gameState.js ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ // Game state management
3
+ class GameState {
4
+ constructor() {
5
+ // Initialize with rooms from apt.json
6
+ this.rooms = [
7
+ "My Bedroom",
8
+ "Kichen",
9
+ "Storage",
10
+ "Bathroom",
11
+ "Main Hallway",
12
+ "Living Room",
13
+ "Guest Bedroom",
14
+ "Dining Room",
15
+ "Office",
16
+ "North Hallway"
17
+ ];
18
+
19
+ this.hideTargets = [
20
+ {"room": "My Bedroom", "hiding_place": "bed"},
21
+ {"room": "North Hallway", "hiding_place": "coat closet"},
22
+ {"room": "Dining Room", "hiding_place": "table"},
23
+ {"room": "Guest Bedroom", "hiding_place": "bed"}
24
+ ]
25
+
26
+ this.searchTargets = ["dresser", "desk", "bookcase", "cabinet", "dead body", "fridge", "stove", "coffee table"];
27
+
28
+ this.items = ["lock pick", "book note", "flashlight", "knife", "oil", "remote"];
29
+
30
+ this.useTargets = ["bedroom door", "bookcase", "storage door", "dead body","coffee table","TV"];
31
+
32
+ // Track current game state
33
+ this.currentRoom = this.rooms[0];
34
+ this.inventory = [];
35
+ this.isHiding = false;
36
+ }
37
+ setCurrentRoom(room) {
38
+ this.currentRoom = room;
39
+ }
40
+
41
+ handleAction(action, target) {
42
+ switch (action) {
43
+ case 'go':
44
+ this.currentRoom = target;
45
+ break;
46
+ case 'hide':
47
+ this.isHiding = true;
48
+ break;
49
+ case 'search':
50
+ // Could add found items to inventory
51
+ break;
52
+ case 'use':
53
+ // Handle item usage
54
+ break;
55
+ }
56
+ }
57
+
58
+ getPrompt() {
59
+ return `You are the game master of "Get Me Out!", a single-player text-based survival game. The scenario: a player must guide their girlfriend to safety via text messages while she's being hunted by a murderous clown in their apartment. The player has access to security cameras and can give instructions through text.
60
+
61
+ Current state:
62
+ - Room: ${this.currentRoom}
63
+ - Hiding: ${this.isHiding}
64
+ - Inventory: ${this.inventory.join(", ") || "empty"}
65
+
66
+ RESPONSE FORMAT:
67
+ You must ALWAYS respond with a JSON object. The response should reflect the girlfriend's reaction to the player's message.
68
+
69
+ 1. For movement instructions ("go" action):
70
+ {
71
+ "action": "go",
72
+ "to": "[room name]",
73
+ "textMessage": "[girlfriend's response]"
74
+ }
75
+
76
+ 2. For any other input or unclear instructions:
77
+ {
78
+ "textMessage": "[girlfriend's response]"
79
+ }
80
+
81
+ VALID ROOMS:
82
+ Only these rooms are recognized for movement:
83
+ ${this.rooms.join('\n')}
84
+
85
+ CHARACTER BEHAVIOR:
86
+ The girlfriend is aware of the danger and extremely distressed. Her text responses should be:
87
+ - Brief and urgent
88
+ - Reflect genuine fear and panic
89
+ - Written like real text messages (short, quick responses)
90
+ - No time for pleasantries or long explanations
91
+ - May include typos or rushed writing due to stress`;
92
+ }
93
+
94
+ }
static/game/index.html CHANGED
@@ -3,6 +3,7 @@
3
  <head>
4
  <title>Get Me Out! - Apartment Escape Game with Chat</title>
5
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/p5.min.js"></script>
 
6
  <style>
7
  /* Layout */
8
  body {
@@ -47,6 +48,8 @@
47
  padding: 20px;
48
  border-top: 1px solid #ddd;
49
  background: white;
 
 
50
  }
51
  .chat-message {
52
  padding: 10px;
@@ -67,13 +70,11 @@
67
  margin-right: auto;
68
  border-radius: 16px 16px 16px 4px;
69
  }
70
- textarea {
71
- width: 100%;
72
  padding: 10px;
73
  border: 1px solid #ddd;
74
  border-radius: 4px;
75
- margin-bottom: 10px;
76
- resize: none;
77
  font-family: inherit;
78
  }
79
  button {
@@ -140,8 +141,8 @@
140
  <div id="chatSection">
141
  <div id="chatHistory"></div>
142
  <div id="chatControls">
143
- <textarea id="prompt" placeholder="Type your message..." rows="3"></textarea>
144
- <button onclick="sendMessage()">Send</button>
145
  </div>
146
  </div>
147
  </div>
@@ -170,45 +171,12 @@
170
  let isMoving = false;
171
  let moveInterval = null;
172
 
 
 
 
173
  // Chat variables
174
  let apiKey = localStorage.getItem('mistralApiKey');
175
- const systemPrompt = `You are the game master of "Get Me Out!", a single-player text-based survival game. The scenario: a player must guide their girlfriend to safety via text messages while she's being hunted by a murderous clown in their apartment. The player has access to security cameras and can send instructions through text.
176
-
177
- RESPONSE FORMAT:
178
- You must ALWAYS respond with a JSON object. The response should reflect the girlfriend's reaction to the player's message.
179
-
180
- 1. For movement instructions ("go" action):
181
- {
182
- "action": "go",
183
- "to": "[room name]",
184
- "textMessage": "[girlfriend's response]"
185
- }
186
-
187
- 2. For any other input or unclear instructions:
188
- {
189
- "textMessage": "[girlfriend's response]"
190
- }
191
-
192
- VALID ROOMS:
193
- Only these rooms are recognized for movement:
194
- - Main Bathroom
195
- - Guest Toilet
196
- - Dining Room
197
- - Kitchen
198
- - TV Room
199
- - Living Room
200
- - Hallway
201
- - Office
202
- - Bedroom
203
-
204
- CHARACTER BEHAVIOR:
205
- The girlfriend is aware of the danger and extremely distressed. Her text responses should be:
206
- - Brief and urgent
207
- - Reflect genuine fear and panic
208
- - Written like real text messages (short, quick responses)
209
- - No time for pleasantries or long explanations
210
- - May include typos or rushed writing due to stress
211
- `;
212
 
213
  // Load apt.json upon page start
214
  async function loadAptJson() {
@@ -445,6 +413,14 @@ The girlfriend is aware of the danger and extremely distressed. Her text respons
445
  if (path.length === 0) {
446
  isMoving = false;
447
  clearInterval(moveInterval);
 
 
 
 
 
 
 
 
448
  return;
449
  }
450
  const nextPos = path.shift();
@@ -473,9 +449,12 @@ The girlfriend is aware of the danger and extremely distressed. Her text respons
473
  messageDiv.textContent = content;
474
  chatHistory.appendChild(messageDiv);
475
  chatHistory.scrollTop = chatHistory.scrollHeight;
 
 
 
476
  }
477
 
478
- async function sendMessage() {
479
  const prompt = document.getElementById('prompt').value.trim();
480
  if (!prompt) return;
481
 
@@ -485,17 +464,24 @@ The girlfriend is aware of the danger and extremely distressed. Her text respons
485
  return;
486
  }
487
 
488
- // Add user message to the chat
489
  addMessageToChat('user', prompt);
490
  document.getElementById('prompt').value = '';
491
 
492
- // Add loading indicator
493
  const chatHistory = document.getElementById('chatHistory');
494
  const loadingIndicator = createLoadingIndicator();
495
  chatHistory.appendChild(loadingIndicator);
496
 
497
  try {
498
- // Make call to Mistral or any AI endpoint
 
 
 
 
 
 
 
 
 
499
  const response = await fetch('https://api.mistral.ai/v1/chat/completions', {
500
  method: 'POST',
501
  headers: {
@@ -504,10 +490,7 @@ The girlfriend is aware of the danger and extremely distressed. Her text respons
504
  },
505
  body: JSON.stringify({
506
  model: 'mistral-large-latest',
507
- messages: [
508
- { role: 'system', content: systemPrompt },
509
- { role: 'user', content: prompt }
510
- ]
511
  })
512
  });
513
 
@@ -516,6 +499,7 @@ The girlfriend is aware of the danger and extremely distressed. Her text respons
516
  }
517
 
518
  const data = await response.json();
 
519
  const assistantResponse = data.choices[0].message.content || "";
520
 
521
  try {
@@ -523,6 +507,7 @@ The girlfriend is aware of the danger and extremely distressed. Her text respons
523
  const jsonEnd = assistantResponse.lastIndexOf('}') + 1;
524
  const jsonContent = assistantResponse.substring(jsonStart, jsonEnd);
525
  const jsonResponse = JSON.parse(jsonContent);
 
526
 
527
  if (jsonResponse.textMessage) {
528
  addMessageToChat('assistant', jsonResponse.textMessage);
@@ -550,12 +535,12 @@ The girlfriend is aware of the danger and extremely distressed. Her text respons
550
  }
551
  }
552
 
553
- // Allow sending message with Enter
554
  document.addEventListener('DOMContentLoaded', () => {
555
  document.getElementById('prompt').addEventListener('keypress', function(e) {
556
- if (e.key === 'Enter' && !e.shiftKey) {
557
  e.preventDefault();
558
- sendMessage();
559
  }
560
  });
561
 
 
3
  <head>
4
  <title>Get Me Out! - Apartment Escape Game with Chat</title>
5
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/p5.min.js"></script>
6
+ <script src="/static/game/gameState.js"></script>
7
  <style>
8
  /* Layout */
9
  body {
 
48
  padding: 20px;
49
  border-top: 1px solid #ddd;
50
  background: white;
51
+ display: flex;
52
+ gap: 10px;
53
  }
54
  .chat-message {
55
  padding: 10px;
 
70
  margin-right: auto;
71
  border-radius: 16px 16px 16px 4px;
72
  }
73
+ input {
74
+ flex: 1;
75
  padding: 10px;
76
  border: 1px solid #ddd;
77
  border-radius: 4px;
 
 
78
  font-family: inherit;
79
  }
80
  button {
 
141
  <div id="chatSection">
142
  <div id="chatHistory"></div>
143
  <div id="chatControls">
144
+ <input type="text" id="prompt" placeholder="Type your message...">
145
+ <button onclick="Message()">▶</button>
146
  </div>
147
  </div>
148
  </div>
 
171
  let isMoving = false;
172
  let moveInterval = null;
173
 
174
+ // Initialize game state
175
+ const gameState = new GameState();
176
+
177
  // Chat variables
178
  let apiKey = localStorage.getItem('mistralApiKey');
179
+ let chatMessages = []; // Array to store chat history
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
 
181
  // Load apt.json upon page start
182
  async function loadAptJson() {
 
413
  if (path.length === 0) {
414
  isMoving = false;
415
  clearInterval(moveInterval);
416
+ // Update game state with new room when movement is complete
417
+ const currentRoomEntry = Array.from(rooms.entries())
418
+ .find(([_, cells]) => cells.some(cell =>
419
+ cell.x === characterPos.x && cell.y === characterPos.y
420
+ ));
421
+ if (currentRoomEntry) {
422
+ gameState.setCurrentRoom(currentRoomEntry[0]);
423
+ }
424
  return;
425
  }
426
  const nextPos = path.shift();
 
449
  messageDiv.textContent = content;
450
  chatHistory.appendChild(messageDiv);
451
  chatHistory.scrollTop = chatHistory.scrollHeight;
452
+
453
+ // Store message in chat history
454
+ chatMessages.push({ role, content });
455
  }
456
 
457
+ async function Message() {
458
  const prompt = document.getElementById('prompt').value.trim();
459
  if (!prompt) return;
460
 
 
464
  return;
465
  }
466
 
 
467
  addMessageToChat('user', prompt);
468
  document.getElementById('prompt').value = '';
469
 
 
470
  const chatHistory = document.getElementById('chatHistory');
471
  const loadingIndicator = createLoadingIndicator();
472
  chatHistory.appendChild(loadingIndicator);
473
 
474
  try {
475
+ // Get last 5 messages from chat history
476
+ const recentMessages = chatMessages.slice(-5);
477
+
478
+ // Prepare messages array for Mistral
479
+ const messages = [
480
+ { role: 'system', content: gameState.getPrompt() },
481
+ ...recentMessages,
482
+ { role: 'user', content: prompt }
483
+ ];
484
+
485
  const response = await fetch('https://api.mistral.ai/v1/chat/completions', {
486
  method: 'POST',
487
  headers: {
 
490
  },
491
  body: JSON.stringify({
492
  model: 'mistral-large-latest',
493
+ messages: messages
 
 
 
494
  })
495
  });
496
 
 
499
  }
500
 
501
  const data = await response.json();
502
+ console.log('Mistral response:', data.choices[0].message.content);
503
  const assistantResponse = data.choices[0].message.content || "";
504
 
505
  try {
 
507
  const jsonEnd = assistantResponse.lastIndexOf('}') + 1;
508
  const jsonContent = assistantResponse.substring(jsonStart, jsonEnd);
509
  const jsonResponse = JSON.parse(jsonContent);
510
+ console.log('Parsed JSON response:', jsonResponse);
511
 
512
  if (jsonResponse.textMessage) {
513
  addMessageToChat('assistant', jsonResponse.textMessage);
 
535
  }
536
  }
537
 
538
+ // Allow ing message with Enter
539
  document.addEventListener('DOMContentLoaded', () => {
540
  document.getElementById('prompt').addEventListener('keypress', function(e) {
541
+ if (e.key === 'Enter') {
542
  e.preventDefault();
543
+ Message();
544
  }
545
  });
546
 
static/game/test prompt.txt ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ You are the game master of "Get Me Out!", a single-player text-based survival game. The scenario: a player must guide their girlfriend to safety via text messages while she's being hunted by a murderous clown in their apartment. The player has access to security cameras and can instructions through text.
2
+
3
+ RESPONSE FORMAT:
4
+ You must ALWAYS respond with a JSON object. The response should reflect the girlfriend's reaction to the player's message.
5
+
6
+ 1. For movement instructions ("go" action):
7
+ {
8
+ "action": "go",
9
+ "to": "[room name]",
10
+ "textMessage": "[girlfriend's response]"
11
+ }
12
+
13
+ 2. For any other input or unclear instructions:
14
+ {
15
+ "textMessage": "[girlfriend's response]"
16
+ }
17
+
18
+ VALID ROOMS:
19
+ Only these rooms are recognized for movement:
20
+ - Main Bathroom
21
+ - Guest Toilet
22
+ - Dining Room
23
+ - Kitchen
24
+ - TV Room
25
+ - Living Room
26
+ - Hallway
27
+ - Office
28
+ - Bedroom
29
+
30
+ CHARACTER BEHAVIOR:
31
+ The girlfriend is aware of the danger and extremely distressed. Her text responses should be:
32
+ - Brief and urgent
33
+ - Reflect genuine fear and panic
34
+ - Written like real text messages (short, quick responses)
35
+ - No time for pleasantries or long explanations
36
+ - May include typos or rushed writing due to stress
37
+
38
+
39
+
static/how-to-play.html ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Through Their Eyes - How to Play</title>
7
+ <link rel="stylesheet" href="/src/style.css" />
8
+ </head>
9
+ <body>
10
+ <div class="led-bar">
11
+ <div class="light-beam"></div>
12
+ </div>
13
+
14
+ <div class="background-elements">
15
+ <img src="public/blood-2.png" alt="" class="blood blood-top-left" />
16
+ <img src="public/help.png" alt="" class="blood blood-top-right" />
17
+ <img src="public/splatter.png" alt="" class="blood splatter" />
18
+ <img src="public/hand.png" alt="" class="blood blood-bottom-right" />
19
+ </div>
20
+
21
+ <main>
22
+ <div class="content">
23
+ <h1>How to Play</h1>
24
+ <!-- Contenu à ajouter -->
25
+ </div>
26
+
27
+ <a href="index.html" class="back-button">Back to Menu</a>
28
+ </main>
29
+
30
+ <script type="module" src="/src/main.js"></script>
31
+ </body>
32
+ </html>
static/index.html CHANGED
@@ -1,150 +1,34 @@
1
  <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <title>The Last Message - Horror Escape Game</title>
5
- <style>
6
- body {
7
- margin: 0;
8
- padding: 0;
9
- min-height: 100vh;
10
- font-family: 'Arial', sans-serif;
11
- background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%);
12
- color: #ffffff;
13
- display: flex;
14
- flex-direction: column;
15
- align-items: center;
16
- justify-content: center;
17
- overflow: hidden;
18
- }
19
-
20
- .container {
21
- text-align: center;
22
- padding: 2rem;
23
- max-width: 800px;
24
- position: relative;
25
- z-index: 1;
26
- }
27
-
28
- h1 {
29
- font-size: 4rem;
30
- margin-bottom: 1rem;
31
- text-shadow: 0 0 10px #ff0000, 0 0 20px #ff0000;
32
- animation: flicker 4s infinite;
33
- letter-spacing: 2px;
34
- }
35
-
36
- .subtitle {
37
- font-size: 1.2rem;
38
- color: #bb0000;
39
- margin-bottom: 3rem;
40
- text-shadow: 0 0 5px #ff0000;
41
- }
42
-
43
- .menu {
44
- display: grid;
45
- gap: 2rem;
46
- margin-top: 2rem;
47
- }
48
-
49
- .menu-item {
50
- background: rgba(20, 20, 20, 0.8);
51
- padding: 1.5rem;
52
- border-radius: 5px;
53
- cursor: pointer;
54
- transition: all 0.3s ease;
55
- text-decoration: none;
56
- color: white;
57
- border: 1px solid #440000;
58
- box-shadow: 0 0 10px rgba(255, 0, 0, 0.2);
59
- }
60
-
61
- .menu-item:hover {
62
- transform: scale(1.05);
63
- background: rgba(40, 0, 0, 0.8);
64
- border-color: #ff0000;
65
- box-shadow: 0 0 20px rgba(255, 0, 0, 0.4);
66
- }
67
-
68
- .menu-item h2 {
69
- margin: 0 0 0.5rem 0;
70
- font-size: 1.8rem;
71
- color: #ff0000;
72
- }
73
-
74
- .menu-item p {
75
- margin: 0;
76
- color: #cccccc;
77
- font-size: 1rem;
78
- }
79
-
80
- @keyframes flicker {
81
- 0%, 100% { opacity: 1; }
82
- 92% { opacity: 1; }
83
- 93% { opacity: 0.8; }
84
- 94% { opacity: 1; }
85
- 95% { opacity: 0.9; }
86
- 96% { opacity: 1; }
87
- }
88
-
89
- /* Blood drips */
90
- .blood-drip {
91
- position: fixed;
92
- top: 0;
93
- width: 3px;
94
- height: 60px;
95
- background: linear-gradient(to bottom, transparent, rgba(255, 0, 0, 0.8));
96
- animation: drip 4s infinite;
97
- opacity: 0.7;
98
- }
99
-
100
- @keyframes drip {
101
- 0% {
102
- transform: translateY(-60px);
103
- opacity: 0;
104
- }
105
- 50% {
106
- opacity: 0.7;
107
- }
108
- 100% {
109
- transform: translateY(100vh);
110
- opacity: 0;
111
- }
112
- }
113
-
114
-
115
- </style>
116
- </head>
117
- <body>
118
- <!-- Blood drip effects -->
119
- <div class="blood-drip" style="left: 15%"></div>
120
- <div class="blood-drip" style="left: 35%; animation-delay: 1.5s"></div>
121
- <div class="blood-drip" style="left: 55%; animation-delay: 2.5s"></div>
122
- <div class="blood-drip" style="left: 75%; animation-delay: 1s"></div>
123
- <div class="blood-drip" style="left: 95%; animation-delay: 2s"></div>
124
-
125
- <div class="container">
126
- <h1>THE LAST MESSAGE</h1>
127
- <div class="subtitle">Will your final words save them?</div>
128
-
129
- <div class="menu">
130
- <a href="game/" class="menu-item">
131
- <h2>📱 PLAY THE STORY</h2>
132
- <p>Experience the original nightmare</p>
133
- </a>
134
-
135
- <a href="maker/" class="menu-item">
136
- <h2>🏗️ CREATE & PLAY</h2>
137
- <p>Design and play your own horror scenario</p>
138
- </a>
139
- </div>
140
-
141
  </div>
142
 
143
- <script>
144
- // Add random delay and duration to blood drips
145
- document.querySelectorAll('.blood-drip').forEach(drip => {
146
- drip.style.animationDuration = (3 + Math.random() * 2) + 's';
147
- });
148
- </script>
149
- </body>
150
- </html>
 
 
 
 
 
 
 
 
1
  <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Through Their Eyes</title>
7
+ <link rel="stylesheet" href="assets/css/style.css">
8
+ </head>
9
+ <body>
10
+ <div class="led-bar">
11
+ <div class="light-beam"></div>
12
+ </div>
13
+ <div class="background-elements">
14
+ <img src="assets/img/blood-2.png" alt="" class="blood blood-top-left" />
15
+ <img src="assets/img/help.png" alt="" class="blood blood-top-right" />
16
+ <img src="assets/img/splatter.png" alt="" class="blood splatter" />
17
+ <img src="assets/img/hand.png" alt="" class="blood blood-bottom-right" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  </div>
19
 
20
+ <main>
21
+ <div class="logo">
22
+ <img src="assets/img/logo.png" alt="Through Their Eyes" />
23
+ </div>
24
+
25
+ <nav class="menu">
26
+ <a href="game/" class="menu-item new-game">NEW GAME</a>
27
+ <a href="how-to-play.html" class="menu-item how-to-play">HOW TO PLAY</a>
28
+ </nav>
29
+ <div class="character">
30
+ <img src="assets/img/perso.png" alt="Character" />
31
+ </div>
32
+ </main>
33
+ </body>
34
+ </html>
static/maker/index.html CHANGED
@@ -57,6 +57,7 @@
57
  |
58
  <button onclick="saveMap()">Save Map</button>
59
  <button onclick="loadMap()">Load Map</button>
 
60
  |
61
  <input type="number" id="gridCols" value="40" min="1" style="width: 60px"> ×
62
  <input type="number" id="gridRows" value="20" min="1" style="width: 60px">
@@ -69,7 +70,7 @@
69
  <!-- Move the JSON text areas here so they appear after the editor and canvas -->
70
  <div style="margin-top: 20px; width: 100%; max-width: 1200px; text-align: center;">
71
  <textarea id="roomsJson" readonly style="width: 90%; height: 100px; margin: 10px; padding: 10px;" placeholder="Rooms will appear here in JSON format"></textarea>
72
- <textarea id="gameJson" readonly style="width: 90%; height: 200px; margin: 10px; padding: 10px;" placeholder="Complete game definition will appear here in JSON format"></textarea>
73
  </div>
74
 
75
  <script>
@@ -481,6 +482,43 @@
481
  updateGameJson(); // Update game JSON after loading
482
  }
483
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
484
  function removeSelectedRoom() {
485
  const roomName = document.getElementById('roomSelect').value;
486
  if (!roomName) return;
 
57
  |
58
  <button onclick="saveMap()">Save Map</button>
59
  <button onclick="loadMap()">Load Map</button>
60
+ <button onclick="loadFromJson()">Load from JSON</button>
61
  |
62
  <input type="number" id="gridCols" value="40" min="1" style="width: 60px"> ×
63
  <input type="number" id="gridRows" value="20" min="1" style="width: 60px">
 
70
  <!-- Move the JSON text areas here so they appear after the editor and canvas -->
71
  <div style="margin-top: 20px; width: 100%; max-width: 1200px; text-align: center;">
72
  <textarea id="roomsJson" readonly style="width: 90%; height: 100px; margin: 10px; padding: 10px;" placeholder="Rooms will appear here in JSON format"></textarea>
73
+ <textarea id="gameJson" style="width: 90%; height: 200px; margin: 10px; padding: 10px;" placeholder="Complete game definition will appear here in JSON format"></textarea>
74
  </div>
75
 
76
  <script>
 
482
  updateGameJson(); // Update game JSON after loading
483
  }
484
 
485
+ function loadFromJson() {
486
+ const gameJson = document.getElementById('gameJson').value;
487
+ if (!gameJson) {
488
+ alert('Please paste a valid game JSON in the textarea below');
489
+ return;
490
+ }
491
+
492
+ try {
493
+ const data = JSON.parse(gameJson);
494
+
495
+ // Validate required fields
496
+ if (!data.grid || !Array.isArray(data.grid)) {
497
+ throw new Error('Invalid grid data');
498
+ }
499
+
500
+ // Update grid dimensions and input fields
501
+ GRID_COLS = data.gridCols || data.grid[0].length;
502
+ GRID_ROWS = data.gridRows || data.grid.length;
503
+ document.getElementById('gridCols').value = GRID_COLS;
504
+ document.getElementById('gridRows').value = GRID_ROWS;
505
+
506
+ // Resize canvas and initialize grid
507
+ resizeCanvas(GRID_COLS * CELL_SIZE, GRID_ROWS * CELL_SIZE);
508
+ initGrid();
509
+
510
+ // Load saved data
511
+ grid = data.grid;
512
+ rooms = new Map(data.rooms || []);
513
+ characterPos = data.characterPos;
514
+
515
+ updateRoomDropdown();
516
+ updateGameJson();
517
+ } catch (error) {
518
+ alert('Error loading JSON: ' + error.message);
519
+ }
520
+ }
521
+
522
  function removeSelectedRoom() {
523
  const roomName = document.getElementById('roomSelect').value;
524
  if (!roomName) return;
static/mistral/index.html ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Mistral Chat Test Bed</title>
7
+ <style>
8
+ body {
9
+ font-family: Arial, sans-serif;
10
+ max-width: 800px;
11
+ margin: 20px auto;
12
+ padding: 0 20px;
13
+ }
14
+
15
+ /* System Prompt Section */
16
+ #systemPromptSection {
17
+ margin-bottom: 20px;
18
+ padding: 10px;
19
+ background: #f8f9fa;
20
+ border-radius: 4px;
21
+ }
22
+
23
+ textarea {
24
+ width: 100%;
25
+ height: 150px;
26
+ margin: 10px 0;
27
+ padding: 8px;
28
+ font-family: monospace;
29
+ }
30
+
31
+ /* Chat Section */
32
+ #chatHistory {
33
+ max-height: 400px;
34
+ overflow-y: auto;
35
+ margin: 20px 0;
36
+ padding: 10px;
37
+ border: 1px solid #ddd;
38
+ border-radius: 4px;
39
+ }
40
+
41
+ .chat-message {
42
+ padding: 10px;
43
+ margin: 5px 0;
44
+ border-radius: 4px;
45
+ }
46
+
47
+ .user-message {
48
+ background: #e3f2fd;
49
+ }
50
+
51
+ .assistant-message {
52
+ background: #f5f5f5;
53
+ white-space: pre-wrap;
54
+ }
55
+
56
+ /* Raw JSON Section */
57
+ .raw-json {
58
+ font-family: monospace;
59
+ font-size: 0.9em;
60
+ color: #666;
61
+ margin-top: 5px;
62
+ padding: 5px;
63
+ background: #f8f9fa;
64
+ border-left: 3px solid #007bff;
65
+ }
66
+
67
+ /* Controls */
68
+ #controls {
69
+ display: flex;
70
+ gap: 10px;
71
+ margin: 20px 0;
72
+ }
73
+
74
+ input[type="text"] {
75
+ flex: 1;
76
+ padding: 8px;
77
+ }
78
+
79
+ button {
80
+ padding: 8px 16px;
81
+ background: #007bff;
82
+ color: white;
83
+ border: none;
84
+ border-radius: 4px;
85
+ cursor: pointer;
86
+ }
87
+
88
+ button:hover {
89
+ background: #0056b3;
90
+ }
91
+
92
+ /* Display Options */
93
+ .display-options {
94
+ margin: 10px 0;
95
+ padding: 10px;
96
+ background: #f8f9fa;
97
+ border-radius: 4px;
98
+ }
99
+ </style>
100
+ </head>
101
+ <body>
102
+ <h1>Mistral Chat Test Bed</h1>
103
+
104
+ <div id="systemPromptSection">
105
+ <h3>System Prompt</h3>
106
+ <textarea id="systemPrompt">You are the game master of "Get Me Out!", a single-player text-based survival game. The scenario: a player must guide their girlfriend to safety via text messages while she's being hunted by a murderous clown in their apartment. The player has access to security cameras and can give instructions through text.
107
+
108
+ Current state:
109
+ - Room: Main Bathroom
110
+ - Hiding: false
111
+ - Inventory: empty
112
+
113
+ RESPONSE FORMAT:
114
+ You must ALWAYS respond with a JSON object. The response should reflect the girlfriend's reaction to the player's message.
115
+
116
+ 1. For movement instructions ("go" action):
117
+ {
118
+ "action": "go",
119
+ "to": "[room name]",
120
+ "textMessage": "[girlfriend's response]"
121
+ }
122
+
123
+ 2. For any other input or unclear instructions:
124
+ {
125
+ "textMessage": "[girlfriend's response]"
126
+ }
127
+
128
+ VALID ROOMS:
129
+ Only these rooms are recognized for movement:
130
+ Main Bathroom
131
+ Guest Toilet
132
+ Dining Room
133
+ Kitchen
134
+ TV Room
135
+ Living Room
136
+ Hallway
137
+ Office
138
+ Bedroom
139
+
140
+ CHARACTER BEHAVIOR:
141
+ The girlfriend is aware of the danger and extremely distressed. Her text responses should be:
142
+ - Brief and urgent
143
+ - Reflect genuine fear and panic
144
+ - Written like real text messages (short, quick responses)
145
+ - No time for pleasantries or long explanations
146
+ - May include typos or rushed writing due to stress</textarea>
147
+ </div>
148
+
149
+ <div class="display-options">
150
+ <label>
151
+ <input type="checkbox" id="showRawJson" checked>
152
+ Show Raw JSON Response
153
+ </label>
154
+ </div>
155
+
156
+ <div id="chatHistory"></div>
157
+
158
+ <div id="controls">
159
+ <input type="text" id="userInput" placeholder="Type your message...">
160
+ <button onclick="sendMessage()">Send</button>
161
+ </div>
162
+
163
+ <div id="apiKeySection">
164
+ <h3>API Key</h3>
165
+ <input type="password" id="apiKey" placeholder="Enter Mistral API Key">
166
+ <button onclick="saveApiKey()">Save API Key</button>
167
+ </div>
168
+
169
+ <script>
170
+ let apiKey = localStorage.getItem('mistralApiKey');
171
+
172
+ if (apiKey) {
173
+ document.getElementById('apiKey').value = apiKey;
174
+ }
175
+
176
+ function saveApiKey() {
177
+ const key = document.getElementById('apiKey').value.trim();
178
+ if (key) {
179
+ apiKey = key;
180
+ localStorage.setItem('mistralApiKey', key);
181
+ alert('API key saved!');
182
+ }
183
+ }
184
+
185
+ function addMessage(role, content, rawJson = null) {
186
+ const chatHistory = document.getElementById('chatHistory');
187
+ const messageDiv = document.createElement('div');
188
+ messageDiv.className = `chat-message ${role}-message`;
189
+ messageDiv.textContent = content;
190
+
191
+ if (rawJson && document.getElementById('showRawJson').checked) {
192
+ const jsonDiv = document.createElement('div');
193
+ jsonDiv.className = 'raw-json';
194
+ jsonDiv.textContent = JSON.stringify(rawJson, null, 2);
195
+ messageDiv.appendChild(jsonDiv);
196
+ }
197
+
198
+ chatHistory.appendChild(messageDiv);
199
+ chatHistory.scrollTop = chatHistory.scrollHeight;
200
+ }
201
+
202
+ async function sendMessage() {
203
+ const userInput = document.getElementById('userInput');
204
+ const message = userInput.value.trim();
205
+
206
+ if (!message) return;
207
+ if (!apiKey) {
208
+ alert('Please enter your Mistral API key first');
209
+ return;
210
+ }
211
+
212
+ // Add user message to chat
213
+ addMessage('user', message);
214
+ userInput.value = '';
215
+
216
+ try {
217
+ const response = await fetch('https://api.mistral.ai/v1/chat/completions', {
218
+ method: 'POST',
219
+ headers: {
220
+ 'Content-Type': 'application/json',
221
+ 'Authorization': `Bearer ${apiKey}`
222
+ },
223
+ body: JSON.stringify({
224
+ model: 'mistral-large-latest',
225
+ messages: [
226
+ {
227
+ role: 'system',
228
+ content: document.getElementById('systemPrompt').value
229
+ },
230
+ {
231
+ role: 'user',
232
+ content: message
233
+ }
234
+ ]
235
+ })
236
+ });
237
+
238
+ if (!response.ok) {
239
+ throw new Error(`HTTP error! status: ${response.status}`);
240
+ }
241
+
242
+ const data = await response.json();
243
+ const assistantResponse = data.choices[0].message.content;
244
+
245
+ try {
246
+ // Extract JSON from response
247
+ const jsonStart = assistantResponse.indexOf('{');
248
+ const jsonEnd = assistantResponse.lastIndexOf('}') + 1;
249
+ const jsonContent = assistantResponse.substring(jsonStart, jsonEnd);
250
+ const jsonResponse = JSON.parse(jsonContent);
251
+
252
+ // Add assistant message with both text and raw JSON
253
+ addMessage('assistant', jsonResponse.textMessage, jsonResponse);
254
+
255
+ } catch (e) {
256
+ console.error('Error parsing JSON from response:', e);
257
+ addMessage('assistant', assistantResponse);
258
+ }
259
+
260
+ } catch (error) {
261
+ addMessage('assistant', `Error: ${error.message}`);
262
+ }
263
+ }
264
+
265
+ // Allow sending message with Enter key
266
+ document.getElementById('userInput').addEventListener('keypress', function(e) {
267
+ if (e.key === 'Enter') {
268
+ e.preventDefault();
269
+ sendMessage();
270
+ }
271
+ });
272
+ </script>
273
+ </body>
274
+ </html>
test/.DS_Store ADDED
Binary file (6.15 kB). View file