Sync Dream QA composer debug UI
#27
by ADJCJH - opened
- README.md +16 -0
- docs/local-space-mirror.md +56 -0
- dream_customs/ui/app.py +216 -69
- dream_customs/ui/copy.py +4 -0
- dream_customs/ui/styles.py +733 -16
- modal_backend/dream_customs_modal.py +1 -1
- scripts/local_space_mirror.py +142 -0
- scripts/smoke_local_space_mirror.py +105 -0
- tests/test_local_space_mirror.py +98 -0
- tests/test_ui_actions.py +89 -9
README.md
CHANGED
|
@@ -81,6 +81,22 @@ python app.py
|
|
| 81 |
|
| 82 |
Open `http://127.0.0.1:7860`.
|
| 83 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
## Optional Ollama Models
|
| 85 |
|
| 86 |
```bash
|
|
|
|
| 81 |
|
| 82 |
Open `http://127.0.0.1:7860`.
|
| 83 |
|
| 84 |
+
## Local Space Mirror
|
| 85 |
+
|
| 86 |
+
For pre-merge UI review, run the same `app.py` entrypoint with the local Space mirror wrapper:
|
| 87 |
+
|
| 88 |
+
```bash
|
| 89 |
+
.venv/bin/python scripts/local_space_mirror.py
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
Open `http://127.0.0.1:7862`, then verify the Gradio config in another terminal:
|
| 93 |
+
|
| 94 |
+
```bash
|
| 95 |
+
.venv/bin/python scripts/smoke_local_space_mirror.py
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
Use this path before merging Hugging Face Space PRs so UI changes can be reviewed locally. Details: `docs/local-space-mirror.md`.
|
| 99 |
+
|
| 100 |
## Optional Ollama Models
|
| 101 |
|
| 102 |
```bash
|
docs/local-space-mirror.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Local Space Mirror
|
| 2 |
+
|
| 3 |
+
Use this when you want to review Dream QA changes before they are merged into the Hugging Face Space.
|
| 4 |
+
|
| 5 |
+
The mirror does not copy the app into a second implementation. It imports the same `app.py` entrypoint and therefore uses the same `dream_customs.ui.app.build_demo()` path as the Space. Missing Modal endpoint secrets fall back to deterministic demo behavior, just like the public app.
|
| 6 |
+
|
| 7 |
+
## Start
|
| 8 |
+
|
| 9 |
+
```bash
|
| 10 |
+
.venv/bin/python scripts/local_space_mirror.py
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
Open:
|
| 14 |
+
|
| 15 |
+
```text
|
| 16 |
+
http://127.0.0.1:7862
|
| 17 |
+
```
|
| 18 |
+
|
| 19 |
+
The script prints a small manifest before starting Gradio:
|
| 20 |
+
|
| 21 |
+
- `app_file` should be `app.py`.
|
| 22 |
+
- default text, vision, and voice backends should be `modal`.
|
| 23 |
+
- endpoint/token fields are only reported as configured or not configured; values are not printed.
|
| 24 |
+
|
| 25 |
+
## Verify
|
| 26 |
+
|
| 27 |
+
In another terminal:
|
| 28 |
+
|
| 29 |
+
```bash
|
| 30 |
+
.venv/bin/python scripts/smoke_local_space_mirror.py
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
The smoke checks `/config` for the current Dream QA app title, composer CSS markers, debug panel marker, and modal backend defaults.
|
| 34 |
+
|
| 35 |
+
## Optional Runtime Secrets
|
| 36 |
+
|
| 37 |
+
If you want the local mirror to call the same private Modal routes as the configured Space, export these in your shell before starting the mirror:
|
| 38 |
+
|
| 39 |
+
```bash
|
| 40 |
+
export DREAM_CUSTOMS_TEXT_ENDPOINT=...
|
| 41 |
+
export DREAM_CUSTOMS_VISION_ENDPOINT=...
|
| 42 |
+
export DREAM_CUSTOMS_ASR_ENDPOINT=...
|
| 43 |
+
export DREAM_CUSTOMS_HOSTED_TOKEN=...
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
Do not commit these values, paste them into docs, or include them in screenshots. Without them, the app remains usable through the deterministic fallback route.
|
| 47 |
+
|
| 48 |
+
## When To Use
|
| 49 |
+
|
| 50 |
+
Run this before creating or merging a Hugging Face Space PR whenever a change affects:
|
| 51 |
+
|
| 52 |
+
- Gradio UI layout or CSS.
|
| 53 |
+
- Stepper state.
|
| 54 |
+
- Image, voice, or text input behavior.
|
| 55 |
+
- Debug/runtime panels.
|
| 56 |
+
- Default backend behavior.
|
dream_customs/ui/app.py
CHANGED
|
@@ -70,6 +70,8 @@ VOICE_JS = r"""
|
|
| 70 |
}
|
| 71 |
button.dataset.bound = "true";
|
| 72 |
|
|
|
|
|
|
|
| 73 |
const setStatus = (message, mode) => {
|
| 74 |
if (status) {
|
| 75 |
status.textContent = message;
|
|
@@ -94,23 +96,15 @@ VOICE_JS = r"""
|
|
| 94 |
button.addEventListener("click", async () => {
|
| 95 |
const Recognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
| 96 |
if (!Recognition) {
|
| 97 |
-
setStatus("This browser cannot transcribe voice here. You can still type the dream.", "error");
|
| 98 |
textarea.focus();
|
| 99 |
return;
|
| 100 |
}
|
| 101 |
|
| 102 |
-
|
| 103 |
-
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
| 104 |
-
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
| 105 |
-
stream.getTracks().forEach((track) => track.stop());
|
| 106 |
-
}
|
| 107 |
-
} catch (error) {
|
| 108 |
-
setStatus("Microphone permission was not granted. Allow recording and try again.", "error");
|
| 109 |
-
return;
|
| 110 |
-
}
|
| 111 |
|
| 112 |
const recognition = new Recognition();
|
| 113 |
-
recognition.lang = "en-US";
|
| 114 |
recognition.interimResults = true;
|
| 115 |
recognition.continuous = false;
|
| 116 |
recognition.maxAlternatives = 1;
|
|
@@ -118,7 +112,7 @@ VOICE_JS = r"""
|
|
| 118 |
let latestTranscript = "";
|
| 119 |
|
| 120 |
recognition.onstart = () => {
|
| 121 |
-
setStatus("Listening. Say the dream fragment when you are ready.", "listening");
|
| 122 |
};
|
| 123 |
|
| 124 |
recognition.onresult = (event) => {
|
|
@@ -133,17 +127,17 @@ VOICE_JS = r"""
|
|
| 133 |
|
| 134 |
recognition.onerror = (event) => {
|
| 135 |
const message = event.error === "not-allowed"
|
| 136 |
-
? "Microphone permission was denied. Allow recording and try again."
|
| 137 |
-
: "I did not catch that. Tap the microphone again if you want to retry.";
|
| 138 |
setStatus(message, "error");
|
| 139 |
};
|
| 140 |
|
| 141 |
recognition.onend = () => {
|
| 142 |
if (latestTranscript) {
|
| 143 |
appendTranscript(latestTranscript);
|
| 144 |
-
setStatus("Added to the dream note.", "done");
|
| 145 |
} else if (button.dataset.mode === "listening") {
|
| 146 |
-
setStatus("No speech detected. Tap again if you want to retry.", "idle");
|
| 147 |
}
|
| 148 |
};
|
| 149 |
|
|
@@ -151,8 +145,130 @@ VOICE_JS = r"""
|
|
| 151 |
});
|
| 152 |
};
|
| 153 |
|
| 154 |
-
|
| 155 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
observer.observe(document.body, { childList: true, subtree: true });
|
| 157 |
}
|
| 158 |
"""
|
|
@@ -335,13 +451,20 @@ def _hero_html(language: str = DEFAULT_LANGUAGE, status: str = "record") -> str:
|
|
| 335 |
active_step = _active_step_for_status(status)
|
| 336 |
step_html = []
|
| 337 |
for index, label in enumerate(steps, start=1):
|
| 338 |
-
classes = []
|
| 339 |
if index < active_step:
|
| 340 |
classes.append("is-complete")
|
| 341 |
if index == active_step:
|
| 342 |
classes.append("is-active")
|
| 343 |
-
|
| 344 |
-
step_html.append(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 345 |
return f"""
|
| 346 |
<header class="dc-hero">
|
| 347 |
<div class="dc-hero-top">
|
|
@@ -349,7 +472,7 @@ def _hero_html(language: str = DEFAULT_LANGUAGE, status: str = "record") -> str:
|
|
| 349 |
<div class="dc-brand-lockup">
|
| 350 |
<div>
|
| 351 |
<h1>{copy['title']}</h1>
|
| 352 |
-
<p class="dc-brand-subtitle">{copy['
|
| 353 |
</div>
|
| 354 |
</div>
|
| 355 |
<div class="dc-sun-mark" aria-hidden="true">☀</div>
|
|
@@ -374,7 +497,18 @@ def _mic_html(language: str = DEFAULT_LANGUAGE) -> str:
|
|
| 374 |
copy = copy_for(language)
|
| 375 |
return f"""
|
| 376 |
<div class="dc-mic-control">
|
| 377 |
-
<button
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 378 |
<span class="dc-mic-glyph" aria-hidden="true"></span>
|
| 379 |
</button>
|
| 380 |
<div class="dc-mic-status" aria-live="polite">{escape(copy['mic_idle'])}</div>
|
|
@@ -382,6 +516,24 @@ def _mic_html(language: str = DEFAULT_LANGUAGE) -> str:
|
|
| 382 |
""".strip()
|
| 383 |
|
| 384 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 385 |
def _field_tip_html(language: str = DEFAULT_LANGUAGE) -> str:
|
| 386 |
return f"<p class=\"dc-field-tip\">{escape(copy_for(language)['field_tip'])}</p>"
|
| 387 |
|
|
@@ -424,7 +576,7 @@ def build_demo() -> gr.Blocks:
|
|
| 424 |
initial = _load_view(initial_view)
|
| 425 |
initial_copy = copy_for(DEFAULT_LANGUAGE)
|
| 426 |
|
| 427 |
-
with gr.Blocks(css=CSS, title=APP_TITLE) as demo:
|
| 428 |
session_state = gr.State(initial_state)
|
| 429 |
view_state = gr.State(initial_view)
|
| 430 |
|
|
@@ -445,21 +597,23 @@ def build_demo() -> gr.Blocks:
|
|
| 445 |
elem_classes=["dc-dream-text"],
|
| 446 |
)
|
| 447 |
mic_html = gr.HTML(_mic_html(DEFAULT_LANGUAGE))
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
|
|
|
|
|
|
| 454 |
audio_input = gr.Audio(
|
| 455 |
label=initial_copy["voice_label"],
|
| 456 |
-
sources=["
|
| 457 |
type="filepath",
|
| 458 |
format="wav",
|
| 459 |
elem_classes=["dc-voice-input"],
|
| 460 |
visible=False,
|
| 461 |
)
|
| 462 |
-
|
| 463 |
with gr.Row(elem_classes=["dc-submit-row"]):
|
| 464 |
example_button = gr.Button(initial_copy["example_button"], variant="secondary")
|
| 465 |
submit_button = gr.Button(initial_copy["submit_button"], variant="primary")
|
|
@@ -491,6 +645,13 @@ def build_demo() -> gr.Blocks:
|
|
| 491 |
show_copy_button=True,
|
| 492 |
elem_classes=["dc-hidden-text"],
|
| 493 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 494 |
|
| 495 |
with gr.Column(elem_classes=["dc-side-panel"]):
|
| 496 |
language = gr.Radio(
|
|
@@ -503,6 +664,27 @@ def build_demo() -> gr.Blocks:
|
|
| 503 |
side_stamp_html = gr.HTML(_side_stamp_html(DEFAULT_LANGUAGE))
|
| 504 |
with gr.Accordion("Advanced", open=False, elem_classes=["dc-dev"]):
|
| 505 |
dev_help_html = gr.HTML(_dev_help_html(DEFAULT_LANGUAGE))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 506 |
text_backend = gr.Dropdown(
|
| 507 |
label="Text generation",
|
| 508 |
choices=[
|
|
@@ -570,41 +752,6 @@ def build_demo() -> gr.Blocks:
|
|
| 570 |
value=DEFAULT_ASR_LATENCY_BUDGET_MS,
|
| 571 |
precision=0,
|
| 572 |
)
|
| 573 |
-
text_temperature = gr.Slider(
|
| 574 |
-
label="Text temperature",
|
| 575 |
-
minimum=0,
|
| 576 |
-
maximum=0.7,
|
| 577 |
-
step=0.05,
|
| 578 |
-
value=DEFAULT_TEXT_TEMPERATURE,
|
| 579 |
-
)
|
| 580 |
-
vision_temperature = gr.Slider(
|
| 581 |
-
label="Image temperature",
|
| 582 |
-
minimum=0,
|
| 583 |
-
maximum=0.7,
|
| 584 |
-
step=0.05,
|
| 585 |
-
value=DEFAULT_VISION_TEMPERATURE,
|
| 586 |
-
)
|
| 587 |
-
text_max_tokens = gr.Slider(
|
| 588 |
-
label="Text max tokens",
|
| 589 |
-
minimum=64,
|
| 590 |
-
maximum=1200,
|
| 591 |
-
step=1,
|
| 592 |
-
value=DEFAULT_TEXT_MAX_TOKENS,
|
| 593 |
-
)
|
| 594 |
-
vision_max_tokens = gr.Slider(
|
| 595 |
-
label="Image max tokens",
|
| 596 |
-
minimum=64,
|
| 597 |
-
maximum=800,
|
| 598 |
-
step=1,
|
| 599 |
-
value=DEFAULT_VISION_MAX_TOKENS,
|
| 600 |
-
)
|
| 601 |
-
with gr.Accordion(initial_copy["debug_title"], open=False, elem_classes=["dc-debug-panel"]) as debug_panel:
|
| 602 |
-
debug_help_html = gr.HTML(_debug_help_html(DEFAULT_LANGUAGE))
|
| 603 |
-
debug_json = gr.Code(
|
| 604 |
-
label=initial_copy["debug_state_label"],
|
| 605 |
-
value=json.dumps(initial.get("debug", {}), ensure_ascii=False, indent=2),
|
| 606 |
-
language="json",
|
| 607 |
-
)
|
| 608 |
|
| 609 |
outputs = [
|
| 610 |
session_state,
|
|
@@ -688,7 +835,7 @@ def build_demo() -> gr.Blocks:
|
|
| 688 |
_section_title_html(1, copy["dream_label"]),
|
| 689 |
gr.update(label=copy["dream_label"], placeholder=copy["dream_placeholder"]),
|
| 690 |
_mic_html(selected_language),
|
| 691 |
-
|
| 692 |
gr.update(label=copy["image_label"]),
|
| 693 |
_field_tip_html(selected_language),
|
| 694 |
gr.update(value=copy["example_button"]),
|
|
@@ -722,7 +869,7 @@ def build_demo() -> gr.Blocks:
|
|
| 722 |
dream_section_html,
|
| 723 |
dream_text,
|
| 724 |
mic_html,
|
| 725 |
-
|
| 726 |
image_input,
|
| 727 |
field_tip_html,
|
| 728 |
example_button,
|
|
|
|
| 70 |
}
|
| 71 |
button.dataset.bound = "true";
|
| 72 |
|
| 73 |
+
const messageFor = (key, fallback) => button.dataset[key] || fallback;
|
| 74 |
+
|
| 75 |
const setStatus = (message, mode) => {
|
| 76 |
if (status) {
|
| 77 |
status.textContent = message;
|
|
|
|
| 96 |
button.addEventListener("click", async () => {
|
| 97 |
const Recognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
| 98 |
if (!Recognition) {
|
| 99 |
+
setStatus(messageFor("unsupported", "This browser cannot transcribe voice here. You can still type the dream."), "error");
|
| 100 |
textarea.focus();
|
| 101 |
return;
|
| 102 |
}
|
| 103 |
|
| 104 |
+
setStatus(messageFor("checking", "Checking microphone permission..."), "listening");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
|
| 106 |
const recognition = new Recognition();
|
| 107 |
+
recognition.lang = button.dataset.language === "zh" ? "zh-CN" : "en-US";
|
| 108 |
recognition.interimResults = true;
|
| 109 |
recognition.continuous = false;
|
| 110 |
recognition.maxAlternatives = 1;
|
|
|
|
| 112 |
let latestTranscript = "";
|
| 113 |
|
| 114 |
recognition.onstart = () => {
|
| 115 |
+
setStatus(messageFor("listening", "Listening. Say the dream fragment when you are ready."), "listening");
|
| 116 |
};
|
| 117 |
|
| 118 |
recognition.onresult = (event) => {
|
|
|
|
| 127 |
|
| 128 |
recognition.onerror = (event) => {
|
| 129 |
const message = event.error === "not-allowed"
|
| 130 |
+
? messageFor("permission", "Microphone permission was denied. Allow recording and try again.")
|
| 131 |
+
: messageFor("empty", "I did not catch that. Tap the microphone again if you want to retry.");
|
| 132 |
setStatus(message, "error");
|
| 133 |
};
|
| 134 |
|
| 135 |
recognition.onend = () => {
|
| 136 |
if (latestTranscript) {
|
| 137 |
appendTranscript(latestTranscript);
|
| 138 |
+
setStatus(messageFor("done", "Added to the dream note."), "done");
|
| 139 |
} else if (button.dataset.mode === "listening") {
|
| 140 |
+
setStatus(messageFor("empty", "No speech detected. Tap again if you want to retry."), "idle");
|
| 141 |
}
|
| 142 |
};
|
| 143 |
|
|
|
|
| 145 |
});
|
| 146 |
};
|
| 147 |
|
| 148 |
+
const bindAttachmentButton = () => {
|
| 149 |
+
const button = document.querySelector(".dc-attach-button");
|
| 150 |
+
const composer = button?.closest(".dc-composer");
|
| 151 |
+
|
| 152 |
+
if (!button || !composer || button.dataset.bound === "true") {
|
| 153 |
+
return;
|
| 154 |
+
}
|
| 155 |
+
button.dataset.bound = "true";
|
| 156 |
+
|
| 157 |
+
const localizeImagePopover = () => {
|
| 158 |
+
const control = composer.querySelector(".dc-attach-control");
|
| 159 |
+
const popover = composer.querySelector(".dc-image-popover");
|
| 160 |
+
if (!control || !popover) {
|
| 161 |
+
return;
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
const copy = {
|
| 165 |
+
imageLabel: control.dataset.imageLabel || "Image clue",
|
| 166 |
+
upload: control.dataset.upload || "Upload image",
|
| 167 |
+
paste: control.dataset.paste || "Paste from Clipboard",
|
| 168 |
+
};
|
| 169 |
+
composer.dataset.imageLanguage = control.dataset.language || "en";
|
| 170 |
+
const sourceButtons = popover.querySelectorAll('[data-testid="source-select"] button');
|
| 171 |
+
const clipboardSelected = sourceButtons.length > 1
|
| 172 |
+
&& sourceButtons[sourceButtons.length - 1].classList.contains("selected");
|
| 173 |
+
const primaryCopy = clipboardSelected ? copy.paste : copy.upload;
|
| 174 |
+
|
| 175 |
+
const label = popover.querySelector('[data-testid="block-label"]');
|
| 176 |
+
if (label) {
|
| 177 |
+
let labelText = label.querySelector(".dc-image-label-copy");
|
| 178 |
+
if (!labelText) {
|
| 179 |
+
labelText = document.createElement("span");
|
| 180 |
+
labelText.className = "dc-image-label-copy";
|
| 181 |
+
label.appendChild(labelText);
|
| 182 |
+
}
|
| 183 |
+
Array.from(label.childNodes).forEach((node) => {
|
| 184 |
+
if (node.nodeType === Node.TEXT_NODE) {
|
| 185 |
+
node.textContent = "";
|
| 186 |
+
}
|
| 187 |
+
});
|
| 188 |
+
labelText.textContent = copy.imageLabel;
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
const uploadWrap = popover.querySelector(".upload-container button .wrap");
|
| 192 |
+
if (uploadWrap) {
|
| 193 |
+
Array.from(uploadWrap.childNodes).forEach((node) => {
|
| 194 |
+
if (node.nodeType === Node.TEXT_NODE) {
|
| 195 |
+
node.textContent = "";
|
| 196 |
+
}
|
| 197 |
+
});
|
| 198 |
+
let uploadText = uploadWrap.querySelector(".dc-image-upload-copy");
|
| 199 |
+
if (!uploadText) {
|
| 200 |
+
uploadText = document.createElement("strong");
|
| 201 |
+
uploadText.className = "dc-image-upload-copy";
|
| 202 |
+
uploadWrap.appendChild(uploadText);
|
| 203 |
+
}
|
| 204 |
+
uploadText.textContent = primaryCopy;
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
const uploadButton = popover.querySelector(".upload-container button");
|
| 208 |
+
uploadButton?.setAttribute("aria-label", primaryCopy);
|
| 209 |
+
|
| 210 |
+
if (sourceButtons[0]) {
|
| 211 |
+
sourceButtons[0].setAttribute("aria-label", copy.upload);
|
| 212 |
+
}
|
| 213 |
+
if (sourceButtons[sourceButtons.length - 1]) {
|
| 214 |
+
sourceButtons[sourceButtons.length - 1].setAttribute("aria-label", copy.paste);
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
const replaceText = (root) => {
|
| 218 |
+
Array.from(root.childNodes).forEach((node) => {
|
| 219 |
+
if (node.nodeType === Node.TEXT_NODE) {
|
| 220 |
+
const text = node.textContent || "";
|
| 221 |
+
if (text.includes("Paste from Clipboard") || text.includes("从剪贴板粘贴")) {
|
| 222 |
+
node.textContent = text.replace("Paste from Clipboard", copy.paste).replace("从剪贴板粘贴", copy.paste);
|
| 223 |
+
}
|
| 224 |
+
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
| 225 |
+
replaceText(node);
|
| 226 |
+
}
|
| 227 |
+
});
|
| 228 |
+
};
|
| 229 |
+
replaceText(popover);
|
| 230 |
+
};
|
| 231 |
+
|
| 232 |
+
const setOpen = (open) => {
|
| 233 |
+
localizeImagePopover();
|
| 234 |
+
composer.classList.toggle("dc-image-open", open);
|
| 235 |
+
button.setAttribute("aria-expanded", open ? "true" : "false");
|
| 236 |
+
};
|
| 237 |
+
|
| 238 |
+
button.addEventListener("click", (event) => {
|
| 239 |
+
event.preventDefault();
|
| 240 |
+
event.stopPropagation();
|
| 241 |
+
const shouldOpen = !composer.classList.contains("dc-image-open");
|
| 242 |
+
setOpen(shouldOpen);
|
| 243 |
+
if (shouldOpen) {
|
| 244 |
+
const uploadInput = composer.querySelector(".dc-image-popover input[type='file']");
|
| 245 |
+
uploadInput?.focus();
|
| 246 |
+
}
|
| 247 |
+
});
|
| 248 |
+
|
| 249 |
+
composer.addEventListener("click", localizeImagePopover);
|
| 250 |
+
localizeImagePopover();
|
| 251 |
+
|
| 252 |
+
document.addEventListener("click", (event) => {
|
| 253 |
+
if (!composer.contains(event.target)) {
|
| 254 |
+
setOpen(false);
|
| 255 |
+
}
|
| 256 |
+
});
|
| 257 |
+
|
| 258 |
+
document.addEventListener("keydown", (event) => {
|
| 259 |
+
if (event.key === "Escape") {
|
| 260 |
+
setOpen(false);
|
| 261 |
+
}
|
| 262 |
+
});
|
| 263 |
+
};
|
| 264 |
+
|
| 265 |
+
const bindComposerControls = () => {
|
| 266 |
+
bindVoiceButton();
|
| 267 |
+
bindAttachmentButton();
|
| 268 |
+
};
|
| 269 |
+
|
| 270 |
+
bindComposerControls();
|
| 271 |
+
const observer = new MutationObserver(bindComposerControls);
|
| 272 |
observer.observe(document.body, { childList: true, subtree: true });
|
| 273 |
}
|
| 274 |
"""
|
|
|
|
| 451 |
active_step = _active_step_for_status(status)
|
| 452 |
step_html = []
|
| 453 |
for index, label in enumerate(steps, start=1):
|
| 454 |
+
classes = ["dc-step"]
|
| 455 |
if index < active_step:
|
| 456 |
classes.append("is-complete")
|
| 457 |
if index == active_step:
|
| 458 |
classes.append("is-active")
|
| 459 |
+
aria_current = ' aria-current="step"' if index == active_step else ""
|
| 460 |
+
step_html.append(
|
| 461 |
+
f'<span class="{" ".join(classes)}"{aria_current}><strong>{index}</strong>{label}</span>'
|
| 462 |
+
)
|
| 463 |
+
if index < len(steps):
|
| 464 |
+
line_classes = ["dc-stepper-line"]
|
| 465 |
+
if index < active_step:
|
| 466 |
+
line_classes.append("is-complete")
|
| 467 |
+
step_html.append(f'<i class="{" ".join(line_classes)}" aria-hidden="true"></i>')
|
| 468 |
return f"""
|
| 469 |
<header class="dc-hero">
|
| 470 |
<div class="dc-hero-top">
|
|
|
|
| 472 |
<div class="dc-brand-lockup">
|
| 473 |
<div>
|
| 474 |
<h1>{copy['title']}</h1>
|
| 475 |
+
<p class="dc-brand-subtitle">{copy['subtitle']}</p>
|
| 476 |
</div>
|
| 477 |
</div>
|
| 478 |
<div class="dc-sun-mark" aria-hidden="true">☀</div>
|
|
|
|
| 497 |
copy = copy_for(language)
|
| 498 |
return f"""
|
| 499 |
<div class="dc-mic-control">
|
| 500 |
+
<button
|
| 501 |
+
type="button"
|
| 502 |
+
class="dc-mic-button"
|
| 503 |
+
aria-label="{escape(copy['mic_idle'])}"
|
| 504 |
+
data-language="{escape(language)}"
|
| 505 |
+
data-checking="{escape('正在请求麦克风权限...' if language == 'zh' else 'Checking microphone permission...')}"
|
| 506 |
+
data-unsupported="{escape(copy['mic_unsupported'])}"
|
| 507 |
+
data-permission="{escape(copy['mic_permission'])}"
|
| 508 |
+
data-listening="{escape(copy['mic_listening'])}"
|
| 509 |
+
data-done="{escape(copy['mic_done'])}"
|
| 510 |
+
data-empty="{escape(copy['mic_empty'])}"
|
| 511 |
+
>
|
| 512 |
<span class="dc-mic-glyph" aria-hidden="true"></span>
|
| 513 |
</button>
|
| 514 |
<div class="dc-mic-status" aria-live="polite">{escape(copy['mic_idle'])}</div>
|
|
|
|
| 516 |
""".strip()
|
| 517 |
|
| 518 |
|
| 519 |
+
def _attachment_html(language: str = DEFAULT_LANGUAGE) -> str:
|
| 520 |
+
copy = copy_for(language)
|
| 521 |
+
label = escape(copy["image_accordion"])
|
| 522 |
+
return f"""
|
| 523 |
+
<div
|
| 524 |
+
class="dc-attach-control"
|
| 525 |
+
data-language="{escape(language)}"
|
| 526 |
+
data-image-label="{escape(copy['image_label'])}"
|
| 527 |
+
data-upload="{escape(copy['image_upload'])}"
|
| 528 |
+
data-paste="{escape(copy['image_paste'])}"
|
| 529 |
+
>
|
| 530 |
+
<button type="button" class="dc-attach-button" aria-label="{label}" aria-expanded="false">
|
| 531 |
+
<span aria-hidden="true">+</span>
|
| 532 |
+
</button>
|
| 533 |
+
</div>
|
| 534 |
+
""".strip()
|
| 535 |
+
|
| 536 |
+
|
| 537 |
def _field_tip_html(language: str = DEFAULT_LANGUAGE) -> str:
|
| 538 |
return f"<p class=\"dc-field-tip\">{escape(copy_for(language)['field_tip'])}</p>"
|
| 539 |
|
|
|
|
| 576 |
initial = _load_view(initial_view)
|
| 577 |
initial_copy = copy_for(DEFAULT_LANGUAGE)
|
| 578 |
|
| 579 |
+
with gr.Blocks(css=CSS, js=VOICE_JS, title=APP_TITLE) as demo:
|
| 580 |
session_state = gr.State(initial_state)
|
| 581 |
view_state = gr.State(initial_view)
|
| 582 |
|
|
|
|
| 597 |
elem_classes=["dc-dream-text"],
|
| 598 |
)
|
| 599 |
mic_html = gr.HTML(_mic_html(DEFAULT_LANGUAGE))
|
| 600 |
+
attachment_html = gr.HTML(_attachment_html(DEFAULT_LANGUAGE))
|
| 601 |
+
image_input = gr.Image(
|
| 602 |
+
label=initial_copy["image_label"],
|
| 603 |
+
sources=["upload", "clipboard"],
|
| 604 |
+
type="filepath",
|
| 605 |
+
height=180,
|
| 606 |
+
elem_classes=["dc-image-popover"],
|
| 607 |
+
)
|
| 608 |
audio_input = gr.Audio(
|
| 609 |
label=initial_copy["voice_label"],
|
| 610 |
+
sources=["upload"],
|
| 611 |
type="filepath",
|
| 612 |
format="wav",
|
| 613 |
elem_classes=["dc-voice-input"],
|
| 614 |
visible=False,
|
| 615 |
)
|
| 616 |
+
field_tip_html = gr.HTML(_field_tip_html(DEFAULT_LANGUAGE))
|
| 617 |
with gr.Row(elem_classes=["dc-submit-row"]):
|
| 618 |
example_button = gr.Button(initial_copy["example_button"], variant="secondary")
|
| 619 |
submit_button = gr.Button(initial_copy["submit_button"], variant="primary")
|
|
|
|
| 645 |
show_copy_button=True,
|
| 646 |
elem_classes=["dc-hidden-text"],
|
| 647 |
)
|
| 648 |
+
with gr.Accordion(initial_copy["debug_title"], open=False, elem_classes=["dc-debug-panel"]) as debug_panel:
|
| 649 |
+
debug_help_html = gr.HTML(_debug_help_html(DEFAULT_LANGUAGE))
|
| 650 |
+
debug_json = gr.Code(
|
| 651 |
+
label=initial_copy["debug_state_label"],
|
| 652 |
+
value=json.dumps(initial.get("debug", {}), ensure_ascii=False, indent=2),
|
| 653 |
+
language="json",
|
| 654 |
+
)
|
| 655 |
|
| 656 |
with gr.Column(elem_classes=["dc-side-panel"]):
|
| 657 |
language = gr.Radio(
|
|
|
|
| 664 |
side_stamp_html = gr.HTML(_side_stamp_html(DEFAULT_LANGUAGE))
|
| 665 |
with gr.Accordion("Advanced", open=False, elem_classes=["dc-dev"]):
|
| 666 |
dev_help_html = gr.HTML(_dev_help_html(DEFAULT_LANGUAGE))
|
| 667 |
+
with gr.Group(elem_classes=["dc-dev-tuning"]):
|
| 668 |
+
text_temperature = gr.Number(
|
| 669 |
+
label="Text temperature",
|
| 670 |
+
value=DEFAULT_TEXT_TEMPERATURE,
|
| 671 |
+
precision=2,
|
| 672 |
+
)
|
| 673 |
+
vision_temperature = gr.Number(
|
| 674 |
+
label="Image temperature",
|
| 675 |
+
value=DEFAULT_VISION_TEMPERATURE,
|
| 676 |
+
precision=2,
|
| 677 |
+
)
|
| 678 |
+
text_max_tokens = gr.Number(
|
| 679 |
+
label="Text max tokens",
|
| 680 |
+
value=DEFAULT_TEXT_MAX_TOKENS,
|
| 681 |
+
precision=0,
|
| 682 |
+
)
|
| 683 |
+
vision_max_tokens = gr.Number(
|
| 684 |
+
label="Image max tokens",
|
| 685 |
+
value=DEFAULT_VISION_MAX_TOKENS,
|
| 686 |
+
precision=0,
|
| 687 |
+
)
|
| 688 |
text_backend = gr.Dropdown(
|
| 689 |
label="Text generation",
|
| 690 |
choices=[
|
|
|
|
| 752 |
value=DEFAULT_ASR_LATENCY_BUDGET_MS,
|
| 753 |
precision=0,
|
| 754 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 755 |
|
| 756 |
outputs = [
|
| 757 |
session_state,
|
|
|
|
| 835 |
_section_title_html(1, copy["dream_label"]),
|
| 836 |
gr.update(label=copy["dream_label"], placeholder=copy["dream_placeholder"]),
|
| 837 |
_mic_html(selected_language),
|
| 838 |
+
_attachment_html(selected_language),
|
| 839 |
gr.update(label=copy["image_label"]),
|
| 840 |
_field_tip_html(selected_language),
|
| 841 |
gr.update(value=copy["example_button"]),
|
|
|
|
| 869 |
dream_section_html,
|
| 870 |
dream_text,
|
| 871 |
mic_html,
|
| 872 |
+
attachment_html,
|
| 873 |
image_input,
|
| 874 |
field_tip_html,
|
| 875 |
example_button,
|
dream_customs/ui/copy.py
CHANGED
|
@@ -33,6 +33,8 @@ APP_COPY = {
|
|
| 33 |
),
|
| 34 |
"image_accordion": "+",
|
| 35 |
"image_label": "Image clue",
|
|
|
|
|
|
|
| 36 |
"question_kicker": "Question",
|
| 37 |
"question_title": "What do you most want to understand in this dream?",
|
| 38 |
"question_body": "Answer in one or two lines, or skip and get a Today Tip from the clues already here.",
|
|
@@ -87,6 +89,8 @@ APP_COPY = {
|
|
| 87 |
),
|
| 88 |
"image_accordion": "+",
|
| 89 |
"image_label": "图片线索",
|
|
|
|
|
|
|
| 90 |
"question_kicker": "追问",
|
| 91 |
"question_title": "在这个梦里,你最想理解的是什么呢?",
|
| 92 |
"question_body": "回答一两句就好;也可以跳过,直接得到一个基于现有线索的今日小 Tips。",
|
|
|
|
| 33 |
),
|
| 34 |
"image_accordion": "+",
|
| 35 |
"image_label": "Image clue",
|
| 36 |
+
"image_upload": "Upload image",
|
| 37 |
+
"image_paste": "Paste from Clipboard",
|
| 38 |
"question_kicker": "Question",
|
| 39 |
"question_title": "What do you most want to understand in this dream?",
|
| 40 |
"question_body": "Answer in one or two lines, or skip and get a Today Tip from the clues already here.",
|
|
|
|
| 89 |
),
|
| 90 |
"image_accordion": "+",
|
| 91 |
"image_label": "图片线索",
|
| 92 |
+
"image_upload": "上传图片",
|
| 93 |
+
"image_paste": "从剪贴板粘贴",
|
| 94 |
"question_kicker": "追问",
|
| 95 |
"question_title": "在这个梦里,你最想理解的是什么呢?",
|
| 96 |
"question_body": "回答一两句就好;也可以跳过,直接得到一个基于现有线索的今日小 Tips。",
|
dream_customs/ui/styles.py
CHANGED
|
@@ -262,7 +262,7 @@ body,
|
|
| 262 |
}
|
| 263 |
|
| 264 |
.dc-workspace-grid {
|
| 265 |
-
align-items:
|
| 266 |
display: grid !important;
|
| 267 |
gap: 18px !important;
|
| 268 |
grid-template-columns: minmax(0, 1fr) minmax(300px, 340px);
|
|
@@ -939,6 +939,11 @@ a.built-with[href*="gradio.app"] {
|
|
| 939 |
padding: 10px !important;
|
| 940 |
}
|
| 941 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 942 |
.dc-debug-panel > button {
|
| 943 |
color: var(--dc-ink) !important;
|
| 944 |
font-size: 0.92rem !important;
|
|
@@ -1199,33 +1204,40 @@ body,
|
|
| 1199 |
}
|
| 1200 |
|
| 1201 |
.dc-stepper {
|
| 1202 |
-
align-items: start;
|
| 1203 |
-
display:
|
| 1204 |
gap: 0;
|
| 1205 |
-
grid-template-columns: repeat(4, minmax(0, 1fr));
|
| 1206 |
margin: clamp(28px, 5vw, 58px) auto 0;
|
| 1207 |
max-width: 920px;
|
| 1208 |
position: relative;
|
| 1209 |
}
|
| 1210 |
|
| 1211 |
-
.dc-stepper
|
| 1212 |
background: #9cadb8;
|
| 1213 |
-
|
|
|
|
|
|
|
| 1214 |
height: 3px;
|
| 1215 |
-
|
| 1216 |
-
|
| 1217 |
-
|
| 1218 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1219 |
}
|
| 1220 |
|
| 1221 |
.dc-stepper span {
|
| 1222 |
color: #667989;
|
| 1223 |
display: grid;
|
|
|
|
| 1224 |
font-size: clamp(1rem, 2.3vw, 1.45rem);
|
| 1225 |
gap: 10px;
|
| 1226 |
justify-items: center;
|
|
|
|
| 1227 |
position: relative;
|
| 1228 |
-
|
| 1229 |
}
|
| 1230 |
|
| 1231 |
.dc-stepper strong {
|
|
@@ -1514,6 +1526,699 @@ button.secondary {
|
|
| 1514 |
grid-template-columns: repeat(4, minmax(0, 1fr));
|
| 1515 |
}
|
| 1516 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1517 |
@media (max-width: 900px) {
|
| 1518 |
.dc-shell {
|
| 1519 |
padding: 0;
|
|
@@ -1544,10 +2249,10 @@ button.secondary {
|
|
| 1544 |
margin-top: 36px;
|
| 1545 |
}
|
| 1546 |
|
| 1547 |
-
.dc-stepper
|
| 1548 |
-
|
| 1549 |
-
|
| 1550 |
-
|
| 1551 |
}
|
| 1552 |
|
| 1553 |
.dc-stepper strong {
|
|
@@ -1573,11 +2278,23 @@ button.secondary {
|
|
| 1573 |
right: 20px;
|
| 1574 |
}
|
| 1575 |
|
| 1576 |
-
.dc-
|
| 1577 |
bottom: 22px;
|
| 1578 |
left: 20px;
|
| 1579 |
}
|
| 1580 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1581 |
.dqa-tip-page {
|
| 1582 |
border-radius: 0;
|
| 1583 |
}
|
|
|
|
| 262 |
}
|
| 263 |
|
| 264 |
.dc-workspace-grid {
|
| 265 |
+
align-items: start !important;
|
| 266 |
display: grid !important;
|
| 267 |
gap: 18px !important;
|
| 268 |
grid-template-columns: minmax(0, 1fr) minmax(300px, 340px);
|
|
|
|
| 939 |
padding: 10px !important;
|
| 940 |
}
|
| 941 |
|
| 942 |
+
.dc-flow-column .dc-debug-panel {
|
| 943 |
+
align-self: stretch;
|
| 944 |
+
width: 100%;
|
| 945 |
+
}
|
| 946 |
+
|
| 947 |
.dc-debug-panel > button {
|
| 948 |
color: var(--dc-ink) !important;
|
| 949 |
font-size: 0.92rem !important;
|
|
|
|
| 1204 |
}
|
| 1205 |
|
| 1206 |
.dc-stepper {
|
| 1207 |
+
align-items: flex-start;
|
| 1208 |
+
display: flex;
|
| 1209 |
gap: 0;
|
|
|
|
| 1210 |
margin: clamp(28px, 5vw, 58px) auto 0;
|
| 1211 |
max-width: 920px;
|
| 1212 |
position: relative;
|
| 1213 |
}
|
| 1214 |
|
| 1215 |
+
.dc-stepper-line {
|
| 1216 |
background: #9cadb8;
|
| 1217 |
+
border-radius: 999px;
|
| 1218 |
+
display: block;
|
| 1219 |
+
flex: 1 1 70px;
|
| 1220 |
height: 3px;
|
| 1221 |
+
margin-top: clamp(24px, 3.4vw, 35px);
|
| 1222 |
+
min-width: 34px;
|
| 1223 |
+
transition: background-color 180ms ease, box-shadow 180ms ease;
|
| 1224 |
+
}
|
| 1225 |
+
|
| 1226 |
+
.dc-stepper-line.is-complete {
|
| 1227 |
+
background: var(--dqa-sage);
|
| 1228 |
+
box-shadow: 0 0 0 1px rgba(79, 138, 88, 0.14);
|
| 1229 |
}
|
| 1230 |
|
| 1231 |
.dc-stepper span {
|
| 1232 |
color: #667989;
|
| 1233 |
display: grid;
|
| 1234 |
+
flex: 0 0 clamp(72px, 13vw, 128px);
|
| 1235 |
font-size: clamp(1rem, 2.3vw, 1.45rem);
|
| 1236 |
gap: 10px;
|
| 1237 |
justify-items: center;
|
| 1238 |
+
min-width: 0;
|
| 1239 |
position: relative;
|
| 1240 |
+
text-align: center;
|
| 1241 |
}
|
| 1242 |
|
| 1243 |
.dc-stepper strong {
|
|
|
|
| 1526 |
grid-template-columns: repeat(4, minmax(0, 1fr));
|
| 1527 |
}
|
| 1528 |
|
| 1529 |
+
.dc-stage .dc-composer {
|
| 1530 |
+
background: transparent !important;
|
| 1531 |
+
border: 0 !important;
|
| 1532 |
+
border-radius: 0 !important;
|
| 1533 |
+
box-shadow: none !important;
|
| 1534 |
+
min-height: 0 !important;
|
| 1535 |
+
overflow: visible !important;
|
| 1536 |
+
padding: 0 !important;
|
| 1537 |
+
position: relative;
|
| 1538 |
+
}
|
| 1539 |
+
|
| 1540 |
+
.dc-stage > .styler {
|
| 1541 |
+
background: transparent !important;
|
| 1542 |
+
}
|
| 1543 |
+
|
| 1544 |
+
.dc-stage .dc-composer .form {
|
| 1545 |
+
background: transparent !important;
|
| 1546 |
+
border: 0 !important;
|
| 1547 |
+
box-shadow: none !important;
|
| 1548 |
+
}
|
| 1549 |
+
|
| 1550 |
+
.dc-stage .dc-composer .dc-section-title {
|
| 1551 |
+
color: var(--dqa-ink) !important;
|
| 1552 |
+
margin: 0 0 14px;
|
| 1553 |
+
}
|
| 1554 |
+
|
| 1555 |
+
.dc-stage .dc-composer .dc-section-title strong {
|
| 1556 |
+
color: var(--dqa-ink) !important;
|
| 1557 |
+
}
|
| 1558 |
+
|
| 1559 |
+
.dc-side-panel .dc-section-title,
|
| 1560 |
+
.dc-side-panel .dc-section-title strong {
|
| 1561 |
+
color: var(--dqa-ink) !important;
|
| 1562 |
+
opacity: 1 !important;
|
| 1563 |
+
}
|
| 1564 |
+
|
| 1565 |
+
.dc-shell,
|
| 1566 |
+
.dc-stage,
|
| 1567 |
+
.dc-side-panel,
|
| 1568 |
+
.dc-dev,
|
| 1569 |
+
.dc-debug-panel {
|
| 1570 |
+
color-scheme: light;
|
| 1571 |
+
}
|
| 1572 |
+
|
| 1573 |
+
.dc-side-panel {
|
| 1574 |
+
background: rgba(255, 253, 248, 0.96) !important;
|
| 1575 |
+
border-color: var(--dqa-line) !important;
|
| 1576 |
+
color: var(--dqa-ink) !important;
|
| 1577 |
+
}
|
| 1578 |
+
|
| 1579 |
+
.dc-side-panel > .styler,
|
| 1580 |
+
.dc-side-panel .form,
|
| 1581 |
+
.dc-side-panel .block,
|
| 1582 |
+
.dc-side-panel .wrap,
|
| 1583 |
+
.dc-side-panel .container,
|
| 1584 |
+
.dc-side-panel .input-container {
|
| 1585 |
+
background: transparent !important;
|
| 1586 |
+
border-color: rgba(217, 226, 220, 0.88) !important;
|
| 1587 |
+
box-shadow: none !important;
|
| 1588 |
+
color: var(--dqa-ink) !important;
|
| 1589 |
+
}
|
| 1590 |
+
|
| 1591 |
+
.dc-side-panel .block:has(input[type="radio"]),
|
| 1592 |
+
.dc-side-panel .wrap:has(input[type="radio"]) {
|
| 1593 |
+
background: transparent !important;
|
| 1594 |
+
}
|
| 1595 |
+
|
| 1596 |
+
.dc-side-panel label,
|
| 1597 |
+
.dc-side-panel [data-testid="block-label"],
|
| 1598 |
+
.dc-side-panel [data-testid="block-info"] {
|
| 1599 |
+
color: var(--dqa-ink) !important;
|
| 1600 |
+
}
|
| 1601 |
+
|
| 1602 |
+
.dc-side-panel label:has(input[type="radio"]),
|
| 1603 |
+
.dc-side-panel .wrap label {
|
| 1604 |
+
background: rgba(255, 253, 248, 0.96) !important;
|
| 1605 |
+
border: 1px solid var(--dqa-line) !important;
|
| 1606 |
+
border-radius: 12px !important;
|
| 1607 |
+
color: var(--dqa-ink) !important;
|
| 1608 |
+
}
|
| 1609 |
+
|
| 1610 |
+
.dc-side-panel label:has(input[type="radio"]:checked) {
|
| 1611 |
+
background: var(--dqa-sage-soft) !important;
|
| 1612 |
+
border-color: rgba(79, 138, 88, 0.42) !important;
|
| 1613 |
+
color: var(--dqa-sage-deep) !important;
|
| 1614 |
+
}
|
| 1615 |
+
|
| 1616 |
+
.dc-side-panel input,
|
| 1617 |
+
.dc-side-panel select,
|
| 1618 |
+
.dc-side-panel button,
|
| 1619 |
+
.dc-side-panel .dropdown,
|
| 1620 |
+
.dc-side-panel .svelte-1gfkn6j,
|
| 1621 |
+
.dc-side-panel .svelte-1hfxrpf,
|
| 1622 |
+
.dc-side-panel .svelte-1b6s6s {
|
| 1623 |
+
color: var(--dqa-ink) !important;
|
| 1624 |
+
}
|
| 1625 |
+
|
| 1626 |
+
.dc-side-panel select,
|
| 1627 |
+
.dc-side-panel .wrap:has(select),
|
| 1628 |
+
.dc-side-panel .container:has(select),
|
| 1629 |
+
.dc-side-panel [role="listbox"],
|
| 1630 |
+
.dc-side-panel [aria-haspopup="listbox"] {
|
| 1631 |
+
background: rgba(255, 253, 248, 0.98) !important;
|
| 1632 |
+
border-color: var(--dqa-line) !important;
|
| 1633 |
+
border-radius: 12px !important;
|
| 1634 |
+
color: var(--dqa-ink) !important;
|
| 1635 |
+
}
|
| 1636 |
+
|
| 1637 |
+
.dc-side-panel svg,
|
| 1638 |
+
.dc-side-panel img {
|
| 1639 |
+
color: var(--dqa-muted) !important;
|
| 1640 |
+
opacity: 0.86;
|
| 1641 |
+
}
|
| 1642 |
+
|
| 1643 |
+
.dc-side-panel .dc-dev,
|
| 1644 |
+
.dc-side-panel .dc-dev > .label-wrap,
|
| 1645 |
+
.dc-side-panel .dc-dev > button,
|
| 1646 |
+
.dc-side-panel button[aria-expanded] {
|
| 1647 |
+
background: rgba(255, 253, 248, 0.96) !important;
|
| 1648 |
+
border-color: var(--dqa-line) !important;
|
| 1649 |
+
color: var(--dqa-muted) !important;
|
| 1650 |
+
}
|
| 1651 |
+
|
| 1652 |
+
.dc-side-panel .block:has(input[role="listbox"]) {
|
| 1653 |
+
background: rgba(255, 253, 248, 0.98) !important;
|
| 1654 |
+
border: 1px solid var(--dqa-line) !important;
|
| 1655 |
+
border-radius: 16px !important;
|
| 1656 |
+
box-shadow: none !important;
|
| 1657 |
+
padding: 20px 22px 18px !important;
|
| 1658 |
+
}
|
| 1659 |
+
|
| 1660 |
+
.dc-side-panel .form:has(input[role="listbox"]) {
|
| 1661 |
+
background: transparent !important;
|
| 1662 |
+
border: 0 !important;
|
| 1663 |
+
border-radius: 0 !important;
|
| 1664 |
+
box-shadow: none !important;
|
| 1665 |
+
padding: 0 !important;
|
| 1666 |
+
}
|
| 1667 |
+
|
| 1668 |
+
.dc-side-panel .block:has(input[role="listbox"]) .wrap,
|
| 1669 |
+
.dc-side-panel .block:has(input[role="listbox"]) .container,
|
| 1670 |
+
.dc-side-panel .block:has(input[role="listbox"]) .wrap-inner,
|
| 1671 |
+
.dc-side-panel .block:has(input[role="listbox"]) .secondary-wrap,
|
| 1672 |
+
.dc-side-panel .block:has(input[role="listbox"]) .input-container,
|
| 1673 |
+
.dc-side-panel .block:has(input[role="listbox"]) input[role="listbox"] {
|
| 1674 |
+
background: transparent !important;
|
| 1675 |
+
border: 0 !important;
|
| 1676 |
+
border-radius: 0 !important;
|
| 1677 |
+
box-shadow: none !important;
|
| 1678 |
+
}
|
| 1679 |
+
|
| 1680 |
+
.dc-side-panel .block:has(input[role="listbox"]) .wrap {
|
| 1681 |
+
min-height: 34px !important;
|
| 1682 |
+
}
|
| 1683 |
+
|
| 1684 |
+
.dc-side-panel .block:has(input[role="listbox"]) input[role="listbox"] {
|
| 1685 |
+
font-size: 1rem !important;
|
| 1686 |
+
min-height: 30px !important;
|
| 1687 |
+
}
|
| 1688 |
+
|
| 1689 |
+
.dc-side-panel .block:has(input[role="listbox"]) * {
|
| 1690 |
+
--input-border-width: 0px;
|
| 1691 |
+
--block-border-width: 0px;
|
| 1692 |
+
}
|
| 1693 |
+
|
| 1694 |
+
.dc-side-panel .block:has(input[role="listbox"]) .wrap,
|
| 1695 |
+
.dc-side-panel .block:has(input[role="listbox"]) .container,
|
| 1696 |
+
.dc-side-panel .block:has(input[role="listbox"]) .wrap-inner,
|
| 1697 |
+
.dc-side-panel .block:has(input[role="listbox"]) .secondary-wrap,
|
| 1698 |
+
.dc-side-panel .block:has(input[role="listbox"]) .input-container,
|
| 1699 |
+
.dc-side-panel .block:has(input[role="listbox"]) input[role="listbox"] {
|
| 1700 |
+
border: 0 !important;
|
| 1701 |
+
border-color: transparent !important;
|
| 1702 |
+
outline: 0 !important;
|
| 1703 |
+
}
|
| 1704 |
+
|
| 1705 |
+
.dc-side-panel .block:has(input[role="listbox"]) .icon-wrap {
|
| 1706 |
+
pointer-events: none !important;
|
| 1707 |
+
transition: color 160ms ease, transform 160ms ease;
|
| 1708 |
+
}
|
| 1709 |
+
|
| 1710 |
+
.dc-side-panel .block:has(input[role="listbox"]) .dropdown-arrow {
|
| 1711 |
+
color: var(--dqa-muted) !important;
|
| 1712 |
+
transition: color 160ms ease, transform 160ms ease;
|
| 1713 |
+
transform-origin: center;
|
| 1714 |
+
transform-box: fill-box;
|
| 1715 |
+
}
|
| 1716 |
+
|
| 1717 |
+
.dc-side-panel .block:has(> .container input[role="listbox"][aria-expanded="true"]) > .container .dropdown-arrow {
|
| 1718 |
+
color: var(--dqa-sage-deep) !important;
|
| 1719 |
+
transform: rotate(180deg) !important;
|
| 1720 |
+
}
|
| 1721 |
+
|
| 1722 |
+
.dc-side-panel .block:has(input[role="listbox"]:hover) .dropdown-arrow,
|
| 1723 |
+
.dc-side-panel .block:has(input[role="listbox"]:focus) .dropdown-arrow,
|
| 1724 |
+
.dc-side-panel .block:has(input[role="listbox"]) .icon-wrap:hover .dropdown-arrow {
|
| 1725 |
+
color: var(--dqa-sage-deep) !important;
|
| 1726 |
+
}
|
| 1727 |
+
|
| 1728 |
+
.dc-side-panel .dc-dev {
|
| 1729 |
+
background: rgba(255, 253, 248, 0.98) !important;
|
| 1730 |
+
border: 1px solid var(--dqa-line) !important;
|
| 1731 |
+
border-radius: 20px !important;
|
| 1732 |
+
box-shadow: none !important;
|
| 1733 |
+
color: var(--dqa-ink) !important;
|
| 1734 |
+
padding: 14px !important;
|
| 1735 |
+
}
|
| 1736 |
+
|
| 1737 |
+
.dc-side-panel .dc-dev > button {
|
| 1738 |
+
align-items: center !important;
|
| 1739 |
+
background: transparent !important;
|
| 1740 |
+
border: 0 !important;
|
| 1741 |
+
border-radius: 14px !important;
|
| 1742 |
+
box-shadow: none !important;
|
| 1743 |
+
color: var(--dqa-ink) !important;
|
| 1744 |
+
font-size: 1rem !important;
|
| 1745 |
+
font-weight: 680 !important;
|
| 1746 |
+
min-height: 46px !important;
|
| 1747 |
+
padding: 0 4px !important;
|
| 1748 |
+
}
|
| 1749 |
+
|
| 1750 |
+
.dc-side-panel .dc-dev > button:hover,
|
| 1751 |
+
.dc-side-panel .dc-dev > button[aria-expanded="true"] {
|
| 1752 |
+
background: var(--dqa-sage-soft) !important;
|
| 1753 |
+
color: var(--dqa-sage-deep) !important;
|
| 1754 |
+
}
|
| 1755 |
+
|
| 1756 |
+
.dc-side-panel .dc-dev-help {
|
| 1757 |
+
color: #60717a !important;
|
| 1758 |
+
display: grid !important;
|
| 1759 |
+
font-size: 0.92rem !important;
|
| 1760 |
+
gap: 8px !important;
|
| 1761 |
+
line-height: 1.5 !important;
|
| 1762 |
+
margin: 8px 0 14px !important;
|
| 1763 |
+
}
|
| 1764 |
+
|
| 1765 |
+
.dc-side-panel .dc-dev-help strong {
|
| 1766 |
+
color: var(--dqa-ink) !important;
|
| 1767 |
+
font-size: 0.94rem !important;
|
| 1768 |
+
font-weight: 760 !important;
|
| 1769 |
+
line-height: 1.42 !important;
|
| 1770 |
+
}
|
| 1771 |
+
|
| 1772 |
+
.dc-side-panel .dc-dev-help span {
|
| 1773 |
+
color: #60717a !important;
|
| 1774 |
+
opacity: 1 !important;
|
| 1775 |
+
}
|
| 1776 |
+
|
| 1777 |
+
.dc-side-panel .dc-dev .block:has(input[role="listbox"]) {
|
| 1778 |
+
background: rgba(255, 253, 248, 0.98) !important;
|
| 1779 |
+
border: 1px solid var(--dqa-line) !important;
|
| 1780 |
+
border-radius: 14px !important;
|
| 1781 |
+
box-shadow: none !important;
|
| 1782 |
+
margin: 8px 0 !important;
|
| 1783 |
+
padding: 12px 14px !important;
|
| 1784 |
+
}
|
| 1785 |
+
|
| 1786 |
+
.dc-side-panel .dc-dev .block:has(input[role="listbox"]) label,
|
| 1787 |
+
.dc-side-panel .dc-dev .block:has(input[role="listbox"]) [data-testid="block-label"] {
|
| 1788 |
+
color: var(--dqa-ink) !important;
|
| 1789 |
+
font-size: 0.84rem !important;
|
| 1790 |
+
font-weight: 680 !important;
|
| 1791 |
+
margin-bottom: 8px !important;
|
| 1792 |
+
}
|
| 1793 |
+
|
| 1794 |
+
.dc-side-panel .dc-dev .block:has(input[role="listbox"]) .wrap,
|
| 1795 |
+
.dc-side-panel .dc-dev .block:has(input[role="listbox"]) .container,
|
| 1796 |
+
.dc-side-panel .dc-dev .block:has(input[role="listbox"]) .wrap-inner,
|
| 1797 |
+
.dc-side-panel .dc-dev .block:has(input[role="listbox"]) .secondary-wrap,
|
| 1798 |
+
.dc-side-panel .dc-dev .block:has(input[role="listbox"]) .input-container,
|
| 1799 |
+
.dc-side-panel .dc-dev .block:has(input[role="listbox"]) input[role="listbox"] {
|
| 1800 |
+
min-height: 32px !important;
|
| 1801 |
+
padding: 0 !important;
|
| 1802 |
+
}
|
| 1803 |
+
|
| 1804 |
+
.dc-side-panel .dc-dev .block:has(input[role="listbox"]) input[role="listbox"] {
|
| 1805 |
+
color: var(--dqa-ink) !important;
|
| 1806 |
+
font-size: 0.94rem !important;
|
| 1807 |
+
font-weight: 560 !important;
|
| 1808 |
+
overflow: hidden !important;
|
| 1809 |
+
text-overflow: ellipsis !important;
|
| 1810 |
+
white-space: nowrap !important;
|
| 1811 |
+
width: 100% !important;
|
| 1812 |
+
}
|
| 1813 |
+
|
| 1814 |
+
.dc-side-panel .dc-dev .block:has(input[role="listbox"]) .wrap-inner {
|
| 1815 |
+
align-items: center !important;
|
| 1816 |
+
display: flex !important;
|
| 1817 |
+
gap: 8px !important;
|
| 1818 |
+
}
|
| 1819 |
+
|
| 1820 |
+
.dc-side-panel .dc-dev .block:has(input[role="listbox"]) .secondary-wrap {
|
| 1821 |
+
flex: 1 1 auto !important;
|
| 1822 |
+
max-width: calc(100% - 38px) !important;
|
| 1823 |
+
min-width: 0 !important;
|
| 1824 |
+
overflow: hidden !important;
|
| 1825 |
+
}
|
| 1826 |
+
|
| 1827 |
+
.dc-side-panel .dc-dev .block:has(input[role="listbox"]) .icon-wrap {
|
| 1828 |
+
flex: 0 0 28px !important;
|
| 1829 |
+
height: 28px !important;
|
| 1830 |
+
width: 28px !important;
|
| 1831 |
+
}
|
| 1832 |
+
|
| 1833 |
+
.dc-side-panel .dc-dev-tuning {
|
| 1834 |
+
background: rgba(255, 253, 248, 0.98) !important;
|
| 1835 |
+
border: 1px solid var(--dqa-line) !important;
|
| 1836 |
+
border-radius: 16px !important;
|
| 1837 |
+
box-shadow: none !important;
|
| 1838 |
+
display: grid !important;
|
| 1839 |
+
gap: 0 !important;
|
| 1840 |
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
| 1841 |
+
margin: 6px 0 12px !important;
|
| 1842 |
+
overflow: hidden !important;
|
| 1843 |
+
padding: 6px !important;
|
| 1844 |
+
}
|
| 1845 |
+
|
| 1846 |
+
.dc-side-panel .dc-dev-tuning > .styler,
|
| 1847 |
+
.dc-side-panel .dc-dev-tuning .form {
|
| 1848 |
+
background: transparent !important;
|
| 1849 |
+
border: 0 !important;
|
| 1850 |
+
border-radius: 12px !important;
|
| 1851 |
+
box-shadow: none !important;
|
| 1852 |
+
display: grid !important;
|
| 1853 |
+
gap: 6px !important;
|
| 1854 |
+
grid-column: 1 / -1 !important;
|
| 1855 |
+
grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
|
| 1856 |
+
width: 100% !important;
|
| 1857 |
+
}
|
| 1858 |
+
|
| 1859 |
+
.dc-side-panel .dc-dev-tuning .block {
|
| 1860 |
+
background: rgba(255, 255, 255, 0.58) !important;
|
| 1861 |
+
border: 1px solid rgba(217, 226, 220, 0.72) !important;
|
| 1862 |
+
border-radius: 12px !important;
|
| 1863 |
+
box-shadow: none !important;
|
| 1864 |
+
margin: 0 !important;
|
| 1865 |
+
min-width: 0 !important;
|
| 1866 |
+
padding: 9px 10px !important;
|
| 1867 |
+
}
|
| 1868 |
+
|
| 1869 |
+
.dc-side-panel .dc-dev-tuning .wrap.hide {
|
| 1870 |
+
display: none !important;
|
| 1871 |
+
height: 0 !important;
|
| 1872 |
+
min-height: 0 !important;
|
| 1873 |
+
}
|
| 1874 |
+
|
| 1875 |
+
.dc-side-panel .dc-dev-tuning .container {
|
| 1876 |
+
min-height: 0 !important;
|
| 1877 |
+
padding: 0 !important;
|
| 1878 |
+
}
|
| 1879 |
+
|
| 1880 |
+
.dc-side-panel .dc-dev-tuning label,
|
| 1881 |
+
.dc-side-panel .dc-dev-tuning [data-testid="block-label"] {
|
| 1882 |
+
color: var(--dqa-muted) !important;
|
| 1883 |
+
font-size: 0.74rem !important;
|
| 1884 |
+
font-weight: 700 !important;
|
| 1885 |
+
line-height: 1.25 !important;
|
| 1886 |
+
margin-bottom: 0 !important;
|
| 1887 |
+
}
|
| 1888 |
+
|
| 1889 |
+
.dc-side-panel .dc-dev-tuning input {
|
| 1890 |
+
background: transparent !important;
|
| 1891 |
+
border: 0 !important;
|
| 1892 |
+
box-shadow: none !important;
|
| 1893 |
+
color: var(--dqa-ink) !important;
|
| 1894 |
+
font-size: 0.92rem !important;
|
| 1895 |
+
font-weight: 700 !important;
|
| 1896 |
+
min-height: 30px !important;
|
| 1897 |
+
padding: 0 !important;
|
| 1898 |
+
}
|
| 1899 |
+
|
| 1900 |
+
.dc-side-panel .dc-dev-advanced {
|
| 1901 |
+
background: rgba(255, 253, 248, 0.98) !important;
|
| 1902 |
+
border: 1px solid var(--dqa-line) !important;
|
| 1903 |
+
border-radius: 14px !important;
|
| 1904 |
+
box-shadow: none !important;
|
| 1905 |
+
margin-top: 12px !important;
|
| 1906 |
+
}
|
| 1907 |
+
|
| 1908 |
+
.dc-side-panel .dc-dev-advanced > button {
|
| 1909 |
+
color: var(--dqa-ink) !important;
|
| 1910 |
+
font-size: 0.94rem !important;
|
| 1911 |
+
font-weight: 650 !important;
|
| 1912 |
+
min-height: 42px !important;
|
| 1913 |
+
padding: 0 12px !important;
|
| 1914 |
+
}
|
| 1915 |
+
|
| 1916 |
+
.gradio-container [role="listbox"],
|
| 1917 |
+
.gradio-container .options,
|
| 1918 |
+
.gradio-container .dropdown-options {
|
| 1919 |
+
background: rgba(255, 253, 248, 0.99) !important;
|
| 1920 |
+
border: 1px solid var(--dqa-line) !important;
|
| 1921 |
+
border-radius: 16px !important;
|
| 1922 |
+
box-shadow: 0 16px 38px rgba(30, 42, 48, 0.12) !important;
|
| 1923 |
+
color: var(--dqa-ink) !important;
|
| 1924 |
+
overflow: hidden !important;
|
| 1925 |
+
}
|
| 1926 |
+
|
| 1927 |
+
.gradio-container [role="option"],
|
| 1928 |
+
.gradio-container .option,
|
| 1929 |
+
.gradio-container .item {
|
| 1930 |
+
background: transparent !important;
|
| 1931 |
+
color: var(--dqa-ink) !important;
|
| 1932 |
+
}
|
| 1933 |
+
|
| 1934 |
+
.gradio-container [role="option"]:hover,
|
| 1935 |
+
.gradio-container [role="option"][aria-selected="true"],
|
| 1936 |
+
.gradio-container [role="option"].selected,
|
| 1937 |
+
.gradio-container .option:hover,
|
| 1938 |
+
.gradio-container .option.selected,
|
| 1939 |
+
.gradio-container .item:hover,
|
| 1940 |
+
.gradio-container .item.selected {
|
| 1941 |
+
background: var(--dqa-sage-soft) !important;
|
| 1942 |
+
color: var(--dqa-ink) !important;
|
| 1943 |
+
}
|
| 1944 |
+
|
| 1945 |
+
.gradio-container [role="option"]:focus,
|
| 1946 |
+
.gradio-container .option:focus,
|
| 1947 |
+
.gradio-container .item:focus {
|
| 1948 |
+
background: rgba(233, 243, 231, 0.78) !important;
|
| 1949 |
+
color: var(--dqa-ink) !important;
|
| 1950 |
+
outline: 2px solid rgba(79, 138, 88, 0.34) !important;
|
| 1951 |
+
outline-offset: -2px;
|
| 1952 |
+
}
|
| 1953 |
+
|
| 1954 |
+
.dc-stage .dc-dream-text,
|
| 1955 |
+
.dc-stage .dc-dream-text .wrap,
|
| 1956 |
+
.dc-stage .dc-dream-text .container,
|
| 1957 |
+
.dc-stage .dc-dream-text .input-container,
|
| 1958 |
+
.dc-stage .dc-dream-text .form,
|
| 1959 |
+
.dc-stage .dc-dream-text > div {
|
| 1960 |
+
background: transparent !important;
|
| 1961 |
+
border: 0 !important;
|
| 1962 |
+
border-radius: 0 !important;
|
| 1963 |
+
box-shadow: none !important;
|
| 1964 |
+
margin: 0 !important;
|
| 1965 |
+
overflow: visible !important;
|
| 1966 |
+
padding: 0 !important;
|
| 1967 |
+
}
|
| 1968 |
+
|
| 1969 |
+
.dc-stage .dc-dream-text [data-testid="block-label"],
|
| 1970 |
+
.dc-stage .dc-dream-text [data-testid="block-info"],
|
| 1971 |
+
.dc-stage .dc-dream-text .block-label {
|
| 1972 |
+
display: none !important;
|
| 1973 |
+
}
|
| 1974 |
+
|
| 1975 |
+
.dc-stage .dc-dream-text textarea {
|
| 1976 |
+
background: rgba(255, 253, 248, 0.98) !important;
|
| 1977 |
+
border: 1px solid var(--dqa-line) !important;
|
| 1978 |
+
border-radius: 30px !important;
|
| 1979 |
+
box-shadow:
|
| 1980 |
+
inset 0 1px 0 rgba(255, 255, 255, 0.82),
|
| 1981 |
+
0 18px 38px rgba(30, 42, 48, 0.1) !important;
|
| 1982 |
+
color: var(--dqa-ink) !important;
|
| 1983 |
+
min-height: 320px !important;
|
| 1984 |
+
overflow: hidden !important;
|
| 1985 |
+
padding: 28px 96px 82px 78px !important;
|
| 1986 |
+
resize: none !important;
|
| 1987 |
+
}
|
| 1988 |
+
|
| 1989 |
+
.dc-attach-control,
|
| 1990 |
+
.dc-mic-control {
|
| 1991 |
+
bottom: 24px !important;
|
| 1992 |
+
position: absolute;
|
| 1993 |
+
z-index: 18;
|
| 1994 |
+
}
|
| 1995 |
+
|
| 1996 |
+
.dc-attach-control {
|
| 1997 |
+
left: 24px;
|
| 1998 |
+
}
|
| 1999 |
+
|
| 2000 |
+
.dc-mic-control {
|
| 2001 |
+
display: block !important;
|
| 2002 |
+
height: 52px !important;
|
| 2003 |
+
right: 24px !important;
|
| 2004 |
+
width: 52px !important;
|
| 2005 |
+
}
|
| 2006 |
+
|
| 2007 |
+
.dc-stage .dc-attach-control .dc-attach-button,
|
| 2008 |
+
.dc-stage .dc-mic-control .dc-mic-button {
|
| 2009 |
+
align-items: center !important;
|
| 2010 |
+
background: rgba(255, 253, 248, 0.98) !important;
|
| 2011 |
+
border: 1px solid rgba(7, 60, 67, 0.22) !important;
|
| 2012 |
+
border-radius: 999px !important;
|
| 2013 |
+
box-sizing: border-box !important;
|
| 2014 |
+
box-shadow: 0 10px 22px rgba(7, 60, 67, 0.12) !important;
|
| 2015 |
+
color: var(--dc-teal-dark) !important;
|
| 2016 |
+
display: inline-flex !important;
|
| 2017 |
+
height: 52px !important;
|
| 2018 |
+
justify-content: center !important;
|
| 2019 |
+
line-height: 1 !important;
|
| 2020 |
+
max-height: 52px !important;
|
| 2021 |
+
min-height: 52px !important;
|
| 2022 |
+
padding: 0 !important;
|
| 2023 |
+
width: 52px !important;
|
| 2024 |
+
}
|
| 2025 |
+
|
| 2026 |
+
.dc-stage .dc-attach-control .dc-attach-button {
|
| 2027 |
+
cursor: pointer;
|
| 2028 |
+
font-size: 1.6rem !important;
|
| 2029 |
+
font-weight: 720 !important;
|
| 2030 |
+
opacity: 1 !important;
|
| 2031 |
+
}
|
| 2032 |
+
|
| 2033 |
+
.dc-stage .dc-attach-control .dc-attach-button span {
|
| 2034 |
+
color: var(--dc-teal-dark) !important;
|
| 2035 |
+
opacity: 1 !important;
|
| 2036 |
+
}
|
| 2037 |
+
|
| 2038 |
+
.dc-stage .dc-attach-control .dc-attach-button:hover,
|
| 2039 |
+
.dc-stage .dc-attach-control .dc-attach-button[aria-expanded="true"],
|
| 2040 |
+
.dc-stage .dc-mic-control .dc-mic-button:hover {
|
| 2041 |
+
border-color: rgba(234, 107, 44, 0.42) !important;
|
| 2042 |
+
color: var(--dc-coral-dark) !important;
|
| 2043 |
+
transform: translateY(-1px);
|
| 2044 |
+
}
|
| 2045 |
+
|
| 2046 |
+
.dc-stage .dc-mic-control .dc-mic-status {
|
| 2047 |
+
bottom: 62px;
|
| 2048 |
+
position: absolute;
|
| 2049 |
+
right: 0;
|
| 2050 |
+
white-space: nowrap;
|
| 2051 |
+
}
|
| 2052 |
+
|
| 2053 |
+
.dc-image-popover {
|
| 2054 |
+
bottom: 88px;
|
| 2055 |
+
background: rgba(255, 253, 248, 0.99) !important;
|
| 2056 |
+
border: 1px solid var(--dqa-line) !important;
|
| 2057 |
+
border-radius: 26px !important;
|
| 2058 |
+
border-style: solid !important;
|
| 2059 |
+
box-shadow: 0 18px 42px rgba(30, 42, 48, 0.16) !important;
|
| 2060 |
+
color: var(--dqa-ink) !important;
|
| 2061 |
+
height: 220px !important;
|
| 2062 |
+
left: 24px;
|
| 2063 |
+
margin: 0 !important;
|
| 2064 |
+
max-width: calc(100% - 48px);
|
| 2065 |
+
opacity: 0;
|
| 2066 |
+
overflow: hidden !important;
|
| 2067 |
+
pointer-events: none;
|
| 2068 |
+
position: absolute !important;
|
| 2069 |
+
transform: translateY(8px) scale(0.98);
|
| 2070 |
+
transition: opacity 160ms ease, transform 160ms ease, visibility 160ms ease;
|
| 2071 |
+
visibility: hidden;
|
| 2072 |
+
width: min(420px, calc(100% - 48px));
|
| 2073 |
+
z-index: 20;
|
| 2074 |
+
}
|
| 2075 |
+
|
| 2076 |
+
.dc-composer.dc-image-open .dc-image-popover {
|
| 2077 |
+
opacity: 1;
|
| 2078 |
+
pointer-events: auto;
|
| 2079 |
+
transform: translateY(0) scale(1);
|
| 2080 |
+
visibility: visible;
|
| 2081 |
+
}
|
| 2082 |
+
|
| 2083 |
+
.dc-image-popover .wrap,
|
| 2084 |
+
.dc-image-popover .container,
|
| 2085 |
+
.dc-image-popover .upload-container,
|
| 2086 |
+
.dc-image-popover .image-container,
|
| 2087 |
+
.dc-image-popover [data-testid="image"],
|
| 2088 |
+
.dc-image-popover [data-testid="file-upload"] {
|
| 2089 |
+
background: transparent !important;
|
| 2090 |
+
border: 0 !important;
|
| 2091 |
+
border-radius: 0 !important;
|
| 2092 |
+
box-shadow: none !important;
|
| 2093 |
+
}
|
| 2094 |
+
|
| 2095 |
+
.dc-image-popover [data-testid="block-label"] {
|
| 2096 |
+
align-items: center !important;
|
| 2097 |
+
background: rgba(238, 247, 236, 0.96) !important;
|
| 2098 |
+
border: 1px solid rgba(88, 142, 94, 0.2) !important;
|
| 2099 |
+
border-radius: 999px !important;
|
| 2100 |
+
box-shadow: none !important;
|
| 2101 |
+
color: var(--dqa-sage-deep) !important;
|
| 2102 |
+
display: inline-flex !important;
|
| 2103 |
+
font-family: inherit !important;
|
| 2104 |
+
font-size: 0.84rem !important;
|
| 2105 |
+
font-weight: 720 !important;
|
| 2106 |
+
gap: 7px !important;
|
| 2107 |
+
left: 16px !important;
|
| 2108 |
+
line-height: 1 !important;
|
| 2109 |
+
padding: 8px 12px !important;
|
| 2110 |
+
top: 14px !important;
|
| 2111 |
+
}
|
| 2112 |
+
|
| 2113 |
+
.dc-image-popover [data-testid="block-label"] svg {
|
| 2114 |
+
color: var(--dqa-sage-deep) !important;
|
| 2115 |
+
height: 16px !important;
|
| 2116 |
+
width: 16px !important;
|
| 2117 |
+
}
|
| 2118 |
+
|
| 2119 |
+
.dc-image-popover .image-container {
|
| 2120 |
+
height: 100% !important;
|
| 2121 |
+
overflow: hidden !important;
|
| 2122 |
+
}
|
| 2123 |
+
|
| 2124 |
+
.dc-image-popover .upload-container {
|
| 2125 |
+
height: 100% !important;
|
| 2126 |
+
padding: 18px 18px 64px !important;
|
| 2127 |
+
}
|
| 2128 |
+
|
| 2129 |
+
.dc-image-popover .upload-container button {
|
| 2130 |
+
align-items: center !important;
|
| 2131 |
+
background: transparent !important;
|
| 2132 |
+
border: 0 !important;
|
| 2133 |
+
border-radius: 22px !important;
|
| 2134 |
+
box-shadow: none !important;
|
| 2135 |
+
color: var(--dqa-ink) !important;
|
| 2136 |
+
display: flex !important;
|
| 2137 |
+
justify-content: center !important;
|
| 2138 |
+
min-height: 130px !important;
|
| 2139 |
+
width: 100% !important;
|
| 2140 |
+
}
|
| 2141 |
+
|
| 2142 |
+
.dc-image-popover .upload-container button:hover {
|
| 2143 |
+
background: rgba(238, 247, 236, 0.56) !important;
|
| 2144 |
+
}
|
| 2145 |
+
|
| 2146 |
+
.dc-image-popover .upload-container .wrap {
|
| 2147 |
+
align-items: center !important;
|
| 2148 |
+
color: var(--dqa-ink) !important;
|
| 2149 |
+
display: flex !important;
|
| 2150 |
+
flex-direction: column !important;
|
| 2151 |
+
font-size: 0 !important;
|
| 2152 |
+
gap: 12px !important;
|
| 2153 |
+
justify-content: center !important;
|
| 2154 |
+
}
|
| 2155 |
+
|
| 2156 |
+
.dc-image-popover .upload-container .icon-wrap,
|
| 2157 |
+
.dc-image-popover .upload-container svg {
|
| 2158 |
+
color: var(--dqa-sage-deep) !important;
|
| 2159 |
+
height: 32px !important;
|
| 2160 |
+
width: 32px !important;
|
| 2161 |
+
}
|
| 2162 |
+
|
| 2163 |
+
.dc-image-popover .upload-container .or {
|
| 2164 |
+
display: none !important;
|
| 2165 |
+
}
|
| 2166 |
+
|
| 2167 |
+
.dc-image-upload-copy {
|
| 2168 |
+
color: var(--dqa-ink) !important;
|
| 2169 |
+
display: block !important;
|
| 2170 |
+
font-size: 1rem !important;
|
| 2171 |
+
font-weight: 760 !important;
|
| 2172 |
+
letter-spacing: 0 !important;
|
| 2173 |
+
}
|
| 2174 |
+
|
| 2175 |
+
.dc-image-popover [data-testid="source-select"] {
|
| 2176 |
+
align-items: center !important;
|
| 2177 |
+
background: transparent !important;
|
| 2178 |
+
border: 0 !important;
|
| 2179 |
+
border-top: 0 !important;
|
| 2180 |
+
bottom: 16px !important;
|
| 2181 |
+
box-shadow: none !important;
|
| 2182 |
+
display: flex !important;
|
| 2183 |
+
gap: 12px !important;
|
| 2184 |
+
justify-content: center !important;
|
| 2185 |
+
left: 0 !important;
|
| 2186 |
+
padding: 0 !important;
|
| 2187 |
+
position: absolute !important;
|
| 2188 |
+
right: 0 !important;
|
| 2189 |
+
}
|
| 2190 |
+
|
| 2191 |
+
.dc-image-popover [data-testid="source-select"]::before,
|
| 2192 |
+
.dc-image-popover [data-testid="source-select"]::after {
|
| 2193 |
+
display: none !important;
|
| 2194 |
+
}
|
| 2195 |
+
|
| 2196 |
+
.dc-image-popover [data-testid="source-select"] button {
|
| 2197 |
+
align-items: center !important;
|
| 2198 |
+
background: rgba(255, 253, 248, 0.92) !important;
|
| 2199 |
+
border: 1px solid var(--dqa-line) !important;
|
| 2200 |
+
border-radius: 999px !important;
|
| 2201 |
+
box-shadow: none !important;
|
| 2202 |
+
color: var(--dqa-muted) !important;
|
| 2203 |
+
display: inline-flex !important;
|
| 2204 |
+
height: 42px !important;
|
| 2205 |
+
justify-content: center !important;
|
| 2206 |
+
min-height: 42px !important;
|
| 2207 |
+
padding: 0 !important;
|
| 2208 |
+
width: 42px !important;
|
| 2209 |
+
}
|
| 2210 |
+
|
| 2211 |
+
.dc-image-popover [data-testid="source-select"] button:hover,
|
| 2212 |
+
.dc-image-popover [data-testid="source-select"] button.selected {
|
| 2213 |
+
background: var(--dqa-sage-soft) !important;
|
| 2214 |
+
border-color: rgba(88, 142, 94, 0.26) !important;
|
| 2215 |
+
color: var(--dqa-sage-deep) !important;
|
| 2216 |
+
}
|
| 2217 |
+
|
| 2218 |
+
.dc-image-popover [aria-label="Capture from camera"] {
|
| 2219 |
+
display: none !important;
|
| 2220 |
+
}
|
| 2221 |
+
|
| 2222 |
@media (max-width: 900px) {
|
| 2223 |
.dc-shell {
|
| 2224 |
padding: 0;
|
|
|
|
| 2249 |
margin-top: 36px;
|
| 2250 |
}
|
| 2251 |
|
| 2252 |
+
.dc-stepper-line {
|
| 2253 |
+
flex-basis: 26px;
|
| 2254 |
+
margin-top: 24px;
|
| 2255 |
+
min-width: 20px;
|
| 2256 |
}
|
| 2257 |
|
| 2258 |
.dc-stepper strong {
|
|
|
|
| 2278 |
right: 20px;
|
| 2279 |
}
|
| 2280 |
|
| 2281 |
+
.dc-attach-control {
|
| 2282 |
bottom: 22px;
|
| 2283 |
left: 20px;
|
| 2284 |
}
|
| 2285 |
|
| 2286 |
+
.dc-stage .dc-dream-text textarea {
|
| 2287 |
+
border-radius: 24px !important;
|
| 2288 |
+
min-height: 300px !important;
|
| 2289 |
+
padding: 24px 78px 78px 70px !important;
|
| 2290 |
+
}
|
| 2291 |
+
|
| 2292 |
+
.dc-image-popover {
|
| 2293 |
+
bottom: 84px;
|
| 2294 |
+
left: 20px;
|
| 2295 |
+
width: min(360px, calc(100% - 40px));
|
| 2296 |
+
}
|
| 2297 |
+
|
| 2298 |
.dqa-tip-page {
|
| 2299 |
border-radius: 0;
|
| 2300 |
}
|
modal_backend/dream_customs_modal.py
CHANGED
|
@@ -35,7 +35,7 @@ health_image = (
|
|
| 35 |
|
| 36 |
image = (
|
| 37 |
modal.Image.debian_slim(python_version="3.11")
|
| 38 |
-
.apt_install("git")
|
| 39 |
.pip_install(
|
| 40 |
"accelerate",
|
| 41 |
"einops",
|
|
|
|
| 35 |
|
| 36 |
image = (
|
| 37 |
modal.Image.debian_slim(python_version="3.11")
|
| 38 |
+
.apt_install("ffmpeg", "git")
|
| 39 |
.pip_install(
|
| 40 |
"accelerate",
|
| 41 |
"einops",
|
scripts/local_space_mirror.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Run the Dream QA Hugging Face Space app locally for pre-merge review."""
|
| 3 |
+
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import argparse
|
| 7 |
+
import json
|
| 8 |
+
import os
|
| 9 |
+
import sys
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
ROOT = Path(__file__).resolve().parents[1]
|
| 14 |
+
SPACE_ID = "build-small-hackathon/dream-customs"
|
| 15 |
+
DEFAULT_RUNTIME_ENV_JSON = Path("/tmp/dream-customs-runtime.json")
|
| 16 |
+
RUNTIME_ENV_KEYS = {
|
| 17 |
+
"DREAM_CUSTOMS_TEXT_ENDPOINT",
|
| 18 |
+
"DREAM_CUSTOMS_VISION_ENDPOINT",
|
| 19 |
+
"DREAM_CUSTOMS_ASR_ENDPOINT",
|
| 20 |
+
"DREAM_CUSTOMS_HOSTED_TOKEN",
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def _repo_root_on_path() -> None:
|
| 25 |
+
root = str(ROOT)
|
| 26 |
+
if root not in sys.path:
|
| 27 |
+
sys.path.insert(0, root)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def _configured_env() -> dict:
|
| 31 |
+
return {
|
| 32 |
+
"space_id": os.getenv("SPACE_ID", SPACE_ID),
|
| 33 |
+
"text_endpoint_configured": bool(os.getenv("DREAM_CUSTOMS_TEXT_ENDPOINT", "").strip()),
|
| 34 |
+
"vision_endpoint_configured": bool(os.getenv("DREAM_CUSTOMS_VISION_ENDPOINT", "").strip()),
|
| 35 |
+
"asr_endpoint_configured": bool(os.getenv("DREAM_CUSTOMS_ASR_ENDPOINT", "").strip()),
|
| 36 |
+
"hosted_token_configured": bool(os.getenv("DREAM_CUSTOMS_HOSTED_TOKEN", "").strip()),
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def load_runtime_env_json(path: Path) -> dict:
|
| 41 |
+
if not path.exists():
|
| 42 |
+
return {"loaded": False, "path": str(path), "reason": "missing"}
|
| 43 |
+
try:
|
| 44 |
+
data = json.loads(path.read_text(encoding="utf-8"))
|
| 45 |
+
except (OSError, json.JSONDecodeError) as exc:
|
| 46 |
+
return {"loaded": False, "path": str(path), "reason": exc.__class__.__name__}
|
| 47 |
+
|
| 48 |
+
loaded_keys = []
|
| 49 |
+
for key in sorted(RUNTIME_ENV_KEYS):
|
| 50 |
+
value = str(data.get(key, "")).strip()
|
| 51 |
+
if value:
|
| 52 |
+
os.environ[key] = value
|
| 53 |
+
loaded_keys.append(key)
|
| 54 |
+
return {
|
| 55 |
+
"loaded": bool(loaded_keys),
|
| 56 |
+
"path": str(path),
|
| 57 |
+
"configured_keys": loaded_keys,
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def mirror_manifest(host: str, port: int) -> dict:
|
| 62 |
+
_repo_root_on_path()
|
| 63 |
+
from dream_customs.defaults import DEFAULT_ASR_BACKEND, DEFAULT_TEXT_BACKEND, DEFAULT_VISION_BACKEND
|
| 64 |
+
|
| 65 |
+
return {
|
| 66 |
+
"mode": "local-space-mirror",
|
| 67 |
+
"space_id": SPACE_ID,
|
| 68 |
+
"url": f"http://{host}:{port}",
|
| 69 |
+
"app_file": "app.py",
|
| 70 |
+
"default_backends": {
|
| 71 |
+
"text": DEFAULT_TEXT_BACKEND,
|
| 72 |
+
"vision": DEFAULT_VISION_BACKEND,
|
| 73 |
+
"asr": DEFAULT_ASR_BACKEND,
|
| 74 |
+
},
|
| 75 |
+
"env": _configured_env(),
|
| 76 |
+
"notes": [
|
| 77 |
+
"Uses the same app.py/build_demo path as the Hugging Face Space.",
|
| 78 |
+
"Missing hosted endpoint secrets fall back to deterministic demo behavior.",
|
| 79 |
+
"Do not print or store endpoint URLs or tokens in logs.",
|
| 80 |
+
],
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
| 85 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 86 |
+
parser.add_argument("--host", default=os.getenv("GRADIO_SERVER_NAME", "127.0.0.1"))
|
| 87 |
+
parser.add_argument("--port", type=int, default=int(os.getenv("GRADIO_SERVER_PORT", "7862")))
|
| 88 |
+
parser.add_argument("--share", action="store_true", help="Create a public Gradio share link.")
|
| 89 |
+
parser.add_argument(
|
| 90 |
+
"--manifest-only",
|
| 91 |
+
action="store_true",
|
| 92 |
+
help="Print the local mirror manifest without starting Gradio.",
|
| 93 |
+
)
|
| 94 |
+
parser.add_argument(
|
| 95 |
+
"--runtime-env-json",
|
| 96 |
+
type=Path,
|
| 97 |
+
default=Path(os.getenv("DREAM_CUSTOMS_RUNTIME_ENV_JSON", DEFAULT_RUNTIME_ENV_JSON)),
|
| 98 |
+
help=(
|
| 99 |
+
"Load hosted endpoint/token secrets from a local JSON file before launching. "
|
| 100 |
+
"Only configuration booleans are printed."
|
| 101 |
+
),
|
| 102 |
+
)
|
| 103 |
+
parser.add_argument(
|
| 104 |
+
"--no-runtime-env-json",
|
| 105 |
+
action="store_true",
|
| 106 |
+
help="Do not auto-load the default local runtime env JSON.",
|
| 107 |
+
)
|
| 108 |
+
return parser.parse_args(argv)
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def main(argv: list[str] | None = None) -> int:
|
| 112 |
+
args = parse_args(argv)
|
| 113 |
+
os.environ["GRADIO_SERVER_NAME"] = args.host
|
| 114 |
+
os.environ["GRADIO_SERVER_PORT"] = str(args.port)
|
| 115 |
+
os.environ.setdefault("SPACE_ID", SPACE_ID)
|
| 116 |
+
os.environ.setdefault("GRADIO_ANALYTICS_ENABLED", "False")
|
| 117 |
+
|
| 118 |
+
runtime_env = {"loaded": False, "reason": "disabled"}
|
| 119 |
+
if not args.no_runtime_env_json:
|
| 120 |
+
runtime_env = load_runtime_env_json(args.runtime_env_json)
|
| 121 |
+
|
| 122 |
+
manifest = mirror_manifest(args.host, args.port)
|
| 123 |
+
manifest["runtime_env_json"] = runtime_env
|
| 124 |
+
print(json.dumps(manifest, ensure_ascii=False, indent=2), flush=True)
|
| 125 |
+
if args.manifest_only:
|
| 126 |
+
return 0
|
| 127 |
+
|
| 128 |
+
_repo_root_on_path()
|
| 129 |
+
from app import demo
|
| 130 |
+
|
| 131 |
+
demo.launch(
|
| 132 |
+
server_name=args.host,
|
| 133 |
+
server_port=args.port,
|
| 134 |
+
show_api=False,
|
| 135 |
+
show_error=True,
|
| 136 |
+
share=args.share,
|
| 137 |
+
)
|
| 138 |
+
return 0
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
if __name__ == "__main__":
|
| 142 |
+
raise SystemExit(main())
|
scripts/smoke_local_space_mirror.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Verify a running local Dream QA Space mirror."""
|
| 3 |
+
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import argparse
|
| 7 |
+
import json
|
| 8 |
+
from typing import Any
|
| 9 |
+
from urllib.request import urlopen
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
REQUIRED_CSS_MARKERS = [
|
| 13 |
+
".dc-stepper",
|
| 14 |
+
".dc-mic-button",
|
| 15 |
+
".dc-attach-button",
|
| 16 |
+
".dc-image-popover",
|
| 17 |
+
".dc-debug-panel",
|
| 18 |
+
]
|
| 19 |
+
|
| 20 |
+
REQUIRED_LABELS = [
|
| 21 |
+
"Dream note",
|
| 22 |
+
"Language",
|
| 23 |
+
"Text generation",
|
| 24 |
+
"Image understanding",
|
| 25 |
+
"Voice input",
|
| 26 |
+
"Runtime state",
|
| 27 |
+
]
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def _component_labels(config: dict[str, Any]) -> set[str]:
|
| 31 |
+
labels = set()
|
| 32 |
+
for component in config.get("components", []):
|
| 33 |
+
props = component.get("props") or {}
|
| 34 |
+
label = props.get("label")
|
| 35 |
+
if isinstance(label, str):
|
| 36 |
+
labels.add(label)
|
| 37 |
+
return labels
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def _component_values(config: dict[str, Any]) -> dict[str, Any]:
|
| 41 |
+
values = {}
|
| 42 |
+
for component in config.get("components", []):
|
| 43 |
+
props = component.get("props") or {}
|
| 44 |
+
label = props.get("label")
|
| 45 |
+
if isinstance(label, str):
|
| 46 |
+
values[label] = props.get("value")
|
| 47 |
+
return values
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def inspect_config(config: dict[str, Any]) -> dict[str, Any]:
|
| 51 |
+
css = config.get("css") or ""
|
| 52 |
+
labels = _component_labels(config)
|
| 53 |
+
values = _component_values(config)
|
| 54 |
+
missing_css = [marker for marker in REQUIRED_CSS_MARKERS if marker not in css]
|
| 55 |
+
missing_labels = [label for label in REQUIRED_LABELS if label not in labels]
|
| 56 |
+
backend_defaults = {
|
| 57 |
+
"Text generation": values.get("Text generation"),
|
| 58 |
+
"Image understanding": values.get("Image understanding"),
|
| 59 |
+
"Voice input": values.get("Voice input"),
|
| 60 |
+
}
|
| 61 |
+
backend_failures = {
|
| 62 |
+
label: value for label, value in backend_defaults.items() if value != "modal"
|
| 63 |
+
}
|
| 64 |
+
title = config.get("title")
|
| 65 |
+
failures = {}
|
| 66 |
+
if title != "Dream QA":
|
| 67 |
+
failures["title"] = title
|
| 68 |
+
if missing_css:
|
| 69 |
+
failures["missing_css"] = missing_css
|
| 70 |
+
if missing_labels:
|
| 71 |
+
failures["missing_labels"] = missing_labels
|
| 72 |
+
if backend_failures:
|
| 73 |
+
failures["backend_defaults"] = backend_failures
|
| 74 |
+
return {
|
| 75 |
+
"title": title,
|
| 76 |
+
"component_count": len(config.get("components", [])),
|
| 77 |
+
"backend_defaults": backend_defaults,
|
| 78 |
+
"missing_css": missing_css,
|
| 79 |
+
"missing_labels": missing_labels,
|
| 80 |
+
"passes": not failures,
|
| 81 |
+
"failures": failures,
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def fetch_config(base_url: str, timeout: float = 20.0) -> dict[str, Any]:
|
| 86 |
+
with urlopen(f"{base_url.rstrip('/')}/config", timeout=timeout) as response:
|
| 87 |
+
return json.load(response)
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
| 91 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 92 |
+
parser.add_argument("--url", default="http://127.0.0.1:7862")
|
| 93 |
+
parser.add_argument("--timeout", type=float, default=20.0)
|
| 94 |
+
return parser.parse_args(argv)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def main(argv: list[str] | None = None) -> int:
|
| 98 |
+
args = parse_args(argv)
|
| 99 |
+
result = inspect_config(fetch_config(args.url, timeout=args.timeout))
|
| 100 |
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
| 101 |
+
return 0 if result["passes"] else 1
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
if __name__ == "__main__":
|
| 105 |
+
raise SystemExit(main())
|
tests/test_local_space_mirror.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
|
| 3 |
+
from scripts.local_space_mirror import load_runtime_env_json, mirror_manifest
|
| 4 |
+
from scripts.smoke_local_space_mirror import inspect_config
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def test_local_space_mirror_manifest_matches_space_entrypoint():
|
| 8 |
+
manifest = mirror_manifest("127.0.0.1", 7862)
|
| 9 |
+
|
| 10 |
+
assert manifest["mode"] == "local-space-mirror"
|
| 11 |
+
assert manifest["space_id"] == "build-small-hackathon/dream-customs"
|
| 12 |
+
assert manifest["app_file"] == "app.py"
|
| 13 |
+
assert manifest["url"] == "http://127.0.0.1:7862"
|
| 14 |
+
assert manifest["default_backends"] == {
|
| 15 |
+
"text": "modal",
|
| 16 |
+
"vision": "modal",
|
| 17 |
+
"asr": "modal",
|
| 18 |
+
}
|
| 19 |
+
assert manifest["env"]["text_endpoint_configured"] is False
|
| 20 |
+
assert manifest["env"]["vision_endpoint_configured"] is False
|
| 21 |
+
assert manifest["env"]["asr_endpoint_configured"] is False
|
| 22 |
+
assert manifest["env"]["hosted_token_configured"] is False
|
| 23 |
+
serialized_env = json.dumps(manifest["env"]).lower()
|
| 24 |
+
assert "https://" not in serialized_env
|
| 25 |
+
assert "secret" not in serialized_env
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def test_local_space_mirror_loads_runtime_env_json_without_printing_values(tmp_path, monkeypatch):
|
| 29 |
+
for key in [
|
| 30 |
+
"DREAM_CUSTOMS_TEXT_ENDPOINT",
|
| 31 |
+
"DREAM_CUSTOMS_VISION_ENDPOINT",
|
| 32 |
+
"DREAM_CUSTOMS_ASR_ENDPOINT",
|
| 33 |
+
"DREAM_CUSTOMS_HOSTED_TOKEN",
|
| 34 |
+
]:
|
| 35 |
+
monkeypatch.delenv(key, raising=False)
|
| 36 |
+
runtime_json = tmp_path / "runtime.json"
|
| 37 |
+
runtime_json.write_text(
|
| 38 |
+
json.dumps(
|
| 39 |
+
{
|
| 40 |
+
"DREAM_CUSTOMS_TEXT_ENDPOINT": "https://example.test/text",
|
| 41 |
+
"DREAM_CUSTOMS_VISION_ENDPOINT": "https://example.test/vision",
|
| 42 |
+
"DREAM_CUSTOMS_ASR_ENDPOINT": "https://example.test/asr",
|
| 43 |
+
"DREAM_CUSTOMS_HOSTED_TOKEN": "super-secret-token",
|
| 44 |
+
"UNRELATED": "ignored",
|
| 45 |
+
}
|
| 46 |
+
),
|
| 47 |
+
encoding="utf-8",
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
load_result = load_runtime_env_json(runtime_json)
|
| 51 |
+
manifest = mirror_manifest("127.0.0.1", 7862)
|
| 52 |
+
|
| 53 |
+
assert load_result["loaded"] is True
|
| 54 |
+
assert "UNRELATED" not in load_result["configured_keys"]
|
| 55 |
+
assert manifest["env"]["text_endpoint_configured"] is True
|
| 56 |
+
assert manifest["env"]["vision_endpoint_configured"] is True
|
| 57 |
+
assert manifest["env"]["asr_endpoint_configured"] is True
|
| 58 |
+
assert manifest["env"]["hosted_token_configured"] is True
|
| 59 |
+
serialized = json.dumps(manifest, ensure_ascii=False)
|
| 60 |
+
assert "https://example.test" not in serialized
|
| 61 |
+
assert "super-secret-token" not in serialized
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def test_local_space_mirror_config_smoke_requires_composer_debug_markers():
|
| 65 |
+
config = {
|
| 66 |
+
"title": "Dream QA",
|
| 67 |
+
"css": ".dc-stepper .dc-mic-button .dc-attach-button .dc-image-popover .dc-debug-panel",
|
| 68 |
+
"components": [
|
| 69 |
+
{"props": {"label": "Dream note"}},
|
| 70 |
+
{"props": {"label": "Language"}},
|
| 71 |
+
{"props": {"label": "Text generation", "value": "modal"}},
|
| 72 |
+
{"props": {"label": "Image understanding", "value": "modal"}},
|
| 73 |
+
{"props": {"label": "Voice input", "value": "modal"}},
|
| 74 |
+
{"props": {"label": "Runtime state"}},
|
| 75 |
+
],
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
result = inspect_config(config)
|
| 79 |
+
|
| 80 |
+
assert result["passes"] is True
|
| 81 |
+
assert result["backend_defaults"]["Text generation"] == "modal"
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def test_local_space_mirror_config_smoke_fails_on_stale_ui():
|
| 85 |
+
config = {
|
| 86 |
+
"title": "Dream QA",
|
| 87 |
+
"css": ".dc-stepper",
|
| 88 |
+
"components": [
|
| 89 |
+
{"props": {"label": "Dream note"}},
|
| 90 |
+
{"props": {"label": "Text generation", "value": "demo"}},
|
| 91 |
+
],
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
result = inspect_config(config)
|
| 95 |
+
|
| 96 |
+
assert result["passes"] is False
|
| 97 |
+
assert ".dc-mic-button" in result["failures"]["missing_css"]
|
| 98 |
+
assert result["failures"]["backend_defaults"]["Text generation"] == "demo"
|
tests/test_ui_actions.py
CHANGED
|
@@ -6,6 +6,7 @@ from dream_customs.ui.actions import answer_to_card_action, initial_mobile_state
|
|
| 6 |
import dream_customs.ui.app as ui_app
|
| 7 |
from dream_customs.ui.app import _reset
|
| 8 |
from dream_customs.ui.copy import DEFAULT_MOOD, PROCESSING_NOTE
|
|
|
|
| 9 |
|
| 10 |
|
| 11 |
def test_mobile_defaults_to_modal_backends():
|
|
@@ -18,16 +19,21 @@ def test_mobile_defaults_to_modal_backends():
|
|
| 18 |
|
| 19 |
def test_runtime_settings_are_collapsed_for_public_flow():
|
| 20 |
source = inspect.getsource(ui_app.build_demo)
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
assert 'gr.Accordion("Advanced", open=False' in source
|
| 23 |
assert 'with gr.Accordion(initial_copy["debug_title"], open=False' in source
|
|
|
|
| 24 |
|
| 25 |
|
| 26 |
def test_voice_input_keeps_modal_asr_component_but_uses_inline_mic_button():
|
| 27 |
source = inspect.getsource(ui_app.build_demo)
|
| 28 |
|
| 29 |
assert "audio_input = gr.Audio(" in source
|
| 30 |
-
assert 'sources=["
|
|
|
|
| 31 |
assert 'type="filepath"' in source
|
| 32 |
assert "visible=False" in source
|
| 33 |
assert "mic_html = gr.HTML(_mic_html(DEFAULT_LANGUAGE))" in source
|
|
@@ -38,20 +44,94 @@ def test_voice_input_keeps_modal_asr_component_but_uses_inline_mic_button():
|
|
| 38 |
def test_image_upload_is_composer_plus_drawer():
|
| 39 |
source = inspect.getsource(ui_app.build_demo)
|
| 40 |
|
| 41 |
-
assert
|
| 42 |
-
assert
|
| 43 |
-
assert '
|
| 44 |
-
assert '
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
|
| 46 |
|
| 47 |
def test_hero_stepper_tracks_app_status():
|
| 48 |
ask_html = ui_app._hero_html(status="ask")
|
| 49 |
tip_html = ui_app._hero_html(status="tip")
|
| 50 |
|
| 51 |
-
assert '<span class="is-complete"><strong>1</strong>' in ask_html
|
| 52 |
-
assert '<
|
| 53 |
-
assert '<span class="is-
|
| 54 |
-
assert '<span class="is-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
|
| 56 |
|
| 57 |
def test_processing_note_is_story_copy_not_backend_jargon():
|
|
|
|
| 6 |
import dream_customs.ui.app as ui_app
|
| 7 |
from dream_customs.ui.app import _reset
|
| 8 |
from dream_customs.ui.copy import DEFAULT_MOOD, PROCESSING_NOTE
|
| 9 |
+
from dream_customs.ui.styles import CSS
|
| 10 |
|
| 11 |
|
| 12 |
def test_mobile_defaults_to_modal_backends():
|
|
|
|
| 19 |
|
| 20 |
def test_runtime_settings_are_collapsed_for_public_flow():
|
| 21 |
source = inspect.getsource(ui_app.build_demo)
|
| 22 |
+
flow_column_source = source.split('with gr.Column(elem_classes=["dc-flow-column"]):', 1)[1].split(
|
| 23 |
+
'with gr.Column(elem_classes=["dc-side-panel"]):', 1
|
| 24 |
+
)[0]
|
| 25 |
|
| 26 |
assert 'gr.Accordion("Advanced", open=False' in source
|
| 27 |
assert 'with gr.Accordion(initial_copy["debug_title"], open=False' in source
|
| 28 |
+
assert 'with gr.Accordion(initial_copy["debug_title"], open=False' in flow_column_source
|
| 29 |
|
| 30 |
|
| 31 |
def test_voice_input_keeps_modal_asr_component_but_uses_inline_mic_button():
|
| 32 |
source = inspect.getsource(ui_app.build_demo)
|
| 33 |
|
| 34 |
assert "audio_input = gr.Audio(" in source
|
| 35 |
+
assert 'sources=["upload"]' in source
|
| 36 |
+
assert 'sources=["microphone", "upload"]' not in source
|
| 37 |
assert 'type="filepath"' in source
|
| 38 |
assert "visible=False" in source
|
| 39 |
assert "mic_html = gr.HTML(_mic_html(DEFAULT_LANGUAGE))" in source
|
|
|
|
| 44 |
def test_image_upload_is_composer_plus_drawer():
|
| 45 |
source = inspect.getsource(ui_app.build_demo)
|
| 46 |
|
| 47 |
+
assert "attachment_html = gr.HTML(_attachment_html(DEFAULT_LANGUAGE))" in source
|
| 48 |
+
assert 'elem_classes=["dc-image-popover"]' in source
|
| 49 |
+
assert 'image_input = gr.Image(' in source
|
| 50 |
+
assert 'sources=["upload", "clipboard"]' in source
|
| 51 |
+
assert "gr.Accordion(" not in source.split("image_input = gr.Image(")[0].split("with gr.Group(elem_classes=[\"dc-composer\"]):")[-1]
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def test_image_popover_uses_unified_upload_clipboard_ui():
|
| 55 |
+
assert "data-upload" in ui_app._attachment_html("en")
|
| 56 |
+
assert "data-paste" in ui_app._attachment_html("zh")
|
| 57 |
+
assert "上传图片" in ui_app._attachment_html("zh")
|
| 58 |
+
assert ".dc-image-popover [data-testid=\"source-select\"]" in CSS
|
| 59 |
+
assert "border-top: 0 !important;" in CSS
|
| 60 |
+
assert '[aria-label="Capture from camera"]' in CSS
|
| 61 |
+
assert "display: none !important;" in CSS
|
| 62 |
+
assert ".dc-image-upload-copy" in CSS
|
| 63 |
+
assert ".dc-image-label-copy" in inspect.getsource(ui_app)
|
| 64 |
+
assert "clipboardSelected ? copy.paste : copy.upload" in inspect.getsource(ui_app)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def test_composer_scripts_are_attached_to_blocks():
|
| 68 |
+
source = inspect.getsource(ui_app.build_demo)
|
| 69 |
+
|
| 70 |
+
assert "with gr.Blocks(css=CSS, js=VOICE_JS, title=APP_TITLE)" in source
|
| 71 |
+
assert "mic_html = gr.HTML(_mic_html(DEFAULT_LANGUAGE))" in source
|
| 72 |
+
assert "attachment_html = gr.HTML(_attachment_html(DEFAULT_LANGUAGE))" in source
|
| 73 |
|
| 74 |
|
| 75 |
def test_hero_stepper_tracks_app_status():
|
| 76 |
ask_html = ui_app._hero_html(status="ask")
|
| 77 |
tip_html = ui_app._hero_html(status="tip")
|
| 78 |
|
| 79 |
+
assert '<span class="dc-step is-complete"><strong>1</strong>' in ask_html
|
| 80 |
+
assert '<i class="dc-stepper-line is-complete" aria-hidden="true"></i>' in ask_html
|
| 81 |
+
assert '<span class="dc-step is-active" aria-current="step"><strong>2</strong>' in ask_html
|
| 82 |
+
assert '<span class="dc-step is-complete"><strong>3</strong>' in tip_html
|
| 83 |
+
assert '<span class="dc-step is-active" aria-current="step"><strong>4</strong>' in tip_html
|
| 84 |
+
assert tip_html.count("dc-stepper-line is-complete") == 3
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def test_hero_subtitle_explains_app_instead_of_legacy_name():
|
| 88 |
+
hero_html = ui_app._hero_html()
|
| 89 |
+
|
| 90 |
+
assert "grounded Today Tip" in hero_html
|
| 91 |
+
assert "Dream Customs" not in hero_html
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def test_side_panel_dropdown_hover_stays_readable():
|
| 95 |
+
assert '.dc-side-panel .dc-section-title strong' in CSS
|
| 96 |
+
assert '.gradio-container [role="option"]:hover' in CSS
|
| 97 |
+
assert 'background: var(--dqa-sage-soft) !important;' in CSS
|
| 98 |
+
assert 'color: var(--dqa-ink) !important;' in CSS
|
| 99 |
+
assert '.dc-side-panel .block:has(input[role="listbox"])' in CSS
|
| 100 |
+
assert 'resize: none !important;' in CSS
|
| 101 |
+
assert 'pointer-events: none !important;' in CSS
|
| 102 |
+
assert '> .container input[role="listbox"][aria-expanded="true"]) > .container .dropdown-arrow' in CSS
|
| 103 |
+
assert '.dc-side-panel .block:has(.wrap-inner.show_options) .dropdown-arrow' not in CSS
|
| 104 |
+
assert '.dc-side-panel .block:has(.options) .dropdown-arrow' not in CSS
|
| 105 |
+
assert 'transform: rotate(180deg) !important;' in CSS
|
| 106 |
+
assert '.dc-side-panel .block:has(input[role="listbox"]) .wrap-inner' in CSS
|
| 107 |
+
assert '.dc-side-panel .block:has(input[role="listbox"]) .secondary-wrap' in CSS
|
| 108 |
+
assert '.dc-side-panel .form:has(input[role="listbox"])' in CSS
|
| 109 |
+
assert '--input-border-width: 0px;' in CSS
|
| 110 |
+
assert '--block-border-width: 0px;' in CSS
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def test_advanced_panel_uses_compact_readable_controls():
|
| 114 |
+
assert '.dc-side-panel .dc-dev-help span' in CSS
|
| 115 |
+
assert 'color: #60717a !important;' in CSS
|
| 116 |
+
assert '.dc-side-panel .dc-dev .block:has(input[role="listbox"])' in CSS
|
| 117 |
+
assert 'padding: 12px 14px !important;' in CSS
|
| 118 |
+
assert '.dc-side-panel .dc-dev .block:has(input[role="listbox"]) input[role="listbox"]' in CSS
|
| 119 |
+
assert 'font-size: 0.94rem !important;' in CSS
|
| 120 |
+
assert '.dc-side-panel .dc-dev > button' in CSS
|
| 121 |
+
assert 'min-height: 46px !important;' in CSS
|
| 122 |
+
assert '.dc-side-panel .dc-dev-tuning' in CSS
|
| 123 |
+
assert 'background: rgba(255, 253, 248, 0.98) !important;' in CSS
|
| 124 |
+
assert 'overflow: hidden !important;' in CSS
|
| 125 |
+
assert '.dc-side-panel .dc-dev-tuning > .styler' in CSS
|
| 126 |
+
assert 'background: transparent !important;' in CSS
|
| 127 |
+
assert '.dc-flow-column .dc-debug-panel' in CSS
|
| 128 |
+
assert 'align-items: start !important;' in CSS
|
| 129 |
+
source = inspect.getsource(ui_app.build_demo)
|
| 130 |
+
assert 'with gr.Group(elem_classes=["dc-dev-tuning"])' in source
|
| 131 |
+
assert "text_temperature = gr.Number(" in source
|
| 132 |
+
assert "text_temperature = gr.Slider(" not in source
|
| 133 |
+
assert source.index("text_temperature = gr.Number(") < source.index("text_backend = gr.Dropdown(")
|
| 134 |
+
assert source.index("text_temperature = gr.Number(") < source.index('with gr.Accordion("Advanced endpoints"')
|
| 135 |
|
| 136 |
|
| 137 |
def test_processing_note_is_story_copy_not_backend_jargon():
|