Spaces:
Running
Running
Add box-prompt examples and editable prompts
Browse files- README.md +2 -2
- app.py +110 -5
- examples/box_parking_lot.png +0 -0
- examples/box_product_shelf.png +0 -0
- scripts/generate_examples.py +55 -0
- tests/test_app.py +22 -0
README.md
CHANGED
|
@@ -15,9 +15,9 @@ short_description: A Space to try the new Qwen 3.8 Max for free
|
|
| 15 |
|
| 16 |
Try Qwen 3.8 Max with text and image inputs. Thinking mode is enabled by default; its reasoning summary appears in a collapsible panel while the final answer streams token by token.
|
| 17 |
|
| 18 |
-
The Space also includes a box-prompting playground inspired by [Roboflow's Qwen 3.8 Max evaluation](https://blog.roboflow.com/qwen3-8-max/): mark positive and optional negative examples directly on an image, then ask Qwen to find similar objects. Detection results are parsed from normalized XYXY coordinates and rendered back onto the original image.
|
| 19 |
|
| 20 |
-
This is an unofficial community demo, generously backed by a limited pool of
|
| 21 |
|
| 22 |
## Configuration
|
| 23 |
|
|
|
|
| 15 |
|
| 16 |
Try Qwen 3.8 Max with text and image inputs. Thinking mode is enabled by default; its reasoning summary appears in a collapsible panel while the final answer streams token by token.
|
| 17 |
|
| 18 |
+
The Space also includes a box-prompting playground inspired by [Roboflow's Qwen 3.8 Max evaluation](https://blog.roboflow.com/qwen3-8-max/): mark positive and optional negative examples directly on an image, then ask Qwen to find similar objects. Three ready-made presets demonstrate shape, color, and size matching; each loads an editable instruction and example boxes. Detection results are parsed from normalized XYXY coordinates and rendered back onto the original image.
|
| 19 |
|
| 20 |
+
This is an unofficial community demo, generously backed by a limited pool of Qwen API tokens. Availability is best-effort and the demo may be paused when the pool is exhausted.
|
| 21 |
|
| 22 |
## Configuration
|
| 23 |
|
app.py
CHANGED
|
@@ -44,6 +44,35 @@ VISUAL_EXAMPLES = [
|
|
| 44 |
"prompt": "Solve the visual logic puzzle. State the rule you infer and identify the missing tile.",
|
| 45 |
},
|
| 46 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
_requests_by_session: dict[str, deque[float]] = defaultdict(deque)
|
| 49 |
_rate_limit_lock = Lock()
|
|
@@ -304,15 +333,43 @@ def _draw_prompt_boxes(
|
|
| 304 |
|
| 305 |
def load_box_image(
|
| 306 |
image_path: str | None,
|
| 307 |
-
) -> tuple[str | None, list[Any], None, str | None, str]:
|
| 308 |
if not image_path:
|
| 309 |
-
return None, [], None, None, "Upload an image to begin."
|
| 310 |
return (
|
| 311 |
image_path,
|
| 312 |
[],
|
| 313 |
None,
|
| 314 |
image_path,
|
| 315 |
"Choose Positive or Negative, then click two opposite corners of an object.",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 316 |
)
|
| 317 |
|
| 318 |
|
|
@@ -414,6 +471,7 @@ def _parse_detection_boxes(answer: str, width: int, height: int) -> list[tuple[t
|
|
| 414 |
def detect_similar(
|
| 415 |
original_path: str | None,
|
| 416 |
boxes: list[dict[str, Any]] | None,
|
|
|
|
| 417 |
enable_thinking: bool,
|
| 418 |
max_output_tokens: int,
|
| 419 |
request: gr.Request,
|
|
@@ -426,9 +484,14 @@ def detect_similar(
|
|
| 426 |
_check_rate_limit(getattr(request, "session_hash", None))
|
| 427 |
|
| 428 |
annotated_image = _draw_prompt_boxes(original_path, annotations)
|
|
|
|
|
|
|
|
|
|
| 429 |
prompt = f"""
|
| 430 |
The image contains visual box prompts drawn by the user. Green boxes marked with + are positive examples of the object to find. Red boxes marked with − are negative examples that must be ignored.
|
| 431 |
|
|
|
|
|
|
|
| 432 |
Find every other unboxed object in this same image that matches the positive examples while respecting the negative examples. Return only valid JSON in this exact shape:
|
| 433 |
{{"objects": [{{"label": "match", "box_2d": [x_min, y_min, x_max, y_max]}}]}}
|
| 434 |
|
|
@@ -508,6 +571,7 @@ body > gradio-app, gradio-app {
|
|
| 508 |
#chat-shell { max-width: 840px; margin: 0 auto; width: 100%; }
|
| 509 |
#box-shell { max-width: 960px; margin: 0 auto; width: 100%; }
|
| 510 |
#examples-gallery { margin-top: 0.35rem; }
|
|
|
|
| 511 |
#example-prompts { color: var(--body-text-color-subdued); font-size: 0.92rem; }
|
| 512 |
@media (max-width: 560px) {
|
| 513 |
.gradio-container { width: calc(100% - 16px) !important; }
|
|
@@ -529,7 +593,7 @@ with gr.Blocks(title="Free Qwen 3.8 Max") as demo:
|
|
| 529 |
<img src="{QWEN_LOGO_DATA_URL}" alt="Qwen logo">
|
| 530 |
<h1>Free Qwen 3.8 Max</h1>
|
| 531 |
</div>
|
| 532 |
-
<p><strong>100 million
|
| 533 |
""",
|
| 534 |
elem_id="hero",
|
| 535 |
)
|
|
@@ -611,6 +675,20 @@ with gr.Blocks(title="Free Qwen 3.8 Max") as demo:
|
|
| 611 |
Upload an image, choose **Positive** or **Negative**, and click two opposite corners to mark each example. Qwen will find other objects that look like the positive examples while avoiding the negatives.
|
| 612 |
"""
|
| 613 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
with gr.Row():
|
| 615 |
with gr.Column(scale=3):
|
| 616 |
box_image = gr.Image(
|
|
@@ -622,6 +700,11 @@ with gr.Blocks(title="Free Qwen 3.8 Max") as demo:
|
|
| 622 |
)
|
| 623 |
box_status = gr.Markdown("Upload an image to begin.")
|
| 624 |
with gr.Column(scale=1, min_width=220):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 625 |
box_kind = gr.Radio(
|
| 626 |
["Positive", "Negative"],
|
| 627 |
value="Positive",
|
|
@@ -666,7 +749,29 @@ with gr.Blocks(title="Free Qwen 3.8 Max") as demo:
|
|
| 666 |
box_image.upload(
|
| 667 |
load_box_image,
|
| 668 |
inputs=box_image,
|
| 669 |
-
outputs=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 670 |
queue=False,
|
| 671 |
api_name=False,
|
| 672 |
)
|
|
@@ -693,7 +798,7 @@ with gr.Blocks(title="Free Qwen 3.8 Max") as demo:
|
|
| 693 |
)
|
| 694 |
run_box_prompt.click(
|
| 695 |
detect_similar,
|
| 696 |
-
inputs=[box_original, box_annotations, thinking, max_tokens],
|
| 697 |
outputs=[box_result, box_details],
|
| 698 |
concurrency_limit=1,
|
| 699 |
concurrency_id="qwen-api",
|
|
|
|
| 44 |
"prompt": "Solve the visual logic puzzle. State the rule you infer and identify the missing tile.",
|
| 45 |
},
|
| 46 |
]
|
| 47 |
+
BOX_EXAMPLES = [
|
| 48 |
+
{
|
| 49 |
+
"title": "Blue circles, not squares",
|
| 50 |
+
"image": str(EXAMPLES_DIR / "crowded_shapes.png"),
|
| 51 |
+
"prompt": "Find every other blue circle. Ignore blue squares and shapes of other colors.",
|
| 52 |
+
"boxes": [
|
| 53 |
+
{"box": [761, 81, 849, 169], "kind": "positive"},
|
| 54 |
+
{"box": [205, 96, 281, 172], "kind": "negative"},
|
| 55 |
+
],
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"title": "Matching mint bottles",
|
| 59 |
+
"image": str(EXAMPLES_DIR / "box_product_shelf.png"),
|
| 60 |
+
"prompt": "Find every other teal MINT bottle. Ignore the purple MINT bottles and all cans.",
|
| 61 |
+
"boxes": [
|
| 62 |
+
{"box": [80, 118, 142, 292], "kind": "positive"},
|
| 63 |
+
{"box": [780, 118, 842, 292], "kind": "negative"},
|
| 64 |
+
],
|
| 65 |
+
},
|
| 66 |
+
{
|
| 67 |
+
"title": "Standard green cars",
|
| 68 |
+
"image": str(EXAMPLES_DIR / "box_parking_lot.png"),
|
| 69 |
+
"prompt": "Find the other standard-size green cars. Do not include the oversized green vehicle.",
|
| 70 |
+
"boxes": [
|
| 71 |
+
{"box": [88, 112, 172, 262], "kind": "positive"},
|
| 72 |
+
{"box": [793, 340, 905, 518], "kind": "negative"},
|
| 73 |
+
],
|
| 74 |
+
},
|
| 75 |
+
]
|
| 76 |
|
| 77 |
_requests_by_session: dict[str, deque[float]] = defaultdict(deque)
|
| 78 |
_rate_limit_lock = Lock()
|
|
|
|
| 333 |
|
| 334 |
def load_box_image(
|
| 335 |
image_path: str | None,
|
| 336 |
+
) -> tuple[str | None, list[Any], None, str | None, str, str]:
|
| 337 |
if not image_path:
|
| 338 |
+
return None, [], None, None, "Upload an image to begin.", ""
|
| 339 |
return (
|
| 340 |
image_path,
|
| 341 |
[],
|
| 342 |
None,
|
| 343 |
image_path,
|
| 344 |
"Choose Positive or Negative, then click two opposite corners of an object.",
|
| 345 |
+
"",
|
| 346 |
+
)
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
def load_box_example(
|
| 350 |
+
event: gr.SelectData,
|
| 351 |
+
) -> tuple[str, list[dict[str, Any]], None, Image.Image, str, str, None, str]:
|
| 352 |
+
example = BOX_EXAMPLES[int(event.index)]
|
| 353 |
+
original_path = example["image"]
|
| 354 |
+
annotations = [
|
| 355 |
+
{"box": list(annotation["box"]), "kind": annotation["kind"]}
|
| 356 |
+
for annotation in example["boxes"]
|
| 357 |
+
]
|
| 358 |
+
positive_count = sum(box["kind"] == "positive" for box in annotations)
|
| 359 |
+
negative_count = len(annotations) - positive_count
|
| 360 |
+
status = (
|
| 361 |
+
f"Loaded **{example['title']}** with {positive_count} positive and "
|
| 362 |
+
f"{negative_count} negative example box(es). Edit the prompt or run it as-is."
|
| 363 |
+
)
|
| 364 |
+
return (
|
| 365 |
+
original_path,
|
| 366 |
+
annotations,
|
| 367 |
+
None,
|
| 368 |
+
_draw_prompt_boxes(original_path, annotations),
|
| 369 |
+
status,
|
| 370 |
+
example["prompt"],
|
| 371 |
+
None,
|
| 372 |
+
"",
|
| 373 |
)
|
| 374 |
|
| 375 |
|
|
|
|
| 471 |
def detect_similar(
|
| 472 |
original_path: str | None,
|
| 473 |
boxes: list[dict[str, Any]] | None,
|
| 474 |
+
target_prompt: str,
|
| 475 |
enable_thinking: bool,
|
| 476 |
max_output_tokens: int,
|
| 477 |
request: gr.Request,
|
|
|
|
| 484 |
_check_rate_limit(getattr(request, "session_hash", None))
|
| 485 |
|
| 486 |
annotated_image = _draw_prompt_boxes(original_path, annotations)
|
| 487 |
+
requested_target = target_prompt.strip() or (
|
| 488 |
+
"Find every other unboxed object that visually matches the positive examples."
|
| 489 |
+
)
|
| 490 |
prompt = f"""
|
| 491 |
The image contains visual box prompts drawn by the user. Green boxes marked with + are positive examples of the object to find. Red boxes marked with − are negative examples that must be ignored.
|
| 492 |
|
| 493 |
+
The user's instruction is: {requested_target}
|
| 494 |
+
|
| 495 |
Find every other unboxed object in this same image that matches the positive examples while respecting the negative examples. Return only valid JSON in this exact shape:
|
| 496 |
{{"objects": [{{"label": "match", "box_2d": [x_min, y_min, x_max, y_max]}}]}}
|
| 497 |
|
|
|
|
| 571 |
#chat-shell { max-width: 840px; margin: 0 auto; width: 100%; }
|
| 572 |
#box-shell { max-width: 960px; margin: 0 auto; width: 100%; }
|
| 573 |
#examples-gallery { margin-top: 0.35rem; }
|
| 574 |
+
#box-examples-gallery { margin: 0.35rem 0 1rem; }
|
| 575 |
#example-prompts { color: var(--body-text-color-subdued); font-size: 0.92rem; }
|
| 576 |
@media (max-width: 560px) {
|
| 577 |
.gradio-container { width: calc(100% - 16px) !important; }
|
|
|
|
| 593 |
<img src="{QWEN_LOGO_DATA_URL}" alt="Qwen logo">
|
| 594 |
<h1>Free Qwen 3.8 Max</h1>
|
| 595 |
</div>
|
| 596 |
+
<p><strong>100 million tokens, shared with the community.</strong> Ask anything or attach up to three images to test Qwen's visual understanding.</p>
|
| 597 |
""",
|
| 598 |
elem_id="hero",
|
| 599 |
)
|
|
|
|
| 675 |
Upload an image, choose **Positive** or **Negative**, and click two opposite corners to mark each example. Qwen will find other objects that look like the positive examples while avoiding the negatives.
|
| 676 |
"""
|
| 677 |
)
|
| 678 |
+
box_examples_gallery = gr.Gallery(
|
| 679 |
+
value=[
|
| 680 |
+
(example["image"], example["title"])
|
| 681 |
+
for example in BOX_EXAMPLES
|
| 682 |
+
],
|
| 683 |
+
label="Box-prompt examples — click one to load its image, boxes, and prompt",
|
| 684 |
+
columns=3,
|
| 685 |
+
rows=1,
|
| 686 |
+
height=245,
|
| 687 |
+
object_fit="contain",
|
| 688 |
+
allow_preview=False,
|
| 689 |
+
buttons=[],
|
| 690 |
+
elem_id="box-examples-gallery",
|
| 691 |
+
)
|
| 692 |
with gr.Row():
|
| 693 |
with gr.Column(scale=3):
|
| 694 |
box_image = gr.Image(
|
|
|
|
| 700 |
)
|
| 701 |
box_status = gr.Markdown("Upload an image to begin.")
|
| 702 |
with gr.Column(scale=1, min_width=220):
|
| 703 |
+
box_target_prompt = gr.Textbox(
|
| 704 |
+
label="What should Qwen find?",
|
| 705 |
+
placeholder="For example: Find the other blue circles, but ignore blue squares.",
|
| 706 |
+
lines=4,
|
| 707 |
+
)
|
| 708 |
box_kind = gr.Radio(
|
| 709 |
["Positive", "Negative"],
|
| 710 |
value="Positive",
|
|
|
|
| 749 |
box_image.upload(
|
| 750 |
load_box_image,
|
| 751 |
inputs=box_image,
|
| 752 |
+
outputs=[
|
| 753 |
+
box_original,
|
| 754 |
+
box_annotations,
|
| 755 |
+
pending_corner,
|
| 756 |
+
box_image,
|
| 757 |
+
box_status,
|
| 758 |
+
box_target_prompt,
|
| 759 |
+
],
|
| 760 |
+
queue=False,
|
| 761 |
+
api_name=False,
|
| 762 |
+
)
|
| 763 |
+
box_examples_gallery.select(
|
| 764 |
+
load_box_example,
|
| 765 |
+
outputs=[
|
| 766 |
+
box_original,
|
| 767 |
+
box_annotations,
|
| 768 |
+
pending_corner,
|
| 769 |
+
box_image,
|
| 770 |
+
box_status,
|
| 771 |
+
box_target_prompt,
|
| 772 |
+
box_result,
|
| 773 |
+
box_details,
|
| 774 |
+
],
|
| 775 |
queue=False,
|
| 776 |
api_name=False,
|
| 777 |
)
|
|
|
|
| 798 |
)
|
| 799 |
run_box_prompt.click(
|
| 800 |
detect_similar,
|
| 801 |
+
inputs=[box_original, box_annotations, box_target_prompt, thinking, max_tokens],
|
| 802 |
outputs=[box_result, box_details],
|
| 803 |
concurrency_limit=1,
|
| 804 |
concurrency_id="qwen-api",
|
examples/box_parking_lot.png
ADDED
|
examples/box_product_shelf.png
ADDED
|
scripts/generate_examples.py
CHANGED
|
@@ -82,7 +82,62 @@ def logic_grid() -> None:
|
|
| 82 |
image.save(OUTPUT / "logic_grid.png")
|
| 83 |
|
| 84 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
if __name__ == "__main__":
|
| 86 |
crowded_shapes()
|
| 87 |
mini_receipt()
|
| 88 |
logic_grid()
|
|
|
|
|
|
|
|
|
| 82 |
image.save(OUTPUT / "logic_grid.png")
|
| 83 |
|
| 84 |
|
| 85 |
+
def product_shelf() -> None:
|
| 86 |
+
image = Image.new("RGB", (960, 600), "#f4efe6")
|
| 87 |
+
draw = ImageDraw.Draw(image)
|
| 88 |
+
draw.text((34, 24), "Pantry shelf", font=FONT, fill="#1f2937")
|
| 89 |
+
draw.rectangle((28, 92, 932, 535), fill="#e7dfd2", outline="#c2b6a3", width=3)
|
| 90 |
+
for y in (300, 520):
|
| 91 |
+
draw.rectangle((42, y, 918, y + 15), fill="#8b6f47")
|
| 92 |
+
|
| 93 |
+
def bottle(x: int, y: int, color: str = "#0f9f8f") -> None:
|
| 94 |
+
draw.rounded_rectangle((x + 16, y, x + 46, y + 34), radius=5, fill="#d8f3ef")
|
| 95 |
+
draw.rounded_rectangle((x, y + 26, x + 62, y + 174), radius=18, fill=color)
|
| 96 |
+
draw.rounded_rectangle((x + 8, y + 82, x + 54, y + 126), radius=6, fill="#fffaf0")
|
| 97 |
+
draw.text((x + 17, y + 91), "MINT", font=ImageFont.load_default(size=12), fill="#0f766e")
|
| 98 |
+
|
| 99 |
+
def can(x: int, y: int, color: str) -> None:
|
| 100 |
+
draw.rounded_rectangle((x, y, x + 76, y + 142), radius=12, fill=color, outline="#6b7280", width=2)
|
| 101 |
+
draw.rectangle((x + 8, y + 48, x + 68, y + 92), fill="#fff7ed")
|
| 102 |
+
|
| 103 |
+
bottle(80, 118)
|
| 104 |
+
can(205, 148, "#ef4444")
|
| 105 |
+
bottle(340, 118)
|
| 106 |
+
can(474, 148, "#38bdf8")
|
| 107 |
+
bottle(620, 118)
|
| 108 |
+
bottle(780, 118, "#7c3aed")
|
| 109 |
+
can(110, 365, "#f59e0b")
|
| 110 |
+
bottle(275, 338)
|
| 111 |
+
can(430, 365, "#ef4444")
|
| 112 |
+
bottle(590, 338, "#7c3aed")
|
| 113 |
+
bottle(765, 338)
|
| 114 |
+
image.save(OUTPUT / "box_product_shelf.png")
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def parking_lot() -> None:
|
| 118 |
+
image = Image.new("RGB", (960, 600), "#374151")
|
| 119 |
+
draw = ImageDraw.Draw(image)
|
| 120 |
+
draw.text((34, 22), "Parking lot", font=FONT, fill="white")
|
| 121 |
+
for x in range(65, 930, 145):
|
| 122 |
+
draw.line((x, 90, x, 550), fill="#d1d5db", width=4)
|
| 123 |
+
|
| 124 |
+
def car(x: int, y: int, color: str, width: int = 84, height: int = 150) -> None:
|
| 125 |
+
draw.rounded_rectangle((x, y, x + width, y + height), radius=20, fill=color, outline="#111827", width=3)
|
| 126 |
+
draw.rounded_rectangle((x + 12, y + 32, x + width - 12, y + 82), radius=10, fill="#bfdbfe")
|
| 127 |
+
draw.rectangle((x + 10, y + 112, x + width - 10, y + 126), fill="#e5e7eb")
|
| 128 |
+
|
| 129 |
+
car(88, 112, "#22c55e")
|
| 130 |
+
car(238, 345, "#ef4444")
|
| 131 |
+
car(382, 125, "#22c55e")
|
| 132 |
+
car(522, 330, "#3b82f6")
|
| 133 |
+
car(674, 118, "#22c55e")
|
| 134 |
+
car(793, 340, "#22c55e", width=112, height=178)
|
| 135 |
+
image.save(OUTPUT / "box_parking_lot.png")
|
| 136 |
+
|
| 137 |
+
|
| 138 |
if __name__ == "__main__":
|
| 139 |
crowded_shapes()
|
| 140 |
mini_receipt()
|
| 141 |
logic_grid()
|
| 142 |
+
product_shelf()
|
| 143 |
+
parking_lot()
|
tests/test_app.py
CHANGED
|
@@ -99,3 +99,25 @@ def test_two_clicks_create_positive_box(tmp_path) -> None:
|
|
| 99 |
|
| 100 |
assert boxes == [{"box": [20, 15, 120, 80], "kind": "positive"}]
|
| 101 |
assert pending is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
|
| 100 |
assert boxes == [{"box": [20, 15, 120, 80], "kind": "positive"}]
|
| 101 |
assert pending is None
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def test_box_example_loads_image_boxes_and_prompt() -> None:
|
| 105 |
+
(
|
| 106 |
+
original_path,
|
| 107 |
+
boxes,
|
| 108 |
+
pending,
|
| 109 |
+
annotated_image,
|
| 110 |
+
status,
|
| 111 |
+
prompt,
|
| 112 |
+
result,
|
| 113 |
+
details,
|
| 114 |
+
) = app.load_box_example(SimpleNamespace(index=1))
|
| 115 |
+
|
| 116 |
+
assert original_path.endswith("box_product_shelf.png")
|
| 117 |
+
assert [box["kind"] for box in boxes] == ["positive", "negative"]
|
| 118 |
+
assert pending is None
|
| 119 |
+
assert annotated_image.size == (960, 600)
|
| 120 |
+
assert "Matching mint bottles" in status
|
| 121 |
+
assert "teal MINT bottle" in prompt
|
| 122 |
+
assert result is None
|
| 123 |
+
assert details == ""
|