Audiofool commited on
Commit
3c425d6
·
1 Parent(s): 3d73d01
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ .DS_Store
app.py ADDED
@@ -0,0 +1,516 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import logging
3
+ import os
4
+ import sys
5
+ import time
6
+ import typing as tp
7
+ import warnings
8
+ import base64
9
+ from pathlib import Path
10
+ from tempfile import NamedTemporaryFile
11
+
12
+ from einops import rearrange
13
+ import torch
14
+ import gradio as gr
15
+ import requests
16
+
17
+ from audiocraft.data.audio_utils import convert_audio
18
+ from audiocraft.data.audio import audio_write
19
+ from audiocraft.models.encodec import InterleaveStereoCompressionModel
20
+ from audiocraft.models import MusicGen, MultiBandDiffusion
21
+
22
+ from theme_wave import theme, css
23
+
24
+ # --- Configuration (Main App) ---
25
+ MLLM_API_URL = (
26
+ "http://localhost:8000"
27
+ )
28
+ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
29
+
30
+ # --- Global Variables (Main App) ---
31
+ MODEL = None
32
+ MBD = None
33
+ INTERRUPTING = False
34
+ USE_DIFFUSION = False # Keep this for now, even if unused, for easier switching
35
+
36
+
37
+ # --- Utility Functions (Main App) ---
38
+ def interrupt():
39
+ global INTERRUPTING
40
+ INTERRUPTING = True
41
+
42
+
43
+ class FileCleaner:
44
+ def __init__(self, file_lifetime: float = 3600):
45
+ self.file_lifetime = file_lifetime
46
+ self.files = []
47
+
48
+ def add(self, path: tp.Union[str, Path]):
49
+ self._cleanup()
50
+ self.files.append((time.time(), Path(path)))
51
+
52
+ def _cleanup(self):
53
+ now = time.time()
54
+ for time_added, path in list(self.files):
55
+ if now - time_added > self.file_lifetime:
56
+ if path.exists():
57
+ try:
58
+ path.unlink()
59
+ except Exception as e:
60
+ print(f"Error deleting file {path}: {e}")
61
+ self.files.pop(0)
62
+ else:
63
+ break
64
+
65
+
66
+ file_cleaner = FileCleaner()
67
+
68
+
69
+ def make_waveform(*args, **kwargs):
70
+ with warnings.catch_warnings():
71
+ warnings.simplefilter("ignore")
72
+ return gr.make_waveform(*args, **kwargs)
73
+
74
+
75
+ # --- Model Loading (Main App) ---
76
+
77
+
78
+ def load_musicgen_model(version="facebook/musicgen-stereo-melody-large"):
79
+ global MODEL
80
+ print(f"Loading MusicGen model: {version}")
81
+ if MODEL is None or MODEL.name != version:
82
+ if MODEL is not None:
83
+ del MODEL
84
+ torch.cuda.empty_cache()
85
+ MODEL = MusicGen.get_pretrained(version, device=DEVICE)
86
+
87
+
88
+ def load_diffusion_model():
89
+ global MBD
90
+ if MBD is None:
91
+ print("Loading diffusion model")
92
+ MBD = MultiBandDiffusion.get_mbd_musicgen(device=DEVICE)
93
+
94
+
95
+ # --- API Client Functions ---
96
+
97
+
98
+ def get_mllm_description(media_path: str, user_prompt: str) -> str:
99
+ """Gets the music description from the MLLM API."""
100
+
101
+ try:
102
+ if media_path.lower().endswith((".mp4", ".avi", ".mov", ".mkv")):
103
+ # Video
104
+ with open(media_path, "rb") as f:
105
+ video_data = f.read()
106
+ encoded_video = base64.b64encode(video_data).decode("utf-8")
107
+ response = requests.post(
108
+ f"{MLLM_API_URL}/describe_video/",
109
+ json={"video": encoded_video, "user_prompt": user_prompt},
110
+ )
111
+ elif media_path.lower().endswith((".png", ".jpg", ".jpeg", ".gif", ".bmp")):
112
+ # Image
113
+ with open(media_path, "rb") as f:
114
+ image_data = f.read()
115
+ encoded_image = base64.b64encode(image_data).decode("utf-8")
116
+ response = requests.post(
117
+ f"{MLLM_API_URL}/describe_image/",
118
+ json={"image": encoded_image, "user_prompt": user_prompt},
119
+ )
120
+ else: # Text-only
121
+ response = requests.post(
122
+ f"{MLLM_API_URL}/describe_text/", json={"user_prompt": user_prompt}
123
+ )
124
+
125
+ response.raise_for_status() # Raise an exception for bad status codes (4xx or 5xx).
126
+ return response.json()["description"]
127
+
128
+ except requests.exceptions.RequestException as e:
129
+ raise gr.Error(f"Error communicating with MLLM API: {e}")
130
+ except Exception as e:
131
+ raise gr.Error(f"An unexpected error occurred: {e}")
132
+
133
+
134
+ # --- Music Generation ---
135
+
136
+
137
+ def predict_full(
138
+ model_version,
139
+ media_type,
140
+ image_input,
141
+ video_input,
142
+ text_prompt,
143
+ melody,
144
+ duration,
145
+ topk,
146
+ topp,
147
+ temperature,
148
+ cfg_coef,
149
+ decoder,
150
+ progress=gr.Progress(),
151
+ ):
152
+ global INTERRUPTING, USE_DIFFUSION
153
+ INTERRUPTING = False
154
+ USE_DIFFUSION = decoder == "MultiBand_Diffusion"
155
+
156
+ if media_type == "Image":
157
+ media = image_input if image_input else None
158
+ elif media_type == "Video":
159
+ media = video_input if video_input else None
160
+ else:
161
+ media = None
162
+
163
+ # 1. Get Music Description (using the API client).
164
+ progress(progress=None, desc="Generating music description...")
165
+ if media:
166
+ try:
167
+ music_description = get_mllm_description(media, text_prompt)
168
+ except Exception as e:
169
+ raise gr.Error(str(e)) # Re-raise for Gradio to handle.
170
+ else:
171
+ music_description = text_prompt
172
+
173
+ # 2. Load MusicGen Model (locally).
174
+ progress(progress=None, desc="Loading MusicGen model...")
175
+ load_musicgen_model(model_version)
176
+
177
+ # 3. Set Generation Parameters (locally).
178
+ MODEL.set_generation_params(
179
+ duration=duration,
180
+ top_k=topk,
181
+ top_p=topp,
182
+ temperature=temperature,
183
+ cfg_coef=cfg_coef,
184
+ )
185
+
186
+ # 4. Melody Preprocessing (locally).
187
+ progress(progress=None, desc="Processing melody...")
188
+ melody_tensor = None # Use a different variable name
189
+ if melody:
190
+ try:
191
+ sr, melody_tensor = (
192
+ melody[0],
193
+ torch.from_numpy(melody[1]).to(MODEL.device).float().t(),
194
+ )
195
+ if melody_tensor.dim() == 1:
196
+ melody_tensor = melody_tensor[None]
197
+ melody_tensor = melody_tensor[..., : int(sr * duration)]
198
+ melody_tensor = convert_audio(
199
+ melody_tensor, sr, MODEL.sample_rate, MODEL.audio_channels
200
+ )
201
+
202
+ except Exception as e:
203
+ raise gr.Error(f"Error processing melody: {e}")
204
+
205
+ # 5. Music Generation (locally).
206
+ progress(progress=None, desc="Generating music...")
207
+ if USE_DIFFUSION:
208
+ load_diffusion_model()
209
+
210
+ try:
211
+ if melody_tensor is not None: # Use the new variable
212
+ output = MODEL.generate_with_chroma(
213
+ descriptions=[music_description],
214
+ melody_wavs=[melody_tensor],
215
+ melody_sample_rate=MODEL.sample_rate,
216
+ progress=True,
217
+ return_tokens=USE_DIFFUSION,
218
+ )
219
+ else:
220
+ output = MODEL.generate(
221
+ descriptions=[music_description],
222
+ progress=True,
223
+ return_tokens=USE_DIFFUSION,
224
+ )
225
+ except RuntimeError as e:
226
+ raise gr.Error("Error while generating: " + str(e))
227
+
228
+ if USE_DIFFUSION:
229
+ progress(progress=None, desc="Running MultiBandDiffusion...")
230
+ tokens = output[1]
231
+ if isinstance(MODEL.compression_model, InterleaveStereoCompressionModel):
232
+ left, right = MODEL.compression_model.get_left_right_codes(tokens)
233
+ tokens = torch.cat([left, right])
234
+ outputs_diffusion = MBD.tokens_to_wav(tokens)
235
+ if isinstance(MODEL.compression_model, InterleaveStereoCompressionModel):
236
+ assert outputs_diffusion.shape[1] == 1 # output is mono
237
+ outputs_diffusion = rearrange(
238
+ outputs_diffusion, "(s b) c t -> b (s c) t", s=2
239
+ )
240
+ output_audio = torch.cat([output[0], outputs_diffusion], dim=0)
241
+ else:
242
+ output_audio = output[0]
243
+
244
+ output_audio = output_audio.detach().cpu().float()
245
+
246
+ # 6. Save and Return (locally).
247
+ progress(progress=None, desc="Saving and returning...")
248
+ output_audio_paths = []
249
+
250
+ for i, audio in enumerate(output_audio):
251
+ with NamedTemporaryFile("wb", suffix=".wav", delete=False) as file:
252
+ audio_write(
253
+ file.name,
254
+ audio,
255
+ MODEL.sample_rate,
256
+ strategy="loudness",
257
+ loudness_headroom_db=16,
258
+ loudness_compressor=True,
259
+ add_suffix=False,
260
+ )
261
+ output_audio_paths.append(file.name)
262
+ file_cleaner.add(file.name)
263
+
264
+ if USE_DIFFUSION:
265
+ # Return both audios, but make sure to return the correct one first
266
+ result = (
267
+ output_audio_paths[0], # Original
268
+ output_audio_paths[1], # MBD
269
+ )
270
+ else:
271
+ result = (
272
+ output_audio_paths[0],
273
+ None,
274
+ ) # Only original audio and description
275
+
276
+ del melody_tensor, output, output_audio
277
+ if torch.cuda.is_available():
278
+ torch.cuda.empty_cache()
279
+
280
+ return result
281
+
282
+
283
+ Wave = theme()
284
+
285
+
286
+ def create_ui(launch_kwargs=None):
287
+ """Creates and launches the Gradio UI."""
288
+
289
+ if launch_kwargs is None:
290
+ launch_kwargs = {}
291
+
292
+ def interrupt_handler():
293
+ interrupt()
294
+
295
+ with gr.Blocks(theme=Wave, css=css) as interface:
296
+
297
+ gr.Markdown(
298
+ """
299
+ <div style="text-align: center;">
300
+ <h1>WeaveWave</h1>
301
+ <h2>Towards Multimodal Music Generation</h2>
302
+ </div>
303
+ """
304
+ )
305
+
306
+ with gr.Row():
307
+ with gr.Column():
308
+ with gr.Group():
309
+ image_input = gr.Image(
310
+ value="./assets/WeaveWave.png",
311
+ label="Input Image",
312
+ type="filepath",
313
+ height=320,
314
+ visible=True,
315
+ )
316
+ video_input = gr.Video(
317
+ value="./assets/example_video_1.mp4",
318
+ label="Input Video",
319
+ height=320,
320
+ visible=False,
321
+ )
322
+ with gr.Row():
323
+ media_type = gr.Radio(
324
+ choices=["Image", "Video"],
325
+ value="Image",
326
+ label="",
327
+ interactive=True,
328
+ elem_classes="center-radio compact-radio",
329
+ )
330
+
331
+ def toggle_media(choice):
332
+ return {
333
+ image_input: gr.update(visible=(choice == "Image")),
334
+ video_input: gr.update(visible=(choice == "Video")),
335
+ }
336
+
337
+ media_type.change(
338
+ toggle_media, inputs=media_type, outputs=[image_input, video_input]
339
+ )
340
+ with gr.Column():
341
+ text_input = gr.Text(
342
+ value="Anything you like",
343
+ label="User Prompt",
344
+ )
345
+ melody_input = gr.Audio(
346
+ value="./assets/bach.mp3",
347
+ type="numpy",
348
+ label="Melody",
349
+ )
350
+ with gr.Row():
351
+ submit_button = gr.Button("Generate Music", variant="primary")
352
+ interrupt_button = gr.Button(
353
+ "Interrupt", variant="stop"
354
+ ) # Keep as gr.Button
355
+ with gr.Row():
356
+ model_version = gr.Dropdown(
357
+ [
358
+ "facebook/musicgen-melody",
359
+ "facebook/musicgen-medium",
360
+ "facebook/musicgen-small",
361
+ "facebook/musicgen-large",
362
+ "facebook/musicgen-melody-large",
363
+ "facebook/musicgen-stereo-small",
364
+ "facebook/musicgen-stereo-medium",
365
+ "facebook/musicgen-stereo-melody",
366
+ "facebook/musicgen-stereo-large",
367
+ "facebook/musicgen-stereo-melody-large",
368
+ ],
369
+ label="MusicGen Model",
370
+ value="facebook/musicgen-stereo-melody-large",
371
+ )
372
+ duration = gr.Slider(
373
+ minimum=1, maximum=120, value=10, label="Duration (seconds)"
374
+ )
375
+ with gr.Row():
376
+ topk = gr.Number(label="Top-k", value=250)
377
+ topp = gr.Number(label="Top-p", value=0)
378
+ temperature = gr.Number(label="Temperature", value=1.0)
379
+ cfg_coef = gr.Number(label="Classifier-Free Guidance", value=3.0)
380
+ decoder = gr.Dropdown(
381
+ ["Default", "MultiBand_Diffusion"],
382
+ label="Decoder",
383
+ value="Default",
384
+ interactive=True,
385
+ )
386
+
387
+ # with gr.Row():
388
+ # description_output = gr.Textbox(label="MLLM Generated Description")
389
+ with gr.Row():
390
+ output_audio = gr.Audio(label="Generated Music", type="filepath")
391
+ output_audio_mbd = gr.Audio(
392
+ label="MultiBand Diffusion Decoder", type="filepath"
393
+ )
394
+
395
+ submit_button.click(
396
+ predict_full,
397
+ inputs=[
398
+ model_version,
399
+ media_type,
400
+ image_input,
401
+ video_input,
402
+ text_input,
403
+ melody_input,
404
+ duration,
405
+ topk,
406
+ topp,
407
+ temperature,
408
+ cfg_coef,
409
+ decoder,
410
+ ],
411
+ # outputs=[output_audio, description_output, output_audio_mbd],
412
+ outputs=[output_audio, output_audio_mbd],
413
+ )
414
+ interrupt_button.click(interrupt_handler, [], [])
415
+ if INTERRUPTING:
416
+ raise gr.Error("Interrupted.")
417
+
418
+ gr.Examples(
419
+ examples=[
420
+ [
421
+ "Image",
422
+ "./assets/example_image_1.jpg",
423
+ None,
424
+ "Acoustic guitar solo. Country and folk music.",
425
+ None,
426
+ "facebook/musicgen-stereo-melody-large",
427
+ 10,
428
+ 250,
429
+ 0,
430
+ 1.0,
431
+ 3.0,
432
+ "MultiBand_Diffusion",
433
+ ],
434
+ [
435
+ "Video",
436
+ None,
437
+ "./assets/example_video_1.mp4",
438
+ "Space Rock, Synthwave, 80s. Electric guitar and Drums.",
439
+ None,
440
+ "facebook/musicgen-stereo-melody-large",
441
+ 10,
442
+ 250,
443
+ 0,
444
+ 1.0,
445
+ 3.0,
446
+ "MultiBand_Diffusion",
447
+ ],
448
+ [
449
+ None,
450
+ None,
451
+ None,
452
+ "An 80s driving pop song with heavy drums and synth pads in the background",
453
+ "./assets/bach.mp3",
454
+ "facebook/musicgen-stereo-melody-large",
455
+ 10,
456
+ 250,
457
+ 0,
458
+ 1.0,
459
+ 3.0,
460
+ "MultiBand_Diffusion",
461
+ ],
462
+ ],
463
+ inputs=[
464
+ media_type,
465
+ image_input,
466
+ video_input,
467
+ text_input,
468
+ melody_input,
469
+ model_version,
470
+ duration,
471
+ topk,
472
+ topp,
473
+ temperature,
474
+ cfg_coef,
475
+ decoder,
476
+ ],
477
+ )
478
+ interface.queue().launch(**launch_kwargs)
479
+ return interface
480
+
481
+
482
+ if __name__ == "__main__":
483
+ parser = argparse.ArgumentParser()
484
+ parser.add_argument(
485
+ "--listen",
486
+ type=str,
487
+ default="0.0.0.0" if "SPACE_ID" in os.environ else "127.0.0.1",
488
+ help="IP to listen on",
489
+ )
490
+ parser.add_argument(
491
+ "--username", type=str, default="", help="Username for authentication"
492
+ )
493
+ parser.add_argument(
494
+ "--password", type=str, default="", help="Password for authentication"
495
+ )
496
+ parser.add_argument(
497
+ "--server_port", type=int, default=0, help="Port to run the server on"
498
+ ) # Add server_port argument.
499
+ parser.add_argument("--inbrowser", action="store_true", help="Open in browser")
500
+ parser.add_argument("--share", action="store_true", help="Share the Gradio UI")
501
+
502
+ args = parser.parse_args()
503
+
504
+ launch_kwargs = {}
505
+ launch_kwargs["server_name"] = args.listen
506
+ if args.username and args.password:
507
+ launch_kwargs["auth"] = (args.username, args.password)
508
+ if args.server_port:
509
+ launch_kwargs["server_port"] = args.server_port
510
+ if args.inbrowser:
511
+ launch_kwargs["inbrowser"] = args.inbrowser
512
+ if args.share:
513
+ launch_kwargs["share"] = args.share
514
+
515
+ logging.basicConfig(level=logging.INFO, stream=sys.stderr)
516
+ create_ui(launch_kwargs)
assets/WeaveWave.png ADDED

Git LFS Details

  • SHA256: e27f115157f4dbdf101d108d87c2eda12b29000cb3ebce5620fb743589ece6b6
  • Pointer size: 132 Bytes
  • Size of remote file: 1.79 MB
assets/bach.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9e8815e2f9b9e9b876857c1574de71669ff0696ff189ff910d498ba58b1a8705
3
+ size 160496
assets/example_image_1.jpg ADDED

Git LFS Details

  • SHA256: e3dd07bc31d301889d05ee612411e65ff5bbe05691631b5dc6dda86371e6af89
  • Pointer size: 131 Bytes
  • Size of remote file: 146 kB
assets/example_image_1.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1ceebca8a82b3f4af511689197a6e69ae97332f25d984dc6cb1fc7b61f33b4d3
3
+ size 88891
assets/example_video_1.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e2b6204e421a3f07fc7ffa201b423326529977dc4dec5c3d302ffb44cd9852c9
3
+ size 5605727
theme_wave.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+
3
+ css = """
4
+ .center-radio {
5
+ display: flex;
6
+ justify-content: center;
7
+ align-items: center;
8
+ }
9
+ .compact-radio {
10
+ width: 200px; # 调整宽度
11
+ }
12
+ """
13
+
14
+
15
+ def theme():
16
+ return gr.themes.Default().set(
17
+ # Body Attributes
18
+ body_background_fill="linear-gradient(to bottom, #006994, #00223D)", # Light blue, reminiscent of shallow water #E6F2FF
19
+ body_background_fill_dark="linear-gradient(to bottom, #006994, #00223D)", # Darker blue for dark mode #1A2430
20
+ body_text_color="#1A2430", # Dark blue/grey for contrast
21
+ body_text_color_dark="#E6F2FF", # Light blue for contrast in dark mode
22
+ body_text_size="16px",
23
+ body_text_color_subdued="#758596", # Greyish blue for less important text
24
+ body_text_color_subdued_dark="#A0B0C0", # Lighter greyish blue in dark mode
25
+ body_text_weight="400",
26
+ embed_radius="8px",
27
+ # Element Colors
28
+ background_fill_primary="#FFFFFF", # White background for main content areas
29
+ background_fill_primary_dark="#283442", # Darker background in dark mode
30
+ background_fill_secondary="#F2F8FF", # Slightly off-white for layered elements
31
+ background_fill_secondary_dark="#364250", # Darker off-white in dark mode
32
+ border_color_accent="#4682B4", # Steel blue for accents
33
+ border_color_accent_dark="#6A9ACD", # Lighter steel blue in dark mode
34
+ border_color_accent_subdued="#ADD8E6", # Light blue, more subdued accent
35
+ border_color_accent_subdued_dark="#87CEFA", # Lighter blue, more subdued accent in dark mode
36
+ border_color_primary="#D0E0F0", # Light greyish blue for borders
37
+ border_color_primary_dark="#506070", # Darker greyish blue for dark mode
38
+ color_accent="#29ABE2", # Bright blue for highlights
39
+ color_accent_soft="#87CEEB", # Sky blue, softer accent
40
+ color_accent_soft_dark="#4682B4", # Steel blue, softer accent in dark mode
41
+ # Text
42
+ link_text_color="#0077CC", # Standard blue link color
43
+ link_text_color_dark="#41A0FF", # Lighter blue link in dark mode
44
+ link_text_color_active="#005580", # Darker blue when link is active
45
+ link_text_color_active_dark="#2980B9", # Slightly darker blue when active in dark mode
46
+ link_text_color_hover="#00A0E9", # Brighter blue on hover
47
+ link_text_color_hover_dark="#6AA2E8", # Lighter brighter blue on hover in dark mode
48
+ link_text_color_visited="#551A8B", # Purple for visited links (adjust as desired)
49
+ link_text_color_visited_dark="#8A5ACF", # Lighter purple for visited links in dark mode
50
+ prose_text_size="16px",
51
+ prose_text_weight="400",
52
+ prose_header_text_weight="600",
53
+ code_background_fill="#F0F8FF", # Very light blue for code blocks
54
+ code_background_fill_dark="#303A48", # Darker blue for code blocks in dark mode
55
+ # Shadows
56
+ shadow_drop="0 2px 4px rgba(0, 0, 0, 0.1)",
57
+ shadow_drop_lg="0 4px 8px rgba(0, 0, 0, 0.1)",
58
+ shadow_inset="inset 0 2px 4px rgba(0, 0, 0, 0.1)",
59
+ shadow_spread="0 0 8px rgba(0, 0, 0, 0.1)",
60
+ shadow_spread_dark="0 0 8px rgba(255, 255, 255, 0.05)",
61
+ # ... (Rest of the parameters - apply similar ocean-themed color choices)
62
+ # Example for buttons:
63
+ button_primary_background_fill="#29ABE2", # Bright blue for primary buttons
64
+ button_primary_background_fill_dark="#4682B4", # Steel blue in dark mode
65
+ button_primary_background_fill_hover="#1E88E5", # Slightly darker blue on hover
66
+ button_primary_background_fill_hover_dark="#3070A0", # Slightly darker in dark mode
67
+ button_primary_text_color="#FFFFFF", # White text on blue buttons
68
+ button_primary_text_color_dark="#FFFFFF", # White text in dark mode
69
+ button_primary_border_color="#29ABE2",
70
+ button_primary_border_color_dark="#4682B4",
71
+ button_primary_border_color_hover="#1E88E5",
72
+ button_primary_border_color_hover_dark="#3070A0",
73
+ button_primary_text_color_hover="#FFFFFF",
74
+ button_primary_text_color_hover_dark="#FFFFFF",
75
+ # ... (Continue for other components)
76
+ button_cancel_background_fill="#960018",
77
+ button_cancel_background_fill_dark="#960018",
78
+ button_cancel_background_fill_hover="#800000",
79
+ button_cancel_background_fill_hover_dark="#800000",
80
+ button_cancel_border_color="#960018",
81
+ button_cancel_border_color_dark="#960018",
82
+ )