| <!doctype html> |
| <html lang="ja"> |
| <head> |
| <meta charset="utf-8" /> |
| <title>物件QAシステム</title> |
| <meta name="viewport" content="width=device-width,initial-scale=1" /> |
| <link rel="preconnect" href="https://fonts.googleapis.com" /> |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Noto+Sans+JP:wght@400;500;600;700&display=swap" rel="stylesheet" /> |
| <script src="https://unpkg.com/lucide@0.461.0/dist/umd/lucide.js"></script> |
| <style> |
| :root { |
| |
| --primary: #5B5BF0; |
| --primary-light: #EEEEFE; |
| --primary-dark: #4040D0; |
| --primary-hover: #F6F6FF; |
| --primary-focus: #C5C5F4; |
| --text-primary: #0A0A0A; |
| --text-secondary: #404040; |
| --text-disabled: #888888; |
| --gray-01: #404040; |
| --gray-02: #555555; |
| --gray-03: #888888; |
| --gray-04: #D4D4D4; |
| --gray-05: #E5E5E5; |
| --gray-06: #F0F0F0; |
| --gray-07: #FAFAFA; |
| --star: #FAAF00; |
| --primary-bg: #FAFAFA; |
| --accent-red: #E11D48; |
| --header-bg: #FFFFFF; |
| --header-border: #E5E5E5; |
| --header-h: 60px; |
| --sidebar-w: 280px; |
| --sidebar-w-closed: 60px; |
| } |
| * { box-sizing: border-box; } |
| html, body { margin:0; padding:0; height:100%; background:white; color:var(--text-primary); |
| font-family: "Inter","Noto Sans JP","Hiragino Kaku Gothic ProN","Hiragino Sans","Yu Gothic",sans-serif; |
| font-size:13.5px; line-height:1.55; -webkit-font-smoothing:antialiased; |
| letter-spacing:-0.005em; |
| } |
| button { font-family:inherit; cursor:pointer; } |
| textarea, input { font-family:inherit; } |
| ::-webkit-scrollbar { width:6px; height:6px; } |
| ::-webkit-scrollbar-thumb { background:rgba(128,131,137,.35); border-radius:999px; } |
| ::-webkit-scrollbar-thumb:hover { background:rgba(128,131,137,.55); } |
| |
| |
| .header { |
| position:fixed; top:0; left:0; right:0; height:var(--header-h); |
| background:var(--header-bg); display:flex; align-items:center; padding:0 24px; |
| z-index:100; gap:12px; |
| border-bottom:1px solid var(--header-border); |
| } |
| .header.with-border { border-bottom-color: var(--header-border); } |
| .bot-selector { |
| display:flex; align-items:center; gap:10px; padding:6px 12px 6px 8px; |
| border-radius:8px; cursor:pointer; transition:background .15s; |
| user-select:none; |
| } |
| .bot-selector:hover { background:var(--gray-06); } |
| .bot-logo { |
| width:30px; height:30px; border-radius:6px; |
| background:#0A0A0A; display:flex; align-items:center; justify-content:center; |
| flex-shrink:0; |
| } |
| .bot-logo svg { display:block; } |
| .bot-name { font-size:15px; font-weight:700; color:var(--text-primary); } |
| |
| .header-spacer { flex:1; } |
| .header-right { display:flex; align-items:center; gap:8px; } |
| .icon-btn { |
| width:36px; height:36px; border:none; background:transparent; border-radius:6px; |
| color:var(--gray-02); display:inline-flex; align-items:center; justify-content:center; |
| transition:background .15s; |
| } |
| .icon-btn:hover { background:var(--gray-06); } |
| .icon-btn .lucide-icon svg { width:18px; height:18px; } |
| .toolbar-icon-btn .lucide-icon svg { width:18px; height:18px; } |
| .property-search .lucide-icon svg { width:14px; height:14px; } |
| .citation-card .lucide-icon svg { width:14px; height:14px; } |
| .bot-selector .lucide-icon svg { width:16px; height:16px; } |
| .rp-close .lucide-icon svg { width:16px; height:16px; } |
| .doc-toolbar > .lucide-icon svg { width:18px; height:18px; } |
| .account-chip { |
| display:flex; align-items:center; gap:8px; padding:4px; |
| border-radius:6px; background:transparent; border:none; color:var(--text-primary); |
| transition:background .15s; position:relative; |
| } |
| .account-chip:hover { background:var(--gray-06); } |
| .user-menu { |
| position:absolute; top:calc(100% + 8px); right:0; z-index:200; |
| background:white; border:1px solid var(--gray-05); border-radius:10px; |
| box-shadow:0 8px 24px rgba(0,0,0,.12); padding:16px; width:240px; |
| display:flex; flex-direction:column; gap:12px; |
| } |
| .user-menu-profile { display:flex; align-items:center; gap:12px; } |
| .user-menu-avatar { |
| width:44px; height:44px; border-radius:50%; flex-shrink:0; |
| background:var(--primary-light); color:var(--primary-dark); |
| display:flex; align-items:center; justify-content:center; font-size:15px; font-weight:700; |
| border:1px solid var(--primary-focus); |
| } |
| .user-menu-info { display:flex; flex-direction:column; gap:2px; min-width:0; } |
| .user-menu-name { font-size:14px; font-weight:700; color:var(--text-primary); } |
| .user-menu-account { font-size:12px; color:var(--text-secondary); } |
| .user-menu-email { font-size:11px; color:var(--text-disabled); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } |
| .user-menu-divider { height:1px; background:var(--gray-05); margin:0 -4px; } |
| .user-menu-item { |
| display:flex; align-items:center; gap:8px; padding:7px 8px; border-radius:6px; |
| font-size:13px; color:var(--text-secondary); border:none; background:transparent; |
| width:100%; text-align:left; cursor:pointer; |
| } |
| .user-menu-item:hover { background:var(--gray-06); color:var(--text-primary); } |
| .user-menu-item .lucide-icon svg { width:15px; height:15px; } |
| .account-avatar { |
| width:28px; height:28px; border-radius:50%; |
| background:var(--gray-06); color:var(--text-primary); |
| display:flex; align-items:center; justify-content:center; font-size:11.5px; font-weight:600; |
| border:1px solid var(--gray-05); |
| } |
| |
| |
| .sidebar { |
| position:fixed; left:0; top:var(--header-h); bottom:0; width:var(--sidebar-w); |
| background:var(--gray-07); z-index:90; display:flex; flex-direction:column; |
| transition:width .2s ease; border-right:1px solid var(--gray-05); |
| } |
| .sidebar.collapsed { width:var(--sidebar-w-closed); } |
| .sidebar-top { padding:10px 12px; display:flex; align-items:center; justify-content:flex-end; } |
| .sidebar.collapsed .sidebar-top { justify-content:center; } |
| .logo-wrap { display:flex; align-items:center; } |
| .logo-wrap img { height:30px; width:auto; } |
| .sidebar-toggle { |
| width:30px; height:30px; border-radius:4px; background:white; border:1px solid var(--gray-06); |
| display:flex; align-items:center; justify-content:center; color:var(--gray-02); padding:0; |
| } |
| .sidebar-toggle:hover { background:var(--gray-06); } |
| |
| .sidebar-scroll { |
| flex:1; overflow-y:auto; overflow-x:hidden; padding:0 12px 80px 12px; |
| scrollbar-width:none; |
| } |
| .sidebar-scroll::-webkit-scrollbar { display:none; } |
| |
| |
| .sb-property-head { |
| padding:12px 4px 8px; |
| display:flex; align-items:center; gap:8px; min-width:0; |
| } |
| .sb-property-head .prop-color-dot { |
| width:8px; height:8px; border-radius:50%; flex-shrink:0; |
| } |
| .sb-property-head .sb-prop-name { |
| flex:1; min-width:0; font-size:13px; font-weight:700; |
| color:var(--text-primary); |
| overflow:hidden; text-overflow:ellipsis; white-space:nowrap; |
| } |
| .sb-new-chat { |
| display:flex; align-items:center; gap:6px; |
| width:100%; |
| padding:8px 10px; |
| border:1px dashed var(--gray-04); border-radius:8px; |
| background:white; color:var(--primary); |
| font-family:inherit; font-size:12.5px; font-weight:700; |
| cursor:pointer; transition:all .15s; |
| margin-bottom:10px; |
| } |
| .sb-new-chat:hover { background:var(--primary-hover); border-color:var(--primary-focus); } |
| .sb-new-chat .lucide-icon svg { width:14px; height:14px; } |
| |
| .sb-section { margin-bottom:6px; } |
| .sb-section-head { |
| display:flex; align-items:center; gap:6px; |
| padding:6px 6px; |
| cursor:pointer; |
| color:var(--gray-02); |
| user-select:none; |
| border-radius:4px; |
| transition:background .12s; |
| } |
| .sb-section-head:hover { background:var(--gray-06); } |
| .sb-section-head .sb-chev { |
| color:var(--gray-03); display:inline-flex; |
| transition:transform .15s; |
| } |
| .sb-section-head .sb-chev svg { width:13px; height:13px; } |
| .sb-section-head.open .sb-chev { transform:rotate(90deg); } |
| .sb-section-head .sb-icon { color:#D97706; flex-shrink:0; display:inline-flex; } |
| .sb-section-head .sb-icon svg { width:14px; height:14px; } |
| .sb-section-head.open .sb-icon { color:#B45309; } |
| .sb-section-head .sb-section-name { |
| flex:1; min-width:0; |
| font-size:13px; font-weight:700; |
| color:var(--text-primary); |
| letter-spacing:.01em; |
| overflow:hidden; text-overflow:ellipsis; white-space:nowrap; |
| } |
| .sb-section-head .sb-count { |
| font-size:10.5px; font-weight:600; color:var(--gray-03); |
| background:var(--gray-06); padding:1px 7px; border-radius:999px; |
| flex-shrink:0; |
| } |
| |
| .sb-section-body { |
| padding:2px 0 6px 12px; |
| margin-left:6px; |
| border-left:1px dashed var(--gray-05); |
| } |
| |
| |
| .sb-section-body .folder-tree { gap:1px; } |
| .sb-section-body .folder-row { |
| padding:5px 8px; border-radius:5px; |
| font-size:12px; gap:6px; |
| } |
| .sb-section-body .folder-children { |
| padding-left:14px; margin-left:9px; gap:0; |
| } |
| .sb-section-body .folder-row.type .folder-name { font-size:12px; } |
| .sb-section-body .folder-row.year .folder-name { font-size:11.5px; } |
| .sb-section-body .folder-icon .lucide-icon svg { width:13px; height:13px; } |
| .sb-section-body .folder-chev .lucide-icon svg { width:12px; height:12px; } |
| .sb-section-body .folder-count { |
| font-size:10px; padding:0 6px; |
| } |
| .sb-section-body .folder-row.doc { |
| padding:5px 8px; gap:6px; |
| } |
| .sb-section-body .folder-doc-icon { |
| width:22px; height:22px; border-radius:4px; |
| } |
| .sb-section-body .folder-doc-icon .lucide-icon svg { width:12px; height:12px; } |
| .sb-section-body .folder-doc-name { font-size:11.5px; } |
| .sb-section-body .folder-doc-meta { font-size:10px; } |
| .sb-section-body .folder-doc-chev .lucide-icon svg { width:12px; height:12px; } |
| .sb-section-body .dl-doc-badge { font-size:9px; padding:0 5px; } |
| .sb-section-body .folder-latest { font-size:9px; padding:0 5px; } |
| .sb-section-body .dc-empty { |
| padding:10px 4px; font-size:11px; color:var(--gray-03); text-align:center; |
| } |
| |
| |
| .sb-section.manual { |
| border:1.5px dashed var(--gray-04); |
| border-radius:8px; |
| padding:2px 4px 6px; |
| transition:all .15s; |
| } |
| .sb-section.manual .sb-section-head .sb-icon { color:var(--primary); } |
| .sb-section.manual.drag-over { |
| border-color:var(--primary); |
| border-style:solid; |
| background:var(--primary-light); |
| } |
| .sb-section.manual.drag-over .sb-section-head { color:var(--primary-dark); } |
| |
| .sb-manual-drop-hint { |
| margin:6px 4px 0; |
| padding:6px 8px; |
| display:flex; align-items:center; gap:6px; |
| color:var(--gray-02); |
| font-size:11px; line-height:1.3; |
| pointer-events:none; |
| } |
| .sb-manual-drop-hint .lucide-icon { color:var(--primary); flex-shrink:0; } |
| .sb-manual-drop-hint .lucide-icon svg { width:13px; height:13px; } |
| .sb-section.manual.drag-over .sb-manual-drop-hint { |
| color:var(--primary-dark); |
| } |
| |
| .sb-history-empty { |
| padding:8px 6px; font-size:11px; color:var(--gray-03); |
| } |
| .sb-history-row { |
| display:flex; align-items:center; gap:8px; |
| width:100%; text-align:left; |
| padding:6px 8px; |
| border:none; background:transparent; |
| border-radius:5px; |
| font-family:inherit; |
| font-size:12.5px; color:var(--gray-01); |
| cursor:pointer; transition:background .12s; |
| margin-bottom:1px; |
| } |
| .sb-history-row:hover { background:var(--gray-06); } |
| .sb-history-row.active { |
| background:var(--primary-light); color:var(--primary-dark); font-weight:600; |
| } |
| .sb-history-row .lucide-icon { color:var(--gray-03); flex-shrink:0; } |
| .sb-history-row .lucide-icon svg { width:13px; height:13px; } |
| .sb-history-row.active .lucide-icon { color:var(--primary); } |
| .sb-history-row .label { |
| flex:1; min-width:0; |
| overflow:hidden; text-overflow:ellipsis; white-space:nowrap; |
| } |
| |
| .sb-history-head { |
| display:flex; align-items:center; gap:6px; |
| padding:6px 6px; |
| color:var(--text-primary); |
| } |
| .sb-history-head-name { |
| flex:1; min-width:0; |
| font-size:11.5px; font-weight:700; |
| text-transform:uppercase; letter-spacing:.04em; |
| } |
| .sb-history-head .sb-count { |
| font-size:10.5px; font-weight:600; color:var(--gray-03); |
| background:var(--gray-06); padding:1px 7px; border-radius:999px; |
| flex-shrink:0; |
| } |
| |
| .sb-prop-switch-wrap { |
| position:relative; |
| margin:8px 0 14px; |
| } |
| .sb-prop-switch-wrap .ch-prop-menu { |
| width:100%; min-width:240px; |
| left:0; right:0; |
| top:calc(100% + 6px); |
| } |
| .sb-prop-switch { |
| display:flex; align-items:center; gap:10px; |
| width:100%; padding:10px 12px; |
| background:var(--primary-light); |
| border:1.5px solid var(--primary-focus); |
| border-radius:10px; |
| cursor:pointer; transition:all .15s; |
| font-family:inherit; |
| box-shadow:0 1px 3px rgba(91,91,240,.10); |
| } |
| .sb-prop-switch:hover { |
| background:white; |
| border-color:var(--primary); |
| box-shadow:0 2px 10px rgba(91,91,240,.18); |
| } |
| .sb-prop-switch .sb-ps-icon { |
| width:34px; height:34px; border-radius:8px; |
| background:white; border:1px solid var(--primary-focus); |
| color:var(--primary); |
| display:inline-flex; align-items:center; justify-content:center; |
| flex-shrink:0; |
| } |
| .sb-prop-switch .sb-ps-icon .lucide-icon svg { width:17px; height:17px; } |
| .sb-prop-switch .sb-ps-text { flex:1; min-width:0; text-align:left; } |
| .sb-prop-switch .sb-ps-label { |
| font-size:9.5px; color:var(--primary); font-weight:700; |
| text-transform:uppercase; letter-spacing:.06em; |
| margin-bottom:1px; |
| } |
| .sb-prop-switch .sb-ps-name { |
| font-size:13px; font-weight:700; color:var(--text-primary); |
| line-height:1.25; |
| overflow:hidden; text-overflow:ellipsis; white-space:nowrap; |
| display:flex; align-items:center; gap:6px; |
| } |
| .sb-prop-switch .sb-ps-name .prop-color-dot { |
| width:8px; height:8px; border-radius:50%; flex-shrink:0; |
| } |
| .sb-prop-switch .sb-ps-chev { color:var(--primary); display:inline-flex; flex-shrink:0; } |
| .sb-prop-switch .sb-ps-chev svg { width:15px; height:15px; } |
| |
| |
| .sb-load-more { |
| display:flex; align-items:center; justify-content:center; gap:4px; |
| width:100%; padding:7px 8px; |
| border:none; background:transparent; |
| font-family:inherit; |
| font-size:11.5px; color:var(--gray-02); font-weight:600; |
| cursor:pointer; border-radius:5px; |
| transition:all .15s; |
| margin-top:3px; |
| } |
| .sb-load-more:hover { background:var(--primary-hover); color:var(--primary); } |
| .sb-load-more .lucide-icon svg { width:12px; height:12px; } |
| .sb-load-more .sb-lm-count { |
| font-weight:500; color:var(--gray-03); |
| } |
| .sb-section.chats .sb-section-head .sb-icon { color:var(--primary); } |
| .sb-section.chats .sb-section-head.open .sb-icon { color:var(--primary-dark); } |
| |
| |
| .property-card { |
| background:white; border:1px solid var(--gray-05); border-radius:6px; |
| margin-bottom:8px; overflow:hidden; |
| } |
| .property-card.favorite { background:#FFFCF2; border-color:#F2E2A6; } |
| .property-card-header { |
| display:flex; align-items:center; gap:6px; padding:10px 10px 10px 8px; |
| cursor:pointer; user-select:none; transition:background .15s; |
| } |
| .property-card-header:hover { background:var(--gray-07); } |
| .property-card.favorite .property-card-header:hover { background:#FFF8E0; } |
| .property-card-header .chev { flex-shrink:0; color:var(--gray-03); transition:transform .15s; display:inline-flex; } |
| .property-card-header .chev svg { width:14px; height:14px; } |
| .property-card-header.expanded .chev { transform:rotate(90deg); } |
| .property-card-header .star { color:var(--star); flex-shrink:0; display:inline-flex; } |
| .property-card-header .star svg { width:13px; height:13px; } |
| .property-card-header .prop-color-dot { |
| width:8px; height:8px; border-radius:50%; flex-shrink:0; |
| } |
| .property-card-header .chat-badge { |
| font-size:10.5px; color:var(--gray-02); |
| font-variant-numeric:tabular-nums; font-weight:600; |
| padding:1px 7px; border-radius:999px; |
| background:var(--gray-06); min-width:20px; text-align:center; |
| flex-shrink:0; |
| } |
| .property-card.favorite .property-card-header .chat-badge { |
| background:rgba(0,0,0,.06); color:var(--text-primary); |
| } |
| .property-card-header .name { |
| flex:1; min-width:0; font-size:13px; font-weight:700; color:var(--text-primary); |
| overflow:hidden; text-overflow:ellipsis; white-space:nowrap; |
| } |
| .property-card-header .new-chat-btn { |
| border:1px solid var(--gray-05); background:white; color:var(--primary); |
| padding:3px 6px; border-radius:5px; |
| display:inline-flex; align-items:center; justify-content:center; gap:1px; cursor:pointer; flex-shrink:0; |
| transition:all .15s; |
| } |
| .property-card-header .new-chat-btn:hover { background:var(--primary-light); border-color:var(--primary); } |
| .property-card-header .new-chat-btn .lucide-icon svg { width:11px; height:11px; } |
| |
| .chat-list { |
| padding:0 6px 8px; border-top:1px solid var(--gray-06); |
| } |
| .property-card.favorite .chat-list { border-top-color:#F0DDA0; } |
| .chat-row { |
| display:flex; align-items:center; gap:8px; padding:6px 8px; |
| border-radius:6px; width:100%; text-align:left; border:none; background:transparent; |
| font-size:12.5px; color:var(--gray-01); cursor:pointer; |
| transition:background .15s; margin-top:4px; |
| } |
| .chat-row:hover { background:var(--gray-06); } |
| .chat-row.active { background:var(--primary-light); color:var(--primary-dark); font-weight:700; } |
| .chat-row .lucide-icon { color:var(--gray-03); flex-shrink:0; } |
| .chat-row.active .lucide-icon { color:var(--primary); } |
| .chat-row .lucide-icon svg { width:13px; height:13px; } |
| .chat-row .label { |
| flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; |
| } |
| .chat-row.more { color:var(--gray-03); font-size:11.5px; justify-content:center; } |
| |
| .section-label { |
| font-size:13.5px; color:var(--text-primary); padding:6px 8px 8px; |
| letter-spacing:0; font-weight:700; |
| display:flex; align-items:center; justify-content:space-between; |
| } |
| .section-label .count { font-size:12px; color:var(--gray-03); font-weight:500; } |
| .section-label .add-btn { |
| width:22px; height:22px; border-radius:4px; border:none; background:transparent; |
| color:var(--gray-02); display:inline-flex; align-items:center; justify-content:center; |
| } |
| .section-label .add-btn:hover { background:var(--gray-06); color:var(--primary); } |
| .section-label .add-btn .lucide-icon svg { width:14px; height:14px; } |
| |
| .property-search { |
| margin:0 4px 8px; padding:6px 10px; display:flex; align-items:center; gap:6px; |
| background:white; border:1px solid var(--gray-05); border-radius:6px; |
| } |
| .property-search input { |
| flex:1; border:none; outline:none; font-size:13px; background:transparent; |
| } |
| .property-search i { color:var(--gray-03); font-size:14px; } |
| |
| .new-property-row { |
| display:flex; align-items:center; justify-content:center; gap:6px; padding:9px 10px; |
| border-radius:8px; cursor:pointer; transition:all .15s; |
| width:100%; text-align:center; background:white; |
| font-size:12.5px; color:var(--primary); font-weight:700; margin-bottom:10px; |
| border:1px dashed var(--gray-04); |
| } |
| .new-property-row:hover { background:var(--primary-hover); border-color:var(--primary-focus); } |
| .new-property-row .lucide-icon svg { width:14px; height:14px; } |
| |
| |
| .date-group { margin-bottom:14px; } |
| .date-group-label { |
| font-size:10.5px; font-weight:600; color:var(--gray-03); |
| padding:8px 10px 6px; text-transform:uppercase; letter-spacing:.06em; |
| } |
| .flat-chat-row { |
| display:flex; align-items:center; gap:8px; |
| width:100%; text-align:left; padding:6px 10px; |
| border:none; background:transparent; border-radius:4px; |
| font-size:12.5px; cursor:pointer; transition:background .15s; |
| margin-bottom:1px; |
| color:var(--text-primary); |
| } |
| .flat-chat-row:hover { background:var(--gray-06); } |
| .flat-chat-row.active { |
| background:var(--primary-light); color:var(--primary-dark); font-weight:600; |
| } |
| .flat-chat-row .prop-dot { |
| width:7px; height:7px; border-radius:50%; flex-shrink:0; |
| } |
| .flat-chat-row .chat-label { |
| flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; |
| } |
| .flat-chat-row .prop-label { |
| font-size:10.5px; color:var(--gray-03); flex-shrink:0; |
| max-width:88px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; |
| } |
| .flat-chat-row.active .prop-label { color:var(--primary); } |
| |
| |
| .brand-mark { |
| width:30px; height:30px; border-radius:6px; |
| background:transparent; color:var(--text-primary); |
| display:flex; align-items:center; justify-content:center; flex-shrink:0; |
| border:1px solid var(--gray-05); cursor:pointer; padding:0; |
| transition:background .15s, border-color .15s; |
| } |
| .brand-mark:hover { background:var(--gray-06); border-color:var(--gray-04); } |
| .breadcrumb { |
| display:flex; align-items:center; gap:4px; min-width:0; flex:1; |
| font-size:13px; |
| } |
| .bc-root, .bc-item { |
| border:none; background:transparent; padding:4px 8px; border-radius:4px; |
| font-size:13px; color:var(--gray-02); cursor:pointer; |
| white-space:nowrap; overflow:hidden; text-overflow:ellipsis; |
| max-width:280px; font-family:inherit; |
| } |
| .bc-root:hover, .bc-item:hover { background:var(--gray-06); color:var(--text-primary); } |
| .bc-item.current { color:var(--text-primary); font-weight:600; cursor:default; } |
| .bc-item.current:hover { background:transparent; } |
| .bc-sep { color:var(--gray-04); font-size:13px; user-select:none; flex-shrink:0; } |
| |
| .product-brand { display:none; } |
| |
| .sidebar-footer { |
| position:absolute; bottom:0; left:0; right:0; height:60px; |
| border-top:1px solid var(--gray-05); background:var(--gray-07); |
| display:flex; align-items:center; padding:0 12px; |
| } |
| .menu-item { |
| display:flex; align-items:center; gap:10px; padding:8px 10px; |
| border-radius:6px; color:var(--text-primary); text-decoration:none; |
| font-size:14px; font-weight:500; cursor:pointer; transition:background .15s; |
| border:none; background:transparent; width:100%; text-align:left; |
| } |
| .menu-item:hover { background:var(--gray-06); } |
| .menu-item .lucide-icon { flex-shrink:0; color:var(--gray-02); } |
| .menu-item .lucide-icon svg { width:18px; height:18px; } |
| |
| |
| .main { |
| margin-left:var(--sidebar-w); |
| margin-top:var(--header-h); |
| height:calc(100dvh - var(--header-h)); |
| display:flex; flex-direction:row; min-width:0; |
| transition:margin-left .2s ease; |
| } |
| body.sb-collapsed .main { margin-left:var(--sidebar-w-closed); } |
| |
| .chat-pane { |
| flex:1; min-width:0; display:flex; flex-direction:column; height:100%; |
| position:relative; overflow:hidden; |
| } |
| .chat-container { |
| width:100%; max-width:820px; margin:0 auto; padding:0 24px; |
| display:flex; flex-direction:column; flex:1; min-height:0; |
| } |
| |
| .chat-scroll { |
| flex:1; overflow-y:auto; padding:24px 0 16px; min-height:0; |
| } |
| |
| |
| .chat-bottom-bar { |
| flex-shrink:0; |
| padding:8px 0 16px; |
| background:linear-gradient(to bottom, rgba(255,255,255,0) 0%, white 16px); |
| } |
| |
| |
| .chat-empty-hero { |
| flex:1; min-height:0; overflow-y:auto; |
| display:flex; flex-direction:column; |
| align-items:center; |
| padding:16px 0; |
| } |
| |
| .hero-block { width:100%; max-width:720px; margin:auto 0; } |
| |
| |
| .compose-area { |
| position:relative; |
| background:white; |
| border:1px solid var(--gray-04); |
| border-radius:10px; |
| transition:border-color .15s, box-shadow .15s; |
| } |
| .compose-area:focus-within { |
| border-color:var(--primary); |
| box-shadow:0 0 0 3px var(--primary-light); |
| } |
| .compose-area.drag-over { |
| border-color:var(--primary); |
| box-shadow:0 0 0 3px var(--primary-light); |
| } |
| |
| .compose-area .question-form { |
| border:none; background:transparent; |
| border-radius:0; |
| padding:8px 8px 8px 14px; |
| } |
| .compose-area .question-form:focus-within { |
| border-color:transparent; |
| box-shadow:none; |
| } |
| |
| .attach-zone { |
| display:flex; align-items:center; gap:10px; |
| padding:8px 12px; |
| background:var(--gray-07); |
| border-bottom:1px solid var(--gray-06); |
| cursor:pointer; |
| transition:background .15s; |
| } |
| .attach-zone:hover { background:var(--gray-06); } |
| .attach-zone.filled { |
| align-items:center; flex-wrap:nowrap; |
| cursor:default; |
| } |
| .attach-zone.filled:hover { background:var(--gray-07); } |
| .az-icon { |
| width:28px; height:28px; border-radius:6px; |
| background:white; border:1px dashed var(--gray-04); |
| color:var(--primary); |
| display:inline-flex; align-items:center; justify-content:center; |
| flex-shrink:0; |
| } |
| .attach-zone:hover .az-icon { border-color:var(--primary); } |
| .attach-zone.filled .az-icon { |
| background:var(--primary-light); border-style:solid; border-color:var(--primary-focus); |
| } |
| .az-icon .lucide-icon svg { width:14px; height:14px; } |
| .az-hint { |
| font-size:12.5px; color:var(--gray-02); font-weight:500; |
| } |
| .az-chips { |
| display:flex; flex-wrap:wrap; gap:6px; |
| flex:1; min-width:0; |
| } |
| .az-add-more { |
| display:inline-flex; align-items:center; |
| height:30px; padding:0 12px; |
| border:1px dashed var(--gray-04); border-radius:8px; |
| background:transparent; color:var(--gray-02); |
| font-size:12px; font-weight:600; |
| cursor:pointer; |
| } |
| .attach-zone.filled:hover .az-add-more, |
| .az-add-more:hover { border-color:var(--primary); color:var(--primary); } |
| |
| .attached-chip { |
| display:inline-flex; align-items:center; gap:6px; |
| max-width:240px; |
| height:30px; padding:0 6px 0 10px; |
| background:white; |
| border:1px solid var(--primary-focus); |
| border-radius:8px; |
| font-size:12px; color:var(--primary-dark); |
| } |
| .attached-chip .ac-icon { color:#b3010e; flex-shrink:0; display:inline-flex; } |
| .attached-chip .ac-icon .lucide-icon svg { width:14px; height:14px; } |
| .ac-name { |
| flex:1; min-width:0; font-weight:600; |
| overflow:hidden; text-overflow:ellipsis; white-space:nowrap; |
| } |
| .ac-size { color:var(--gray-03); font-variant-numeric:tabular-nums; flex-shrink:0; } |
| .ac-remove { |
| width:22px; height:22px; border-radius:5px; |
| border:none; background:transparent; color:var(--gray-02); |
| cursor:pointer; flex-shrink:0; |
| display:inline-flex; align-items:center; justify-content:center; |
| transition:all .12s; |
| } |
| .ac-remove:hover { background:var(--gray-06); color:var(--text-primary); } |
| .ac-remove .lucide-icon svg { width:12px; height:12px; } |
| |
| .drop-overlay { |
| position:absolute; inset:0; z-index:5; |
| display:flex; flex-direction:column; align-items:center; justify-content:center; |
| gap:8px; |
| background:rgba(91,91,240,.08); |
| border:2px dashed var(--primary); |
| border-radius:10px; |
| color:var(--primary-dark); |
| font-size:14px; font-weight:700; |
| pointer-events:none; |
| } |
| .drop-overlay .lucide-icon svg { width:28px; height:28px; } |
| |
| |
| .user-msg-files { |
| margin-top:6px; |
| display:flex; flex-wrap:wrap; gap:4px; |
| justify-content:flex-end; |
| } |
| .user-msg-file { |
| display:inline-flex; align-items:center; gap:5px; |
| padding:4px 10px; |
| background:var(--gray-06); color:var(--text-secondary); |
| border:1px solid var(--gray-05); border-radius:6px; |
| font-size:11.5px; |
| } |
| .user-msg-file .lucide-icon { color:#b3010e; flex-shrink:0; } |
| .user-msg-file .lucide-icon svg { width:12px; height:12px; } |
| |
| |
| .turn { margin-bottom:36px; } |
| |
| .msg-header { |
| display:flex; align-items:center; gap:8px; |
| font-size:11px; font-weight:600; |
| color:var(--text-primary); |
| text-transform:uppercase; letter-spacing:.06em; |
| margin-bottom:4px; |
| } |
| .msg-header .time { |
| font-weight:400; color:var(--gray-03); |
| text-transform:none; letter-spacing:0; |
| font-size:11px; |
| } |
| |
| .user-msg-wrap { |
| display:flex; flex-direction:column; |
| margin-bottom:20px; |
| } |
| .user-msg { |
| background:transparent; color:var(--text-primary); |
| padding:0; border-radius:0; max-width:none; |
| font-size:14.5px; line-height:1.65; |
| white-space:pre-wrap; word-break:break-word; |
| } |
| |
| .bot-msg-wrap { |
| display:flex; flex-direction:column; |
| } |
| .bot-msg-wrap .msg-header .name { color:var(--primary-dark); } |
| .bot-msg { |
| width:100%; min-width:0; |
| font-size:14.5px; line-height:1.7; color:var(--text-primary); |
| } |
| .bot-msg p { margin:0 0 12px; } |
| .bot-msg p:last-child { margin-bottom:0; } |
| .bot-msg ul, .bot-msg ol { margin:0 0 12px; padding-left:24px; } |
| .bot-msg li { margin-bottom:4px; } |
| .bot-msg strong { font-weight:700; } |
| .bot-msg .citation { |
| display:inline-flex; align-items:center; justify-content:center; min-width:18px; height:18px; padding:0 5px; |
| background:var(--primary-light); color:var(--primary-dark); border-radius:4px; |
| font-size:11px; font-weight:700; vertical-align:1px; margin:0 2px; cursor:pointer; |
| transition:background .15s; border:none; |
| } |
| .bot-msg .citation:hover { background:var(--primary-focus); } |
| .bot-msg .citation.active { background:var(--primary); color:white; } |
| |
| .msg-actions { |
| display:flex; gap:4px; margin-top:6px; |
| opacity:0; transition:opacity .15s; |
| } |
| .bot-msg-wrap:hover .msg-actions { opacity:1; } |
| .msg-action-btn { |
| width:28px; height:28px; border:none; background:transparent; border-radius:4px; |
| color:var(--gray-02); display:inline-flex; align-items:center; justify-content:center; |
| transition:background .15s; |
| } |
| .msg-action-btn:hover { background:var(--gray-06); } |
| .msg-action-btn .lucide-icon svg { width:14px; height:14px; } |
| |
| |
| .citation-list { |
| margin-top:16px; padding:12px; background:var(--gray-07); border-radius:8px; |
| border:1px solid var(--gray-06); |
| } |
| .citation-list-title { |
| font-size:12px; font-weight:700; color:var(--gray-02); margin-bottom:8px; |
| } |
| .citation-card { |
| display:flex; gap:8px; align-items:flex-start; padding:8px 10px; |
| border-radius:6px; cursor:pointer; transition:background .15s; |
| } |
| .citation-card:hover { background:white; } |
| .citation-card.active { background:white; box-shadow:inset 0 0 0 1px var(--primary-focus); } |
| .citation-card .num { |
| width:18px; height:18px; flex-shrink:0; |
| background:var(--primary-light); color:var(--primary-dark); |
| border-radius:4px; font-size:11px; font-weight:700; |
| display:flex; align-items:center; justify-content:center; |
| margin-top:2px; |
| } |
| .citation-card .body { flex:1; min-width:0; } |
| .citation-card .title { |
| font-size:13px; font-weight:500; color:var(--text-primary); |
| overflow:hidden; text-overflow:ellipsis; white-space:nowrap; |
| } |
| .citation-card .meta { font-size:11px; color:var(--gray-03); margin-top:2px; } |
| |
| |
| .typing { display:inline-flex; gap:4px; padding:4px 0; } |
| .typing span { |
| width:6px; height:6px; border-radius:50%; background:var(--gray-03); |
| animation: typing 1.2s infinite ease-in-out; |
| } |
| .typing span:nth-child(2) { animation-delay:.15s; } |
| .typing span:nth-child(3) { animation-delay:.3s; } |
| @keyframes typing { |
| 0%, 60%, 100% { transform: translateY(0); opacity:.4; } |
| 30% { transform: translateY(-4px); opacity:1; } |
| } |
| |
| |
| .input-wrap { |
| position:relative; padding:8px 0 8px; |
| display:flex; flex-direction:column; gap:8px; |
| } |
| .input-main { min-width:0; } |
| .filter-row { |
| display:flex; align-items:center; justify-content:flex-start; gap:8px; padding:0 4px 8px; flex-wrap:wrap; |
| } |
| .filter-chip { |
| display:inline-flex; align-items:center; gap:6px; height:30px; padding:0 12px; |
| border-radius:999px; border:1px solid var(--gray-05); background:white; |
| font-size:12px; color:var(--gray-01); cursor:pointer; transition:all .15s; |
| user-select:none; |
| } |
| .filter-chip:hover { border-color:var(--primary-focus); background:var(--primary-hover); } |
| .filter-chip.active { |
| border-color:var(--primary); background:var(--primary-light); color:var(--primary-dark); |
| } |
| .filter-chip .lucide-icon svg { width:13px; height:13px; } |
| .filter-chip .check { |
| width:14px; height:14px; border-radius:3px; border:1.5px solid var(--gray-04); |
| display:inline-flex; align-items:center; justify-content:center; flex-shrink:0; |
| background:white; transition:all .15s; |
| } |
| .filter-chip.active .check { |
| background:var(--primary); border-color:var(--primary); |
| } |
| .filter-chip.active .check .lucide-icon svg { width:10px; height:10px; color:white; } |
| .filter-chip .check .lucide-icon { color:white; } |
| .filter-hint { |
| font-size:11px; color:var(--gray-03); margin-left:auto; padding:0 4px; |
| } |
| |
| .fy-range-group { |
| display:inline-flex; align-items:center; gap:6px; |
| height:30px; padding:0 12px; |
| border-radius:999px; border:1px solid var(--gray-05); background:white; |
| } |
| .fy-range-group .lucide-icon svg { width:13px; height:13px; } |
| .fy-pill { |
| border:none; background:transparent; cursor:pointer; |
| display:inline-flex; align-items:center; gap:2px; |
| font-size:12.5px; color:var(--text-primary); font-weight:700; |
| padding:2px 4px; border-radius:4px; transition:background .15s; |
| } |
| .fy-pill:hover { background:var(--gray-06); } |
| .fy-pill .lucide-icon svg { width:12px; height:12px; color:var(--gray-03); } |
| .fy-menu { |
| position:absolute; bottom:calc(100% + 4px); left:0; z-index:50; |
| background:white; border:1px solid var(--gray-05); border-radius:8px; |
| box-shadow:0 4px 16px rgba(0,0,0,.08); padding:4px; min-width:90px; |
| max-height:220px; overflow-y:auto; |
| } |
| .fy-menu-item { |
| display:block; width:100%; text-align:left; padding:7px 12px; |
| border:none; background:transparent; border-radius:6px; |
| font-size:13px; color:var(--text-primary); cursor:pointer; |
| } |
| .fy-menu-item:hover { background:var(--gray-06); } |
| .fy-menu-item.active { background:var(--primary-light); color:var(--primary-dark); font-weight:700; } |
| .question-form { |
| border:1px solid var(--gray-04); border-radius:8px; background:white; |
| padding:8px 8px 8px 14px; transition:border-color .15s, box-shadow .15s; |
| position:relative; |
| } |
| .question-form:focus-within { |
| border-color:var(--primary); |
| box-shadow:0 0 0 3px var(--primary-light); |
| } |
| |
| |
| .qf-filters { |
| display:flex; flex-wrap:wrap; gap:6px; |
| padding:2px 0 8px; |
| border-bottom:1px dashed var(--gray-05); |
| margin-bottom:6px; |
| } |
| .qf-filter-chip { |
| display:inline-flex; align-items:center; gap:5px; |
| height:24px; padding:0 4px 0 8px; |
| border-radius:4px; background:var(--primary-light); color:var(--primary-dark); |
| font-size:11.5px; font-weight:500; |
| } |
| .qf-filter-chip .lucide-icon svg { width:11px; height:11px; } |
| .qf-filter-x { |
| width:16px; height:16px; border:none; background:transparent; |
| display:inline-flex; align-items:center; justify-content:center; |
| cursor:pointer; color:var(--primary-dark); border-radius:3px; |
| } |
| .qf-filter-x:hover { background:rgba(91,91,240,.18); } |
| .qf-filter-x .lucide-icon svg { width:10px; height:10px; } |
| |
| .slash-menu { |
| position:absolute; bottom:calc(100% + 8px); left:0; |
| width:300px; max-height:320px; overflow-y:auto; |
| background:white; border:1px solid var(--gray-05); border-radius:8px; |
| box-shadow:0 8px 28px rgba(0,0,0,.10); padding:4px; |
| z-index:60; |
| } |
| .slash-menu-label { |
| font-size:10px; color:var(--gray-03); font-weight:600; |
| text-transform:uppercase; letter-spacing:.06em; |
| padding:8px 10px 6px; |
| } |
| .slash-menu-item { |
| display:flex; align-items:center; gap:8px; width:100%; text-align:left; |
| padding:7px 10px; border:none; background:transparent; |
| border-radius:5px; font-size:13px; color:var(--text-primary); |
| cursor:pointer; |
| } |
| .slash-menu-item:hover { background:var(--gray-06); } |
| .slash-menu-item .lucide-icon svg { width:14px; height:14px; color:var(--gray-02); } |
| .slash-menu-item .item-arrow { margin-left:auto; color:var(--gray-04); display:inline-flex; } |
| .slash-menu-item .item-arrow svg { width:12px; height:12px; } |
| .slash-menu-back { |
| display:flex; align-items:center; gap:6px; width:100%; text-align:left; |
| padding:8px 10px; border:none; background:transparent; |
| border-radius:5px; font-size:11px; color:var(--gray-02); |
| text-transform:uppercase; letter-spacing:.06em; font-weight:600; |
| cursor:pointer; |
| border-bottom:1px solid var(--gray-06); margin-bottom:4px; |
| } |
| .slash-menu-back:hover { color:var(--text-primary); } |
| .slash-menu-back .lucide-icon svg { width:12px; height:12px; } |
| .slash-hint { |
| font-size:11px; color:var(--gray-03); padding:0 6px; |
| } |
| .slash-hint kbd { |
| display:inline-block; padding:1px 5px; border-radius:3px; |
| border:1px solid var(--gray-04); background:white; |
| font-size:10.5px; font-family:inherit; color:var(--gray-02); |
| } |
| .question-row { |
| display:flex; align-items:flex-end; gap:8px; |
| } |
| .question-input { |
| flex:1; border:none; outline:none; background:transparent; |
| padding:8px 0; font-size:15px; line-height:1.6; color:var(--text-primary); |
| resize:none; min-height:24px; max-height:200px; |
| } |
| .question-input::placeholder { color:var(--gray-03); } |
| .form-toolbar { display:flex; align-items:center; gap:4px; padding-top:6px; flex-wrap:wrap; } |
| .toolbar-icon-btn { |
| width:32px; height:32px; border-radius:6px; border:none; background:transparent; |
| color:var(--gray-02); display:inline-flex; align-items:center; justify-content:center; |
| } |
| .toolbar-icon-btn:hover { background:var(--gray-06); } |
| @keyframes mic-pulse { |
| 0%, 100% { box-shadow: 0 0 0 0 rgba(91, 91, 240, 0.4); } |
| 60% { box-shadow: 0 0 0 7px rgba(91, 91, 240, 0); } |
| } |
| .toolbar-icon-btn.recording { |
| background: var(--primary); color: white; |
| animation: mic-pulse 1.4s ease-out infinite; |
| } |
| .send-btn { |
| width:32px; height:32px; border-radius:6px; border:none; background:var(--primary); color:white; |
| display:inline-flex; align-items:center; justify-content:center; transition:background .15s; |
| } |
| .send-btn:hover:not(:disabled) { background:var(--primary-dark); } |
| .send-btn:disabled { background:var(--gray-04); cursor:not-allowed; } |
| .send-btn .lucide-icon svg { width:18px; height:18px; color:white; } |
| |
| .input-top-row { |
| display:flex; align-items:center; justify-content:flex-end; gap:10px; |
| } |
| .new-chat-btn-top { |
| display:inline-flex; align-items:center; gap:5px; |
| height:30px; padding:0 14px; |
| border-radius:999px; |
| border:1px solid var(--gray-05); |
| background:white; |
| font-size:12px; font-weight:500; color:var(--gray-01); |
| cursor:pointer; transition:all .15s; |
| white-space:nowrap; flex-shrink:0; |
| } |
| .new-chat-btn-top:hover { |
| border-color:var(--primary-focus); background:var(--primary-hover); color:var(--primary-dark); |
| } |
| .new-chat-btn-top .lucide-icon svg { width:13px; height:13px; } |
| |
| .model-selector { position:relative; } |
| .model-pill { |
| display:inline-flex; align-items:center; gap:6px; |
| height:32px; padding:0 8px 0 6px; |
| border-radius:8px; border:none; background:transparent; |
| font-size:12.5px; font-weight:500; color:var(--text-primary); |
| cursor:pointer; transition:background .15s; |
| } |
| .model-pill:hover { background:var(--gray-06); } |
| .model-pill .chev { color:var(--gray-03); display:inline-flex; } |
| .model-pill .chev svg { width:13px; height:13px; } |
| .model-pill .sparkle { |
| width:20px; height:20px; border-radius:5px; |
| background:var(--primary-light); |
| color:var(--primary); |
| display:inline-flex; align-items:center; justify-content:center; |
| flex-shrink:0; |
| } |
| .model-pill .sparkle .lucide-icon svg { width:12px; height:12px; color:var(--primary); } |
| .model-menu { |
| position:absolute; bottom:calc(100% + 4px); right:0; z-index:50; |
| background:white; border:1px solid var(--gray-05); border-radius:8px; |
| box-shadow:0 4px 16px rgba(0,0,0,.10); padding:4px; min-width:220px; |
| } |
| .model-menu-item { |
| display:flex; align-items:center; width:100%; text-align:left; |
| padding:8px 12px; border:none; background:transparent; border-radius:6px; |
| font-size:13px; color:var(--text-primary); cursor:pointer; gap:8px; |
| } |
| .model-menu-item:hover { background:var(--gray-06); } |
| .model-menu-item.active { background:var(--primary-light); color:var(--primary-dark); font-weight:700; } |
| .model-menu-item .check-mark { margin-left:auto; color:var(--primary); display:inline-flex; } |
| .model-menu-item .check-mark svg { width:14px; height:14px; } |
| |
| .guideline-notice { |
| text-align:center; font-size:11px; color:var(--gray-03); padding:6px 0 12px; |
| } |
| |
| |
| |
| body.settings-view .sidebar { display:none; } |
| body.settings-view .main { margin-left:0; } |
| |
| .chat-home-screen { |
| flex:1; min-width:0; height:100%; overflow-y:auto; |
| display:flex; flex-direction:column; align-items:center; |
| padding:48px 24px 96px; |
| } |
| .ch-container { |
| width:100%; max-width:760px; |
| margin:auto 0; |
| } |
| .ch-hero { |
| text-align:center; margin-bottom:24px; |
| } |
| .ch-hero h1 { |
| font-size:24px; font-weight:600; color:var(--text-primary); |
| margin:0; letter-spacing:.01em; |
| line-height:1.5; |
| } |
| |
| .ch-h1-pill-wrap { |
| display:inline-block; |
| position:relative; |
| vertical-align:middle; |
| } |
| .ch-h1-pill { |
| display:inline-flex; align-items:center; gap:8px; |
| padding:6px 14px 6px 12px; |
| background:var(--primary-light); |
| border:1.5px solid var(--primary-focus); |
| border-radius:12px; |
| font-family:inherit; |
| font-size:22px; font-weight:600; |
| color:var(--primary-dark); |
| cursor:pointer; transition:all .15s; |
| letter-spacing:.01em; |
| line-height:1.2; |
| } |
| .ch-h1-pill:hover { |
| background:white; |
| border-color:var(--primary); |
| box-shadow:0 2px 10px rgba(91,91,240,.18); |
| } |
| .ch-h1-pill .lucide-icon { color:var(--primary); flex-shrink:0; display:inline-flex; } |
| .ch-h1-pill .lucide-icon svg { width:18px; height:18px; } |
| .ch-h1-pill .chev { color:var(--primary); display:inline-flex; } |
| .ch-h1-pill .chev svg { width:18px; height:18px; } |
| .ch-h1-pill-wrap .ch-prop-menu { |
| top:calc(100% + 6px); left:0; right:auto; |
| text-align:left; |
| font-size:13px; font-weight:400; |
| color:var(--text-primary); |
| letter-spacing:0; |
| } |
| |
| |
| .ch-prop-bar { |
| display:flex; justify-content:flex-start; align-items:center; gap:8px; |
| padding:0 6px 8px; |
| position:relative; |
| } |
| .ch-prop-pill { |
| display:inline-flex; align-items:center; gap:8px; |
| height:34px; padding:0 12px; |
| background:white; border:1px solid var(--gray-04); |
| border-radius:999px; |
| font-family:inherit; font-size:13px; |
| color:var(--text-primary); cursor:pointer; |
| transition:all .15s; |
| } |
| .ch-prop-pill:hover { border-color:var(--primary-focus); background:var(--primary-hover); } |
| .ch-prop-pill.empty { |
| color:var(--primary-dark); border-color:var(--primary-focus); |
| background:var(--primary-light); |
| } |
| .ch-prop-pill .lucide-icon { color:var(--primary); flex-shrink:0; } |
| .ch-prop-pill .lucide-icon svg { width:14px; height:14px; } |
| .ch-prop-pill .chev { color:var(--gray-03); display:inline-flex; } |
| .ch-prop-pill .chev svg { width:13px; height:13px; } |
| .ch-prop-pill .ch-pill-name { font-weight:600; } |
| |
| .ch-prop-menu { |
| position:absolute; top:calc(100% + 4px); left:6px; |
| width:340px; max-height:420px; overflow-y:auto; |
| background:white; border:1px solid var(--gray-05); |
| border-radius:10px; box-shadow:0 8px 28px rgba(0,0,0,.10); |
| padding:6px; z-index:60; |
| } |
| .ch-prop-menu .pm-search { |
| display:flex; align-items:center; gap:6px; |
| padding:6px 10px; margin-bottom:4px; |
| border:1px solid var(--gray-05); border-radius:6px; |
| background:var(--gray-07); |
| } |
| .ch-prop-menu .pm-search .lucide-icon { color:var(--gray-03); } |
| .ch-prop-menu .pm-search .lucide-icon svg { width:13px; height:13px; } |
| .ch-prop-menu .pm-search input { |
| flex:1; border:none; outline:none; background:transparent; |
| font-family:inherit; font-size:12.5px; |
| } |
| .ch-prop-menu-item { |
| display:flex; align-items:center; gap:10px; |
| width:100%; text-align:left; |
| padding:9px 10px; border:none; background:transparent; |
| border-radius:6px; cursor:pointer; |
| font-family:inherit; |
| transition:background .12s; |
| } |
| .ch-prop-menu-item:hover { background:var(--gray-06); } |
| .ch-prop-menu-item.active { background:var(--primary-light); } |
| .ch-prop-menu-item .pm-dot { |
| width:8px; height:8px; border-radius:50%; flex-shrink:0; |
| } |
| .ch-prop-menu-item .pm-name { |
| flex:1; min-width:0; font-size:13px; font-weight:600; |
| color:var(--text-primary); |
| overflow:hidden; text-overflow:ellipsis; white-space:nowrap; |
| } |
| .ch-prop-menu-item.active .pm-name { color:var(--primary-dark); } |
| .ch-prop-menu-item .pm-meta { |
| font-size:10.5px; color:var(--gray-03); flex-shrink:0; |
| font-variant-numeric:tabular-nums; |
| } |
| .ch-prop-menu-item .pm-check { color:var(--primary); flex-shrink:0; display:inline-flex; } |
| .ch-prop-menu-item .pm-check svg { width:14px; height:14px; } |
| .ch-prop-menu-empty { |
| padding:18px; text-align:center; font-size:12px; color:var(--gray-03); |
| } |
| |
| .ch-compose-wrap { |
| display:flex; flex-direction:column; gap:8px; |
| } |
| .ch-disabled-hint { |
| text-align:center; font-size:12px; color:var(--gray-03); |
| padding:6px 0; |
| } |
| .ch-disabled-hint strong { color:var(--primary-dark); font-weight:600; } |
| |
| |
| .ch-suggest-sections { |
| display:flex; flex-direction:column; gap:18px; |
| padding:20px 4px 4px; |
| width:100%; max-width:560px; |
| margin:0 auto; |
| } |
| .ch-suggest-section { |
| display:flex; flex-direction:column; gap:8px; |
| align-items:center; |
| } |
| .ch-suggest-section-head { |
| display:inline-flex; align-items:center; gap:6px; |
| padding:0 4px; |
| font-size:11px; font-weight:700; |
| color:var(--gray-02); |
| text-transform:none; |
| letter-spacing:.04em; |
| text-align:center; |
| } |
| .ch-suggest-section-head .lucide-icon { color:var(--gray-03); flex-shrink:0; } |
| .ch-suggest-section-head .lucide-icon svg { width:12px; height:12px; } |
| .ch-suggest-grid { |
| display:grid; grid-template-columns:1fr 1fr; gap:8px; |
| width:100%; |
| justify-content:center; |
| } |
| |
| .ch-suggest-tile:only-child { |
| grid-column:1 / -1; |
| width:calc(50% - 4px); |
| justify-self:center; |
| } |
| @media (max-width: 520px) { |
| .ch-suggest-grid { grid-template-columns:1fr; } |
| } |
| .ch-suggest-tile { |
| display:flex; align-items:center; gap:8px; |
| padding:10px 12px; |
| background:white; border:1px solid var(--gray-05); |
| border-radius:8px; |
| font-family:inherit; |
| font-size:12.5px; color:var(--text-primary); |
| cursor:pointer; transition:all .15s; |
| text-align:left; |
| } |
| .ch-suggest-tile:hover { |
| border-color:var(--primary-focus); |
| background:var(--primary-hover); |
| color:var(--primary-dark); |
| } |
| .ch-suggest-tile-text { |
| flex:1; min-width:0; |
| overflow:hidden; text-overflow:ellipsis; white-space:nowrap; |
| } |
| .ch-suggest-tile-arrow { |
| color:var(--gray-04); flex-shrink:0; |
| display:inline-flex; align-items:center; justify-content:center; |
| transition:color .15s, transform .15s; |
| } |
| .ch-suggest-tile-arrow .lucide-icon svg { width:13px; height:13px; } |
| .ch-suggest-tile:hover .ch-suggest-tile-arrow { |
| color:var(--primary); |
| transform:translate(2px, -2px); |
| } |
| |
| .ch-home-footer { |
| position:fixed; bottom:12px; left:12px; |
| z-index:5; |
| } |
| |
| |
| |
| .home-screen { |
| flex:1; min-width:0; height:100%; overflow-y:auto; |
| padding:48px 32px 64px; |
| } |
| .home-container { |
| max-width:1100px; margin:0 auto; |
| } |
| .home-hero { |
| text-align:center; margin-bottom:40px; |
| } |
| .home-hero h1 { |
| font-size:30px; font-weight:700; color:var(--text-primary); |
| margin:0 0 10px; letter-spacing:.02em; |
| } |
| .home-hero h1 .accent { color:var(--primary); } |
| .home-hero p { |
| font-size:14px; color:var(--gray-02); margin:0; |
| } |
| |
| .home-section { margin-bottom:36px; } |
| .home-section-title { |
| font-size:13px; font-weight:700; color:var(--text-primary); |
| margin:0 0 14px; padding:0 4px; |
| display:flex; align-items:center; gap:8px; |
| } |
| .home-section-title .lucide-icon { color:var(--gray-02); } |
| .home-section-title .lucide-icon svg { width:15px; height:15px; } |
| .home-section-title .star { color:var(--star); } |
| .home-section-title .star svg { fill:currentColor; } |
| .home-section-title .count { |
| font-size:12px; color:var(--gray-03); font-weight:500; |
| } |
| |
| .home-search-wrap { |
| max-width:420px; margin:0 0 16px; |
| background:white; border:1px solid var(--gray-04); border-radius:8px; |
| padding:8px 12px; display:flex; align-items:center; gap:8px; |
| transition:border-color .15s; |
| } |
| .home-search-wrap:focus-within { border-color:var(--primary); } |
| .home-search-wrap .lucide-icon { color:var(--gray-03); flex-shrink:0; } |
| .home-search-wrap .lucide-icon svg { width:15px; height:15px; } |
| .home-search-wrap input { |
| flex:1; border:none; outline:none; font-size:13px; background:transparent; |
| color:var(--text-primary); |
| } |
| .home-search-wrap input::placeholder { color:var(--gray-03); } |
| |
| .property-grid { |
| display:grid; grid-template-columns:repeat(auto-fill, minmax(280px, 1fr)); |
| gap:14px; |
| } |
| |
| |
| .property-table { |
| background:white; border:1px solid var(--gray-05); border-radius:6px; |
| overflow:hidden; |
| } |
| .pt-head, .pt-row { |
| display:grid; |
| grid-template-columns:38px minmax(0,1fr) minmax(0,1.6fr) 240px; |
| align-items:center; |
| gap:14px; |
| } |
| .pt-head { |
| font-size:10.5px; font-weight:600; color:var(--gray-03); |
| text-transform:uppercase; letter-spacing:.06em; |
| padding:10px 16px; background:var(--gray-07); |
| border-bottom:1px solid var(--gray-05); |
| } |
| .pt-row { |
| padding:14px 16px; |
| border-bottom:1px solid var(--gray-06); |
| font-size:13px; |
| transition:background .15s; |
| } |
| .pt-row:last-child { border-bottom:none; } |
| .pt-row:hover { background:var(--gray-07); } |
| .pt-col-fav { display:flex; align-items:center; justify-content:center; } |
| .pt-col-fav .pg-star { width:28px; height:28px; } |
| .pt-col-fav .pg-star .lucide-icon svg { width:16px; height:16px; } |
| .pt-col-name { |
| display:flex; align-items:center; gap:9px; |
| font-size:15px; font-weight:600; |
| min-width:0; color:var(--text-primary); |
| letter-spacing:-.005em; |
| } |
| .pt-col-name > .lucide-icon { color:var(--gray-02); flex-shrink:0; } |
| .pt-col-name > .lucide-icon svg { width:16px; height:16px; } |
| .pt-col-name .pt-name-text { |
| overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0; |
| } |
| .pt-col-update { |
| display:flex; align-items:baseline; gap:10px; |
| min-width:0; |
| } |
| .pt-col-update .update-date { |
| font-size:12.5px; color:var(--text-primary); |
| font-variant-numeric:tabular-nums; font-weight:500; |
| flex-shrink:0; |
| } |
| .pt-col-update .update-label { |
| font-size:12.5px; color:var(--gray-02); |
| overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0; |
| } |
| .pt-col-actions { |
| display:flex; gap:6px; justify-content:flex-end; |
| } |
| @media (max-width: 900px) { |
| .pt-head, .pt-row { grid-template-columns:32px 1fr 1.4fr; } |
| .pt-col-actions { display:none; } |
| } |
| |
| .pg-card { |
| background:white; border:1px solid var(--gray-05); border-radius:6px; |
| padding:14px 16px 12px; transition:all .15s; |
| display:flex; flex-direction:column; gap:10px; text-align:left; |
| } |
| .pg-card:hover { |
| border-color:var(--primary-focus); |
| box-shadow:0 2px 8px rgba(0,60,127,.10); |
| } |
| .pg-card.favorite { |
| background:#FFFCF2; border-color:#F2E2A6; |
| } |
| .pg-card.favorite:hover { |
| border-color:#E0C66B; |
| box-shadow:0 2px 8px rgba(250,175,0,.12); |
| } |
| .pg-card .pg-head { |
| display:flex; align-items:center; gap:8px; |
| } |
| .pg-card .pg-head > .lucide-icon { color:var(--gray-02); flex-shrink:0; } |
| .pg-card .pg-head > .lucide-icon svg { width:16px; height:16px; } |
| .pg-card .pg-name { |
| font-size:15px; font-weight:700; color:var(--text-primary); |
| flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; |
| } |
| .pg-star { |
| flex-shrink:0; |
| width:30px; height:30px; border-radius:6px; |
| border:none; background:transparent; cursor:pointer; |
| color:var(--gray-04); |
| display:inline-flex; align-items:center; justify-content:center; |
| transition:all .15s; |
| } |
| .pg-star:hover { background:var(--gray-06); color:var(--star); } |
| .pg-star.on { color:var(--star); } |
| .pg-star.on svg { fill:currentColor; } |
| .pg-star .lucide-icon svg { width:18px; height:18px; } |
| |
| .pg-card .pg-meta { |
| display:flex; flex-direction:column; gap:2px; |
| font-size:12.5px; color:var(--text-primary); line-height:1.5; |
| } |
| .pg-card .pg-meta .meta-line { |
| overflow:hidden; text-overflow:ellipsis; white-space:nowrap; |
| } |
| .pg-card .pg-meta .label { |
| color:var(--gray-03); margin-right:4px; |
| } |
| .pg-card .pg-meta .meta-date { |
| font-size:11.5px; color:var(--gray-03); |
| } |
| |
| .pg-card .pg-bottom { |
| display:flex; align-items:center; justify-content:space-between; |
| padding-top:10px; margin-top:2px; gap:8px; |
| border-top:1px solid var(--gray-06); |
| } |
| .pg-card.favorite .pg-bottom { border-top-color:#F0DDA0; } |
| .pg-card .pg-bottom .chat-count { |
| display:inline-flex; align-items:center; gap:5px; |
| font-size:11.5px; color:var(--gray-02); font-weight:500; |
| } |
| .pg-card .pg-bottom .chat-count .lucide-icon { color:var(--gray-03); } |
| .pg-card .pg-bottom .chat-count .lucide-icon svg { width:13px; height:13px; } |
| |
| .pg-actions { display:flex; gap:6px; } |
| .pg-action-btn { |
| display:inline-flex; align-items:center; gap:5px; |
| height:28px; padding:0 10px; |
| border-radius:6px; border:1px solid var(--gray-05); |
| background:white; font-size:11.5px; font-weight:500; |
| color:var(--text-primary); cursor:pointer; transition:all .15s; |
| white-space:nowrap; |
| } |
| .pg-action-btn:hover { |
| background:var(--primary-hover); border-color:var(--primary-focus); color:var(--primary-dark); |
| } |
| .pg-action-btn.primary { |
| background:var(--primary); color:white; border-color:var(--primary); |
| } |
| .pg-action-btn.primary:hover { |
| background:var(--primary-dark); border-color:var(--primary-dark); color:white; |
| } |
| .pg-action-btn .lucide-icon svg { width:12px; height:12px; } |
| |
| .home-empty { |
| padding:40px 16px; text-align:center; |
| color:var(--gray-03); font-size:13px; |
| background:white; border:1px dashed var(--gray-04); border-radius:6px; |
| } |
| |
| .product-brand.clickable { cursor:pointer; padding:4px 10px 4px 4px; border-radius:8px; transition:background .15s; } |
| .product-brand.clickable:hover { background:rgba(255,255,255,.55); } |
| |
| .home-actions { |
| display:flex; justify-content:flex-end; gap:8px; margin-bottom:12px; |
| } |
| .upload-btn-primary { |
| display:inline-flex; align-items:center; gap:6px; |
| height:36px; padding:0 14px; |
| border-radius:8px; border:1px solid var(--primary); |
| background:var(--primary); color:white; font-size:13px; font-weight:700; |
| cursor:pointer; transition:all .15s; |
| } |
| .upload-btn-primary:hover { |
| background:var(--primary-dark); border-color:var(--primary-dark); |
| } |
| .upload-btn-primary .lucide-icon svg { width:14px; height:14px; } |
| |
| |
| .upload-back-btn { |
| width:34px; height:34px; border-radius:6px; border:1px solid var(--gray-05); |
| background:white; color:var(--gray-02); cursor:pointer; |
| display:inline-flex; align-items:center; justify-content:center; |
| transition:all .15s; flex-shrink:0; |
| } |
| .upload-back-btn:hover { background:var(--gray-06); color:var(--text-primary); } |
| .upload-back-btn .lucide-icon svg { width:16px; height:16px; } |
| .upload-title-block { flex:1; min-width:0; } |
| .upload-title { |
| font-size:22px; font-weight:700; color:var(--text-primary); margin:0; |
| } |
| .upload-subtitle { |
| font-size:13px; color:var(--gray-02); margin:2px 0 0; |
| display:inline-flex; align-items:center; gap:6px; |
| } |
| .upload-subtitle .lucide-icon { color:var(--gray-03); } |
| .upload-subtitle .lucide-icon svg { width:13px; height:13px; } |
| |
| |
| .documents-screen { |
| flex:1; min-width:0; height:100%; |
| display:flex; flex-direction:column; |
| overflow:hidden; |
| background:var(--gray-07); |
| } |
| .documents-header { |
| display:flex; align-items:center; gap:14px; |
| padding:18px 28px; |
| border-bottom:1px solid var(--gray-05); |
| background:white; |
| flex-shrink:0; |
| } |
| .documents-filter { |
| display:inline-flex; align-items:center; gap:8px; |
| flex-shrink:0; |
| } |
| .df-label { |
| display:inline-flex; align-items:center; gap:6px; |
| font-size:14px; color:var(--gray-02); font-weight:700; |
| } |
| .df-label .lucide-icon { color:var(--gray-03); } |
| .df-label .lucide-icon svg { width:16px; height:16px; } |
| |
| .documents-filter .fy-pill { |
| height:42px; padding:0 16px; |
| background:white; border:1.5px solid var(--gray-05); |
| border-radius:10px; font-size:15px; font-weight:700; |
| gap:6px; |
| } |
| .documents-filter .fy-pill:hover { |
| background:var(--gray-07); border-color:var(--primary-focus); |
| } |
| .documents-filter .fy-pill .lucide-icon svg { width:16px; height:16px; } |
| .documents-filter .fy-menu { |
| bottom:auto; top:calc(100% + 6px); right:0; left:auto; |
| min-width:160px; |
| } |
| .documents-filter .fy-menu-item { font-size:14px; padding:9px 14px; } |
| |
| .documents-columns { |
| flex:1; min-height:0; min-width:0; |
| display:grid; grid-template-columns:1fr 1fr; |
| gap:18px; |
| padding:18px 28px 28px; |
| overflow-y:auto; |
| } |
| @media (max-width: 900px) { |
| .documents-columns { grid-template-columns:1fr; } |
| } |
| .dc-col { |
| background:white; |
| border:1px solid var(--gray-05); |
| border-radius:12px; |
| display:flex; flex-direction:column; |
| min-width:0; min-height:0; |
| overflow:hidden; |
| } |
| .dc-col-head { |
| display:flex; align-items:center; gap:12px; |
| padding:14px 18px; |
| min-height:68px; |
| border-bottom:1px solid var(--gray-06); |
| } |
| .dc-col-icon { |
| width:36px; height:36px; border-radius:8px; |
| display:inline-flex; align-items:center; justify-content:center; |
| flex-shrink:0; |
| } |
| .dc-col-icon.mkcore { background:var(--primary-light); color:var(--primary); } |
| .dc-col-icon.manual { background:#FFF4E5; color:#D97706; } |
| .dc-col-icon .lucide-icon svg { width:18px; height:18px; } |
| .dc-col-titles { flex:1; min-width:0; } |
| .dc-col-title-row { |
| display:flex; align-items:center; gap:10px; |
| } |
| .dc-col-title { |
| font-size:16px; font-weight:700; color:var(--text-primary); |
| letter-spacing:-.005em; |
| } |
| .dc-col-count { |
| font-size:11.5px; color:var(--gray-02); font-weight:600; |
| background:var(--gray-06); padding:2px 9px; border-radius:999px; |
| } |
| .dc-col-desc { |
| margin-top:1px; |
| font-size:11.5px; color:var(--gray-03); line-height:1.4; |
| } |
| .dc-col-body { |
| flex:1; min-height:0; |
| padding:14px 16px 18px; |
| overflow-y:auto; |
| } |
| .dc-empty { |
| padding:36px 8px; text-align:center; |
| font-size:12.5px; color:var(--gray-03); |
| } |
| |
| |
| .folder-tree { |
| display:flex; flex-direction:column; gap:2px; |
| font-family:inherit; |
| } |
| .folder-node { display:flex; flex-direction:column; } |
| .folder-children { |
| display:flex; flex-direction:column; gap:1px; |
| padding-left:18px; |
| margin:1px 0 4px; |
| border-left:1px dashed var(--gray-05); |
| margin-left:11px; |
| } |
| .folder-row-wrap { |
| display:flex; align-items:center; gap:4px; |
| } |
| .folder-row-wrap > .folder-row { flex:1; min-width:0; } |
| .folder-row { |
| width:100%; text-align:left; |
| display:flex; align-items:center; gap:8px; |
| padding:8px 10px; |
| border:1px solid transparent; border-radius:8px; |
| background:transparent; |
| cursor:pointer; transition:all .12s; |
| font-family:inherit; |
| color:var(--text-primary); |
| user-select:none; |
| } |
| .folder-add { |
| height:38px; padding:0 16px 0 13px; |
| border-radius:999px; |
| border:1.5px solid var(--primary); |
| background:var(--primary); color:white; |
| cursor:pointer; flex-shrink:0; |
| display:inline-flex; align-items:center; gap:7px; |
| font-family:inherit; |
| font-size:13px; font-weight:700; letter-spacing:.01em; |
| transition:all .15s; |
| align-self:center; |
| box-shadow:0 1px 4px rgba(91,91,240,.20); |
| } |
| .folder-add:hover { |
| background:var(--primary-dark); border-color:var(--primary-dark); |
| box-shadow:0 3px 10px rgba(91,91,240,.30); |
| } |
| .folder-add .lucide-icon { color:inherit; } |
| .folder-add .lucide-icon svg { width:15px; height:15px; } |
| .folder-add-label { letter-spacing:.02em; } |
| @media (max-width: 1100px) { |
| .folder-add-label { display:none; } |
| .folder-add { padding:0; width:38px; justify-content:center; } |
| } |
| .folder-row:hover { |
| background:var(--gray-07); |
| border-color:var(--gray-05); |
| } |
| .folder-row.open { background:var(--gray-07); } |
| .folder-chev { |
| width:14px; flex-shrink:0; |
| display:inline-flex; align-items:center; justify-content:center; |
| color:var(--gray-03); |
| transition:transform .15s; |
| } |
| .folder-chev .lucide-icon svg { width:14px; height:14px; } |
| .folder-icon { |
| flex-shrink:0; |
| display:inline-flex; align-items:center; justify-content:center; |
| } |
| .folder-icon .lucide-icon svg { width:16px; height:16px; } |
| .folder-row.type .folder-icon { color:#D97706; } |
| .folder-row.type.open .folder-icon { color:#B45309; } |
| .folder-row.year .folder-icon { color:var(--primary); } |
| .folder-row.year.open .folder-icon { color:var(--primary-dark); } |
| .folder-name { |
| flex:1; min-width:0; |
| overflow:hidden; text-overflow:ellipsis; white-space:nowrap; |
| } |
| .folder-row.type .folder-name { font-size:14px; font-weight:700; } |
| .folder-row.year .folder-name { |
| font-size:13px; font-weight:600; color:var(--text-secondary); |
| font-variant-numeric:tabular-nums; |
| } |
| .folder-count { |
| font-size:11px; color:var(--gray-03); font-weight:500; |
| flex-shrink:0; |
| background:var(--gray-06); padding:1px 8px; border-radius:999px; |
| } |
| .folder-row.type:hover .folder-count, |
| .folder-row.type.open .folder-count { |
| background:white; border:1px solid var(--gray-05); |
| } |
| .folder-latest { |
| font-size:10px; font-weight:700; |
| padding:1px 7px; border-radius:999px; |
| background:var(--primary-light); color:var(--primary-dark); |
| flex-shrink:0; |
| } |
| |
| |
| .folder-row.doc { |
| padding:9px 10px; |
| gap:10px; |
| } |
| .folder-row.doc:hover { |
| background:var(--primary-hover); |
| border-color:var(--primary-focus); |
| } |
| .folder-row.doc:hover .folder-doc-chev { |
| color:var(--primary); transform:translateX(2px); |
| } |
| .folder-row.doc.selected { |
| background:var(--primary-light); |
| border-color:var(--primary); |
| box-shadow:inset 2px 0 0 var(--primary); |
| } |
| .folder-row.doc.selected .folder-doc-icon { |
| background:var(--primary); color:white; |
| } |
| .folder-row.doc.selected .folder-doc-name { color:var(--primary-dark); } |
| .folder-row.doc.selected .folder-doc-chev { color:var(--primary); } |
| .folder-doc-icon { |
| width:30px; height:30px; border-radius:6px; |
| background:#FEECEC; color:#b3010e; |
| display:inline-flex; align-items:center; justify-content:center; |
| flex-shrink:0; |
| } |
| .folder-doc-icon .lucide-icon svg { width:15px; height:15px; } |
| .folder-doc-body { flex:1; min-width:0; } |
| .folder-doc-name { |
| font-size:13px; color:var(--text-primary); font-weight:600; |
| display:flex; align-items:center; gap:6px; min-width:0; |
| } |
| .folder-doc-name > span:first-child { |
| overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0; |
| } |
| .folder-doc-meta { |
| font-size:11.5px; color:var(--gray-03); margin-top:3px; |
| font-variant-numeric:tabular-nums; |
| } |
| .folder-doc-chev { |
| color:var(--gray-04); flex-shrink:0; |
| display:inline-flex; align-items:center; justify-content:center; |
| transition:all .15s; |
| } |
| .folder-doc-chev .lucide-icon svg { width:14px; height:14px; } |
| |
| |
| .dl-doc-badge { |
| font-size:10px; font-weight:700; |
| padding:1px 7px; border-radius:999px; |
| background:var(--primary-light); color:var(--primary-dark); |
| flex-shrink:0; |
| } |
| |
| |
| .dpm-backdrop { |
| position:fixed; inset:0; z-index:200; |
| background:rgba(10, 10, 10, .55); |
| display:flex; align-items:center; justify-content:center; |
| padding:32px; |
| animation:dpm-fade .15s ease-out; |
| } |
| @keyframes dpm-fade { from { opacity:0; } to { opacity:1; } } |
| .dpm-card { |
| background:var(--gray-06); |
| border-radius:10px; |
| width:100%; max-width:920px; |
| max-height:100%; min-height:0; |
| display:flex; flex-direction:column; |
| overflow:hidden; |
| box-shadow:0 12px 40px rgba(0,0,0,.25); |
| } |
| .dpm-toolbar { |
| display:flex; align-items:center; gap:10px; |
| padding:12px 18px; |
| background:white; |
| border-bottom:1px solid var(--gray-05); |
| flex-shrink:0; |
| } |
| .dpm-toolbar > .lucide-icon { flex-shrink:0; } |
| .dpm-toolbar > .lucide-icon svg { width:18px; height:18px; } |
| .dpm-toolbar-body { flex:1; min-width:0; } |
| .dpm-doc-name { |
| font-size:14px; font-weight:600; color:var(--text-primary); |
| overflow:hidden; text-overflow:ellipsis; white-space:nowrap; |
| } |
| .dpm-doc-meta { |
| font-size:11.5px; color:var(--gray-03); margin-top:1px; |
| } |
| .dpm-close { |
| width:32px; height:32px; border-radius:6px; |
| border:1px solid var(--gray-05); background:white; color:var(--gray-02); |
| cursor:pointer; flex-shrink:0; |
| display:inline-flex; align-items:center; justify-content:center; |
| transition:all .15s; |
| } |
| .dpm-close:hover { background:var(--gray-06); color:var(--text-primary); } |
| .dpm-close .lucide-icon svg { width:16px; height:16px; } |
| .dpm-body { |
| flex:1; min-height:0; overflow-y:auto; |
| padding:28px; |
| display:flex; justify-content:center; |
| } |
| |
| |
| .mock-pdf-page { |
| aspect-ratio:auto; |
| max-width:720px; |
| min-height:920px; |
| padding:48px 56px; |
| } |
| |
| |
| .upm-card { |
| background:white; |
| border-radius:12px; |
| width:100%; max-width:560px; |
| max-height:100%; min-height:0; |
| display:flex; flex-direction:column; |
| overflow:hidden; |
| box-shadow:0 12px 40px rgba(0,0,0,.25); |
| } |
| .upm-head { |
| display:flex; align-items:flex-start; gap:12px; |
| padding:18px 20px; |
| border-bottom:1px solid var(--gray-05); |
| flex-shrink:0; |
| } |
| .upm-head > div:first-child { flex:1; min-width:0; } |
| .upm-title { |
| font-size:16px; font-weight:700; color:var(--text-primary); |
| } |
| .upm-sub { |
| margin-top:3px; |
| display:inline-flex; align-items:center; gap:5px; |
| font-size:12px; color:var(--gray-02); |
| } |
| .upm-sub .lucide-icon { color:var(--gray-03); } |
| .upm-sub .lucide-icon svg { width:13px; height:13px; } |
| .upm-body { |
| flex:1; min-height:0; overflow-y:auto; |
| padding:20px; |
| } |
| .upm-field-label { |
| font-size:12.5px; font-weight:700; color:var(--text-primary); |
| margin-bottom:10px; |
| } |
| .upm-cat-grid { |
| display:grid; grid-template-columns:repeat(2, 1fr); gap:8px; |
| } |
| .upm-cat-card { |
| display:inline-flex; align-items:center; gap:8px; |
| padding:12px 14px; |
| border:1.5px solid var(--gray-05); border-radius:8px; |
| background:white; |
| font-size:13px; color:var(--text-primary); |
| cursor:pointer; transition:all .15s; |
| user-select:none; |
| } |
| .upm-cat-card input { display:none; } |
| .upm-cat-card .lucide-icon { color:var(--gray-03); flex-shrink:0; } |
| .upm-cat-card .lucide-icon svg { width:15px; height:15px; } |
| .upm-cat-card:hover { border-color:var(--primary-focus); background:var(--primary-hover); } |
| .upm-cat-card.active { |
| border-color:var(--primary); background:var(--primary-light); |
| color:var(--primary-dark); font-weight:600; |
| } |
| .upm-cat-card.active .lucide-icon { color:var(--primary); } |
| |
| .upm-drop { |
| width:100%; |
| display:flex; align-items:center; gap:14px; |
| padding:20px; |
| border:1.5px dashed var(--gray-04); border-radius:10px; |
| background:var(--gray-07); |
| cursor:pointer; transition:all .15s; |
| font-family:inherit; text-align:left; |
| } |
| .upm-drop:hover { |
| border-color:var(--primary); background:var(--primary-hover); |
| } |
| .upm-drop.has-file { |
| border-style:solid; border-color:var(--primary-focus); |
| background:white; |
| } |
| .upm-drop > .lucide-icon { color:var(--primary); flex-shrink:0; } |
| .upm-drop > .lucide-icon svg { width:24px; height:24px; } |
| .upm-drop-text { flex:1; min-width:0; } |
| .upm-drop-main { |
| font-size:13.5px; font-weight:600; color:var(--text-primary); |
| overflow:hidden; text-overflow:ellipsis; white-space:nowrap; |
| } |
| .upm-drop-sub { |
| font-size:11.5px; color:var(--gray-03); margin-top:3px; |
| } |
| |
| .upm-foot { |
| display:flex; justify-content:flex-end; gap:8px; |
| padding:14px 20px; |
| border-top:1px solid var(--gray-05); |
| background:var(--gray-07); |
| flex-shrink:0; |
| } |
| .upm-foot .settings-btn .lucide-icon svg { width:14px; height:14px; } |
| |
| |
| .settings-screen { |
| flex:1; min-width:0; height:100%; overflow:hidden; |
| padding:0; |
| display:flex; |
| } |
| .settings-container { max-width:1280px; margin:0 auto; } |
| |
| .settings-twopane { |
| display:grid; grid-template-columns:240px 1fr; |
| width:100%; height:100%; min-height:0; |
| } |
| .settings-nav { |
| border-right:1px solid var(--gray-05); |
| background:var(--gray-07); |
| display:flex; flex-direction:column; |
| min-height:0; |
| padding:0; |
| } |
| .settings-nav-header { |
| padding:16px 16px 10px; |
| } |
| .settings-nav-body { |
| padding:8px 14px 28px; flex:1; overflow-y:auto; |
| display:flex; flex-direction:column; |
| gap:2px; |
| } |
| .settings-nav-section-title { |
| font-size:10.5px; font-weight:700; color:var(--gray-03); |
| text-transform:uppercase; letter-spacing:.06em; |
| padding:14px 10px 8px; |
| } |
| .settings-nav-item { |
| display:flex; align-items:center; gap:12px; |
| width:100%; text-align:left; |
| padding:11px 12px; border:none; background:transparent; |
| border-radius:8px; |
| font-size:14px; color:var(--text-primary); cursor:pointer; |
| font-family:inherit; |
| transition:background .15s; |
| } |
| .settings-nav-item:hover { background:var(--gray-06); } |
| .settings-nav-item.active { |
| background:var(--gray-06); color:var(--text-primary); font-weight:700; |
| } |
| .settings-nav-item .lucide-icon { color:var(--gray-02); flex-shrink:0; } |
| .settings-nav-item.active .lucide-icon { color:var(--text-primary); } |
| .settings-nav-item .lucide-icon svg { width:20px; height:20px; } |
| |
| .settings-content { |
| display:flex; flex-direction:column; |
| min-width:0; min-height:0; overflow:hidden; |
| } |
| .settings-content-header { |
| display:flex; align-items:center; gap:10px; |
| padding:18px 28px; |
| border-bottom:1px solid var(--gray-05); |
| flex-shrink:0; |
| background:white; |
| } |
| .settings-content-header h2 { |
| font-size:22px; font-weight:700; margin:0; |
| color:var(--text-primary); letter-spacing:-.005em; |
| } |
| .settings-content-body { |
| padding:24px 32px 80px; max-width:1080px; flex:1; |
| overflow-y:auto; |
| } |
| .settings-form { |
| display:flex; flex-direction:column; gap:24px; |
| } |
| .settings-empty { |
| padding:80px 20px; text-align:center; |
| color:var(--gray-03); font-size:13px; |
| } |
| |
| .settings-back-link { |
| display:inline-flex; align-items:center; gap:6px; |
| width:100%; justify-content:flex-start; |
| padding:10px 14px; |
| border-radius:8px; |
| border:1px solid var(--gray-05); background:white; |
| font-family:inherit; |
| font-size:13px; color:var(--text-primary); font-weight:500; |
| cursor:pointer; transition:all .15s; |
| } |
| .settings-back-link:hover { background:var(--gray-06); border-color:var(--gray-04); } |
| .settings-back-link .lucide-icon { color:var(--gray-02); } |
| .settings-back-link .lucide-icon svg { width:14px; height:14px; } |
| |
| .settings-top-bar { |
| display:flex; align-items:center; justify-content:space-between; |
| margin-bottom:8px; gap:8px; |
| } |
| .settings-page-actions { display:flex; gap:8px; } |
| |
| .settings-row { |
| display:flex; align-items:center; gap:24px; |
| padding:14px 0; |
| border-bottom:1px solid var(--gray-06); |
| } |
| .settings-row:last-child { border-bottom:none; } |
| .settings-row-label { |
| width:200px; flex-shrink:0; |
| font-size:13px; font-weight:600; color:var(--text-primary); |
| display:flex; align-items:center; gap:5px; |
| } |
| .settings-row-label .help-icon { color:var(--gray-03); display:inline-flex; } |
| .settings-row-label .help-icon svg { width:13px; height:13px; } |
| .settings-row-control { flex:1; min-width:0; } |
| .settings-row-hint { |
| font-size:11.5px; color:var(--gray-03); margin-top:4px; |
| } |
| |
| .settings-select { |
| display:inline-flex; align-items:center; gap:8px; |
| height:34px; padding:0 12px; min-width:220px; |
| background:white; border:1px solid var(--gray-05); border-radius:6px; |
| font-size:13px; color:var(--text-primary); cursor:pointer; |
| transition:border-color .15s; |
| } |
| .settings-select:hover { border-color:var(--primary-focus); } |
| .settings-select .chev { color:var(--gray-03); margin-left:auto; display:inline-flex; } |
| .settings-select .chev svg { width:14px; height:14px; } |
| |
| .settings-toggle { |
| width:40px; height:22px; border-radius:999px; |
| background:var(--gray-04); cursor:pointer; |
| position:relative; transition:background .15s; |
| flex-shrink:0; |
| } |
| .settings-toggle.on { background:var(--primary); } |
| .settings-toggle-knob { |
| position:absolute; top:2px; left:2px; |
| width:18px; height:18px; border-radius:50%; |
| background:white; transition:left .15s; |
| box-shadow:0 1px 2px rgba(0,0,0,.15); |
| } |
| .settings-toggle.on .settings-toggle-knob { left:20px; } |
| |
| |
| .home-footer-btn { |
| position:fixed; bottom:12px; left:12px; |
| display:inline-flex; align-items:center; gap:10px; |
| padding:8px 12px; |
| border-radius:6px; border:none; |
| background:transparent; |
| color:var(--text-primary); |
| font-size:14px; font-weight:500; |
| cursor:pointer; transition:background .15s; |
| z-index:5; |
| } |
| .home-footer-btn:hover { background:var(--gray-06); } |
| .home-footer-btn .lucide-icon { color:var(--gray-02); } |
| .home-footer-btn .lucide-icon svg { width:18px; height:18px; } |
| |
| .settings-tabs { |
| display:flex; gap:4px; |
| border-bottom:1px solid var(--gray-05); |
| margin-bottom:24px; |
| } |
| .settings-tab { |
| padding:10px 18px; border:none; background:transparent; |
| font-size:13.5px; font-weight:600; color:var(--gray-02); |
| border-bottom:2px solid transparent; margin-bottom:-1px; |
| cursor:pointer; transition:color .15s; |
| } |
| .settings-tab:hover { color:var(--text-primary); } |
| .settings-tab.active { |
| color:var(--primary); border-bottom-color:var(--primary); |
| } |
| |
| .settings-agent-header { |
| display:flex; align-items:center; gap:14px; |
| padding:8px 0 24px; |
| } |
| .settings-agent-icon { |
| width:44px; height:44px; border-radius:8px; |
| background:#0A0A0A; |
| color:white; flex-shrink:0; |
| display:flex; align-items:center; justify-content:center; |
| } |
| .settings-agent-icon .lucide-icon svg { width:24px; height:24px; } |
| .settings-agent-title { |
| flex:1; min-width:0; |
| font-size:20px; font-weight:700; color:var(--text-primary); |
| } |
| .settings-agent-actions { display:flex; gap:8px; flex-shrink:0; } |
| .settings-icon-btn { |
| width:36px; height:36px; border-radius:6px; |
| border:1px solid var(--gray-05); background:white; |
| color:var(--gray-02); cursor:pointer; |
| display:inline-flex; align-items:center; justify-content:center; |
| transition:all .15s; |
| } |
| .settings-icon-btn:hover { background:var(--gray-06); } |
| .settings-icon-btn .lucide-icon svg { width:16px; height:16px; } |
| .settings-btn { |
| height:36px; padding:0 16px; border-radius:6px; |
| font-size:13px; font-weight:600; |
| cursor:pointer; transition:all .15s; |
| display:inline-flex; align-items:center; gap:6px; |
| } |
| .settings-btn.danger { |
| background:var(--accent-red); color:white; border:1px solid var(--accent-red); |
| } |
| .settings-btn.danger:hover { background:#8c0009; } |
| .settings-btn.primary { |
| background:var(--primary); color:white; border:1px solid var(--primary); |
| } |
| .settings-btn.primary:hover { background:var(--primary-dark); } |
| .settings-btn.primary:disabled { |
| background:var(--gray-04); border-color:var(--gray-04); |
| color:white; cursor:not-allowed; opacity:.7; |
| } |
| .settings-btn.outline { |
| background:white; color:var(--primary); |
| border:1px solid var(--primary); |
| } |
| .settings-btn.outline:hover { background:var(--primary-hover); } |
| .settings-btn.ghost { |
| background:white; color:var(--gray-02); |
| border:1px solid var(--gray-05); |
| } |
| .settings-btn.ghost:hover { background:var(--gray-06); color:var(--text-primary); } |
| .settings-btn.ghost:disabled { |
| color:var(--gray-04); cursor:not-allowed; background:white; |
| } |
| .settings-btn.ghost .lucide-icon svg { width:14px; height:14px; } |
| .settings-btn.primary .lucide-icon svg { width:14px; height:14px; } |
| .settings-btn.outline .lucide-icon svg { width:14px; height:14px; } |
| |
| |
| .account-pane { display:flex; flex-direction:column; gap:14px; } |
| .account-toolbar { |
| display:flex; align-items:center; gap:10px; |
| } |
| .account-title { |
| margin:0; font-size:18px; font-weight:700; color:var(--text-primary); |
| flex:1; |
| } |
| .account-toolbar-actions { |
| display:flex; align-items:center; gap:8px; |
| } |
| |
| .account-subbar { |
| display:flex; align-items:center; gap:16px; justify-content:space-between; |
| } |
| .account-search { |
| flex:1; max-width:320px; |
| display:flex; align-items:center; gap:8px; |
| height:36px; padding:0 12px; |
| background:white; border:1px solid var(--gray-05); border-radius:6px; |
| color:var(--gray-03); |
| } |
| .account-search:focus-within { border-color:var(--primary-focus); } |
| .account-search .lucide-icon svg { width:14px; height:14px; } |
| .account-search input { |
| flex:1; border:none; outline:none; background:transparent; |
| font-size:13px; color:var(--text-primary); |
| font-family:inherit; |
| } |
| .account-pagination { |
| display:flex; align-items:center; gap:14px; |
| font-size:12.5px; color:var(--gray-02); |
| } |
| .account-pagination .ap-label { color:var(--gray-03); } |
| .account-pagination .ap-select { |
| display:inline-flex; align-items:center; gap:4px; |
| height:28px; padding:0 8px; |
| background:white; border:1px solid var(--gray-05); border-radius:4px; |
| font-size:12.5px; color:var(--text-primary); cursor:pointer; |
| font-family:inherit; |
| } |
| .account-pagination .ap-select .lucide-icon svg { width:12px; height:12px; } |
| .account-pagination .ap-range { |
| color:var(--text-primary); font-variant-numeric:tabular-nums; |
| } |
| .account-pagination .ap-arrows { display:flex; gap:2px; } |
| .account-pagination .ap-arrows button { |
| width:28px; height:28px; border-radius:4px; |
| border:none; background:transparent; cursor:pointer; |
| color:var(--gray-02); |
| display:inline-flex; align-items:center; justify-content:center; |
| } |
| .account-pagination .ap-arrows button:hover { background:var(--gray-06); color:var(--text-primary); } |
| .account-pagination .ap-arrows .lucide-icon svg { width:14px; height:14px; } |
| |
| .account-table { |
| background:white; border:1px solid var(--gray-05); border-radius:6px; |
| overflow:hidden; |
| } |
| .at-head, .at-row { |
| display:grid; |
| grid-template-columns:48px minmax(0,1.4fr) minmax(0,1.6fr) 160px 48px; |
| align-items:center; |
| } |
| .at-head { |
| font-size:11px; font-weight:600; color:var(--gray-03); |
| text-transform:uppercase; letter-spacing:.04em; |
| padding:0; background:var(--gray-07); |
| border-bottom:1px solid var(--gray-05); |
| } |
| .at-head .at-cell { |
| padding:12px 14px; |
| display:flex; align-items:center; gap:6px; |
| font-size:12px; color:var(--gray-02); |
| text-transform:none; letter-spacing:0; |
| font-weight:600; |
| } |
| .at-head .at-sort { |
| color:var(--gray-03); display:inline-flex; |
| } |
| .at-head .at-sort .lucide-icon svg { width:12px; height:12px; } |
| .at-row { |
| border-bottom:1px solid var(--gray-06); |
| font-size:13px; color:var(--text-primary); |
| transition:background .15s; |
| } |
| .at-row:last-child { border-bottom:none; } |
| .at-row:hover { background:var(--gray-07); } |
| .at-row .at-cell { |
| padding:12px 14px; |
| overflow:hidden; text-overflow:ellipsis; white-space:nowrap; |
| } |
| .at-cell.at-check { |
| display:flex; align-items:center; justify-content:flex-start; |
| padding-left:18px; |
| } |
| .at-cell.at-check input[type="checkbox"] { |
| width:16px; height:16px; cursor:pointer; accent-color:var(--primary); |
| } |
| .at-cell.at-email { color:var(--gray-02); } |
| .at-cell.at-role { color:var(--text-primary); } |
| .at-cell.at-actions { |
| display:flex; justify-content:center; |
| } |
| .at-more { |
| width:28px; height:28px; border-radius:4px; |
| border:none; background:transparent; cursor:pointer; |
| color:var(--gray-03); |
| display:inline-flex; align-items:center; justify-content:center; |
| } |
| .at-more:hover { background:var(--gray-06); color:var(--text-primary); } |
| .at-more .lucide-icon svg { width:16px; height:16px; } |
| |
| |
| .property-name-text { |
| display:block; |
| font-size:13px; color:var(--text-primary); |
| font-weight:600; |
| overflow:hidden; text-overflow:ellipsis; white-space:nowrap; |
| max-width:100%; |
| } |
| .pt-mkcore-label { color:var(--text-primary); } |
| .pt-mkcore-date { color:var(--gray-03); margin-left:6px; font-variant-numeric:tabular-nums; } |
| |
| |
| |
| |
| .mode-row, .input-row { |
| display:flex; align-items:center; gap:12px; |
| padding:12px 0; |
| border-bottom:1px solid var(--gray-06); |
| } |
| .mode-row:last-child, .input-row:last-child { border-bottom:none; } |
| .mode-row .mode-label, .input-row .input-label { |
| flex:1; min-width:0; |
| display:flex; align-items:center; gap:9px; |
| font-size:13.5px; color:var(--text-primary); font-weight:500; |
| } |
| .mode-row .mode-label .lucide-icon, |
| .input-row .input-label .lucide-icon { |
| color:var(--gray-02); flex-shrink:0; |
| } |
| .mode-row .mode-label .lucide-icon svg, |
| .input-row .input-label .lucide-icon svg { width:16px; height:16px; } |
| .mode-row .mode-name { font-weight:600; } |
| .mode-row .mode-desc { |
| font-size:11.5px; color:var(--gray-03); margin-top:1px; |
| font-weight:400; |
| } |
| .mode-row .mode-text { display:flex; flex-direction:column; min-width:0; } |
| .mode-row .mode-badge { |
| display:inline-flex; align-items:center; |
| height:20px; padding:0 7px; |
| font-size:10px; font-weight:700; border-radius:4px; |
| background:var(--gray-06); color:var(--gray-02); |
| flex-shrink:0; margin-left:6px; |
| } |
| .mode-row .mode-badge.preview { background:#FFF4E5; color:#D97706; } |
| .mode-row .mode-badge.required { background:var(--primary-light); color:var(--primary-dark); } |
| .mode-row .mode-controls { |
| display:flex; align-items:center; gap:8px; |
| flex-shrink:0; |
| } |
| .settings-toggle.disabled { |
| opacity:.55; cursor:not-allowed; |
| } |
| |
| |
| .input-row-control { |
| display:flex; align-items:center; gap:10px; flex-shrink:0; |
| } |
| .check-box { |
| width:18px; height:18px; border-radius:4px; |
| border:1.5px solid var(--gray-04); background:white; |
| cursor:pointer; transition:all .15s; flex-shrink:0; |
| display:inline-flex; align-items:center; justify-content:center; |
| color:white; |
| } |
| .check-box.on { background:var(--primary); border-color:var(--primary); } |
| .check-box .lucide-icon svg { width:12px; height:12px; } |
| |
| |
| .advanced-toggle { |
| display:inline-flex; align-items:center; gap:5px; |
| border:none; background:transparent; |
| color:var(--primary); font-size:13px; font-weight:600; |
| cursor:pointer; padding:6px 4px; |
| margin-top:6px; |
| } |
| .advanced-toggle:hover { color:var(--primary-dark); } |
| .advanced-toggle .lucide-icon svg { width:14px; height:14px; } |
| |
| .advanced-body { |
| border-top:1px dashed var(--gray-05); |
| padding-top:8px; margin-top:8px; |
| display:flex; flex-direction:column; gap:14px; |
| } |
| |
| |
| .template-pane { display:flex; flex-direction:column; gap:14px; } |
| .template-input { |
| flex:1; min-width:0; |
| height:36px; padding:0 12px; |
| background:white; border:1px solid var(--gray-05); border-radius:6px; |
| font-size:13px; color:var(--text-primary); font-family:inherit; |
| outline:none; |
| } |
| .template-input:focus { border-color:var(--primary-focus); } |
| .template-add-group { |
| display:flex; align-items:center; gap:8px; |
| padding:12px; |
| background:var(--primary-hover); |
| border:1px dashed var(--primary-focus); |
| border-radius:8px; |
| } |
| |
| .template-group { |
| background:white; border:1px solid var(--gray-05); border-radius:8px; |
| overflow:hidden; |
| } |
| .tg-head { |
| display:flex; align-items:center; gap:10px; |
| padding:10px 12px; |
| background:var(--gray-07); |
| border-bottom:1px solid var(--gray-06); |
| } |
| .tg-icon { |
| color:var(--primary); flex-shrink:0; |
| display:inline-flex; align-items:center; justify-content:center; |
| } |
| .tg-icon .lucide-icon svg { width:16px; height:16px; } |
| .tg-name-input { |
| flex:1; min-width:0; |
| border:1px solid transparent; background:transparent; |
| padding:4px 6px; border-radius:4px; |
| font-size:14px; font-weight:700; color:var(--text-primary); |
| font-family:inherit; outline:none; |
| } |
| .tg-name-input:hover { background:white; border-color:var(--gray-05); } |
| .tg-name-input:focus { background:white; border-color:var(--primary-focus); } |
| .tg-count { |
| font-size:11.5px; color:var(--gray-03); flex-shrink:0; |
| } |
| .tg-delete { |
| width:28px; height:28px; border-radius:4px; |
| border:none; background:transparent; cursor:pointer; |
| color:var(--gray-03); flex-shrink:0; |
| display:inline-flex; align-items:center; justify-content:center; |
| } |
| .tg-delete:hover { background:#FEE2E2; color:var(--accent-red); } |
| .tg-delete .lucide-icon svg { width:14px; height:14px; } |
| |
| .tg-body { padding:8px 12px 12px; display:flex; flex-direction:column; gap:6px; } |
| .tg-empty { |
| padding:10px 4px; font-size:12.5px; color:var(--gray-03); |
| } |
| .tg-item { |
| display:flex; align-items:center; gap:8px; |
| padding:8px 10px; |
| border:1px solid var(--gray-06); |
| border-radius:6px; |
| background:white; |
| transition:border-color .15s, background .15s; |
| } |
| .tg-item:hover { border-color:var(--primary-focus); background:var(--primary-hover); } |
| .tg-item-icon { color:var(--gray-03); flex-shrink:0; display:inline-flex; } |
| .tg-item-icon .lucide-icon svg { width:13px; height:13px; } |
| .tg-item-text { |
| flex:1; min-width:0; |
| font-size:13px; color:var(--text-primary); |
| overflow:hidden; text-overflow:ellipsis; white-space:nowrap; |
| } |
| .tg-item-remove { |
| width:24px; height:24px; border-radius:4px; |
| border:none; background:transparent; cursor:pointer; |
| color:var(--gray-03); flex-shrink:0; |
| display:inline-flex; align-items:center; justify-content:center; |
| opacity:0; transition:opacity .15s, background .15s, color .15s; |
| } |
| .tg-item:hover .tg-item-remove { opacity:1; } |
| .tg-item-remove:hover { background:#FEE2E2; color:var(--accent-red); } |
| .tg-item-remove .lucide-icon svg { width:13px; height:13px; } |
| |
| .tg-add-row { |
| display:flex; align-items:center; gap:8px; |
| margin-top:4px; |
| padding-top:10px; |
| border-top:1px dashed var(--gray-05); |
| } |
| |
| .settings-section { |
| background:white; border:1px solid var(--gray-05); border-radius:12px; |
| margin-bottom:16px; overflow:hidden; |
| } |
| .settings-section-header { |
| display:flex; align-items:center; gap:12px; |
| padding:18px 22px; cursor:pointer; |
| user-select:none; transition:background .15s; |
| } |
| .settings-section-header:hover { background:var(--gray-07); } |
| .settings-section-header .title-icon { color:var(--primary); flex-shrink:0; } |
| .settings-section-header .title-icon svg { width:18px; height:18px; } |
| .settings-section-header .title { |
| flex:1; font-size:16px; font-weight:700; color:var(--text-primary); |
| letter-spacing:.01em; |
| } |
| .settings-section-header .help-icon { color:var(--gray-03); flex-shrink:0; } |
| .settings-section-header .help-icon svg { width:14px; height:14px; } |
| .settings-section-header .chev { |
| color:var(--gray-03); transition:transform .15s; |
| display:inline-flex; flex-shrink:0; |
| } |
| .settings-section-header .chev svg { width:20px; height:20px; } |
| .settings-section-header.open .chev { transform:rotate(180deg); } |
| .settings-section-body { |
| padding:18px 24px 24px; |
| border-top:1px solid var(--gray-06); |
| } |
| |
| .settings-field { margin-top:22px; } |
| .settings-field:first-child { margin-top:0; } |
| .settings-field-label { |
| display:flex; align-items:center; gap:6px; |
| font-size:14px; font-weight:600; color:var(--text-primary); |
| margin-bottom:10px; |
| } |
| .settings-field-label .req { color:var(--accent-red); } |
| .settings-field-label .help-icon { color:var(--gray-03); cursor:help; display:inline-flex; } |
| .settings-field-label .help-icon svg { width:13px; height:13px; } |
| |
| .tool-chip-row { display:flex; gap:8px; flex-wrap:wrap; } |
| .tool-chip { |
| display:inline-flex; align-items:center; gap:5px; |
| height:30px; padding:0 12px; |
| border-radius:999px; |
| border:1px solid var(--primary); |
| background:var(--primary-light); |
| color:var(--primary-dark); |
| font-size:12.5px; font-weight:500; |
| } |
| .tool-chip .lucide-icon svg { width:13px; height:13px; } |
| .tool-add-btn { |
| width:30px; height:30px; |
| border-radius:999px; |
| border:1px solid var(--gray-05); background:white; |
| color:var(--gray-02); cursor:pointer; |
| display:inline-flex; align-items:center; justify-content:center; |
| transition:all .15s; |
| } |
| .tool-add-btn:hover { border-color:var(--primary); color:var(--primary); } |
| .tool-add-btn .lucide-icon svg { width:14px; height:14px; } |
| |
| .model-chips-container { |
| background:var(--gray-07); |
| border:1px solid var(--gray-05); |
| border-radius:8px; |
| padding:12px 14px; |
| display:flex; flex-wrap:wrap; gap:6px; |
| position:relative; min-height:50px; |
| } |
| .model-chip { |
| display:inline-flex; align-items:center; gap:2px; |
| height:26px; padding:0 4px 0 10px; |
| border-radius:999px; |
| background:var(--gray-06); color:var(--gray-01); |
| font-size:11.5px; white-space:nowrap; |
| } |
| .model-chip .remove-btn { |
| width:18px; height:18px; border-radius:50%; |
| border:none; background:transparent; |
| color:var(--gray-03); cursor:pointer; |
| display:inline-flex; align-items:center; justify-content:center; |
| margin-left:2px; |
| } |
| .model-chip .remove-btn:hover { background:var(--gray-05); color:var(--text-primary); } |
| .model-chip .remove-btn .lucide-icon svg { width:12px; height:12px; } |
| .model-chips-expand { |
| position:absolute; right:6px; bottom:6px; |
| width:28px; height:28px; border-radius:6px; |
| border:none; background:white; |
| color:var(--gray-02); cursor:pointer; |
| display:inline-flex; align-items:center; justify-content:center; |
| box-shadow:0 1px 3px rgba(0,0,0,.06); |
| } |
| .model-chips-expand:hover { background:var(--gray-06); } |
| .model-chips-expand .lucide-icon svg { width:14px; height:14px; } |
| |
| .custom-instruction { |
| background:white; border:1px solid var(--gray-05); border-radius:8px; |
| overflow:hidden; |
| } |
| .ci-toolbar { |
| display:flex; align-items:center; gap:2px; |
| padding:6px 8px; |
| border-bottom:1px solid var(--gray-06); |
| background:var(--gray-07); |
| } |
| .ci-toolbar-btn { |
| height:28px; padding:0 8px; border-radius:5px; border:none; |
| background:transparent; color:var(--gray-02); |
| cursor:pointer; display:inline-flex; align-items:center; gap:4px; |
| font-size:12.5px; |
| } |
| .ci-toolbar-btn:hover { background:var(--gray-06); color:var(--text-primary); } |
| .ci-toolbar-btn.active { background:var(--gray-06); color:var(--text-primary); } |
| .ci-toolbar-btn .lucide-icon svg { width:14px; height:14px; } |
| .ci-toolbar-btn.icon-only { width:28px; padding:0; justify-content:center; } |
| .ci-toolbar-divider { |
| width:1px; height:18px; background:var(--gray-05); |
| margin:0 4px; |
| } |
| .ci-toolbar-spacer { flex:1; } |
| .ci-content { |
| padding:18px 22px; |
| font-size:13.5px; color:var(--text-primary); line-height:1.7; |
| } |
| .ci-content h3 { font-size:15px; font-weight:700; margin:0 0 10px; } |
| .ci-content ul { margin:0 0 10px; padding-left:24px; } |
| .ci-content ol { margin:0 0 10px; padding-left:32px; } |
| .ci-content li { margin-bottom:4px; } |
| .ci-content a { color:var(--primary); text-decoration:none; word-break:break-all; } |
| .ci-content a:hover { text-decoration:underline; } |
| |
| |
| .chat-pane-header { |
| height:52px; flex-shrink:0; |
| display:flex; align-items:center; justify-content:space-between; |
| padding:0 24px; gap:12px; |
| border-bottom:1px solid var(--gray-05); |
| background:white; |
| } |
| .cph-property { |
| display:inline-flex; align-items:center; gap:10px; |
| min-width:0; flex:1; |
| font-size:20px; font-weight:700; color:var(--text-primary); |
| letter-spacing:.01em; |
| } |
| .cph-property .lucide-icon { color:var(--primary); flex-shrink:0; } |
| .cph-property .lucide-icon svg { width:20px; height:20px; } |
| .cph-property .cph-name { |
| overflow:hidden; text-overflow:ellipsis; white-space:nowrap; |
| } |
| |
| |
| .right-panel { |
| width:540px; min-width:0; flex-shrink:0; height:100%; |
| border-left:1px solid var(--gray-05); display:flex; flex-direction:column; |
| background:white; overflow:hidden; |
| } |
| .rp-header { |
| height:52px; display:flex; align-items:center; padding:0 12px 0 16px; |
| border-bottom:1px solid var(--gray-05); flex-shrink:0; |
| justify-content:space-between; |
| } |
| .rp-title { font-size:14px; font-weight:700; color:var(--text-primary); } |
| .rp-close { width:32px; height:32px; border:none; background:transparent; border-radius:6px; |
| color:var(--gray-02); display:inline-flex; align-items:center; justify-content:center; |
| } |
| .rp-close:hover { background:var(--gray-06); } |
| |
| .rp-body { flex:1; overflow:auto; min-height:0; background:var(--gray-07); } |
| |
| .doc-toolbar { |
| height:44px; display:flex; align-items:center; padding:0 12px; gap:8px; |
| border-bottom:1px solid var(--gray-05); background:white; |
| } |
| .doc-name { |
| font-size:13px; font-weight:500; color:var(--text-primary); |
| overflow:hidden; text-overflow:ellipsis; white-space:nowrap; |
| } |
| .doc-meta { font-size:11px; color:var(--gray-03); } |
| .doc-action-btn { |
| height:28px; padding:0 10px; border-radius:6px; border:1px solid var(--gray-05); |
| background:white; color:var(--gray-01); font-size:12px; |
| display:inline-flex; align-items:center; gap:4px; |
| } |
| .doc-action-btn:hover { background:var(--gray-06); } |
| .doc-action-btn .lucide-icon svg { width:13px; height:13px; } |
| |
| .pdf-viewer { |
| padding:24px; display:flex; flex-direction:column; gap:16px; align-items:center; |
| } |
| .pdf-page { |
| width:100%; max-width:480px; aspect-ratio: 1 / 1.414; |
| background:white; border:1px solid var(--gray-05); border-radius:4px; |
| box-shadow:0 2px 12px rgba(23,54,120,.08); padding:36px 40px; |
| font-size:11px; line-height:1.5; color:var(--text-primary); |
| position:relative; |
| } |
| .pdf-page h1 { font-size:14px; font-weight:700; margin:0 0 8px; } |
| .pdf-page h2 { font-size:12px; font-weight:700; margin:14px 0 6px; } |
| .pdf-page p { margin:0 0 8px; } |
| .pdf-page .table { width:100%; border-collapse:collapse; font-size:10px; margin:8px 0; } |
| .pdf-page .table th, .pdf-page .table td { |
| border:1px solid var(--gray-04); padding:4px 6px; text-align:left; |
| } |
| .pdf-page .table th { background:var(--gray-06); font-weight:700; } |
| .pdf-page .pageno { |
| position:absolute; bottom:16px; right:24px; font-size:9px; color:var(--gray-03); |
| } |
| .notif-panel { |
| position:fixed; right:0; top:var(--header-h); bottom:0; width:320px; |
| background:white; border-left:1px solid var(--gray-05); |
| z-index:95; display:flex; flex-direction:column; |
| transform:translateX(100%); transition:transform .2s ease; |
| } |
| .notif-panel.open { transform:translateX(0); } |
| .notif-panel-header { |
| height:52px; display:flex; align-items:center; padding:0 12px 0 16px; |
| border-bottom:1px solid var(--gray-05); flex-shrink:0; |
| justify-content:space-between; |
| } |
| .notif-panel-title { font-size:14px; font-weight:700; color:var(--text-primary); } |
| .notif-list { flex:1; overflow-y:auto; padding:8px; display:flex; flex-direction:column; gap:2px; } |
| .notif-item { |
| padding:10px 12px; border-radius:6px; cursor:default; |
| display:flex; flex-direction:column; gap:3px; |
| } |
| .notif-item:hover { background:var(--gray-07); } |
| .notif-item.unread { background:var(--primary-hover); } |
| .notif-item.unread:hover { background:#EDEDFE; } |
| .notif-item-title { font-size:13px; font-weight:600; color:var(--text-primary); display:flex; align-items:center; gap:6px; } |
| .notif-item-title .unread-dot { |
| width:6px; height:6px; border-radius:50%; background:var(--primary); flex-shrink:0; |
| } |
| .notif-item-body { font-size:12px; color:var(--text-secondary); line-height:1.5; } |
| .notif-item-time { font-size:11px; color:var(--text-disabled); } |
| .notif-overlay { |
| position:fixed; inset:0; top:var(--header-h); z-index:94; |
| } |
| @keyframes toast-in-out { |
| 0% { opacity:0; transform:translateX(-50%) translateY(8px); } |
| 15% { opacity:1; transform:translateX(-50%) translateY(0); } |
| 75% { opacity:1; transform:translateX(-50%) translateY(0); } |
| 100% { opacity:0; transform:translateX(-50%) translateY(8px); } |
| } |
| .copy-toast { |
| position:fixed; bottom:32px; left:50%; transform:translateX(-50%); |
| background:#1a1a1a; color:white; font-size:13px; font-weight:500; |
| padding:8px 16px; border-radius:8px; |
| box-shadow:0 4px 16px rgba(0,0,0,.2); |
| pointer-events:none; z-index:9999; |
| animation: toast-in-out 2s ease forwards; |
| } |
| </style> |
| </head> |
| <body> |
| <div id="root"></div> |
|
|
| <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script> |
| <script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script> |
| <script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script> |
|
|
| <script type="text/babel" data-presets="react"> |
| |
| const { useState, useRef, useEffect } = React; |
| |
| const Icon = ({name, style, size}) => { |
| const ref = useRef(null); |
| useEffect(() => { |
| if (ref.current && window.lucide) { |
| ref.current.innerHTML = ''; |
| const el = document.createElement('i'); |
| el.setAttribute('data-lucide', name); |
| ref.current.appendChild(el); |
| window.lucide.createIcons({ attrs: { width: size||16, height: size||16 }, nameAttr: 'data-lucide' }); |
| } |
| }, [name, size]); |
| return <span ref={ref} className="lucide-icon" style={{display:'inline-flex', alignItems:'center', justifyContent:'center', ...style}}></span>; |
| }; |
| |
| |
| const NOTIFICATIONS = [ |
| { id:5, unread:false, title:'システムメンテナンスのお知らせ', body:'5月20日 2:00〜4:00 にメンテナンスを実施します。', time:'昨日' }, |
| ]; |
| |
| |
| |
| const PROPERTIES = [ |
| { id:'p1', name:'用賀パークレジデンス', chats:['内見後フォロー','重要事項説明準備','ペット可条件確認','賃料交渉メモ','駐車場の確認'], mkcoreLabel:'第3回理事会議案書', mkcoreDate:'2026/04/28' }, |
| { id:'p2', name:'桜新町テラス', chats:['賃料交渉メモ','契約条件の確認','長期修繕計画'], mkcoreLabel:'第7回理事会議事録', mkcoreDate:'2026/04/22' }, |
| { id:'p3', name:'三軒茶屋スカイコート', chats:['周辺相場のリサーチ','駐車場の有無確認','内見スケジュール'], mkcoreLabel:'第2回総会議事録', mkcoreDate:'2026/04/19' }, |
| { id:'p4', name:'目黒シティタワー', chats:['契約書のドラフト','重要事項説明書 v2','理事会議案書','総会招集通知'], mkcoreLabel:'第5回理事会議案書', mkcoreDate:'2026/04/27' }, |
| { id:'p5', name:'下北沢ガーデン', chats:['設備点検指摘事項','クレーム対応経緯'], mkcoreLabel:'第1回総会議案書', mkcoreDate:'2026/04/15' }, |
| { id:'p6', name:'代々木上原レジデンス', chats:['修繕積立金の見直し','管理委託契約の更新'], mkcoreLabel:'第1回理事会議案書', mkcoreDate:'2026/04/10' }, |
| ]; |
| |
| |
| const FAVORITES = [ |
| { propertyId:'p1', name:'用賀パークレジデンス', chats:['内見後フォロー','重要事項説明準備','ペット可条件確認'], more:4 }, |
| { propertyId:'p2', name:'桜新町テラス', chats:['賃料交渉メモ'], more:2 }, |
| { propertyId:'p4', name:'目黒シティタワー', chats:['理事会議案書','総会招集通知'], more:3 }, |
| ]; |
| |
| |
| const UPLOAD_CATEGORIES = ['物件引継書', '管理規約', '管理委託契約書', 'その他']; |
| |
| |
| const SETTINGS_MODELS = [ |
| 'Claude Haiku 4.5 Thinking','Gemini 2.5 Pro Thinking','GPT-5.4 Thinking','GPT-5.4', |
| 'Gemini 3.1 Flash Lite Preview','Gemini 3.1 Flash Lite Preview Thinking', |
| 'Gemini 3.1 Pro Preview Thinking','Gemini 2.5 Pro','Gemini 2.5 Flash Preview', |
| 'Claude Opus 4.6 Thinking','GPT-5 mini Thinking','Claude Opus 4.7 Thinking', |
| 'Claude Opus 4.6','GPT-5.1 Thinking','Gemini 2.5 Flash','GPT-5 nano Thinking', |
| 'Gemini 2.5 Flash Lite Thinking','GPT-5.4 nano Thinking','GPT-5 nano','GPT-5.1', |
| 'GPT-5 Thinking','Claude Haiku 4.5','Gemini 3 Flash Preview','GPT-5.4 mini', |
| 'Claude Sonnet 4.6 Thinking','o3','Claude Opus 4.7','GPT-5','GPT-5.4 mini Thinking', |
| 'Gemini 2.5 Flash Lite','GPT-5 mini','Claude Sonnet 4.6','Gemini 3.1 Pro Preview', |
| 'Gemini 3 Flash Preview Thinking','GPT-5.2','GPT-5.2 Thinking', |
| 'Gemini 2.5 Flash Thinking','GPT-5.4 nano', |
| ]; |
| |
| |
| const PROPERTY_DOCS = { |
| '物件引継書': [ |
| { name:'物件引継書_2024.pdf', date:'2024/04/15', size:'845 KB' }, |
| ], |
| '管理規約': [ |
| { name:'管理規約_v3.pdf', date:'2026/01/10', size:'2.1 MB' }, |
| { name:'管理規約_v2.pdf', date:'2025/01/15', size:'2.0 MB' }, |
| { name:'管理規約_v1.pdf', date:'2023/03/20', size:'1.9 MB' }, |
| ], |
| '管理委託契約書': [ |
| { name:'管理委託契約書_2026年版.pdf', date:'2026/03/15', size:'1.2 MB' }, |
| { name:'管理委託契約書_2025年版.pdf', date:'2025/04/01', size:'1.1 MB' }, |
| ], |
| 'その他': [ |
| { name:'修繕計画書_2026.pdf', date:'2026/02/20', size:'3.4 MB' }, |
| { name:'会計報告_2025年度.xlsx', date:'2026/03/01', size:'180 KB' }, |
| ], |
| }; |
| |
| |
| const MKCORE_DOC_TYPES = ['理事会議案書', '理事会議事録', '総会議案書', '総会議事録']; |
| const MKCORE_DOCS = (() => { |
| const out = []; |
| let n = 0; |
| const pad2 = (v) => String(v).padStart(2, '0'); |
| const add = (type, year, idx, sizeKb, pages, opts={}) => { |
| n++; |
| const month = pad2(Math.min(2 + (idx - 1) * 2, 12)); |
| const day = pad2(8 + (idx % 12)); |
| out.push({ |
| id: `mk${n}`, type, |
| name: opts.name || `第${idx}回 ${type} ${year}年度`, |
| date: opts.date || `${year}/${month}/${day}`, |
| size: `${sizeKb} KB`, pages, |
| }); |
| }; |
| |
| [[2026,3],[2025,6],[2024,6],[2023,5],[2022,4],[2021,4],[2020,3]].forEach(([y,c]) => { |
| for (let i = c; i >= 1; i--) add('理事会議案書', y, i, 380 + (i*10), 6 + (i % 3)); |
| }); |
| |
| [[2026,2],[2025,6],[2024,6],[2023,5],[2022,4],[2021,4],[2020,3]].forEach(([y,c]) => { |
| for (let i = c; i >= 1; i--) add('理事会議事録', y, i, 300 + (i*8), 4 + (i % 3)); |
| }); |
| |
| [2026,2025,2024,2023,2022,2021,2020].forEach((y, idx) => { |
| add('総会議案書', y, 1, 1200, 14, { |
| name: `第${7 - idx}期 通常総会 議案書`, date: `${y}/05/12`, |
| }); |
| }); |
| |
| [2026,2025,2024,2023,2022,2021,2020].forEach((y, idx) => { |
| add('総会議事録', y, 1, 520, 6, { |
| name: `第${7 - idx}期 通常総会 議事録`, date: `${y}/05/14`, |
| }); |
| }); |
| |
| const latestByType = {}; |
| out.forEach(d => { |
| if (!latestByType[d.type] || d.date > latestByType[d.type]) latestByType[d.type] = d.date; |
| }); |
| return out.map(d => ({ ...d, latest: d.date === latestByType[d.type] })); |
| })(); |
| |
| |
| |
| const CITATIONS = [ |
| { n:1, type:'pdf', title:'第3回 理事会議案書 2026年度', meta:'MKCore自動連携 / 理事会議案書 / 2026年度', page:6, |
| docName:'第3回 理事会議案書 2026年度', |
| folderKeys:['mkcore','sb-mk:理事会議案書','sb-mk:理事会議案書:2026'] }, |
| { n:2, type:'pdf', title:'修繕計画書_2026.pdf', meta:'手動登録 / その他 / 2026年度', page:1, |
| docName:'修繕計画書_2026.pdf', |
| folderKeys:['manual','sb-manual:その他','sb-manual:その他:2026'] }, |
| { n:3, type:'pdf', title:'管理規約_v3.pdf', meta:'手動登録 / 管理規約 / 2026年度', page:1, |
| docName:'管理規約_v3.pdf', |
| folderKeys:['manual','sb-manual:管理規約','sb-manual:管理規約:2026'] }, |
| { n:4, type:'pdf', title:'第6回 理事会議事録 2025年度', meta:'MKCore自動連携 / 理事会議事録 / 2025年度', page:5, |
| docName:'第6回 理事会議事録 2025年度', |
| folderKeys:['mkcore','sb-mk:理事会議事録','sb-mk:理事会議事録:2025'] }, |
| { n:5, type:'pdf', title:'管理委託契約書_2026年版.pdf', meta:'手動登録 / 管理委託契約書 / 2026年度', page:1, |
| docName:'管理委託契約書_2026年版.pdf', |
| folderKeys:['manual','sb-manual:管理委託契約書','sb-manual:管理委託契約書:2026'] }, |
| { n:6, type:'pdf', title:'第7期 通常総会 議事録', meta:'MKCore自動連携 / 総会議事録 / 2026年度', page:6, |
| docName:'第7期 通常総会 議事録', |
| folderKeys:['mkcore','sb-mk:総会議事録','sb-mk:総会議事録:2026'] }, |
| ]; |
| |
| |
| function findDocByName(name) { |
| if (!name) return null; |
| const fromMk = MKCORE_DOCS.find(d => d.name === name); |
| if (fromMk) return fromMk; |
| for (const cat of UPLOAD_CATEGORIES) { |
| const docs = PROPERTY_DOCS[cat] || []; |
| const f = docs.find(d => d.name === name); |
| if (f) return { ...f, type: cat }; |
| } |
| return null; |
| } |
| |
| |
| const PROPERTY_COLORS = { |
| p1: '#5B5BF0', |
| p2: '#10B981', |
| p3: '#F59E0B', |
| p4: '#EC4899', |
| p5: '#8B5CF6', |
| p6: '#06B6D4', |
| }; |
| |
| |
| const FLAT_CHATS_BY_DATE = [ |
| { bucket:'今日', items:[ |
| { pid:'p1', chat:'内見後フォロー' }, |
| { pid:'p1', chat:'重要事項説明準備' }, |
| { pid:'p2', chat:'賃料交渉メモ' }, |
| { pid:'p4', chat:'契約書のドラフト' }, |
| ]}, |
| { bucket:'昨日', items:[ |
| { pid:'p1', chat:'ペット可条件確認' }, |
| { pid:'p4', chat:'重要事項説明書 v2' }, |
| { pid:'p3', chat:'周辺相場のリサーチ' }, |
| ]}, |
| { bucket:'今週', items:[ |
| { pid:'p2', chat:'契約条件の確認' }, |
| { pid:'p4', chat:'理事会議案書' }, |
| { pid:'p5', chat:'設備点検指摘事項' }, |
| { pid:'p1', chat:'駐車場の確認' }, |
| ]}, |
| { bucket:'それ以前', items:[ |
| { pid:'p2', chat:'長期修繕計画' }, |
| { pid:'p3', chat:'駐車場の有無確認' }, |
| { pid:'p3', chat:'内見スケジュール' }, |
| { pid:'p4', chat:'総会招集通知' }, |
| { pid:'p5', chat:'クレーム対応経緯' }, |
| { pid:'p6', chat:'修繕積立金の見直し' }, |
| { pid:'p6', chat:'管理委託契約の更新' }, |
| ]}, |
| ]; |
| |
| |
| const SYNTH_CHAT_TOPICS = [ |
| '清掃業者見積もり比較', '月次収支報告の確認', 'エレベーター点検結果', |
| '消防設備点検指摘対応', '管理員勤務シフト調整', '住民アンケート集計', |
| '保険更新時期の確認', '駐輪場再整備の検討', '排水溝清掃の日程', |
| '理事会議事録ドラフト', '駐車場再抽選の結果', '修繕履歴の整理', |
| '近隣騒音クレーム対応', '宅配ボックス導入検討', '共用部清掃チェック', |
| '町内会との連携メモ', '防犯カメラ更新の見積', '電気料金プランの比較', |
| '管理規約改定の論点', '理事会開催通知ドラフト', |
| ]; |
| const getExtendedChats = (p) => p ? [...p.chats, ...SYNTH_CHAT_TOPICS] : []; |
| |
| |
| |
| const INITIAL_TEMPLATE_GROUPS = [ |
| { id:'g1', cat:'理事会業務', icon:'gavel', items:[ |
| { theme:'大規模修繕の履歴確認', text:'前回の大規模修繕工事の実施時期・施工範囲・検討経緯を教えてください。' }, |
| { theme:'点検指摘から改修箇所', text:'点検指摘事項から改修すべき箇所を教えてください。' }, |
| ]}, |
| { id:'g2', cat:'総会業務', icon:'users', items:[ |
| { theme:'長期修繕計画の確認', text:'長期修繕計画の当初計上年度や一部実施有無を教えてください' }, |
| { theme:'管理委託契約 比較表', text:'管理委託契約書の前年度との主な変更点・比較表を作成してください' }, |
| ]}, |
| { id:'g3', cat:'物件引継', icon:'clipboard-list', items:[ |
| { theme:'見送った修繕の整理', text:'過去検討した際に実施を見送った修繕を項目と理由を教えてください。' }, |
| ]}, |
| ]; |
| |
| |
| function Sidebar({ |
| collapsed, onToggle, activeChatKey, onSelectChat, onNewChat, |
| onSettingsClick, onSelectProperty, |
| currentPropertyId, |
| openFolders, toggleFolder, |
| selectedDocName, onPickDoc, |
| }) { |
| const property = PROPERTIES.find(p => p.id === currentPropertyId); |
| |
| const [uploadOpen, setUploadOpen] = useState(false); |
| const [uploadCategory, setUploadCategory] = useState(null); |
| const [dragOver, setDragOver] = useState(false); |
| const [chatVisibleCount, setChatVisibleCount] = useState(5); |
| const [propMenuOpen, setPropMenuOpen] = useState(false); |
| |
| |
| useEffect(() => { setChatVisibleCount(5); }, [currentPropertyId]); |
| |
| |
| useEffect(() => { |
| if (!propMenuOpen) return; |
| const onDown = () => setPropMenuOpen(false); |
| document.addEventListener('mousedown', onDown); |
| return () => document.removeEventListener('mousedown', onDown); |
| }, [propMenuOpen]); |
| |
| |
| const mkcoreSections = property ? MKCORE_DOC_TYPES.map(t => { |
| const docs = MKCORE_DOCS.filter(d => d.type === t); |
| return { key:`sb-mk:${t}`, title:t, ...buildYearSections(docs, `sb-mk:${t}`) }; |
| }) : []; |
| const mkcoreCount = mkcoreSections.reduce((n,s)=>n+s.total, 0); |
| |
| const manualSections = property ? UPLOAD_CATEGORIES.map(cat => { |
| const docs = (PROPERTY_DOCS[cat] || []).map((d, i) => ({...d, type:cat, id:`${cat}-${i}`})); |
| return { key:`sb-manual:${cat}`, title:cat, ...buildYearSections(docs, `sb-manual:${cat}`) }; |
| }) : []; |
| const manualCount = manualSections.reduce((n,s)=>n+s.total, 0); |
| |
| const openUploadFor = (cat) => { |
| setUploadCategory(cat); |
| setUploadOpen(true); |
| }; |
| |
| const handleUploadSubmit = ({category, fileName}) => { |
| alert(`${category} に「${fileName}」をアップロードしました(モック)${property ? '\n対象: ' + property.name : ''}`); |
| setUploadOpen(false); |
| }; |
| |
| const onSidebarDrop = (e) => { |
| e.preventDefault(); |
| setDragOver(false); |
| const files = e.dataTransfer?.files; |
| if (files && files.length > 0) { |
| const names = Array.from(files).map(f => f.name).join(', '); |
| alert(`手動登録に ${files.length} 件のファイルをアップロードしました(モック)\n${names}${property ? '\n対象: ' + property.name : ''}`); |
| } |
| }; |
| |
| |
| const allChats = getExtendedChats(property); |
| const visibleChats = allChats.slice(0, chatVisibleCount); |
| const hasMoreChats = chatVisibleCount < allChats.length; |
| const remainingChats = allChats.length - chatVisibleCount; |
| |
| return ( |
| <aside className={"sidebar"+(collapsed?" collapsed":"")}> |
| <div className="sidebar-top"> |
| <button className="sidebar-toggle" onClick={onToggle} aria-label="toggle sidebar"> |
| <Icon name={collapsed ? "chevron-right" : "chevron-left"} /> |
| </button> |
| </div> |
| |
| <div className="sidebar-scroll"> |
| {!collapsed && ( |
| <> |
| {/* 物件切り替えプルダウン (インライン) */} |
| <div className="sb-prop-switch-wrap" onMouseDown={(e)=>e.stopPropagation()}> |
| <button |
| className="sb-prop-switch" |
| onClick={()=>setPropMenuOpen(!propMenuOpen)} |
| title="物件を切り替え" |
| > |
| <span className="sb-ps-icon"><Icon name="building-2"/></span> |
| <div className="sb-ps-text"> |
| <div className="sb-ps-label">対象物件</div> |
| <div className="sb-ps-name"> |
| <span>{property?.name || '物件未選択'}</span> |
| </div> |
| </div> |
| <span className="sb-ps-chev"><Icon name="chevrons-up-down"/></span> |
| </button> |
| {propMenuOpen && ( |
| <PropertyMenu |
| selectedId={currentPropertyId} |
| onSelect={(pid)=>{ onSelectProperty(pid); setPropMenuOpen(false); }} |
| onClose={()=>setPropMenuOpen(false)} |
| /> |
| )} |
| </div> |
| |
| {/* 新規チャット */} |
| <button className="sb-new-chat" onClick={onNewChat}> |
| <Icon name="square-pen"/> |
| <span style={{flex:1, textAlign:'left'}}>新しいチャット</span> |
| <Icon name="plus"/> |
| </button> |
| |
| {/* チャット履歴フォルダ (ロードモア対応) */} |
| <div className="sb-section chats"> |
| <div |
| className={"sb-section-head"+(openFolders.has('chats')?' open':'')} |
| onClick={()=>toggleFolder('chats')} |
| > |
| <span className="sb-chev"><Icon name="chevron-right"/></span> |
| <span className="sb-icon"> |
| <Icon name={openFolders.has('chats')?'folder-open':'folder'}/> |
| </span> |
| <span className="sb-section-name">チャット履歴</span> |
| </div> |
| {openFolders.has('chats') && ( |
| <div className="sb-section-body"> |
| {allChats.length === 0 ? ( |
| <div className="dc-empty">履歴はまだありません</div> |
| ) : ( |
| <> |
| {visibleChats.map((c, i) => { |
| const ck = `${currentPropertyId}::${c}`; |
| return ( |
| <button |
| key={i} |
| className={"sb-history-row"+(activeChatKey===ck?' active':'')} |
| onClick={()=>onSelectChat(currentPropertyId, c)} |
| > |
| <Icon name="message-square"/> |
| <span className="label">{c}</span> |
| </button> |
| ); |
| })} |
| {hasMoreChats && ( |
| <button |
| className="sb-load-more" |
| onClick={()=>setChatVisibleCount(c => Math.min(c + 5, allChats.length))} |
| > |
| <Icon name="chevron-down"/> |
| <span>さらに読み込む</span> |
| </button> |
| )} |
| </> |
| )} |
| </div> |
| )} |
| </div> |
| |
| {/* MKCore自動連携フォルダ */} |
| <div className="sb-section"> |
| <div |
| className={"sb-section-head"+(openFolders.has('mkcore')?' open':'')} |
| onClick={()=>toggleFolder('mkcore')} |
| > |
| <span className="sb-chev"><Icon name="chevron-right"/></span> |
| <span className="sb-icon"> |
| <Icon name={openFolders.has('mkcore')?'folder-open':'folder'}/> |
| </span> |
| <span className="sb-section-name">MKCore自動連携</span> |
| </div> |
| {openFolders.has('mkcore') && ( |
| <div className="sb-section-body"> |
| <FolderTree |
| sections={mkcoreSections} |
| openFolders={openFolders} |
| toggleFolder={toggleFolder} |
| onPickDoc={onPickDoc} |
| emptyText={property ? '資料はありません' : '物件を選択してください'} |
| hideCounts={true} |
| selectedDocName={selectedDocName} |
| /> |
| </div> |
| )} |
| </div> |
| |
| {/* 手動登録フォルダ (ドラッグ&ドロップ対応 — UIヒントは非表示) */} |
| <div |
| className={"sb-section manual"+(dragOver?' drag-over':'')} |
| onDragOver={(e)=>{ e.preventDefault(); setDragOver(true); }} |
| onDragLeave={()=>setDragOver(false)} |
| onDrop={onSidebarDrop} |
| > |
| <div |
| className={"sb-section-head"+(openFolders.has('manual')?' open':'')} |
| onClick={()=>toggleFolder('manual')} |
| > |
| <span className="sb-chev"><Icon name="chevron-right"/></span> |
| <span className="sb-icon"> |
| <Icon name={openFolders.has('manual')?'folder-open':'folder'}/> |
| </span> |
| <span className="sb-section-name">手動登録</span> |
| </div> |
| {openFolders.has('manual') && ( |
| <div className="sb-section-body"> |
| {property && ( |
| <div className="sb-manual-drop-hint"> |
| <Icon name="upload-cloud"/> |
| <span>ドラッグ&ドロップ</span> |
| </div> |
| )} |
| <FolderTree |
| sections={manualSections} |
| openFolders={openFolders} |
| toggleFolder={toggleFolder} |
| onPickDoc={onPickDoc} |
| emptyText={property ? '登録された資料はありません' : '物件を選択してください'} |
| hideCounts={true} |
| selectedDocName={selectedDocName} |
| /> |
| </div> |
| )} |
| </div> |
| </> |
| )} |
| </div> |
| |
| <div className="sidebar-footer"> |
| <button className="menu-item" onClick={onSettingsClick}> |
| <Icon name="settings" /> |
| {!collapsed && <span>設定</span>} |
| </button> |
| </div> |
| |
| <UploadDocsModal |
| open={uploadOpen} |
| initialCategory={uploadCategory} |
| onClose={()=>setUploadOpen(false)} |
| onSubmit={handleUploadSubmit} |
| propertyName={property?.name} |
| /> |
| </aside> |
| ); |
| } |
| |
| |
| function Header({withBorder, onHomeClick, propertyName, chatTitle, onPropertyClick, onNotifClick}) { |
| const [userMenuOpen, setUserMenuOpen] = useState(false); |
| React.useEffect(() => { |
| if (!userMenuOpen) return; |
| const close = () => setUserMenuOpen(false); |
| document.addEventListener('mousedown', close); |
| return () => document.removeEventListener('mousedown', close); |
| }, [userMenuOpen]); |
| return ( |
| <header className={"header"+(withBorder?" with-border":"")}> |
| <button className="brand-mark" onClick={onHomeClick} title="ホームへ戻る"> |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 11.5L12 4l9 7.5"/><path d="M5 10.5V20h14v-9.5"/><path d="M10 20v-5h4v5"/></svg> |
| </button> |
| <nav className="breadcrumb"> |
| <button className="bc-root" onClick={onHomeClick}>FM業務AI支援システム</button> |
| {propertyName && ( |
| <> |
| <span className="bc-sep">/</span> |
| {chatTitle ? ( |
| <button className="bc-item" onClick={onPropertyClick} title="この物件のホームへ">{propertyName}</button> |
| ) : ( |
| <span className="bc-item current">{propertyName}</span> |
| )} |
| </> |
| )} |
| {chatTitle && ( |
| <> |
| <span className="bc-sep">/</span> |
| <span className="bc-item current" title={chatTitle}>{chatTitle}</span> |
| </> |
| )} |
| </nav> |
| <div className="header-right"> |
| <button className="icon-btn" aria-label="notifications" onClick={onNotifClick}><Icon name="bell" /></button> |
| <button className="account-chip" onClick={e => { e.stopPropagation(); setUserMenuOpen(o => !o); }}> |
| <div className="account-avatar">SY</div> |
| {userMenuOpen && ( |
| <div className="user-menu" onMouseDown={e => e.stopPropagation()}> |
| <div className="user-menu-profile"> |
| <div className="user-menu-avatar">SY</div> |
| <div className="user-menu-info"> |
| <div className="user-menu-name">山田 信一</div> |
| <div className="user-menu-account">@shinichi.yamada</div> |
| <div className="user-menu-email">s.yamada@fm-example.co.jp</div> |
| </div> |
| </div> |
| <div className="user-menu-divider"/> |
| <button className="user-menu-item"><Icon name="settings"/>設定</button> |
| <button className="user-menu-item"><Icon name="log-out"/>ログアウト</button> |
| </div> |
| )} |
| </button> |
| </div> |
| </header> |
| ); |
| } |
| |
| |
| function PropertyMenu({selectedId, onSelect, onClose}) { |
| const [q, setQ] = useState(''); |
| const inputRef = useRef(null); |
| useEffect(() => { inputRef.current?.focus(); }, []); |
| const filtered = PROPERTIES.filter(p => !q || p.name.includes(q)); |
| return ( |
| <div className="ch-prop-menu" onClick={(e)=>e.stopPropagation()}> |
| <div className="pm-search"> |
| <Icon name="search"/> |
| <input |
| ref={inputRef} |
| placeholder="物件名で検索" |
| value={q} |
| onChange={e=>setQ(e.target.value)} |
| onKeyDown={e=>{ if(e.key==='Escape') onClose(); }} |
| /> |
| </div> |
| {filtered.length === 0 ? ( |
| <div className="ch-prop-menu-empty">該当する物件がありません</div> |
| ) : ( |
| filtered.map(p => ( |
| <button |
| key={p.id} |
| className={"ch-prop-menu-item"+(selectedId===p.id?' active':'')} |
| onClick={()=>{ onSelect(p.id); onClose(); }} |
| > |
| <span className="pm-name">{p.name}</span> |
| {selectedId===p.id && <span className="pm-check"><Icon name="check"/></span>} |
| </button> |
| )) |
| )} |
| </div> |
| ); |
| } |
| |
| |
| function SuggestSections({groups, onPick}) { |
| const visible = groups.filter(g => g.items.length > 0); |
| if (visible.length === 0) return null; |
| return ( |
| <div className="ch-suggest-sections"> |
| {visible.map(g => ( |
| <div key={g.id} className="ch-suggest-section"> |
| <div className="ch-suggest-section-head"> |
| <Icon name={g.icon || 'folder'}/> |
| <span>{g.cat}</span> |
| </div> |
| <div className="ch-suggest-grid"> |
| {g.items.map((it, i) => { |
| const text = typeof it === 'string' ? it : it.text; |
| const theme = typeof it === 'string' ? it : (it.theme || it.text); |
| return ( |
| <button |
| key={i} |
| className="ch-suggest-tile" |
| onClick={()=>onPick(text)} |
| title={text} |
| > |
| <span className="ch-suggest-tile-text">{theme}</span> |
| <span className="ch-suggest-tile-arrow"><Icon name="arrow-up-right"/></span> |
| </button> |
| ); |
| })} |
| </div> |
| </div> |
| ))} |
| </div> |
| ); |
| } |
| |
| function ChatHomeScreen({ |
| currentPropertyId, onSelectProperty, onStartChat, onSettings, |
| draft, setDraft, |
| model, setModel, attachedFiles, setAttachedFiles, |
| templateGroups = [], |
| }) { |
| const [menuOpen, setMenuOpen] = useState(false); |
| const property = PROPERTIES.find(p => p.id === currentPropertyId); |
| const color = property ? (PROPERTY_COLORS[property.id] || 'var(--gray-04)') : null; |
| |
| |
| useEffect(() => { |
| if (!menuOpen) return; |
| const onDown = () => setMenuOpen(false); |
| document.addEventListener('mousedown', onDown); |
| return () => document.removeEventListener('mousedown', onDown); |
| }, [menuOpen]); |
| |
| const handleSend = (text) => { |
| if (!currentPropertyId) { |
| setMenuOpen(true); |
| return; |
| } |
| onStartChat(currentPropertyId, text); |
| }; |
| |
| const noProp = !currentPropertyId; |
| |
| return ( |
| <div className="chat-home-screen"> |
| <div className="ch-container"> |
| <div className="ch-hero"> |
| <h1> |
| <span className="ch-h1-pill-wrap" onMouseDown={(e)=>e.stopPropagation()}> |
| <button |
| className={"ch-h1-pill"+(noProp?' empty':'')} |
| onClick={()=>setMenuOpen(!menuOpen)} |
| > |
| <Icon name="building-2"/> |
| <span>{property ? property.name : '対象物件を選択'}</span> |
| <span className="chev"><Icon name="chevron-down"/></span> |
| </button> |
| {menuOpen && ( |
| <PropertyMenu |
| selectedId={currentPropertyId} |
| onSelect={onSelectProperty} |
| onClose={()=>setMenuOpen(false)} |
| /> |
| )} |
| </span> |
| {' '}に関して質問する |
| </h1> |
| </div> |
| |
| <div className="ch-compose-wrap"> |
| <QuestionForm |
| onSend={handleSend} |
| value={draft} |
| setValue={setDraft} |
| disabled={false} |
| model={model} |
| setModel={setModel} |
| onNewChat={()=>{}} |
| attachedFiles={attachedFiles} |
| setAttachedFiles={setAttachedFiles} |
| hideNewChatBtn={true} |
| placeholder={noProp ? '対象物件を選択してから質問を入力してください' : '質問・依頼を入力してください'} |
| /> |
| <SuggestSections groups={templateGroups} onPick={handleSend}/> |
| </div> |
| </div> |
| </div> |
| ); |
| } |
| |
| |
| |
| function MockPdfPage({doc}) { |
| const renderLines = (text) => |
| text.split('\n').map((line, li) => <span key={li}>{line}<br/></span>); |
| |
| let sections; |
| if (doc.type === '理事会議案書') { |
| sections = [ |
| { h:'1. 開催概要', p:`日時: ${doc.date} 19:00–21:00\n場所: 集会室\n出席予定: 理事長以下 全員(7名)` }, |
| { h:'2. 議題', p:'(1) 定期点検結果と是正対応について\n(2) 共用部 LED化工事の進捗報告\n(3) 駐輪場 増設の検討\n(4) 次回理事会の日程' }, |
| { h:'3. 添付資料', p:'・点検報告書(管理会社作成)\n・LED工事 中間進捗レポート' }, |
| ]; |
| } else if (doc.type === '理事会議事録') { |
| sections = [ |
| { h:'1. 開催概要', p:`日時: ${doc.date}\n場所: 集会室\n出席者: 理事長 ほか6名` }, |
| { h:'2. 議事内容', p:'各議題について議論を行い、以下のとおり決議した。' }, |
| { h:'3. 決議事項', p:'(1) 定期点検の指摘事項 → 補修工事を承認\n(2) LED化工事 → 予定通り進行を承認\n(3) 駐輪場 増設 → 次回までに概算見積を取得' }, |
| ]; |
| } else if (doc.type === '総会議案書') { |
| sections = [ |
| { h:'1. 開催概要', p:`日時: ${doc.date}\n場所: 集会室\n議長: 理事長` }, |
| { h:'2. 第1号議案', p:'前期 収支決算報告および剰余金処分案 承認の件' }, |
| { h:'3. 第2号議案', p:'当期 事業計画案および収支予算案 承認の件' }, |
| { h:'4. 第3号議案', p:'役員選任の件' }, |
| ]; |
| } else if (doc.type === '総会議事録') { |
| sections = [ |
| { h:'1. 開催概要', p:`日時: ${doc.date}\n出席者: 議決権数 / 全議決権数 = 充足` }, |
| { h:'2. 議決結果', p:'第1号議案 〜 第3号議案、いずれも原案どおり可決' }, |
| ]; |
| } else { |
| sections = [ |
| { h:'第1章 文書の概要', p:`本書は ${doc.name} の正式な記録です。` }, |
| { h:'第2章 主な内容', p:'・取り決め事項\n・関係当事者\n・有効期限と更新条件' }, |
| { h:'第3章 補足', p:'本資料は管理会社の所管の下、最新版で参照ください。' }, |
| ]; |
| } |
| |
| return ( |
| <div className="pdf-page mock-pdf-page"> |
| <h1>{doc.name}</h1> |
| <p>{doc.date}{doc.type ? ' / '+doc.type : ''}</p> |
| {sections.map((s, i) => ( |
| <React.Fragment key={i}> |
| <h2>{s.h}</h2> |
| <p>{renderLines(s.p)}</p> |
| </React.Fragment> |
| ))} |
| <div className="pageno">1 / {doc.pages || '–'}</div> |
| </div> |
| ); |
| } |
| |
| |
| function DocumentPreviewModal({doc, onClose}) { |
| useEffect(() => { |
| const onKey = (e) => { if (e.key === 'Escape') onClose(); }; |
| window.addEventListener('keydown', onKey); |
| return () => window.removeEventListener('keydown', onKey); |
| }, [onClose]); |
| |
| if (!doc) return null; |
| return ( |
| <div className="dpm-backdrop" onClick={onClose}> |
| <div className="dpm-card" onClick={(e)=>e.stopPropagation()}> |
| <div className="dpm-toolbar"> |
| <Icon name="file-text" style={{color:'#b3010e'}}/> |
| <div className="dpm-toolbar-body"> |
| <div className="dpm-doc-name">{doc.name}</div> |
| <div className="dpm-doc-meta"> |
| {doc.date} |
| {doc.type ? ' ・ '+doc.type : ''} |
| {doc.size ? ' ・ '+doc.size : ''} |
| {doc.pages ? ' ・ '+doc.pages+'p' : ''} |
| </div> |
| </div> |
| <button className="doc-action-btn"><Icon name="download"/>DL</button> |
| <button className="dpm-close" onClick={onClose} title="閉じる" aria-label="close"> |
| <Icon name="x"/> |
| </button> |
| </div> |
| <div className="dpm-body"> |
| <MockPdfPage doc={doc}/> |
| </div> |
| </div> |
| </div> |
| ); |
| } |
| |
| function UploadDocsModal({open, onClose, onSubmit, propertyName, initialCategory}) { |
| const [category, setCategory] = useState(initialCategory || UPLOAD_CATEGORIES[0]); |
| const [fileName, setFileName] = useState(''); |
| const fileInputRef = useRef(null); |
| |
| useEffect(() => { |
| if (open) { |
| setCategory(initialCategory || UPLOAD_CATEGORIES[0]); |
| setFileName(''); |
| } |
| }, [open, initialCategory]); |
| useEffect(() => { |
| const onKey = (e) => { if (e.key === 'Escape' && open) onClose(); }; |
| window.addEventListener('keydown', onKey); |
| return () => window.removeEventListener('keydown', onKey); |
| }, [onClose, open]); |
| |
| if (!open) return null; |
| return ( |
| <div className="dpm-backdrop" onClick={onClose}> |
| <div className="upm-card" onClick={(e)=>e.stopPropagation()}> |
| <div className="upm-head"> |
| <div> |
| <div className="upm-title">資料をアップロード</div> |
| <div className="upm-sub"> |
| <Icon name="building-2"/> |
| <span>{propertyName || '全物件'}</span> |
| </div> |
| </div> |
| <button className="dpm-close" onClick={onClose} aria-label="close"> |
| <Icon name="x"/> |
| </button> |
| </div> |
| <div className="upm-body"> |
| <div className="upm-field-label">1. カテゴリを選択</div> |
| <div className="upm-cat-grid"> |
| {UPLOAD_CATEGORIES.map(cat => ( |
| <label |
| key={cat} |
| className={"upm-cat-card"+(category===cat?' active':'')} |
| > |
| <input |
| type="radio" name="upload-cat" |
| value={cat} checked={category===cat} |
| onChange={()=>setCategory(cat)} |
| /> |
| <Icon name="folder"/> |
| <span>{cat}</span> |
| </label> |
| ))} |
| </div> |
| |
| <div className="upm-field-label" style={{marginTop:18}}>2. ファイルを選択</div> |
| <button |
| className={"upm-drop"+(fileName?' has-file':'')} |
| onClick={()=>fileInputRef.current?.click()} |
| > |
| <input |
| ref={fileInputRef} |
| type="file" style={{display:'none'}} |
| onChange={(e)=>setFileName(e.target.files?.[0]?.name || '')} |
| /> |
| {fileName ? ( |
| <> |
| <Icon name="file-text" style={{color:'#b3010e'}}/> |
| <div className="upm-drop-text"> |
| <div className="upm-drop-main">{fileName}</div> |
| <div className="upm-drop-sub">クリックして別のファイルを選択</div> |
| </div> |
| </> |
| ) : ( |
| <> |
| <Icon name="upload-cloud"/> |
| <div className="upm-drop-text"> |
| <div className="upm-drop-main">ファイルをドラッグ&ドロップ</div> |
| <div className="upm-drop-sub">またはクリックして選択 ・ PDF / Excel / Word 対応</div> |
| </div> |
| </> |
| )} |
| </button> |
| </div> |
| <div className="upm-foot"> |
| <button className="settings-btn ghost" onClick={onClose}>キャンセル</button> |
| <button |
| className="settings-btn primary" |
| disabled={!fileName} |
| onClick={()=>onSubmit({category, fileName})} |
| > |
| <Icon name="upload-cloud"/> |
| アップロード |
| </button> |
| </div> |
| </div> |
| </div> |
| ); |
| } |
| |
| function FolderTree({sections, openFolders, toggleFolder, onPickDoc, onAddToFolder, emptyText, hideCounts=false, selectedDocName=null}) { |
| const totalDocs = sections.reduce((n, s) => n + s.total, 0); |
| if (totalDocs === 0) { |
| return <div className="dc-empty">{emptyText}</div>; |
| } |
| return ( |
| <div className="folder-tree"> |
| {sections.map(section => { |
| const isOpen = openFolders.has(section.key); |
| const showAdd = !!onAddToFolder; |
| // 手動登録は0件カテゴリでもフォルダだけは表示してそこに追加できるようにする |
| if (section.total === 0 && !showAdd) return null; |
| return ( |
| <div key={section.key} className="folder-node"> |
| <div className={"folder-row-wrap"+(showAdd?' has-add':'')}> |
| <button |
| className={"folder-row type"+(isOpen?' open':'')} |
| onClick={()=>toggleFolder(section.key)} |
| > |
| <span className="folder-chev"> |
| <Icon name={isOpen?'chevron-down':'chevron-right'}/> |
| </span> |
| <span className="folder-icon"> |
| <Icon name={isOpen?'folder-open':'folder'}/> |
| </span> |
| <span className="folder-name">{section.title}</span> |
| {!hideCounts && <span className="folder-count">{section.total}件</span>} |
| </button> |
| {showAdd && ( |
| <button |
| className="folder-add" |
| onClick={()=>onAddToFolder(section)} |
| title={`${section.title} に資料をアップロード`} |
| > |
| <Icon name="upload-cloud"/> |
| <span className="folder-add-label">アップロード</span> |
| </button> |
| )} |
| </div> |
| {isOpen && ( |
| <div className="folder-children"> |
| {section.years.map(year => { |
| const yearOpen = openFolders.has(year.key); |
| const isLatest = year.key === section.latestYearKey; |
| return ( |
| <div key={year.key} className="folder-node"> |
| <button |
| className={"folder-row year"+(yearOpen?' open':'')+(isLatest?' latest':'')} |
| onClick={()=>toggleFolder(year.key)} |
| > |
| <span className="folder-chev"> |
| <Icon name={yearOpen?'chevron-down':'chevron-right'}/> |
| </span> |
| <span className="folder-icon"> |
| <Icon name={yearOpen?'folder-open':'folder'}/> |
| </span> |
| <span className="folder-name">{year.year}年度</span> |
| {isLatest && <span className="folder-latest">最新</span>} |
| {!hideCounts && <span className="folder-count">{year.docs.length}件</span>} |
| </button> |
| {yearOpen && ( |
| <div className="folder-children"> |
| {year.docs.map(d => ( |
| <button |
| key={d.id} |
| className={"folder-row doc"+(selectedDocName===d.name?' selected':'')} |
| onClick={()=>onPickDoc(d)} |
| > |
| <span className="folder-chev placeholder"/> |
| <span className="folder-doc-icon"><Icon name="file-text"/></span> |
| <div className="folder-doc-body"> |
| <div className="folder-doc-name"> |
| <span>{d.name}</span> |
| {d.latest && <span className="dl-doc-badge">最新</span>} |
| </div> |
| <div className="folder-doc-meta">{d.date}</div> |
| </div> |
| <span className="folder-doc-chev"><Icon name="chevron-right"/></span> |
| </button> |
| ))} |
| </div> |
| )} |
| </div> |
| ); |
| })} |
| </div> |
| )} |
| </div> |
| ); |
| })} |
| </div> |
| ); |
| } |
| |
| function buildYearSections(docs, parentKey) { |
| const yearMap = new Map(); |
| docs.forEach(d => { |
| const y = d.date.split('/')[0]; |
| if (!yearMap.has(y)) yearMap.set(y, []); |
| yearMap.get(y).push(d); |
| }); |
| const years = [...yearMap.entries()] |
| .sort((a, b) => b[0].localeCompare(a[0])) |
| .map(([year, ds]) => ({ year, docs:ds, key:`${parentKey}:${year}` })); |
| return { years, latestYearKey: years[0]?.key, total:docs.length }; |
| } |
| |
| function UploadScreen({propertyId, onBack}) { |
| const property = PROPERTIES.find(p => p.id === propertyId); |
| const [previewDoc, setPreviewDoc] = useState(null); |
| const [yearFilter, setYearFilter] = useState('全期間'); |
| const [yearOpen, setYearOpen] = useState(false); |
| const [uploadOpen, setUploadOpen] = useState(false); |
| const [uploadCategory, setUploadCategory] = useState(null); |
| const [openFolders, setOpenFolders] = useState(()=>new Set()); |
| |
| const toggleFolder = (key) => { |
| setOpenFolders(prev => { |
| const next = new Set(prev); |
| next.has(key) ? next.delete(key) : next.add(key); |
| return next; |
| }); |
| }; |
| |
| const openUploadFor = (cat) => { |
| setUploadCategory(cat); |
| setUploadOpen(true); |
| }; |
| |
| const handleUploadSubmit = ({category, fileName}) => { |
| alert(`${category} に「${fileName}」をアップロードしました(モック)${property ? '\n対象: ' + property.name : ''}`); |
| setUploadOpen(false); |
| }; |
| |
| |
| const yearOptions = (() => { |
| const years = new Set(); |
| MKCORE_DOCS.forEach(d => years.add(d.date.split('/')[0])); |
| UPLOAD_CATEGORIES.forEach(cat => { |
| (PROPERTY_DOCS[cat] || []).forEach(d => years.add(d.date.split('/')[0])); |
| }); |
| return ['全期間', ...[...years].sort((a,b)=>b.localeCompare(a))]; |
| })(); |
| |
| const matchYear = (d) => yearFilter === '全期間' || d.date.startsWith(yearFilter+'/'); |
| |
| const mkcoreSections = MKCORE_DOC_TYPES.map(t => { |
| const docs = MKCORE_DOCS.filter(d => d.type === t && matchYear(d)); |
| return { key:`mk:${t}`, title:t, ...buildYearSections(docs, `mk:${t}`) }; |
| }); |
| const mkcoreVisibleCount = mkcoreSections.reduce((n, s) => n + s.total, 0); |
| |
| const manualSections = UPLOAD_CATEGORIES.map(cat => { |
| const docs = (PROPERTY_DOCS[cat] || []) |
| .filter(matchYear) |
| .map((d, i) => ({...d, type:cat, id:`${cat}-${i}`})); |
| return { key:`manual:${cat}`, title:cat, ...buildYearSections(docs, `manual:${cat}`) }; |
| }); |
| const manualVisibleCount = manualSections.reduce((n, s) => n + s.total, 0); |
| |
| |
| useEffect(() => { |
| const next = new Set(); |
| [...mkcoreSections, ...manualSections].forEach(s => { |
| if (s.total === 0 || !s.latestYearKey) return; |
| next.add(s.key); |
| next.add(s.latestYearKey); |
| }); |
| setOpenFolders(next); |
| }, [yearFilter]); |
| |
| return ( |
| <div className="documents-screen"> |
| <div className="documents-header"> |
| <button className="upload-back-btn" onClick={onBack} title="戻る"> |
| <Icon name="arrow-left"/> |
| </button> |
| <div className="upload-title-block"> |
| <h1 className="upload-title">資料</h1> |
| <div className="upload-subtitle"> |
| <Icon name="building-2"/> |
| <span>{property ? property.name : '全物件(物件未指定)'}</span> |
| </div> |
| </div> |
| <div className="documents-filter"> |
| <span className="df-label"> |
| <Icon name="filter"/> |
| 年度 |
| </span> |
| <FYSelect |
| value={yearFilter} |
| options={yearOptions} |
| isOpen={yearOpen} |
| onToggle={()=>setYearOpen(!yearOpen)} |
| onSelect={(v)=>{ setYearFilter(v); setYearOpen(false); }} |
| /> |
| </div> |
| </div> |
| |
| <div className="documents-columns"> |
| <section className="dc-col"> |
| <div className="dc-col-head"> |
| <span className="dc-col-icon mkcore"><Icon name="refresh-cw"/></span> |
| <div className="dc-col-titles"> |
| <div className="dc-col-title-row"> |
| <span className="dc-col-title">MKCore連携</span> |
| <span className="dc-col-count">{mkcoreVisibleCount}件</span> |
| </div> |
| <div className="dc-col-desc"> |
| 最終同期: {property?.mkcoreDate || '–'} |
| </div> |
| </div> |
| </div> |
| <div className="dc-col-body"> |
| <FolderTree |
| sections={mkcoreSections} |
| openFolders={openFolders} |
| toggleFolder={toggleFolder} |
| onPickDoc={setPreviewDoc} |
| emptyText="該当年度の資料はありません" |
| /> |
| </div> |
| </section> |
| |
| <section className="dc-col"> |
| <div className="dc-col-head"> |
| <span className="dc-col-icon manual"><Icon name="folder"/></span> |
| <div className="dc-col-titles"> |
| <div className="dc-col-title-row"> |
| <span className="dc-col-title">手動登録</span> |
| <span className="dc-col-count">{manualVisibleCount}件</span> |
| </div> |
| </div> |
| <button |
| className="folder-add" |
| onClick={()=>openUploadFor(null)} |
| title="資料をアップロード" |
| > |
| <Icon name="upload-cloud"/> |
| <span className="folder-add-label">アップロード</span> |
| </button> |
| </div> |
| |
| <div className="dc-col-body"> |
| <FolderTree |
| sections={manualSections} |
| openFolders={openFolders} |
| toggleFolder={toggleFolder} |
| onPickDoc={setPreviewDoc} |
| emptyText={yearFilter === '全期間' ? '登録された資料はありません' : '該当年度の資料はありません'} |
| /> |
| </div> |
| </section> |
| </div> |
| |
| <DocumentPreviewModal doc={previewDoc} onClose={()=>setPreviewDoc(null)}/> |
| <UploadDocsModal |
| open={uploadOpen} |
| initialCategory={uploadCategory} |
| onClose={()=>setUploadOpen(false)} |
| onSubmit={handleUploadSubmit} |
| propertyName={property?.name} |
| /> |
| </div> |
| ); |
| } |
| |
| |
| const SETTINGS_NAV_ITEMS = [ |
| { id:'basic', icon:'sliders-horizontal', label:'基本設定' }, |
| { id:'templates', icon:'message-square', label:'質問例' }, |
| ]; |
| |
| function SettingsModelsPane() { |
| return ( |
| <div className="settings-form"> |
| <div className="settings-field"> |
| <div className="settings-field-label"><span>デフォルトモデル</span></div> |
| <div className="settings-select" style={{minWidth:280}}> |
| <span>Claude Opus 4.7</span> |
| <span className="chev"><Icon name="chevron-down"/></span> |
| </div> |
| <div className="settings-row-hint">新しいチャット開始時に既定で選ばれるモデルです。</div> |
| </div> |
| <div className="settings-field"> |
| <div className="settings-field-label"><span>有効なモデル</span></div> |
| <div className="model-chips-container"> |
| {SETTINGS_MODELS.map(m => ( |
| <span key={m} className="model-chip"> |
| {m} |
| <button className="remove-btn" title="削除"><Icon name="x"/></button> |
| </span> |
| ))} |
| </div> |
| </div> |
| </div> |
| ); |
| } |
| |
| function SettingsInstructionsPane() { |
| return ( |
| <div className="settings-form"> |
| <div className="settings-field"> |
| <div className="settings-field-label"><span>カスタム指示</span></div> |
| <div className="custom-instruction"> |
| <div className="ci-toolbar"> |
| <button className="ci-toolbar-btn"> |
| テキスト<Icon name="chevron-down"/> |
| </button> |
| <div className="ci-toolbar-divider"></div> |
| <button className="ci-toolbar-btn icon-only" title="太字"><strong style={{fontSize:13}}>B</strong></button> |
| <div className="ci-toolbar-divider"></div> |
| <button className="ci-toolbar-btn icon-only active" title="箇条書き"><Icon name="list"/></button> |
| <button className="ci-toolbar-btn icon-only" title="番号付きリスト"><Icon name="list-ordered"/></button> |
| <button className="ci-toolbar-btn icon-only" title="インデント"><Icon name="indent"/></button> |
| <button className="ci-toolbar-btn icon-only" title="アウトデント"><Icon name="outdent"/></button> |
| <button className="ci-toolbar-btn icon-only" title="リンク"><Icon name="link"/></button> |
| <div className="ci-toolbar-spacer"></div> |
| <button className="ci-toolbar-btn icon-only" title="HTML"><Icon name="code"/></button> |
| <button className="ci-toolbar-btn icon-only" title="コピー" onClick={() => window.showCopyToast?.()}><Icon name="copy"/></button> |
| </div> |
| <div className="ci-content"> |
| <h3>要件</h3> |
| <ul> |
| <li>ユーザーから物件管理に関する質問が来たときは、登録済みの管理規約・物件引継書・MKCore連携資料に基づいて回答すること。資料に記載がない事項は推測せず、確認が必要な旨を明示する。</li> |
| <li>専門用語(管理委託契約・長期修繕計画・収支報告など)は、フロントマネージャー新人にも分かるよう短く補足を加える。</li> |
| <li>関連する参考資料があれば必ず引用ソースとして提示し、ユーザーが該当箇所を確認できるようにする。 |
| <ol> |
| <li>理事会・総会関連の質問: <a href="#">理事会・総会業務マニュアル</a> を参照</li> |
| <li>会計・収支関連の質問: <a href="#">会計処理ガイドライン</a> を参照</li> |
| </ol> |
| </li> |
| </ul> |
| </div> |
| </div> |
| </div> |
| </div> |
| ); |
| } |
| |
| function SettingsTemplatePane({groups, setGroups}) { |
| const [newGroupName, setNewGroupName] = useState(''); |
| const [draftItems, setDraftItems] = useState({}); |
| |
| const addGroup = () => { |
| const name = newGroupName.trim(); |
| if (!name) return; |
| const id = 'g' + Date.now(); |
| setGroups([...groups, { id, cat:name, icon:'folder', items:[] }]); |
| setNewGroupName(''); |
| }; |
| |
| const removeGroup = (gid) => { |
| if (!window.confirm('このグループを削除しますか?登録済みの質問もすべて削除されます。')) return; |
| setGroups(groups.filter(g => g.id !== gid)); |
| }; |
| |
| const renameGroup = (gid, name) => { |
| setGroups(groups.map(g => g.id === gid ? { ...g, cat:name } : g)); |
| }; |
| |
| const addItem = (gid) => { |
| const text = (draftItems[gid] || '').trim(); |
| if (!text) return; |
| setGroups(groups.map(g => g.id === gid ? { ...g, items:[...g.items, text] } : g)); |
| setDraftItems({ ...draftItems, [gid]:'' }); |
| }; |
| |
| const removeItem = (gid, idx) => { |
| setGroups(groups.map(g => g.id === gid |
| ? { ...g, items: g.items.filter((_, i) => i !== idx) } |
| : g |
| )); |
| }; |
| |
| return ( |
| <div className="template-pane"> |
| <div className="template-add-group"> |
| <input |
| className="template-input" |
| placeholder="新しいグループ名(例: 物件管理)" |
| value={newGroupName} |
| onChange={(e)=>setNewGroupName(e.target.value)} |
| onKeyDown={(e)=>{ if (e.key==='Enter') addGroup(); }} |
| /> |
| <button className="settings-btn primary" onClick={addGroup} disabled={!newGroupName.trim()}> |
| <Icon name="plus"/> |
| グループを追加 |
| </button> |
| </div> |
| |
| {groups.length === 0 && ( |
| <div className="settings-empty">グループを追加してテンプレート質問を登録してください。</div> |
| )} |
| |
| {groups.map(g => ( |
| <div key={g.id} className="template-group"> |
| <div className="tg-head"> |
| <span className="tg-icon"><Icon name={g.icon || 'folder'}/></span> |
| <input |
| className="tg-name-input" |
| value={g.cat} |
| onChange={(e)=>renameGroup(g.id, e.target.value)} |
| /> |
| <span className="tg-count">{g.items.length}件</span> |
| <button className="tg-delete" title="グループを削除" onClick={()=>removeGroup(g.id)}> |
| <Icon name="trash-2"/> |
| </button> |
| </div> |
| <div className="tg-body"> |
| {g.items.length === 0 ? ( |
| <div className="tg-empty">質問が登録されていません。</div> |
| ) : ( |
| g.items.map((it, i) => { |
| const text = typeof it === 'string' ? it : (it.text || ''); |
| const theme = typeof it === 'string' ? '' : (it.theme || ''); |
| return ( |
| <div key={i} className="tg-item"> |
| <span className="tg-item-icon"><Icon name="message-square"/></span> |
| <span className="tg-item-text"> |
| {theme && <strong style={{marginRight:6}}>{theme}</strong>} |
| {text} |
| </span> |
| <button className="tg-item-remove" title="削除" onClick={()=>removeItem(g.id, i)}> |
| <Icon name="x"/> |
| </button> |
| </div> |
| ); |
| }) |
| )} |
| <div className="tg-add-row"> |
| <input |
| className="template-input" |
| placeholder="質問を入力してEnter(例: 直近の点検報告書を要約する)" |
| value={draftItems[g.id] || ''} |
| onChange={(e)=>setDraftItems({ ...draftItems, [g.id]:e.target.value })} |
| onKeyDown={(e)=>{ if (e.key==='Enter') addItem(g.id); }} |
| /> |
| <button |
| className="settings-btn outline" |
| onClick={()=>addItem(g.id)} |
| disabled={!(draftItems[g.id] || '').trim()} |
| > |
| <Icon name="plus"/> |
| 質問を追加 |
| </button> |
| </div> |
| </div> |
| </div> |
| ))} |
| </div> |
| ); |
| } |
| |
| |
| function SettingsBasicPane() { |
| const [openNormal, setOpenNormal] = useState(true); |
| const [advancedOpen, setAdvancedOpen] = useState(false); |
| |
| const [attach, setAttach] = useState(true); |
| const [pdfParser, setPdfParser] = useState('標準パーサー'); |
| const [maxHistory, setMaxHistory] = useState('20'); |
| |
| const [defaultModel, setDefaultModel] = useState('Claude Opus 4.7'); |
| const [enabledModels, setEnabledModels] = useState(()=>new Set([ |
| 'Claude Opus 4.7','Claude Sonnet 4.6','Claude Haiku 4.5', |
| 'GPT-5','GPT-5 mini','Gemini 2.5 Pro', |
| ])); |
| const [weeklyLimit, setWeeklyLimit] = useState('無制限'); |
| const [citation, setCitation] = useState(true); |
| |
| const removeModel = (m) => { |
| setEnabledModels(prev => { const n = new Set(prev); n.delete(m); return n; }); |
| }; |
| |
| return ( |
| <div className="settings-form"> |
| {/* 通常チャット設定 */} |
| <div className="settings-section"> |
| <div |
| className={"settings-section-header"+(openNormal?' open':'')} |
| onClick={()=>setOpenNormal(!openNormal)} |
| > |
| <span className="title-icon"><Icon name="message-square"/></span> |
| <span className="title">通常チャット設定</span> |
| <span className="help-icon"><Icon name="help-circle"/></span> |
| <span className="chev"><Icon name="chevron-down"/></span> |
| </div> |
| {openNormal && ( |
| <div className="settings-section-body"> |
| {/* 利用可能モデル */} |
| <div className="settings-field"> |
| <div className="settings-field-label"> |
| <span>利用可能モデル</span> |
| <span className="help-icon"><Icon name="help-circle"/></span> |
| </div> |
| <div className="model-chips-container"> |
| {[...enabledModels].map(m => ( |
| <span key={m} className="model-chip"> |
| {m} |
| <button className="remove-btn" onClick={()=>removeModel(m)}> |
| <Icon name="x"/> |
| </button> |
| </span> |
| ))} |
| <button className="model-chips-expand" title="モデルを追加"> |
| <Icon name="plus"/> |
| </button> |
| </div> |
| <div className="settings-row" style={{marginTop:14, paddingTop:0, borderBottom:'none'}}> |
| <div className="settings-row-label">デフォルトモデル</div> |
| <div className="settings-row-control"> |
| <button className="settings-select" style={{minWidth:240}}> |
| {defaultModel} |
| <span className="chev"><Icon name="chevron-down"/></span> |
| </button> |
| </div> |
| </div> |
| </div> |
| |
| {/* カスタム指示 */} |
| <div className="settings-field"> |
| <div className="settings-field-label"> |
| <span>カスタム指示</span> |
| <span className="help-icon"><Icon name="help-circle"/></span> |
| </div> |
| <div className="custom-instruction"> |
| <div className="ci-toolbar"> |
| <button className="ci-toolbar-btn">テキスト<Icon name="chevron-down"/></button> |
| <div className="ci-toolbar-divider"></div> |
| <button className="ci-toolbar-btn icon-only" title="太字"><strong style={{fontSize:13}}>B</strong></button> |
| <div className="ci-toolbar-divider"></div> |
| <button className="ci-toolbar-btn icon-only" title="箇条書き"><Icon name="list"/></button> |
| <button className="ci-toolbar-btn icon-only" title="番号付きリスト"><Icon name="list-ordered"/></button> |
| <div className="ci-toolbar-spacer"></div> |
| <button className="ci-toolbar-btn icon-only" title="HTML"><Icon name="code"/></button> |
| </div> |
| <div className="ci-content"> |
| <ul> |
| <li>ユーザーから物件管理に関する質問が来たときは、登録済みの管理規約・物件引継書・MKCore連携資料に基づいて回答すること。</li> |
| <li>専門用語(管理委託契約・長期修繕計画・収支報告など)は短く補足を加える。</li> |
| <li>関連する参考資料があれば必ず引用ソースとして提示すること。</li> |
| </ul> |
| </div> |
| </div> |
| </div> |
| |
| {/* 詳細設定 collapsible */} |
| <div className="settings-field"> |
| <button className="advanced-toggle" onClick={()=>setAdvancedOpen(!advancedOpen)}> |
| <Icon name={advancedOpen?'chevron-up':'chevron-down'}/> |
| {advancedOpen ? '詳細設定を非表示' : '詳細設定を表示'} |
| </button> |
| {advancedOpen && ( |
| <div className="advanced-body"> |
| <div className="settings-row"> |
| <div className="settings-row-label"> |
| <Icon name="paperclip"/> |
| 添付ファイル |
| </div> |
| <div className="settings-row-control"> |
| {attach && ( |
| <button className="settings-select" style={{minWidth:160}}> |
| {pdfParser} |
| <span className="chev"><Icon name="chevron-down"/></span> |
| </button> |
| )} |
| <div |
| className={"check-box"+(attach?' on':'')} |
| onClick={()=>setAttach(!attach)} |
| > |
| {attach && <Icon name="check"/>} |
| </div> |
| </div> |
| </div> |
| <div className="settings-row"> |
| <div className="settings-row-label"> |
| <Icon name="history"/> |
| 最大チャット履歴 |
| </div> |
| <div className="settings-row-control"> |
| <button className="settings-select" style={{minWidth:200}}> |
| 直近 {maxHistory} 件 |
| <span className="chev"><Icon name="chevron-down"/></span> |
| </button> |
| <div className="settings-row-hint">過去のやり取りを何件までモデルに渡すかを指定します。</div> |
| </div> |
| </div> |
| <div className="settings-row"> |
| <div className="settings-row-label"> |
| <Icon name="gauge"/> |
| 週間使用上限 |
| </div> |
| <div className="settings-row-control"> |
| <button className="settings-select" style={{minWidth:200}}> |
| {weeklyLimit} |
| <span className="chev"><Icon name="chevron-down"/></span> |
| </button> |
| <div className="settings-row-hint">超過時はフォールバックモデルに自動切替されます。</div> |
| </div> |
| </div> |
| <div className="settings-row" style={{borderBottom:'none'}}> |
| <div className="settings-row-label"> |
| <Icon name="quote"/> |
| 引用の有効化 |
| </div> |
| <div className="settings-row-control"> |
| <div |
| className={"settings-toggle"+(citation?' on':'')} |
| onClick={()=>setCitation(!citation)} |
| > |
| <span className="settings-toggle-knob"></span> |
| </div> |
| </div> |
| </div> |
| </div> |
| )} |
| </div> |
| </div> |
| )} |
| </div> |
| </div> |
| ); |
| } |
| |
| function SettingsScreen({onBack, templateGroups, setTemplateGroups}) { |
| const [activeNav, setActiveNav] = useState('basic'); |
| const activeMeta = SETTINGS_NAV_ITEMS.find(n => n.id === activeNav); |
| |
| return ( |
| <div className="settings-screen"> |
| <div className="settings-twopane"> |
| <aside className="settings-nav"> |
| <div className="settings-nav-header"> |
| <button className="settings-back-link" onClick={onBack}> |
| <Icon name="arrow-left"/> |
| 戻る |
| </button> |
| </div> |
| <div className="settings-nav-body"> |
| <div className="settings-nav-section-title">設定</div> |
| {SETTINGS_NAV_ITEMS.map(n => ( |
| <button key={n.id} |
| className={"settings-nav-item"+(activeNav===n.id?' active':'')} |
| onClick={()=>setActiveNav(n.id)} |
| > |
| <Icon name={n.icon}/> |
| <span>{n.label}</span> |
| </button> |
| ))} |
| </div> |
| </aside> |
| <main className="settings-content"> |
| <div className="settings-content-header"> |
| <h2>{activeMeta?.label}</h2> |
| <div style={{flex:1}}></div> |
| <button className="settings-icon-btn" title="再読み込み"><Icon name="rotate-ccw"/></button> |
| <button className="settings-btn primary" disabled>保存</button> |
| </div> |
| <div className="settings-content-body"> |
| {activeNav === 'basic' && <SettingsBasicPane/>} |
| {activeNav === 'templates' && ( |
| <SettingsTemplatePane groups={templateGroups} setGroups={setTemplateGroups}/> |
| )} |
| </div> |
| </main> |
| </div> |
| </div> |
| ); |
| } |
| |
| |
| function CitationList({onSelect, activeNum}) { |
| return ( |
| <div className="citation-list"> |
| <div className="citation-list-title">参照ソース ({CITATIONS.length})</div> |
| {CITATIONS.map(c => ( |
| <div key={c.n} |
| className={"citation-card"+(activeNum===c.n?' active':'')} |
| onClick={()=>onSelect(c.n)} |
| > |
| <div className="num">{c.n}</div> |
| <div className="body"> |
| <div className="title">{c.title}</div> |
| <div className="meta">{c.meta}</div> |
| </div> |
| </div> |
| ))} |
| </div> |
| ); |
| } |
| |
| function BotAnswer({content, onCiteClick, activeCite, withCitations}) { |
| const renderInline = (text) => { |
| const parts = text.split(/(\{\{cite:\d+\}\})/g); |
| return parts.map((p, i) => { |
| const m = p.match(/^\{\{cite:(\d+)\}\}$/); |
| if (m) { |
| const n = parseInt(m[1]); |
| return ( |
| <button key={i} |
| className={"citation"+(activeCite===n?" active":"")} |
| onClick={()=>onCiteClick(n)} |
| >{n}</button> |
| ); |
| } |
| return <span key={i}>{p}</span>; |
| }); |
| }; |
| |
| return ( |
| <div className="bot-msg-wrap"> |
| <div className="msg-header"> |
| <span className="name">Carina</span> |
| <span className="time">15:42</span> |
| </div> |
| <div className="bot-msg"> |
| {content.map((block, i) => { |
| if (block.type === 'p') return <p key={i}>{renderInline(block.text)}</p>; |
| if (block.type === 'h') return <p key={i}><strong>{block.text}</strong></p>; |
| if (block.type === 'ul') return ( |
| <ul key={i}>{block.items.map((it, j) => <li key={j}>{renderInline(it)}</li>)}</ul> |
| ); |
| return null; |
| })} |
| {withCitations && <CitationList onSelect={onCiteClick} activeNum={activeCite}/>} |
| <div className="msg-actions"> |
| <button className="msg-action-btn" title="コピー" onClick={() => window.showCopyToast?.()}><Icon name="copy"/></button> |
| <button className="msg-action-btn" title="再生成"><Icon name="refresh-cw"/></button> |
| <button className="msg-action-btn" title="高評価"><Icon name="thumbs-up"/></button> |
| <button className="msg-action-btn" title="低評価"><Icon name="thumbs-down"/></button> |
| </div> |
| </div> |
| </div> |
| ); |
| } |
| |
| function TypingBlock() { |
| return ( |
| <div className="bot-msg-wrap"> |
| <div className="msg-header"> |
| <span className="name">Carina</span> |
| </div> |
| <div className="bot-msg"> |
| <div className="typing"><span></span><span></span><span></span></div> |
| </div> |
| </div> |
| ); |
| } |
| |
| |
| function RightPanel({doc, onClose}) { |
| if (!doc) return null; |
| return ( |
| <aside className="right-panel"> |
| <div className="rp-header"> |
| <span className="rp-title">資料ビューア</span> |
| <button className="rp-close" onClick={onClose} aria-label="close"><Icon name="x"/></button> |
| </div> |
| |
| <div className="doc-toolbar"> |
| <Icon name="file-text" style={{color:'#b3010e'}}/> |
| <div style={{flex:1, minWidth:0}}> |
| <div className="doc-name">{doc.name}</div> |
| <div className="doc-meta">{doc.date}{doc.type?' ・ '+doc.type:''}</div> |
| </div> |
| <button className="doc-action-btn"><Icon name="download"/>DL</button> |
| </div> |
| |
| <div className="rp-body"> |
| <div className="pdf-viewer" style={{padding:'24px 28px'}}> |
| <MockPdfPage doc={doc}/> |
| </div> |
| </div> |
| </aside> |
| ); |
| } |
| |
| function PdfPageContent({citation}) { |
| const n = citation.n; |
| if (n === 4) { |
| return ( |
| <div className="pdf-page"> |
| <h1>内見記録 — 用賀パークレジデンス 802号室</h1> |
| <p>記録: 鈴木(営業)/ 2026年3月14日</p> |
| <h2>第1章 内見の概要</h2> |
| <p>日時: 2026年3月14日 14:00 - 15:00<br/> |
| 立会者: 田中様(賃借人候補・1名)、当社 鈴木<br/> |
| 対象物件: 用賀パークレジデンス 802号室(1LDK・8階・専有 65.4㎡)</p> |
| <h2>第2章 設備動作確認</h2> |
| <table className="table"> |
| <thead><tr><th>設備</th><th>仕様</th><th>動作確認</th></tr></thead> |
| <tbody> |
| <tr><td>エアコン</td><td>2024年製・全室標準装備</td><td>OK</td></tr> |
| <tr><td>給湯</td><td>都市ガス(東京ガス)</td><td>OK</td></tr> |
| <tr><td>インターネット</td><td>光ファイバー(NTT東日本)</td><td>OK</td></tr> |
| <tr><td>食洗機</td><td>3口IH連動</td><td>OK</td></tr> |
| <tr><td>浴室乾燥機</td><td>追い焚き機能あり</td><td>OK</td></tr> |
| </tbody> |
| </table> |
| <h2>第3章 確認された懸念事項</h2> |
| <p>- 北側洋室の壁紙に小さな汚れあり / 退去前クリーニング手配済(業者: 〇〇クリーンサービス)<br/> |
| - バルコニー排水溝に若干の詰まり / 補修依頼済(4月初旬予定)</p> |
| <h2>第4章 ご質問内容と即答事項</h2> |
| <p>- 入居可能日:4月1日以降<br/> |
| - ペット可:小型犬・猫1匹まで(敷金+1ヶ月)<br/> |
| - 駐車場:月額25,000円・空き1台あり</p> |
| <div className="pageno">1</div> |
| </div> |
| ); |
| } |
| if (n === 5) { |
| return ( |
| <div className="pdf-page"> |
| <h1>重要事項説明書 — 用賀パークレジデンス 802号室</h1> |
| <p>令和8年2月版 / 法務部確認済み</p> |
| <h2>第1章 物件の表示</h2> |
| <p>所在地: 東京都世田谷区用賀X-X-XX<br/> |
| 建物の構造: 鉄筋コンクリート造10階建<br/> |
| 専有面積: 65.4 m²(壁芯)<br/> |
| 築年月: 2018年3月</p> |
| <h2>第2章 取引条件</h2> |
| <table className="table"> |
| <thead><tr><th>項目</th><th>条件</th></tr></thead> |
| <tbody> |
| <tr><td>賃料</td><td>198,000円 / 月</td></tr> |
| <tr><td>管理費</td><td>12,000円 / 月</td></tr> |
| <tr><td>敷金</td><td>1ヶ月(ペット可は+1ヶ月)</td></tr> |
| <tr><td>礼金</td><td>1ヶ月</td></tr> |
| <tr><td>契約期間</td><td>2年(更新可能)</td></tr> |
| <tr><td>更新料</td><td>新賃料の1ヶ月分</td></tr> |
| </tbody> |
| </table> |
| <h2>第3章 法令上の制限</h2> |
| <p>都市計画区域: 第一種住居地域<br/> |
| 建ぺい率 / 容積率: 60% / 200%<br/> |
| 日影規制: あり</p> |
| <h2>第4章 ペット可条件</h2> |
| <p>小型犬または猫1匹まで飼育可。 |
| ペット飼育の場合は敷金が1ヶ月追加。 |
| 共用部での抱きかかえ移動が必須。 |
| 破損・汚損があった場合は退去時に実費精算。</p> |
| <div className="pageno">3</div> |
| </div> |
| ); |
| } |
| if (n === 6) { |
| return ( |
| <div className="pdf-page"> |
| <h1>物件設備リスト — 用賀パークレジデンス</h1> |
| <p>物件管理部 / 2026年2月1日 更新</p> |
| <h2>共用部設備</h2> |
| <table className="table"> |
| <thead><tr><th>項目</th><th>仕様</th><th>状態</th></tr></thead> |
| <tbody> |
| <tr><td>エントランス</td><td>オートロック・モニター付</td><td>良好</td></tr> |
| <tr><td>エレベーター</td><td>2基(うち1基は荷物用兼用)</td><td>良好</td></tr> |
| <tr><td>宅配ボックス</td><td>12ボックス</td><td>良好</td></tr> |
| <tr><td>駐車場</td><td>機械式 30台</td><td>空き1台</td></tr> |
| <tr><td>駐輪場</td><td>屋根付き 40台</td><td>空きあり</td></tr> |
| </tbody> |
| </table> |
| <h2>専有部設備(802号室)</h2> |
| <p>- システムキッチン(3口IH・食洗機付き)<br/> |
| - 浴室乾燥機・追い焚き機能<br/> |
| - 温水洗浄便座<br/> |
| - 全居室エアコン完備(2024年新調)<br/> |
| - 床暖房(リビング)<br/> |
| - 二重サッシ(北側のみ)</p> |
| <h2>駐車場の詳細</h2> |
| <p>- 月額: 25,000円<br/> |
| - 種類: 機械式(高さ制限 1,550 mm)<br/> |
| - 空き状況: 1台のみ(早期申込推奨)</p> |
| <div className="pageno">5</div> |
| </div> |
| ); |
| } |
| |
| return ( |
| <div className="pdf-page"> |
| <h1>2025年 賃貸市場動向レポート — 東京都心エリア</h1> |
| <p>不動産流通推進センター / 2026年1月発行</p> |
| <h2>第3章 23区 1LDK相場の動向</h2> |
| <p>2025年第4四半期における東京23区の1LDK平均賃料は前年同期比で +3.1% の上昇となり、特に城南3区(目黒・世田谷・品川)では需要超過の状態が継続している。</p> |
| <h2>世田谷区の特徴</h2> |
| <p>世田谷区の1LDK平均賃料は 22.4万円 で、駅徒歩5分以内の築浅物件は25万円〜30万円のレンジが中心となっている。ペット可物件の供給は依然として限定的で、全体の約18%に留まる。</p> |
| <table className="table"> |
| <thead> |
| <tr><th>エリア</th><th>平均賃料</th><th>前年比</th><th>ペット可率</th></tr> |
| </thead> |
| <tbody> |
| <tr><td>用賀</td><td>23.8万円</td><td>+2.4%</td><td>16%</td></tr> |
| <tr><td>桜新町</td><td>24.2万円</td><td>+3.8%</td><td>21%</td></tr> |
| <tr><td>三軒茶屋</td><td>25.1万円</td><td>+4.1%</td><td>15%</td></tr> |
| <tr><td>下北沢</td><td>26.4万円</td><td>+5.2%</td><td>12%</td></tr> |
| </tbody> |
| </table> |
| <h2>需給バランス</h2> |
| <p>2026年に入り供給は限定的に推移し、特に築10年以内の物件は内見申込から成約までの期間が平均6.2日と短期化している。</p> |
| <div className="pageno">12</div> |
| </div> |
| ); |
| } |
| |
| |
| function FYSelect({value, options, isOpen, onToggle, onSelect}) { |
| return ( |
| <div style={{position:'relative'}}> |
| <button className="fy-pill" onClick={onToggle}> |
| {value} |
| <Icon name="chevron-down"/> |
| </button> |
| {isOpen && ( |
| <div className="fy-menu"> |
| {options.map(opt => ( |
| <button key={opt} |
| onClick={()=>onSelect(opt)} |
| className={"fy-menu-item"+(value===opt?' active':'')} |
| >{opt}</button> |
| ))} |
| </div> |
| )} |
| </div> |
| ); |
| } |
| |
| const MODEL_OPTIONS = [ |
| 'Claude Opus 4.7', |
| 'Claude Opus 4.6 Thinking', |
| 'Claude Opus 4.6', |
| 'Claude Sonnet 4.6', |
| 'Claude Haiku 4.5', |
| ]; |
| |
| function QuestionForm({onSend, value, setValue, disabled, model, setModel, onNewChat, attachedFiles=[], setAttachedFiles, hideNewChatBtn=false, placeholder='質問・依頼を入力してください'}) { |
| const taRef = useRef(null); |
| const fileInputRef = useRef(null); |
| const [openMenu, setOpenMenu] = useState(null); |
| const [dragOver, setDragOver] = useState(false); |
| const [isRecording, setIsRecording] = useState(false); |
| |
| const autoresize = () => { |
| const ta = taRef.current; if (!ta) return; |
| ta.style.height = 'auto'; |
| ta.style.height = Math.min(ta.scrollHeight, 200) + 'px'; |
| }; |
| useEffect(()=>{ autoresize(); }, [value]); |
| |
| const submit = () => { |
| if (!value.trim() || disabled) return; |
| onSend(value); |
| }; |
| |
| const addFiles = (fileList) => { |
| if (!setAttachedFiles || !fileList) return; |
| const items = Array.from(fileList).map(f => ({ name:f.name, size:f.size })); |
| setAttachedFiles([...(attachedFiles || []), ...items]); |
| }; |
| const removeFile = (idx) => { |
| if (!setAttachedFiles) return; |
| setAttachedFiles(attachedFiles.filter((_, i) => i !== idx)); |
| }; |
| const onPickFile = () => fileInputRef.current?.click(); |
| const onDrop = (e) => { |
| e.preventDefault(); setDragOver(false); |
| if (e.dataTransfer.files?.length) addFiles(e.dataTransfer.files); |
| }; |
| |
| const fmtSize = (b) => { |
| if (!b && b !== 0) return ''; |
| if (b < 1024) return b+' B'; |
| if (b < 1024*1024) return (b/1024).toFixed(1)+' KB'; |
| return (b/1024/1024).toFixed(1)+' MB'; |
| }; |
| |
| return ( |
| <div className="input-wrap"> |
| {!hideNewChatBtn && ( |
| <div className="input-top-row"> |
| <button className="new-chat-btn-top" onClick={onNewChat}> |
| <Icon name="plus"/> |
| 新しいチャット |
| </button> |
| </div> |
| )} |
| <div |
| className={"compose-area"+(dragOver?' drag-over':'')} |
| onDragOver={(e)=>{ e.preventDefault(); setDragOver(true); }} |
| onDragLeave={()=>setDragOver(false)} |
| onDrop={onDrop} |
| > |
| <input |
| ref={fileInputRef} type="file" multiple |
| style={{display:'none'}} |
| onChange={(e)=>{ addFiles(e.target.files); e.target.value=''; }} |
| /> |
| <div |
| className={"attach-zone"+(attachedFiles.length>0?' filled':'')} |
| onClick={onPickFile} |
| role="button" |
| > |
| <span className="az-icon"><Icon name="paperclip"/></span> |
| {attachedFiles.length === 0 ? ( |
| <span className="az-hint"> |
| ファイルを添付(クリック または ドロップ) |
| </span> |
| ) : ( |
| <div className="az-chips"> |
| {attachedFiles.map((f, i) => ( |
| <div key={i} className="attached-chip" title={f.name}> |
| <span className="ac-icon"><Icon name="file-text"/></span> |
| <span className="ac-name">{f.name}</span> |
| {f.size != null && <span className="ac-size">{fmtSize(f.size)}</span>} |
| <button |
| className="ac-remove" |
| onClick={(e)=>{ e.stopPropagation(); removeFile(i); }} |
| aria-label="削除" |
| > |
| <Icon name="x"/> |
| </button> |
| </div> |
| ))} |
| <span className="az-add-more">+ 追加</span> |
| </div> |
| )} |
| </div> |
| {dragOver && ( |
| <div className="drop-overlay"> |
| <Icon name="upload-cloud"/> |
| <span>ここにドロップして添付</span> |
| </div> |
| )} |
| <div className="question-form"> |
| <div className="question-row"> |
| <textarea |
| ref={taRef} |
| className="question-input" |
| placeholder={placeholder} |
| value={value} |
| onChange={e=>setValue(e.target.value)} |
| onKeyDown={e=>{ |
| if (e.key==='Enter' && (e.metaKey||e.ctrlKey)) { e.preventDefault(); submit(); } |
| }} |
| rows={1} |
| /> |
| </div> |
| <div className="form-toolbar"> |
| <button |
| className={`toolbar-icon-btn${isRecording ? ' recording' : ''}`} |
| title={isRecording ? '音声入力中(タップで停止)' : '音声入力'} |
| onClick={() => setIsRecording(r => !r)} |
| ><Icon name="mic"/></button> |
| <div style={{flex:1}}></div> |
| <div className="model-selector"> |
| <button |
| className="model-pill" |
| onClick={()=>setOpenMenu(openMenu==='model'?null:'model')} |
| > |
| <span className="sparkle"><Icon name="sparkles"/></span> |
| <span>{model}</span> |
| <span className="chev"><Icon name="chevron-down"/></span> |
| </button> |
| {openMenu === 'model' && ( |
| <div className="model-menu"> |
| {MODEL_OPTIONS.map(m => ( |
| <button |
| key={m} |
| className={"model-menu-item"+(model===m?' active':'')} |
| onClick={()=>{ setModel(m); setOpenMenu(null); }} |
| > |
| <span>{m}</span> |
| {model===m && <span className="check-mark"><Icon name="check"/></span>} |
| </button> |
| ))} |
| </div> |
| )} |
| </div> |
| <button className="send-btn" onClick={submit} disabled={!value.trim() || disabled} aria-label="送信"> |
| <Icon name="send-horizontal"/> |
| </button> |
| </div> |
| </div> |
| </div> |
| <div className="guideline-notice"> |
| AIによる回答には誤りが含まれる場合があります。重要な情報は必ず一次資料でご確認ください。 |
| </div> |
| </div> |
| ); |
| } |
| |
| |
| function makeReply(question, property) { |
| const q = question || ''; |
| if (/内見/.test(q)) { |
| return { |
| content: [ |
| { type:'p', text:`${q} の対応状況をまとめました。各項目のリンクをクリックすると、参照資料を右側に表示します。` }, |
| { type:'h', text:'1. 内見時に確認した点' }, |
| { type:'ul', items:[ |
| '主要設備(エアコン・給湯・通信)はすべて動作確認済み{{cite:4}}', |
| '北側洋室の壁紙に小汚れ → 退去前クリーニングを手配済み{{cite:4}}', |
| 'バルコニー排水溝の詰まり → 補修依頼済み{{cite:4}}', |
| ]}, |
| { type:'h', text:'2. お客様からのご質問への回答' }, |
| { type:'ul', items:[ |
| '入居可能日:4月1日以降{{cite:4}}', |
| 'ペット可条件:小型犬・猫1匹まで(要敷金1ヶ月追加){{cite:5}}', |
| '駐車場:月額25,000円・空き1台あり{{cite:6}}', |
| ]}, |
| { type:'h', text:'3. 次のアクション' }, |
| { type:'ul', items:[ |
| '重要事項説明書のドラフト送付{{cite:5}}', |
| '契約条件の最終合意', |
| '入居前クリーニング・補修の進捗確認', |
| ]}, |
| ], |
| withCitations: true, |
| }; |
| } |
| |
| if (property && /(重要事項|重説|契約書|ドラフト)/.test(q)) { |
| return { |
| content: [ |
| { type:'p', text:`${property.name} の重要事項説明書ドラフトを準備します。物件情報・契約条件・法令上の制限を社内テンプレートに沿って構成しました。{{cite:2}}` }, |
| { type:'h', text:'1. 物件の表示' }, |
| { type:'ul', items:[ |
| `所在地:東京都世田谷区(${property.meta})`, |
| `建物の種類・構造:鉄筋コンクリート造 / 共同住宅`, |
| `築年月:登記簿に基づき記載(社内DB参照{{cite:2}})`, |
| ]}, |
| { type:'h', text:'2. 取引条件' }, |
| { type:'ul', items:[ |
| `賃料:${property.meta.split('・')[1]?.trim() || '—'}(管理費別)`, |
| `敷金 / 礼金:1ヶ月 / 1ヶ月(ペット可は敷金+1ヶ月)`, |
| `契約期間:2年(更新料:新賃料の1ヶ月分)`, |
| ]}, |
| { type:'p', text:'最終ドラフトはPDFで右パネルに表示しています。修正点があれば指示してください。' }, |
| ], |
| withCitations: true, |
| }; |
| } |
| if (/(相場|賃料|平均)/.test(q)) { |
| return { |
| content: [ |
| { type:'p', text:'最新の市場レポートと社内DBをもとに相場感をまとめました。{{cite:1}}{{cite:3}}' }, |
| { type:'ul', items:[ |
| '世田谷区の1LDK平均賃料:**22.4万円**(前年同期比 +3.1%){{cite:1}}', |
| '駅徒歩5分以内・築10年以内:25〜30万円レンジが中心', |
| 'ペット可物件は全体の約18%、城南3区では需要超過{{cite:1}}', |
| ]}, |
| { type:'p', text:'特定エリアや条件を絞ってさらに調べますか?' }, |
| ], |
| withCitations: true, |
| }; |
| } |
| if (/(候補|探|物件|検索|見つけ)/.test(q)) { |
| return { |
| content: [ |
| { type:'p', text:'社内データベースから条件に合う候補を3件ピックアップしました。{{cite:2}}' }, |
| { type:'ul', items:[ |
| '**用賀パークレジデンス 802号室** / 24.8万円 / 築6年 / 用賀駅徒歩4分', |
| '**桜新町テラス 305号室** / 25.5万円 / 築3年 / 桜新町駅徒歩6分', |
| '**三軒茶屋スカイコート 1102号室** / 25.0万円 / 築8年 / 三軒茶屋駅徒歩8分', |
| ]}, |
| { type:'p', text:'内見スケジュールの調整、または重要事項説明書のドラフト作成もできます。' }, |
| ], |
| withCitations: true, |
| }; |
| } |
| |
| return { |
| content: [ |
| { type:'p', text:`「${question}」について確認しました。フロントマネージャーとしてどのようにお手伝いしましょうか?` }, |
| { type:'ul', items:[ |
| '物件の条件検索(エリア・間取り・予算など)', |
| '相場感の確認や物件比較', |
| '重要事項説明書・契約書のドラフト作成', |
| '内見・契約スケジュールの整理', |
| ]}, |
| { type:'p', text:'ご希望のタスクを教えてください。' }, |
| ], |
| withCitations: false, |
| }; |
| } |
| |
| |
| const LS_KEY_LAST_PROPERTY = 'carina:lastPropertyId'; |
| |
| function NotifPanel({open, onClose}) { |
| return ( |
| <> |
| {open && <div className="notif-overlay" onClick={onClose}/>} |
| <aside className={`notif-panel${open ? ' open' : ''}`}> |
| <div className="notif-panel-header"> |
| <span className="notif-panel-title">通知</span> |
| <button className="rp-close" onClick={onClose} aria-label="close"><Icon name="x"/></button> |
| </div> |
| <div className="notif-list"> |
| {NOTIFICATIONS.map(n => ( |
| <div key={n.id} className={`notif-item${n.unread ? ' unread' : ''}`}> |
| <div className="notif-item-title"> |
| {n.unread && <span className="unread-dot"/>} |
| {n.title} |
| </div> |
| <div className="notif-item-body">{n.body}</div> |
| <div className="notif-item-time">{n.time}</div> |
| </div> |
| ))} |
| </div> |
| </aside> |
| </> |
| ); |
| } |
| |
| function CopyToast() { |
| const [key, setKey] = React.useState(0); |
| const [visible, setVisible] = React.useState(false); |
| React.useEffect(() => { |
| window.showCopyToast = () => { |
| setKey(k => k + 1); |
| setVisible(true); |
| setTimeout(() => setVisible(false), 2000); |
| }; |
| }, []); |
| if (!visible) return null; |
| return <div key={key} className="copy-toast">クリップボードにコピーしました</div>; |
| } |
| |
| function App() { |
| |
| const initialPid = (() => { |
| try { |
| const v = localStorage.getItem(LS_KEY_LAST_PROPERTY); |
| if (v && PROPERTIES.some(p => p.id === v)) return v; |
| } catch (_) {} |
| return null; |
| })(); |
| |
| const [view, setView] = useState('home'); |
| const [notifOpen, setNotifOpen] = useState(false); |
| const [currentPropertyId, setCurrentPropertyId] = useState(initialPid); |
| |
| const [collapsed, setCollapsed] = useState(true); |
| const [activeChatKey, setActiveChatKey] = useState(null); |
| const [turns, setTurns] = useState([]); |
| const [draft, setDraft] = useState(''); |
| const [attachedFiles, setAttachedFiles] = useState([]); |
| const [busy, setBusy] = useState(false); |
| const [activeCite, setActiveCite] = useState(null); |
| const [panelOpen, setPanelOpen] = useState(false); |
| const [scrolled, setScrolled] = useState(false); |
| const [model, setModel] = useState('Claude Opus 4.7'); |
| const [templateGroups, setTemplateGroups] = useState(INITIAL_TEMPLATE_GROUPS); |
| const scrollRef = useRef(null); |
| |
| |
| const [openFolders, setOpenFolders] = useState(()=>new Set(['chats', 'mkcore', 'manual'])); |
| const [previewDoc, setPreviewDoc] = useState(null); |
| const [selectedDocName, setSelectedDocName] = useState(null); |
| |
| const toggleFolder = (key) => { |
| setOpenFolders(prev => { |
| const n = new Set(prev); |
| n.has(key) ? n.delete(key) : n.add(key); |
| return n; |
| }); |
| }; |
| |
| const onPickDoc = (doc) => { |
| setPreviewDoc(doc); |
| setSelectedDocName(doc?.name || null); |
| setActiveCite(null); |
| setPanelOpen(true); |
| }; |
| |
| const onCloseRightPanel = () => { |
| setPanelOpen(false); |
| setActiveCite(null); |
| setPreviewDoc(null); |
| setSelectedDocName(null); |
| }; |
| |
| |
| const persistProperty = (pid) => { |
| try { |
| if (pid) localStorage.setItem(LS_KEY_LAST_PROPERTY, pid); |
| } catch (_) {} |
| }; |
| |
| useEffect(()=>{ |
| document.body.classList.toggle('sb-collapsed', collapsed); |
| }, [collapsed]); |
| |
| useEffect(()=>{ |
| document.body.classList.toggle('settings-view', view === 'settings'); |
| }, [view]); |
| |
| const onScroll = () => { |
| setScrolled((scrollRef.current?.scrollTop ?? 0) > 0); |
| }; |
| |
| useEffect(()=>{ |
| const el = scrollRef.current; |
| if (el) requestAnimationFrame(()=>{ el.scrollTop = el.scrollHeight; }); |
| }, [turns, busy]); |
| |
| const onCiteClick = (n) => { |
| const cite = CITATIONS.find(c => c.n === n); |
| if (!cite) return; |
| setActiveCite(n); |
| setPanelOpen(true); |
| if (cite.folderKeys) { |
| setOpenFolders(prev => { |
| const next = new Set(prev); |
| cite.folderKeys.forEach(k => next.add(k)); |
| return next; |
| }); |
| } |
| if (cite.docName) { |
| const doc = findDocByName(cite.docName); |
| if (doc) { |
| setPreviewDoc(doc); |
| setSelectedDocName(cite.docName); |
| } |
| } |
| }; |
| |
| const handleSend = (text) => { |
| const userText = text.trim(); |
| if (!userText) return; |
| const next = [...turns, { user: userText, bot: null, files: attachedFiles }]; |
| setTurns(next); |
| setDraft(''); |
| setAttachedFiles([]); |
| setBusy(true); |
| setTimeout(() => { |
| const reply = makeReply(userText, null); |
| setTurns(curr => { |
| const updated = [...curr]; |
| updated[updated.length - 1] = { ...updated[updated.length - 1], bot: reply }; |
| return updated; |
| }); |
| setBusy(false); |
| }, 900 + Math.random()*600); |
| }; |
| |
| |
| const handleStartChatFromHome = (pid, text) => { |
| persistProperty(pid); |
| setCurrentPropertyId(pid); |
| setActiveChatKey(null); |
| setTurns([]); |
| setActiveCite(null); |
| setPanelOpen(false); |
| setView('chat'); |
| if (text && text.trim()) { |
| setDraft(''); |
| setAttachedFiles([]); |
| const userText = text.trim(); |
| const initialFiles = attachedFiles; |
| setTurns([{ user: userText, bot: null, files: initialFiles }]); |
| setBusy(true); |
| setTimeout(() => { |
| const reply = makeReply(userText, null); |
| setTurns(curr => { |
| const updated = [...curr]; |
| if (updated.length > 0) { |
| updated[updated.length - 1] = { ...updated[updated.length - 1], bot: reply }; |
| } |
| return updated; |
| }); |
| setBusy(false); |
| }, 900 + Math.random()*600); |
| } else { |
| setDraft(''); |
| } |
| }; |
| |
| const handleSelectChat = (pid, chatTitle) => { |
| persistProperty(pid); |
| const key = `${pid}::${chatTitle}`; |
| setCurrentPropertyId(pid); |
| setActiveChatKey(key); |
| setTurns([ |
| { user: chatTitle + 'について確認したい', bot: makeReply(chatTitle, null) }, |
| ]); |
| setActiveCite(null); |
| setPanelOpen(false); |
| setDraft(''); |
| setView('chat'); |
| }; |
| |
| const goHome = () => { |
| setView('home'); |
| setActiveChatKey(null); |
| setTurns([]); |
| setActiveCite(null); |
| setPanelOpen(false); |
| setDraft(''); |
| |
| }; |
| |
| const handleNewChat = () => { |
| setActiveChatKey(null); |
| setTurns([]); |
| setActiveCite(null); |
| setPanelOpen(false); |
| setDraft(''); |
| }; |
| |
| |
| const handleSwitchProperty = () => { |
| setView('home'); |
| setActiveChatKey(null); |
| setTurns([]); |
| setActiveCite(null); |
| setPanelOpen(false); |
| setPreviewDoc(null); |
| setSelectedDocName(null); |
| setDraft(''); |
| }; |
| |
| const handleSelectPropertyFromHome = (pid) => { |
| persistProperty(pid); |
| setCurrentPropertyId(pid); |
| }; |
| |
| |
| const handleSelectPropertyFromSidebar = (pid) => { |
| if (pid === currentPropertyId) return; |
| persistProperty(pid); |
| setCurrentPropertyId(pid); |
| setSelectedDocName(null); |
| setPreviewDoc(null); |
| if (view === 'chat') { |
| |
| setActiveChatKey(null); |
| setTurns([]); |
| setActiveCite(null); |
| setPanelOpen(false); |
| setDraft(''); |
| } |
| }; |
| |
| const goToSettings = () => { |
| setView('settings'); |
| setPanelOpen(false); |
| setActiveCite(null); |
| }; |
| |
| const handleSettingsBack = () => { |
| if (currentPropertyId && turns.length > 0) { |
| setView('chat'); |
| } else { |
| setView('home'); |
| } |
| }; |
| |
| const headerProperty = PROPERTIES.find(p=>p.id===currentPropertyId)?.name; |
| const headerChatTitle = activeChatKey ? activeChatKey.split('::')[1] : null; |
| const onPropertyClickFromHeader = () => { |
| if (currentPropertyId) handleSwitchProperty(); |
| }; |
| |
| return ( |
| <> |
| <Header |
| withBorder={scrolled} |
| onHomeClick={goHome} |
| propertyName={view==='chat' ? headerProperty : null} |
| chatTitle={view==='chat' ? headerChatTitle : null} |
| onPropertyClick={onPropertyClickFromHeader} |
| onNotifClick={() => setNotifOpen(o => !o)} |
| /> |
| {view !== 'settings' && ( |
| <Sidebar |
| collapsed={collapsed} |
| onToggle={()=>setCollapsed(!collapsed)} |
| activeChatKey={activeChatKey} |
| onSelectChat={handleSelectChat} |
| onNewChat={handleNewChat} |
| onSettingsClick={goToSettings} |
| onSelectProperty={handleSelectPropertyFromSidebar} |
| currentPropertyId={currentPropertyId} |
| openFolders={openFolders} |
| toggleFolder={toggleFolder} |
| selectedDocName={selectedDocName} |
| onPickDoc={onPickDoc} |
| /> |
| )} |
| <div className="main"> |
| {view === 'home' ? ( |
| <ChatHomeScreen |
| currentPropertyId={currentPropertyId} |
| onSelectProperty={handleSelectPropertyFromHome} |
| onStartChat={handleStartChatFromHome} |
| onSettings={goToSettings} |
| draft={draft} |
| setDraft={setDraft} |
| model={model} |
| setModel={setModel} |
| attachedFiles={attachedFiles} |
| setAttachedFiles={setAttachedFiles} |
| templateGroups={templateGroups} |
| /> |
| ) : view === 'settings' ? ( |
| <SettingsScreen |
| onBack={handleSettingsBack} |
| templateGroups={templateGroups} |
| setTemplateGroups={setTemplateGroups} |
| /> |
| ) : ( |
| <> |
| <div className="chat-pane"> |
| <div className="chat-pane-header"> |
| <div className="cph-property"> |
| <Icon name="building-2"/> |
| <span className="cph-name"> |
| {(PROPERTIES.find(p=>p.id===currentPropertyId)?.name) || '物件未選択'} |
| </span> |
| </div> |
| </div> |
| <div className="chat-container"> |
| {turns.length === 0 && !busy ? ( |
| <div className="chat-empty-hero"> |
| <div className="hero-block"> |
| <SuggestSections groups={templateGroups} onPick={handleSend}/> |
| </div> |
| </div> |
| ) : ( |
| <div className="chat-scroll" ref={scrollRef} onScroll={onScroll}> |
| {turns.map((turn, i) => ( |
| <div key={i} className="turn"> |
| <div className="user-msg-wrap"> |
| <div className="msg-header"> |
| <span className="name">あなた</span> |
| <span className="time">15:42</span> |
| </div> |
| <div className="user-msg">{turn.user}</div> |
| {turn.files && turn.files.length > 0 && ( |
| <div className="user-msg-files"> |
| {turn.files.map((f, fi) => ( |
| <div key={fi} className="user-msg-file"> |
| <Icon name="file-text"/> |
| <span>{f.name}</span> |
| </div> |
| ))} |
| </div> |
| )} |
| </div> |
| {turn.bot ? ( |
| <BotAnswer |
| content={turn.bot.content} |
| onCiteClick={onCiteClick} |
| activeCite={panelOpen?activeCite:null} |
| withCitations={turn.bot.withCitations} |
| /> |
| ) : ( |
| <TypingBlock/> |
| )} |
| </div> |
| ))} |
| </div> |
| )} |
| <div className="chat-bottom-bar"> |
| <QuestionForm |
| onSend={handleSend} |
| value={draft} |
| setValue={setDraft} |
| disabled={busy} |
| model={model} |
| setModel={setModel} |
| onNewChat={handleNewChat} |
| attachedFiles={attachedFiles} |
| setAttachedFiles={setAttachedFiles} |
| /> |
| </div> |
| </div> |
| </div> |
| </> |
| )} |
| {/* 資料ビューア (右パネル) — home/chat の両方で表示 */} |
| {previewDoc && view !== 'settings' && ( |
| <RightPanel doc={previewDoc} onClose={onCloseRightPanel}/> |
| )} |
| </div> |
| <NotifPanel open={notifOpen} onClose={() => setNotifOpen(false)}/> |
| <CopyToast/> |
| </> |
| ); |
| } |
| |
| ReactDOM.createRoot(document.getElementById('root')).render(<App/>); |
| |
| </script> |
| </body> |
| </html> |
|
|