Angelguava commited on
Commit
ae832c8
·
verified ·
1 Parent(s): db5517f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +262 -175
app.py CHANGED
@@ -401,222 +401,306 @@ STRATEGY_INFO = [
401
  ]
402
 
403
  CSS = """
404
- /* ===== 整體底色與文字對比 ===== */
405
- body,
406
- .gradio-container {
407
- background: #f3efe7 !important;
408
- color: #1f1b16 !important;
409
- font-family: "Noto Sans TC", "Microsoft JhengHei", "PingFang TC", sans-serif !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
410
  }
411
 
412
- /* Gradio 外層容器不要太貼邊 */
413
  .gradio-container {
414
- max-width: 1880px !important;
415
- margin: 0 auto !important;
416
  }
417
 
418
- /* ===== 頂部標題卡 ===== */
419
- #hdr {
420
- background: #fbf6ec !important;
421
- border: 1px solid #cfc3b2 !important;
422
- border-top: 4px solid #2d6a4f !important;
423
- border-radius: 14px !important;
424
- padding: 28px 36px !important;
425
- margin-bottom: 20px !important;
426
- box-shadow: 0 2px 10px rgba(31, 27, 22, 0.06) !important;
427
  }
428
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
429
  .hdr-eyebrow {
430
- font-size: 11px !important;
431
- letter-spacing: 2.5px !important;
432
- color: #2d6a4f !important;
433
- text-transform: uppercase !important;
434
- margin-bottom: 6px !important;
435
- font-weight: 800 !important;
436
- opacity: 1 !important;
437
  }
438
-
439
  .hdr-title {
440
- font-size: 26px !important;
441
- font-weight: 800 !important;
442
- color: #1a1714 !important;
443
- margin: 0 0 6px !important;
444
- opacity: 1 !important;
 
 
445
  }
446
-
447
  .hdr-sub {
448
- font-size: 14px !important;
449
- color: #4b4138 !important;
450
- opacity: 1 !important;
 
 
 
451
  }
452
-
 
 
 
 
 
 
 
 
453
  .pill {
454
- display: inline-block !important;
455
- margin: 10px 5px 0 0 !important;
456
- padding: 4px 11px !important;
457
- border-radius: 16px !important;
458
- font-size: 11px !important;
459
- background: #dff0ea !important;
460
- color: #24553f !important;
461
- border: 1px solid rgba(45, 106, 79, .28) !important;
462
- font-weight: 700 !important;
463
  }
464
-
465
- .pill-amber {
466
- background: #f7e8c4 !important;
467
- color: #8a5a11 !important;
468
- border-color: rgba(184, 122, 26, .35) !important;
469
  }
470
 
471
- /* ===== 區塊卡片:避免太白、邊界更清楚 ===== */
472
- .card-box,
473
- .gradio-container .block,
474
- .gradio-container .form,
475
- .gradio-container .panel,
476
- .gradio-container .gr-group {
477
- background: #fbf7ee !important;
478
- border: 1px solid #cfc3b2 !important;
479
- border-radius: 12px !important;
480
- color: #1f1b16 !important;
481
  }
482
 
483
- .card-box {
484
- padding: 16px !important;
485
- box-shadow: 0 2px 8px rgba(31, 27, 22, 0.05) !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
486
  }
487
 
488
- /* API Key 區塊 */
489
  #apikey-box {
490
- background: #fff3d8 !important;
491
- border: 1.5px solid #d49a2a !important;
492
- border-radius: 10px !important;
493
- padding: 12px 14px !important;
494
- margin-bottom: 8px !important;
 
495
  }
496
 
497
- /* ===== 標題與 Label:不要淡到看不見 ===== */
498
- .sec-label {
499
- font-size: 12px !important;
500
- letter-spacing: 1.4px !important;
501
- text-transform: uppercase !important;
502
- color: #3d342c !important;
503
- font-weight: 900 !important;
504
- margin: 16px 0 8px !important;
505
- opacity: 1 !important;
 
 
506
  }
507
-
508
- label,
509
- .gradio-container label,
510
- .gradio-container .label-wrap,
511
- .gradio-container .svelte-1gfkn6j,
512
- .gradio-container span {
513
- color: #2b241e !important;
514
- opacity: 1 !important;
515
  }
 
 
 
516
 
517
- /* ===== 文字輸入框 / 輸出框 ===== */
518
- textarea,
519
- input[type="text"],
520
- input[type="password"],
521
- .gradio-container textarea,
522
- .gradio-container input {
523
- background: #3f3a34 !important;
524
- color: #fffaf0 !important;
525
- border: 1px solid #6b6258 !important;
526
- border-radius: 7px !important;
527
- opacity: 1 !important;
528
- }
529
 
530
- textarea::placeholder,
531
- input::placeholder {
532
- color: #c7bfb3 !important;
533
- opacity: 1 !important;
534
  }
535
 
536
- /* 輸出文字不要灰掉 */
537
- .gradio-container [data-testid="textbox"],
538
- .gradio-container .prose,
539
- .gradio-container p,
540
- .gradio-container div {
541
- opacity: 1 !important;
542
  }
543
 
544
- /* ===== 策略卡片 ===== */
545
- .strat-grid {
546
- display: grid !important;
547
- grid-template-columns: repeat(4, 1fr) !important;
548
- gap: 10px !important;
549
- margin: 10px 0 16px !important;
550
  }
551
 
552
- .strat-card {
553
- background: #5a554e !important;
554
- border: 1.5px solid #756d63 !important;
555
- border-radius: 10px !important;
556
- padding: 10px 12px !important;
557
- cursor: pointer !important;
558
- transition: border-color .15s, box-shadow .15s, background .15s !important;
559
- text-align: left !important;
560
- width: 100% !important;
561
- color: #fffaf0 !important;
562
  }
563
 
564
- .strat-card:hover {
565
- border-color: #2d6a4f !important;
566
- background: #4d6458 !important;
567
- box-shadow: 0 2px 8px rgba(45, 106, 79, .18) !important;
 
 
 
 
 
 
568
  }
569
 
570
- .strat-card.active {
571
- border-color: #2d6a4f !important;
572
- background: #2d6a4f !important;
573
- box-shadow: 0 2px 10px rgba(45, 106, 79, .25) !important;
 
574
  }
575
 
576
- .strat-icon { font-size: 20px !important; margin-bottom: 4px !important; }
577
- .strat-name { font-size: 13px !important; font-weight: 900 !important; color: #fffaf0 !important; margin: 0 0 2px !important; }
578
- .strat-desc { font-size: 11px !important; color: #eee4d6 !important; line-height: 1.4 !important; }
579
-
580
- /* ===== 按鈕 ===== */
581
- button,
582
- .gradio-container button {
583
- font-weight: 800 !important;
584
  opacity: 1 !important;
585
  }
586
 
587
- #ask-btn {
588
- background: #2d6a4f !important;
589
- color: #ffffff !important;
590
- border: 0 !important;
591
- border-radius: 8px !important;
592
  }
593
 
594
- #apply-key-btn {
595
- background: #b87a1a !important;
596
- color: #ffffff !important;
597
- border: 0 !important;
598
- border-radius: 8px !important;
 
 
 
 
 
599
  }
 
600
 
601
- /* Slider 與 Examples 區塊 */
602
- .gradio-container .wrap,
603
- .gradio-container .examples,
604
- .gradio-container table {
605
- background: #fbf7ee !important;
606
- color: #1f1b16 !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
607
  }
608
  """
609
 
610
  HEADER_HTML = """
611
  <div id="hdr">
612
- <div class="hdr-eyebrow">Intelligent Document Analysis · v2</div>
613
- <div class="hdr-title">多策略 RAG 文件問答系統</div>
614
- <div class="hdr-sub">支援 PDF / DOCX 上傳,採用 ChromaDB 持久化向量資料庫與 8 種 RAG 檢索策略</div>
615
- <div>
 
 
616
  <span class="pill">▸ Groq API</span>
617
  <span class="pill">▸ llama-3.1-8b-instant</span>
618
- <span class="pill pill-amber">▸ ChromaDB</span>
619
- <span class="pill pill-amber">▸ PDF / DOCX</span>
620
  <span class="pill">▸ SentenceTransformers</span>
621
  </div>
622
  </div>
@@ -700,8 +784,10 @@ def create_interface():
700
  # ── 左欄 ──────────────────���───────────────
701
  with gr.Column(scale=1, min_width=320, elem_classes="card-box"):
702
 
 
 
703
  # ★ Step 00:API Key 輸入(新增)
704
- gr.HTML("<div class='sec-label'>Step 00 · Groq API Key</div>")
705
  with gr.Group(elem_id="apikey-box"):
706
  api_key_input = gr.Textbox(
707
  label="Groq API Key",
@@ -718,18 +804,18 @@ def create_interface():
718
  value="✓ API Key 已從環境變數載入" if env_key else "⚠ 尚未設定 API Key",
719
  interactive=False,
720
  lines=1,
721
- label="API Key 狀態",
722
  show_label=False,
723
  )
724
 
725
  # Step 01:上傳文件
726
- gr.HTML("<div class='sec-label'>Step 01 · 上傳文件</div>")
727
- file_input = gr.File(label="PDF / DOCX", file_types=[".pdf", ".docx"])
728
- load_btn = gr.Button("↑ 載入文件")
729
- status = gr.Textbox(label="狀態", interactive=False, lines=3)
730
 
731
  # Step 02:RAG 策略
732
- gr.HTML("<div class='sec-label'>Step 02 · 選擇 RAG 策略</div>")
733
  gr.HTML(build_strategy_menu("semantic"))
734
  strategy_input = gr.Textbox(
735
  value="semantic",
@@ -738,26 +824,27 @@ def create_interface():
738
  visible=False,
739
  )
740
  strategy_status = gr.Textbox(
741
- value="✓ 已選擇策略:語意搜尋",
742
  interactive=False,
743
  lines=1,
744
- label="目前策略",
745
  )
746
  gr.HTML(STRATEGY_MENU_JS)
747
 
748
  # Step 03:參數
749
- gr.HTML("<div class='sec-label'>Step 03 · 搜尋參數</div>")
750
- topk = gr.Slider(minimum=1, maximum=10, value=3, step=1, label="Top-K 片段數量")
751
 
752
  # ── 右欄:問答 ────────────────────────────
753
  with gr.Column(scale=2, elem_classes="card-box"):
754
- gr.HTML("<div class='sec-label'>Step 04 · 輸入問題</div>")
 
755
  qin = gr.Textbox(
756
  label="問題",
757
  placeholder="例如:這份文件的核心論點是什麼?",
758
  lines=4,
759
  )
760
- ask_btn = gr.Button("提問", variant="primary", size="lg", elem_id="ask-btn")
761
 
762
  gr.HTML("<div class='sec-label'>AI 回答</div>")
763
  ans = gr.Textbox(label="AI 回答內容", lines=12, interactive=False)
 
401
  ]
402
 
403
  CSS = """
404
+ :root {
405
+ --paper: #f3ecdf;
406
+ --paper-2: #ebe2d1;
407
+ --paper-3: #dcc9a8;
408
+ --ink: #1c2433;
409
+ --ink-soft: #41506a;
410
+ --gold: #b88a42;
411
+ --gold-2: #d1ab63;
412
+ --line: rgba(94, 74, 40, .28);
413
+ --panel: rgba(255, 251, 242, 0.86);
414
+ --panel-strong: rgba(252, 247, 238, 0.96);
415
+ --shadow: 0 16px 40px rgba(29, 28, 26, .12);
416
+ }
417
+
418
+ body, .gradio-container {
419
+ background:
420
+ radial-gradient(circle at top left, rgba(184,138,66,.10), transparent 32%),
421
+ radial-gradient(circle at bottom right, rgba(28,36,51,.08), transparent 30%),
422
+ linear-gradient(180deg, #f6f1e7 0%, #efe7d7 100%) !important;
423
+ color: var(--ink) !important;
424
+ font-family: 'Noto Serif TC', 'Microsoft JhengHei', serif !important;
425
  }
426
 
 
427
  .gradio-container {
428
+ max-width: 1320px !important;
 
429
  }
430
 
431
+ .contain, .app {
432
+ background: transparent !important;
 
 
 
 
 
 
 
433
  }
434
 
435
+ #hdr {
436
+ position: relative;
437
+ overflow: hidden;
438
+ background:
439
+ linear-gradient(135deg, rgba(255,249,239,.97), rgba(246,238,224,.94)),
440
+ linear-gradient(90deg, rgba(184,138,66,.08), rgba(28,36,51,.04));
441
+ border: 1px solid rgba(147, 117, 69, .35);
442
+ border-radius: 26px;
443
+ padding: 30px 34px 28px;
444
+ margin-bottom: 18px;
445
+ box-shadow: var(--shadow);
446
+ }
447
+ #hdr::before {
448
+ content: '';
449
+ position: absolute;
450
+ inset: 0;
451
+ pointer-events: none;
452
+ background:
453
+ radial-gradient(circle at 10% 15%, rgba(184,138,66,.13), transparent 16%),
454
+ radial-gradient(circle at 92% 12%, rgba(28,36,51,.12), transparent 20%),
455
+ linear-gradient(90deg, rgba(184,138,66,.22), transparent 20%, transparent 80%, rgba(184,138,66,.22));
456
+ opacity: .65;
457
+ }
458
+ #hdr::after {
459
+ content: '封\A魔';
460
+ white-space: pre;
461
+ position: absolute;
462
+ top: 22px;
463
+ right: 22px;
464
+ color: rgba(184,138,66,.20);
465
+ font-size: 36px;
466
+ line-height: 1.05;
467
+ font-weight: 700;
468
+ letter-spacing: .18em;
469
+ }
470
  .hdr-eyebrow {
471
+ position: relative;
472
+ z-index: 1;
473
+ font-size: 11px;
474
+ letter-spacing: .32em;
475
+ color: #8f6d32;
476
+ text-transform: uppercase;
477
+ margin-bottom: 10px;
478
  }
 
479
  .hdr-title {
480
+ position: relative;
481
+ z-index: 1;
482
+ font-size: 34px;
483
+ line-height: 1.2;
484
+ font-weight: 800;
485
+ color: #1c2433;
486
+ margin: 0 0 10px;
487
  }
 
488
  .hdr-sub {
489
+ position: relative;
490
+ z-index: 1;
491
+ font-size: 15px;
492
+ line-height: 1.85;
493
+ color: #5a5347;
494
+ max-width: 920px;
495
  }
496
+ .hdr-quote {
497
+ position: relative;
498
+ z-index: 1;
499
+ margin-top: 14px;
500
+ color: #6b604d;
501
+ font-size: 13px;
502
+ letter-spacing: .08em;
503
+ }
504
+ .hdr-pills { position: relative; z-index: 1; margin-top: 16px; }
505
  .pill {
506
+ display: inline-block;
507
+ margin: 8px 8px 0 0;
508
+ padding: 5px 12px;
509
+ border-radius: 999px;
510
+ font-size: 11px;
511
+ color: #7f5a1e;
512
+ background: rgba(245, 233, 205, .95);
513
+ border: 1px solid rgba(184,138,66,.32);
 
514
  }
515
+ .pill-dark {
516
+ color: #e9e7df;
517
+ background: rgba(28,36,51,.92);
518
+ border-color: rgba(28,36,51,.92);
 
519
  }
520
 
521
+ .card-box {
522
+ background: linear-gradient(180deg, rgba(255, 250, 241, .92), rgba(250, 244, 233, .90)) !important;
523
+ border: 1px solid rgba(147, 117, 69, .26) !important;
524
+ border-radius: 24px !important;
525
+ padding: 20px !important;
526
+ box-shadow: var(--shadow);
 
 
 
 
527
  }
528
 
529
+ .sec-label {
530
+ display: flex;
531
+ align-items: center;
532
+ gap: 10px;
533
+ font-size: 15px;
534
+ letter-spacing: .22em;
535
+ text-transform: uppercase;
536
+ color: #26324a;
537
+ font-weight: 800;
538
+ margin: 10px 0 14px;
539
+ }
540
+ .sec-label::after {
541
+ content: '';
542
+ flex: 1;
543
+ height: 1px;
544
+ background: linear-gradient(90deg, rgba(184,138,66,.6), rgba(184,138,66,0));
545
+ }
546
+ .sub-note {
547
+ margin: -4px 0 14px;
548
+ color: #7a6a57;
549
+ font-size: 12px;
550
+ line-height: 1.7;
551
  }
552
 
 
553
  #apikey-box {
554
+ background: linear-gradient(180deg, rgba(255,244,219,.80), rgba(255,249,238,.88));
555
+ border: 1.5px solid rgba(190, 135, 33, .85);
556
+ border-radius: 18px;
557
+ padding: 16px 16px 10px;
558
+ box-shadow: inset 0 0 0 1px rgba(255,255,255,.55);
559
+ margin-bottom: 10px;
560
  }
561
 
562
+ .strat-grid { display:grid; grid-template-columns:repeat(4,1fr); gap:12px; margin:12px 0 16px; }
563
+ .strat-card {
564
+ background: linear-gradient(180deg, rgba(255,249,239,.98), rgba(246,238,223,.94));
565
+ border:1px solid rgba(148,120,73,.28);
566
+ border-radius:16px;
567
+ padding:12px 12px 11px;
568
+ cursor:pointer;
569
+ transition:transform .18s ease, border-color .18s ease, box-shadow .18s ease;
570
+ text-align:left;
571
+ width:100%;
572
+ box-shadow: 0 6px 18px rgba(0,0,0,.04);
573
  }
574
+ .strat-card:hover { border-color:#b88a42; box-shadow:0 10px 24px rgba(184,138,66,.12); transform: translateY(-2px); }
575
+ .strat-card.active {
576
+ border-color:#b88a42;
577
+ background: linear-gradient(180deg, rgba(255,246,224,.98), rgba(249,238,212,.96));
578
+ box-shadow:0 12px 26px rgba(184,138,66,.16);
 
 
 
579
  }
580
+ .strat-icon { font-size:20px; margin-bottom:6px; }
581
+ .strat-name { font-size:14px; font-weight:800; color:#1c2433; margin:0 0 3px; }
582
+ .strat-desc { font-size:11.5px; color:#6d6253; line-height:1.55; }
583
 
584
+ #ask-btn { background: linear-gradient(180deg, #22314b, #172131) !important; color:#fff !important; border:1px solid #172131 !important; border-radius:12px !important; }
585
+ #ask-btn:hover { filter: brightness(1.08); }
586
+ #apply-key-btn { background: linear-gradient(180deg, #c18a22, #a97012) !important; color:#fff !important; border:1px solid #8e5d11 !important; border-radius:12px !important; }
587
+ #apply-key-btn:hover { filter: brightness(1.06); }
 
 
 
 
 
 
 
 
588
 
589
+ button.secondary, .secondary-button {
590
+ border-radius: 12px !important;
 
 
591
  }
592
 
593
+ .gr-textbox, .gr-file, .gr-slider, .gr-accordion, .gr-examples {
594
+ --block-background-fill: transparent !important;
 
 
 
 
595
  }
596
 
597
+ textarea, input, .gradio-container textarea, .gradio-container input {
598
+ font-family: 'Noto Serif TC', 'Microsoft JhengHei', serif !important;
 
 
 
 
599
  }
600
 
601
+ .gr-textbox label, .gr-file label, .gr-slider label {
602
+ color: #3a2e1b !important;
603
+ font-size: 12px !important;
604
+ letter-spacing: .08em !important;
 
 
 
 
 
 
605
  }
606
 
607
+ .gr-textbox textarea,
608
+ .gr-textbox input,
609
+ textarea.scroll-hide,
610
+ input[type='text'],
611
+ input[type='password'] {
612
+ background: linear-gradient(180deg, rgba(29,38,55,.96), rgba(36,49,70,.94)) !important;
613
+ color: #f6efe3 !important;
614
+ border: 1px solid rgba(164, 137, 94, .55) !important;
615
+ border-radius: 16px !important;
616
+ box-shadow: inset 0 0 0 1px rgba(255,255,255,.03) !important;
617
  }
618
 
619
+ .gr-textbox textarea::placeholder,
620
+ .gr-textbox input::placeholder,
621
+ textarea::placeholder,
622
+ input::placeholder {
623
+ color: #b9b3aa !important;
624
  }
625
 
626
+ .gr-textbox textarea[disabled],
627
+ .gr-textbox input[disabled],
628
+ textarea[disabled],
629
+ input[disabled] {
630
+ background: linear-gradient(180deg, rgba(58,53,47,.96), rgba(70,64,56,.94)) !important;
631
+ color: #f3ebdd !important;
 
 
632
  opacity: 1 !important;
633
  }
634
 
635
+ .gr-file {
636
+ background: rgba(255, 251, 243, .75) !important;
637
+ border: 1px dashed rgba(147,117,69,.45) !important;
638
+ border-radius: 16px !important;
639
+ padding: 6px !important;
640
  }
641
 
642
+ .gr-accordion {
643
+ border: 1px solid rgba(147,117,69,.30) !important;
644
+ border-radius: 16px !important;
645
+ overflow: hidden !important;
646
+ background: rgba(255,250,242,.78) !important;
647
+ }
648
+ .gr-accordion summary {
649
+ background: linear-gradient(180deg, rgba(248,240,225,.95), rgba(241,232,213,.94)) !important;
650
+ color: #26324a !important;
651
+ font-weight: 700 !important;
652
  }
653
+ .gr-accordion .label-wrap span { color: #26324a !important; }
654
 
655
+ .gr-examples {
656
+ background: rgba(255,251,243,.72) !important;
657
+ border: 1px solid rgba(147,117,69,.25) !important;
658
+ border-radius: 16px !important;
659
+ padding: 10px 12px !important;
660
+ }
661
+ .gr-examples .label-wrap span,
662
+ .gr-examples .label-wrap label {
663
+ color: #7b6238 !important;
664
+ font-weight: 700 !important;
665
+ }
666
+ .gr-examples button {
667
+ background: rgba(255,247,230,.96) !important;
668
+ border: 1px solid rgba(184,138,66,.30) !important;
669
+ color: #4f4334 !important;
670
+ border-radius: 12px !important;
671
+ }
672
+ .gr-examples button:hover {
673
+ border-color: #b88a42 !important;
674
+ color: #26324a !important;
675
+ }
676
+
677
+ input[type='range'] { accent-color: #b88a42 !important; }
678
+
679
+ @media (max-width: 980px) {
680
+ .strat-grid { grid-template-columns: repeat(2, 1fr); }
681
+ .hdr-title { font-size: 28px; }
682
+ #hdr { padding: 24px 22px; }
683
+ }
684
+ @media (max-width: 640px) {
685
+ .strat-grid { grid-template-columns: 1fr; }
686
+ .sec-label { font-size: 13px; letter-spacing: .16em; }
687
+ .hdr-title { font-size: 24px; }
688
+ #hdr::after { display: none; }
689
  }
690
  """
691
 
692
  HEADER_HTML = """
693
  <div id="hdr">
694
+ <div class="hdr-eyebrow">Arcane Archive · Multi-Strategy Retrieval</div>
695
+ <div class="hdr-title">墨境卷宗|多策略 RAG 文件問答系統</div>
696
+ <div class="hdr-sub">以「古卷、夜藍、鎏金、紙墨」為視覺核心,重塑你的文件問答介面。支援 PDF / DOCX 上傳,採用 ChromaDB 持久化向量資料庫與 8 種 RAG 檢索策略</div>
697
+ <div class="hdr-quote">殿堂、工廠、流浪船、試煉之地 —— 每一次檢索,都是穿越文件迷霧的航行。</div>
698
+ <div class="hdr-pills">
699
+ <span class="pill pill-dark">▸ 多策略檢索</span>
700
  <span class="pill">▸ Groq API</span>
701
  <span class="pill">▸ llama-3.1-8b-instant</span>
702
+ <span class="pill">▸ ChromaDB</span>
703
+ <span class="pill">▸ PDF / DOCX</span>
704
  <span class="pill">▸ SentenceTransformers</span>
705
  </div>
706
  </div>
 
784
  # ── 左欄 ──────────────────���───────────────
785
  with gr.Column(scale=1, min_width=320, elem_classes="card-box"):
786
 
787
+ gr.HTML("<div class='sub-note'>以金紋卷軸風格整理左側控制面板:先點亮金鑰,再載入卷宗,最後挑選檢索術式。</div>")
788
+
789
  # ★ Step 00:API Key 輸入(新增)
790
+ gr.HTML("<div class='sec-label'>Step 00 · 啟封金鑰</div>")
791
  with gr.Group(elem_id="apikey-box"):
792
  api_key_input = gr.Textbox(
793
  label="Groq API Key",
 
804
  value="✓ API Key 已從環境變數載入" if env_key else "⚠ 尚未設定 API Key",
805
  interactive=False,
806
  lines=1,
807
+ label="金鑰狀態",
808
  show_label=False,
809
  )
810
 
811
  # Step 01:上傳文件
812
+ gr.HTML("<div class='sec-label'>Step 01 · 載入卷宗</div>")
813
+ file_input = gr.File(label="上傳卷宗(PDF / DOCX", file_types=[".pdf", ".docx"])
814
+ load_btn = gr.Button("↑ 載入卷宗")
815
+ status = gr.Textbox(label="卷宗狀態", interactive=False, lines=3)
816
 
817
  # Step 02:RAG 策略
818
+ gr.HTML("<div class='sec-label'>Step 02 · 選擇檢索術式</div>")
819
  gr.HTML(build_strategy_menu("semantic"))
820
  strategy_input = gr.Textbox(
821
  value="semantic",
 
824
  visible=False,
825
  )
826
  strategy_status = gr.Textbox(
827
+ value="✓ 已選擇術式:語意搜尋",
828
  interactive=False,
829
  lines=1,
830
+ label="目前術式",
831
  )
832
  gr.HTML(STRATEGY_MENU_JS)
833
 
834
  # Step 03:參數
835
+ gr.HTML("<div class='sec-label'>Step 03 · 檢索參數</div>")
836
+ topk = gr.Slider(minimum=1, maximum=10, value=3, step=1, label="Top-K 檢索片段數量")
837
 
838
  # ── 右欄:問答 ────────────────────────────
839
  with gr.Column(scale=2, elem_classes="card-box"):
840
+ gr.HTML("<div class='sub-note'>右側為提問主場域。介面配色改為夜藍輸入框與紙本質地背景,營造海報式卷宗氛圍。</div>")
841
+ gr.HTML("<div class='sec-label'>Step 04 · 發問</div>")
842
  qin = gr.Textbox(
843
  label="問題",
844
  placeholder="例如:這份文件的核心論點是什麼?",
845
  lines=4,
846
  )
847
+ ask_btn = gr.Button("開始提問", variant="primary", size="lg", elem_id="ask-btn")
848
 
849
  gr.HTML("<div class='sec-label'>AI 回答</div>")
850
  ans = gr.Textbox(label="AI 回答內容", lines=12, interactive=False)