File size: 39,251 Bytes
94e6baf 90e21a2 fa09659 94e6baf fa09659 29ed877 90e21a2 29ed877 90e21a2 29ed877 90e21a2 29ed877 90e21a2 29ed877 90e21a2 29ed877 90e21a2 29ed877 90e21a2 29ed877 90e21a2 29ed877 90e21a2 29ed877 90e21a2 29ed877 90e21a2 29ed877 90e21a2 29ed877 90e21a2 29ed877 90e21a2 29ed877 90e21a2 29ed877 90e21a2 29ed877 90e21a2 29ed877 90e21a2 29ed877 90e21a2 29ed877 40efb1e 90e21a2 40efb1e 29ed877 90e21a2 29ed877 90e21a2 fa09659 90e21a2 29ed877 90e21a2 40efb1e 90e21a2 40efb1e 90e21a2 40efb1e 90e21a2 40efb1e 90e21a2 40efb1e 90e21a2 fa09659 40efb1e 90e21a2 29ed877 90e21a2 29ed877 40efb1e 90e21a2 40efb1e 90e21a2 40efb1e fa09659 29ed877 fa09659 29ed877 90e21a2 29ed877 90e21a2 29ed877 90e21a2 29ed877 90e21a2 29ed877 90e21a2 29ed877 90e21a2 29ed877 fa09659 29ed877 fa09659 464e6d8 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 |
from pathlib import Path
from typing import Any, ClassVar, Dict, List, Optional, Tuple, Union
import gradio as gr
from dotenv import load_dotenv
load_dotenv()
class Document_structureUI:
# サンプルデータ (議案一覧)
sample_data: List[Dict[str, str]] = [
{
"議案番号": "1",
"タイトル": "役員様より各修繕提案の件",
"meeting_no": 4,
"物件名": "経堂パーク・マンション",
"文書種別": "議案書",
"年度": "2021",
"会議日": "2022-05-18",
"議案タイプ": "審議事項",
"議案内容": "前回理事会に引き続きご検討お願いいたします。\n<添付資料>\n·株式会社ホゼン 見積書\n·ルシア株式会社 提案書\n【参考:前回の審議結果】\n<審議経過>\n1非常階段の長尺シートの全面貼り替えの件\n→専門業者から見積りと仕様書の提示を受け検討しました。本件について、他メーカーのカタログを取り寄せ、詳細検討を進めることとしました。\n2正面玄関階段の手すり設置工事の件\n→専門業者からイメージ図を取得し検討をしました。本件について、シンプルな仕様で詳細検討を進めることとし、同社へ次回理事会にてプレゼンテーションを依頼することとしました。",
"AI提案": "設備修繕",
"status": "理事会議論中",
"memorandum": "非常階段の長尺シート貼り替えと正面玄関の手すり設置工事について、業者選定と見積もり取得を進め、来期の修繕計画および予算案に計上する必要がある。",
},
{
"議案番号": "2",
"タイトル": "マンション隣地(東側) ブロック塀及び垣根復旧対応報告",
"meeting_no": 4,
"物件名": "経堂パーク・マンション",
"文書種別": "議案書",
"年度": "2021",
"会議日": "2022-05-18",
"議案タイプ": "報告事項'",
"議案内容": "マンション隣地(東側) ブロック塀および垣根復旧作業が完了しました。 (2022年5月11日完了)\n管理会社にブロック塀に関して当初のコーキング案と今回のステンレスアングル案の実質的な違いに関する質問をし、結論として最終的にはステンレスアングル案の方が当管理組合にとって有利であったという管理会社としてのご見解をいただきました。\n<審議経過>\n大東建託より昨年11月に是正工事の説明を受け、年明けに設計図と完成イメージ図の提出を受け確認しました。 本件について、敷地境界線のコーキングが経年劣化した場合、補修の費用負担は隣地(長島 邸) であると大東建託担当者から説明があったことから、後のことを考えその旨の書面作成を条件に工事許可をすることとしましたが、後日、大東建託担当より書面取り付けが大変厳しい状況である旨報告を受けました。\nその後、代替え案として、劣化の少ないステンレスのアングルを設置し、隙間を埋めて植栽を復旧する工法の提案を受けました。\n本図面を管理会社技術部にて確認した結果、隙間を埋めるのはあくまで美観状の問題のため、耐候性のあるステンレス部材であれば問題ない旨確認しました。 ただし、今後の劣化を考え、アングルを留めるボルトも耐候性、耐食性等のあるステンレス材を使用した方が良い旨、先方に申し送りしアングル留付用ボルトもステンレス製固定となりました。以上から本仕様にて工事承認としました。",
"AI提案": "設備修繕",
"status": "終了済み",
"memorandum": "なし",
},
{
"議案番号": "3",
"タイトル": "管理計画認定制度およびマンション管理適正評価制度への登録の件",
"meeting_no": 4,
"物件名": "経堂パーク・マンション",
"文書種別": "議案書",
"年度": "2021",
"会議日": "2022-05-18",
"議案タイプ": "審議事項",
"議案内容": "標題の件、2022年4月より、マンションにおける建物維持管理の新たな指針として、マンション管理適正化法に基づく「管理計画認定制度」および一般社団法人マンション管理業協会による「マンション管理適正評価制度」の二つの制度が運用開始となります。\n「管理計画認定制度」は、マンションの管理計画が一定の基準を満たす場合に、適切な管理計画を持つマンションとして認定を受けることができる制度であり、認定を受けることができたマンションは市場において評価されることが期待されます。\nまた、「マンション管理適正評価制度」は、同様にマンションの管理状況をチェックし、その情報がポータルサイト等で開示されることで、管理の行き届いているマンションの管理評価が市場価値·流通価格に反映されることが期待されます。一方、登録しない=非開示となっているマンションは市場での評価が困難となることも想定されます。\n本件に関して、管理会社にて両制度の事前評価を実施しましたので、以下の結果をご確認いただくとともに、本結果をご参考のうえ、両制度への登録および情報開示に関して今年度通常総会へ議案上程することについて、ご審議をお願いいたします。\nまた、総会への議案上程に際して、より評価を向上させるための対応策もご提案させていただきますので合わせてご審議ください。\nなお、今後のインセンティブ制度等確立のため、今回ご提出した「マンション管理適正評価制度」の『等級評価結果』および、配管の材質や漏水事故の発生率等の情報をまとめた『維持管理情報』の提供をマンション管理業協会より求められる可能性があります。\nその場合、両制度への登録の有無に関わらず、マンション名付でマンション管理業協会へ提出することについて、ご承諾をお願いいたします。\n※情報提供は、将来の損害保険インセンティブ制度確立のための“参考資料”として取り扱いされますが、個々の提供情報について審査されるものではありません。また、管理会社とマンション管理業協会にて、損害保険インセンティブ制度の検討以外に使用されないこと、秘密情報の取り扱い等を規定した秘密保持契約を締結しております。\n<事前評価結果のご報告>\n事前の評価結果は以下のとおりです。\n管理計画認定制度:不適合\nマンション管理適正評価制度:★4つ (76ポイント)\n※評価結果の詳細は添付の「等級評価シート」をご参照ください。\n<添付資料>\n·管理計画認定制度·マンション管理適正評価制度~登録のご提案~\n·等級評価シート",
"AI提案": "管理計画認定制度",
"status": "総会での報告承認待ち",
"memorandum": "両制度への登録費用、および評価向上策にかかる費用を来期予算に計上することを検討する。総会での承認が必要。",
},
]
THEME_KEYS: ClassVar[list[str]]=[
"照明設備",
"エレベーター",
"エントランス自動ドア",
"通信・IT設備",
"受水槽",
"宅配ボックス",
"固定電話・IP網",
"消火器",
"防犯カメラ",
"大規模修繕",
"建物調査診断",
"設備修繕",
"植栽",
"機械式駐車場",
"自転車置場",
"理事会・総会",
"管理委託契約",
"管理組合運営",
"管理規約・使用細則",
"管理計画認定制度",
"リフォーム工事細則",
"年度予算・検討課題",
"未収金",
"長期修繕計画",
"ごみ",
"防災",
"管理会社対応",
"電子化・DX",
"会計・収支",
"その他",
]
# 物件名の候補リスト
BUKKEN_CHOICES: ClassVar[list[str]] = [
"経堂パーク・マンション",
"祖師ヶ谷大蔵パーク・ホームズ",
"パークシティ弦巻",
"パークホームズ学芸大学グレーススクエア",
"パーク・ノヴァ猿江恩賜公園",
]
def __init__(self) -> None:
"""初期化"""
self.approval_status: Dict[int, bool] = dict.fromkeys(range(len(self.sample_data)), False)
def _get_custom_css(self) -> str:
"""カスタムCSSを取得"""
return """
.status-approved {
color: #059669;
background-color: #d1fae5;
padding: 4px 12px;
border-radius: 9999px;
font-size: 0.875rem;
font-weight: 600;
display: inline-block;
}
.status-pending {
color: #d97706;
background-color: #fef3c7;
padding: 4px 12px;
border-radius: 9999px;
font-size: 0.875rem;
font-weight: 600;
display: inline-block;
}
.status-saved {
color: #059669;
font-size: 0.875rem;
font-weight: 600;
}
/* highlight panels */
.theme-highlight {
background: #eff6ff; /* blue-50 */
border-left: 4px solid #60a5fa; /* blue-400 */
padding: 12px;
border-radius: 6px;
}
.panel-gray {
background: #eff6ff; /* blue-50 */
border-left: 4px solid #60a5fa; /* blue-400 */
padding: 12px;
border-radius: 6px;
}
.panel-white {
background: #ffffff;
border-left: 4px solid #60a5fa; /* blue-400 */
padding: 12px;
border-radius: 6px;
}
.small-hint {
font-size: 12px;
color: #1e40af; /* blue-800 */
}
/* blue buttons */
.btn-blue {
background: linear-gradient(135deg, #4F7FFF 0%, #1D4ED8 100%) !important;
color: #ffffff !important;
border: none !important;
box-shadow: 0 2px 8px rgba(29, 78, 216, 0.25) !important;
}
.btn-blue:hover {
filter: brightness(1.05);
transform: translateY(-1px);
}
/* tabs - active/hover color (force underline to blue) */
button[role="tab"] {
position: relative !important;
border-bottom: 2px solid transparent !important;
box-shadow: inset 0 -2px transparent !important;
}
button[role="tab"]::after {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 2px;
background: transparent;
}
button[role="tab"][aria-selected="true"] {
background: #eff6ff !important; /* blue-50 */
color: #1d4ed8 !important; /* blue-700 */
}
button[role="tab"][aria-selected="true"]::after {
background: #1d4ed8 !important;
}
button[role="tab"]:hover {
color: #1d4ed8 !important;
}
button[role="tab"]:hover::after {
background: #60a5fa !important; /* lighter on hover */
}
/* accordion labels as headings */
details > summary {
font-weight: 700 !important;
font-size: 1.05rem !important;
color: #111827 !important; /* gray-900 */
}
/* approval message textbox */
.msg-approved textarea {
background-color: #d1fae5 !important; /* green-100 */
color: #059669 !important; /* green-600 */
border: 1px solid #059669 !important;
border-left: 4px solid #059669 !important;
}
.bg-white {
background: #fff !important;
border-radius: 6px;
padding: 12px;
}
.btn-water {
background-color: #5bc0eb !important;
color: #ffffff !important;
border-color: #5bc0eb !important;
}
.btn-water:hover, .btn-water:focus {
background-color: #48b3de !important;
border-color: #48b3de !important;
}
"""
def process_file(self, file: Optional[Union[str, Path]]) -> Tuple[Any, Any]:
"""ファイルアップロード処理"""
if file is None:
return gr.update(value="⚠️ ファイルが選択されていません", visible=True), gr.update(), gr.update()
file_name = Path(file).name if isinstance(file, (str, Path)) else "uploaded_file"
return (
gr.update(value=f"{file_name}がアップロードされました", visible=True),
gr.update(visible=True),
gr.update(value="アップロード済み", variant="secondary"),
)
def switch_tab(self,selected:int) -> Any:
return gr.update(selected=selected)
def edit_info(self) -> Any:
return (
gr.update(visible=False),
gr.update(visible=True),
gr.update(visible=False),
gr.update(interactive=True),
gr.update(interactive=True),
gr.update(interactive=True),
gr.update(interactive=True),
)
def save_info(self) -> Any:
return (
gr.update(visible=True),
gr.update(visible=False),
gr.update(visible=True),
gr.update(interactive=False),
gr.update(interactive=False),
gr.update(interactive=False),
gr.update(interactive=False),
)
def approve_item(self, index: int) -> str:
"""個別の議案を承認"""
self.approval_status[index] = True
return f"議案 {self.sample_data[index]['議案番号']}: {self.sample_data[index]['タイトル']} を承認しました。"
def unapprove_item(self, index: int) -> str:
"""個別の議案を承認"""
self.approval_status[index] = False
return f"議案 {self.sample_data[index]['議案番号']}: {self.sample_data[index]['タイトル']} を承認しました。"
def _get_approval_progress_html(self) -> str:
"""承認進捗のHTML (バッジ) を返す"""
approved_count = sum(1 for v in self.approval_status.values() if v is True)
total = len(self.sample_data)
clazz = "status-approved" if approved_count == total else "status-pending"
return "<span class='" + clazz + "'>" + f"{approved_count}/{total} 承認済み" + "</span>"
def on_approve_and_close(self, idx: int) -> Tuple[str, Any, Any, Any]:
if self.approval_status.get(idx) is False:
"""承認メッセージ、メッセージ更新、進捗更新、必要ならタブ遷移を返す"""
msg = self.approve_item(idx)
progress_html = self._get_approval_progress_html()
all_done = sum(1 for v in self.approval_status.values() if v is True) == len(self.sample_data)
register_btn_update = gr.update(visible=all_done)
btn_update = gr.update(value="✅ 承認済み", variant="secondary", interactive=True)
if all_done:
approve_all_btn_update = gr.update(value="✅すべて承認済み", variant="secondary")
else:
approve_all_btn_update = gr.update(value="すべて承認", variant="primary")
return (
msg,
gr.update(value=progress_html),
gr.update(open=False),
btn_update,
register_btn_update,
approve_all_btn_update,
)
msg = self.unapprove_item(idx)
progress_html = self._get_approval_progress_html()
all_done = sum(1 for v in self.approval_status.values() if v is True) == len(self.sample_data)
register_btn_update = gr.update(visible=all_done)
btn_update = gr.update(value="承認", variant="primary", interactive=True)
if all_done:
approve_all_btn_update = gr.update(value="✅すべて承認済み", variant="secondary")
else:
approve_all_btn_update = gr.update(value="すべて承認", variant="primary")
return (
msg,
gr.update(value=progress_html),
gr.update(open=True),
btn_update,
register_btn_update,
approve_all_btn_update,
)
def approve_all_and_go_back(self) -> Any:
all_done = sum(1 for v in self.approval_status.values() if v is True) == len(self.sample_data)
if all_done:
for i in range(len(self.sample_data)):
self.approval_status[i] = False
progress_html = self._get_approval_progress_html()
register_btn_update = gr.update(visible=False)
per_button_updates = [
gr.update(value="承認", variant="primary", interactive=True) for _ in range(len(self.sample_data))
]
acc_updates = [gr.update(open=True) for _ in range(len(self.sample_data))]
return (
gr.update(value=progress_html),
register_btn_update,
gr.update(value="すべて承認", variant="primary"),
*per_button_updates,
*acc_updates,
)
"""すべ議案を承認してStep1に戻る。メッセージは表示しない。"""
for i in range(len(self.sample_data)):
self.approval_status[i] = True
progress_html = self._get_approval_progress_html()
register_btn_update = gr.update(visible=True)
per_button_updates = [
gr.update(value="✅ 承認済み", variant="secondary", interactive=True) for _ in range(len(self.sample_data))
]
acc_updates = [gr.update(open=False) for _ in range(len(self.sample_data))]
return (
gr.update(value=progress_html),
register_btn_update,
gr.update(value="✅すべて承認済み", variant="secondary"),
*per_button_updates,
*acc_updates,
)
def on_register_documents(self):
# TODO: 実際の資料登録処理をここに実装する
# 例: DB保存やAPI呼び出しなど
# 成功メッセージを表示
return gr.update(visible=True),gr.update(visible=True)
def _search_bukken(self, search_text: str) -> tuple[gr.Radio, str, str]:
"""物件名を検索して候補を更新"""
print(f"DEBUG: _search_bukken called with: '{search_text}'")
print(f"DEBUG: Available choices: {self.BUKKEN_CHOICES}")
if not search_text or len(search_text.strip()) == 0:
# 検索テキストが空の場合は候補を非表示
print("DEBUG: Empty search text, returning empty choices")
return gr.Radio(choices=[], visible=False, value=None), "", ""
# 検索テキストで候補を絞り込み(大文字小文字を区別しない)
search_text_lower = search_text.lower()
filtered_choices = [choice for choice in self.BUKKEN_CHOICES if search_text_lower in choice.lower()]
print(f"DEBUG: Filtered choices: {filtered_choices}")
if len(filtered_choices) == 0:
# マッチする候補がない場合
print("DEBUG: No matches found")
return gr.Radio(choices=[], visible=False, value=None), "", ""
# 候補が1件の場合は自動的に選択して値を設定
if len(filtered_choices) == 1:
selected = filtered_choices[0]
keiyaku_name = f"{selected}管理組合"
print(f"DEBUG: Single match found: {selected}")
return (
gr.Radio(choices=filtered_choices, value=selected, visible=True),
selected,
keiyaku_name,
)
# 複数候補がある場合は表示
print(f"DEBUG: Multiple matches found: {filtered_choices}")
return gr.Radio(choices=filtered_choices, value=None, visible=True), "", ""
def _select_bukken(self, selected: str) -> tuple[str, str, str]:
"""物件を選択"""
print(f"DEBUG: _select_bukken called with: '{selected}'")
if not selected:
print("DEBUG: No selection, returning empty values")
return "", "", ""
# 選択された値を各フィールドに反映
keiyaku_name = f"{selected}管理組合"
print(f"DEBUG: Setting selected_bukken='{selected}', keiyaku_name='{keiyaku_name}'")
return selected, selected
def create_interface(self):
"""document_structure_uiの内容を生成する関数"""
components = {}
with gr.Column():
gr.Markdown("# 資料登録システム")
with gr.Tabs() as tabs:
components["tabs"] = tabs
with gr.Tab("Step1: ファイルアップロード", id=0):
gr.Markdown("## 基本情報登録")
with gr.Row():
with gr.Column(scale=0.3, min_width=1):
gr.Markdown("")
with gr.Column(scale=4, min_width=15, variant="panel"):
gr.Markdown("**物件名**")
with gr.Column(scale=16, min_width=300):
with gr.Column():
self.bukken_search = gr.Textbox(
placeholder="🔍物件名を入力してください (例: 経堂、パーク、など) ",
show_label=False,
interactive=True,
container=False,
)
self.bukken_select = gr.Radio(
choices=[],
show_label=False,
interactive=True,
visible=False,
)
# 選択された物件名を保持する隠しフィールド
self.selected_bukken = gr.Textbox(value="", visible=False)
# Step 1: 物件検索機能
self.bukken_search.change(
fn=self._search_bukken,
inputs=[self.bukken_search],
outputs=[self.bukken_select, self.selected_bukken],
)
# Step 1: 物件選択時の処理
self.bukken_select.change(
fn=self._select_bukken,
inputs=[self.bukken_select],
outputs=[self.selected_bukken, self.bukken_search],
)
with gr.Row():
with gr.Column(scale=0.3, min_width=1):
gr.Markdown("")
with gr.Column(scale=4, min_width=15, variant="panel"):
gr.Markdown("**資料タイプ**")
with gr.Column(scale=16, min_width=300):
self.meeting_type = gr.Dropdown(
choices=["理事会", "総会"],
value="理事会",
show_label=False,
interactive=True,
container=False,
)
gr.Markdown("## ファイルアップロード")
with gr.Row():
with gr.Column(scale=2):
file_input = gr.File(
label="ファイルを選択(PDF, DOCX)",
file_types=[".pdf", ".docx"],
file_count="single",
type="filepath",
)
components["file_input"] = file_input
upload_btn = gr.Button("アップロード", variant="primary", size="lg", scale=1)
structure_btn = gr.Button(
"AIによるデータ整理",
variant="primary",
size="lg",
scale=1,
visible=False,
elem_classes=["btn-blue"],
)
components["upload_btn"] = upload_btn
components["structure_btn"] = structure_btn
upload_status = gr.Markdown(value="", visible=False)
components["upload_status"] = upload_status
with gr.Tab("Step2: AI出力の確認", id=1):
gr.Markdown("# 共通データの確認")
with gr.Row():
gr.Markdown("")
with gr.Column():
save_info_message = gr.Textbox(
value="共通データを修正しました。",
label="",
show_label=False,
interactive=False,
visible=False,
elem_classes=["msg-approved"],
container=False
)
components["save_info_message"] = save_info_message
with gr.Column(scale=3):
edit_info_btn = gr.Button(value="共通データの修正", variant="primary")
components["edit_info_btn"] = edit_info_btn
save_info_btn = gr.Button(
"修正内容の確定",
variant="primary", # 見た目は必要に応じて
visible=False, # 任意
)
components["save_info_btn"] = save_info_btn
with gr.Row():
with gr.Column(scale=3.6, min_width=10, variant="panel"):
gr.Markdown("**開催時期**")
with gr.Column(scale=0.2, min_width=1):
gr.Markdown("第")
with gr.Column(scale=3, min_width=120):
meeting_no = gr.Textbox(
value=self.sample_data[0]["meeting_no"],
placeholder="例) 3",
show_label=False,
interactive=False,
container=False,
) # 期数をMarkdownで表示
with gr.Column(scale=0.2, min_width=1):
gr.Markdown("回")
with gr.Column(scale=0.3, min_width=1):
gr.Markdown("")
with gr.Column(scale=3, min_width=120):
year = gr.Textbox(
value=self.sample_data[0]["年度"],
placeholder="例) 2023",
show_label=False,
interactive=False,
container=False,
) # 期数をMarkdownで表示
with gr.Column(scale=0.5, min_width=3):
gr.Markdown("年度")
with gr.Column(scale=0.3, min_width=1):
gr.Markdown("")
with gr.Column(scale=0.9, min_width=1):
gr.Markdown("開催日時")
with gr.Column(scale=4, min_width=120):
meeting_date = gr.DateTime(
value=self.sample_data[0]["会議日"],
include_time=False, # 時間ピッカーを有効
type="string", # 文字列として返す(扱いやすい)
interactive=False,
show_label=False,
)
with gr.Row():
with gr.Column(scale=3, min_width=10, variant="panel"):
gr.Markdown("**文書種別**")
with gr.Column(scale=6, min_width=150):
doc_type = gr.Dropdown(
choices=["議案書", "議事録"],
value=self.sample_data[0]["文書種別"],
show_label=False,
interactive=False,
container=False,
)
with gr.Column(scale=6, min_width=1):
gr.Markdown("")
gr.Markdown("")
gr.Markdown("")
gr.Markdown("# 各議案データの確認")
with gr.Row():
global_message = gr.Textbox(label="", show_label=False, interactive=False, visible=False)
components["global_message"] = global_message
approval_progress = gr.HTML(value=self._get_approval_progress_html())
components["approval_progress"] = approval_progress
with gr.Column():
approve_all_btn = gr.Button(value="すべて承認", variant="primary", size="lg")
components["approve_all_btn"] = approve_all_btn
register_doc_btn = gr.Button(
"このテーマキーで資料登録",
variant="primary", # 見た目は必要に応じて
size="lg",
visible=False, # 任意
)
components["register_doc_btn"] = register_doc_btn
register_doc_message = gr.Textbox(
value="資料登録完了しました",
label="",
show_label=False,
interactive=False,
visible=False,
elem_classes=["msg-approved"],
container=False
)
with gr.Row():
gr.Markdown("")
return_step1_btn = gr.Button(
"ファイルアップロード画面に戻る",
variant="secondary",
visible=False,
)
agenda_components = []
approve_btn_list = []
acc_list = []
gr.Markdown("")
for i, data in enumerate(self.sample_data):
with gr.Blocks():
with gr.Row(elem_classes=["agenda-header-row"]):
with gr.Column(scale=9):
gr.Markdown(
f"### 議案 {data['議案番号']}: {data['タイトル']}",
elem_classes=["agenda-title"],
)
with gr.Column(scale=3):
approve_btn = gr.Button("承認", variant="primary")
approve_btn.elem_classes=["btn-water"]
acc = gr.Accordion(label="", open=True, elem_classes=["agenda-accordion-body"])
with acc:
with gr.Row():
with gr.Column(scale=2):
gr.Dropdown(
choices=["審議事項", "報告事項", "その他"],
value=data["議案タイプ"],
label="議案タイプ",
interactive=True, # 編集可能に
)
gr.Textbox(
value=data["議案内容"],
label="議案内容",
show_label=True,
lines=2,
interactive=False,
)
with gr.Column(scale=1):
gr.Dropdown(
label="ステータス",
choices=["理事会議論中","総会での報告承認待ち","終了済み"],
value=data["status"],
interactive=True,
)
gr.Textbox(
value=data["memorandum"],
label="備忘",
show_label=True,
interactive=True,
)
with gr.Group(elem_classes=["panel-gray"]):
gr.Markdown("### 議案のテーマ付与")
gr.Dropdown(
label="テーマキー",
choices=self.THEME_KEYS,
value=data["AI提案"],
interactive=True,
)
message = gr.Textbox(
label="",
show_label=False,
interactive=False,
visible=False,
elem_classes=["msg-approved"],
)
approve_btn.click(
fn=lambda idx=i: self.on_approve_and_close(idx),
outputs=[
message,
approval_progress,
acc,
approve_btn,
register_doc_btn,
approve_all_btn,
],
)
agenda_components.append({"approve_btn": approve_btn, "message": message})
approve_btn_list.append(approve_btn) # ← 追加
acc_list.append(acc)
gr.Markdown("")
gr.Markdown("")
components["upload_btn"].click(
fn=self.process_file,
inputs=[components["file_input"]],
outputs=[components["upload_status"], components["structure_btn"], components["upload_btn"]],
)
components["structure_btn"].click(
fn=lambda idx=1: self.switch_tab(idx),
outputs=[components["tabs"]],
)
components["edit_info_btn"].click(
fn=self.edit_info,
outputs=[
components["edit_info_btn"],
components["save_info_btn"],
components["save_info_message"],
meeting_no,
year,
meeting_date,
doc_type,
],
)
components["save_info_btn"].click(
fn=self.save_info,
outputs=[components["edit_info_btn"],
components["save_info_btn"],
components["save_info_message"],
meeting_no,
year,
meeting_date,
doc_type,
],
)
components["approve_all_btn"].click(
fn=self.approve_all_and_go_back,
outputs=[
approval_progress,
components["register_doc_btn"],
components["approve_all_btn"],
*approve_btn_list,
*acc_list,
],
)
components["register_doc_btn"].click(
fn=self.on_register_documents,
outputs=[register_doc_message,return_step1_btn],
)
return_step1_btn.click(fn=lambda idx=0: self.switch_tab(idx),outputs=[components["tabs"]])
return components
# アプリケーション起動
def main():
"""メイン関数"""
app = Document_structureUI()
demo = app.create_interface()
demo.launch(server_name="0.0.0.0", server_port=7860, share=False)
if __name__ == "__main__":
main() |