UIdemo / app.py
SHase964
fix: 資料作成/資料構造化
90e21a2
import gradio as gr
from document_structure_ui import Document_structureUI
from report_creator_ui import ReportCreatorUI
# インポート
# ログイン認証情報
VALID_USERNAME = "mfrs_neoai_pj"
VALID_PASSWORD = "K7m2P9x4Q8n6R3"
def authenticate(username: str, password: str) -> tuple[bool, str]:
"""ログイン認証"""
if username == VALID_USERNAME and password == VALID_PASSWORD:
return True, "ログイン成功"
return False, "ユーザー名またはパスワードが正しくありません"
def get_custom_css() -> 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;
}
.theme-highlight {
background: #eff6ff;
border-left: 4px solid #60a5fa;
padding: 12px;
border-radius: 6px;
}
.panel-gray {
background: #eff6ff;
border-left: 4px solid #60a5fa;
padding: 12px;
border-radius: 6px;
}
.small-hint {
font-size: 12px;
color: #1e40af;
}
/* 青いボタンスタイル - より強い優先度 */
button.btn-blue,
.btn-blue button,
button[variant="primary"],
.gradio-container button[variant="primary"],
.main-content button[variant="primary"],
.primary,
button:is([variant="primary"]) {
background: linear-gradient(135deg, #4F7FFF 0%, #1D4ED8 100%) !important;
background-color: #1D4ED8 !important;
color: #ffffff !important;
border: none !important;
border-color: #1D4ED8 !important;
box-shadow: 0 2px 8px rgba(29, 78, 216, 0.25) !important;
}
button.btn-blue:hover,
.btn-blue button:hover,
button[variant="primary"]:hover,
.gradio-container button[variant="primary"]:hover,
.main-content button[variant="primary"]:hover,
.primary:hover,
button:is([variant="primary"]):hover {
background: linear-gradient(135deg, #6366F1 0%, #3B82F6 100%) !important;
background-color: #3B82F6 !important;
filter: brightness(1.05) !important;
transform: translateY(-1px) !important;
}
/* タブボタンも青に */
button[role="tab"][aria-selected="true"] {
background: #eff6ff !important;
color: #1d4ed8 !important;
}
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;
color: #1d4ed8 !important;
}
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;
}
details > summary {
font-weight: 700 !important;
font-size: 1.05rem !important;
color: #111827 !important;
}
.msg-approved textarea {
background-color: #d1fae5 !important;
color: #059669 !important;
border: 1px solid #059669 !important;
border-left: 4px solid #059669 !important;
}
.block {
gap: 0 !important;
}
.gap {
gap: 0 !important;
}
.main-content button[variant="primary"],
.main-content .primary {
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;
}
.main-content button[variant="primary"]:hover,
.main-content .primary:hover {
filter: brightness(1.05) !important;
transform: translateY(-1px) !important;
}
/* ログイン画面 */
.login-container {
max-width: 400px;
margin: 100px auto;
padding: 40px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.login-title {
text-align: center;
font-size: 1.5rem;
font-weight: 700;
color: #1f2937;
margin-bottom: 30px;
}
.error-message {
color: #dc2626;
background-color: #fee2e2;
padding: 12px;
border-radius: 6px;
margin-top: 10px;
text-align: center;
}
/* 決議種別ドロップダウンの文字色 */
.resolution-special {
color: #ea580c !important; /* orange-600 */
}
.resolution-conflict {
color: #2563eb !important; /* blue-600 */
}
.resolution-normal {
color: #000000 !important; /* black */
}
/* ドロップダウンの選択肢の文字色 */
.resolution-special option[value="特別決議"] {
color: #ea580c !important; /* orange-600 */
}
.resolution-conflict option[value="利益相反"] {
color: #2563eb !important; /* blue-600 */
}
.resolution-normal option[value="普通決議"] {
color: #000000 !important; /* black */
}
/* ドロップダウンを開いたときの選択肢の色 */
.gradio-container .resolution-special option,
.gradio-container .resolution-special option[value="特別決議"] {
color: #ea580c !important;
}
.gradio-container .resolution-conflict option,
.gradio-container .resolution-conflict option[value="利益相反"] {
color: #2563eb !important;
}
.gradio-container .resolution-normal option,
.gradio-container .resolution-normal option[value="普通決議"] {
color: #000000 !important;
}
/* Gradioのドロップダウン用のより強力なセレクタ */
.gradio-container .resolution-special,
.gradio-container .resolution-special *,
.gradio-container .resolution-special input,
.gradio-container .resolution-special select,
.gradio-container .resolution-special .wrap,
.gradio-container .resolution-special .wrap *,
.gradio-container .resolution-special .wrap input,
.gradio-container .resolution-special .wrap select {
color: #ea580c !important;
}
.gradio-container .resolution-conflict,
.gradio-container .resolution-conflict *,
.gradio-container .resolution-conflict input,
.gradio-container .resolution-conflict select,
.gradio-container .resolution-conflict .wrap,
.gradio-container .resolution-conflict .wrap *,
.gradio-container .resolution-conflict .wrap input,
.gradio-container .resolution-conflict .wrap select {
color: #2563eb !important;
}
.gradio-container .resolution-normal,
.gradio-container .resolution-normal *,
.gradio-container .resolution-normal input,
.gradio-container .resolution-normal select,
.gradio-container .resolution-normal .wrap,
.gradio-container .resolution-normal .wrap *,
.gradio-container .resolution-normal .wrap input,
.gradio-container .resolution-normal .wrap select {
color: #000000 !important;
}
/* さらに強力なセレクタ - すべての子要素を対象 */
.resolution-special,
.resolution-special *,
.resolution-special input,
.resolution-special select,
.resolution-special option,
.resolution-special span,
.resolution-special div,
.resolution-special .wrap,
.resolution-special .wrap * {
color: #ea580c !important;
}
.resolution-conflict,
.resolution-conflict *,
.resolution-conflict input,
.resolution-conflict select,
.resolution-conflict option,
.resolution-conflict span,
.resolution-conflict div,
.resolution-conflict .wrap,
.resolution-conflict .wrap * {
color: #2563eb !important;
}
.resolution-normal,
.resolution-normal *,
.resolution-normal input,
.resolution-normal select,
.resolution-normal option,
.resolution-normal span,
.resolution-normal div,
.resolution-normal .wrap,
.resolution-normal .wrap * {
color: #000000 !important;
}
"""
def create_main_app():
"""メインアプリケーションを作成"""
with gr.Blocks(title="総会議案書作成システム", css=get_custom_css()) as app:
# ログイン状態を管理するState
login_state = gr.State(False)
# ログイン画面
with gr.Column(visible=True, elem_classes=["login-container"]) as login_page:
gr.HTML("<h1 class='login-title'>総会議案書作成システム</h1>")
gr.Markdown("### ログイン")
username_input = gr.Textbox(label="ユーザー名")
password_input = gr.Textbox(label="パスワード", type="password")
login_btn = gr.Button("ログイン", variant="primary", size="lg")
login_message = gr.HTML(visible=False)
# メインコンテンツ(ログイン後)
with gr.Column(visible=False) as main_content:
# ヘッダータブ
gr.HTML(
"<h1 style='text-align: left; font-size: 1.5rem; font-weight: 700; margin: 20px 0; color: #1f2937;'>総会議案書作成システム</h1>"
)
# タブでセクションを管理
with gr.Tabs():
# 総会資料作成
with gr.Tab("総会資料作成"):
report_ui = ReportCreatorUI()
report_ui.create_interface()
# 資料登録
with gr.Tab("資料登録"):
structure_ui = Document_structureUI()
structure_ui.create_interface()
# ログイン処理
def handle_login(username: str, password: str):
is_valid, message = authenticate(username, password)
if is_valid:
return (
True, # login_state
gr.update(visible=False), # login_page
gr.update(visible=True), # main_content
gr.update(visible=False), # login_message
)
return (
False, # login_state
gr.update(visible=True), # login_page
gr.update(visible=False), # main_content
gr.update(value=f"<div class='error-message'>{message}</div>", visible=True), # login_message
)
login_btn.click(
fn=handle_login,
inputs=[username_input, password_input],
outputs=[login_state, login_page, main_content, login_message],
)
# Enterキーでログイン
password_input.submit(
fn=handle_login,
inputs=[username_input, password_input],
outputs=[login_state, login_page, main_content, login_message],
)
return app
if __name__ == "__main__":
app = create_main_app()
app.launch(server_name="0.0.0.0", server_port=7860, share=True)