UIimage / index.html
riku-1006
UI
8c36e24
Raw
History Blame Contribute Delete
202 kB
<!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 {
/* Monochrome SaaS palette: neutral grays + single indigo accent (Linear/Cursor 系) */
--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 ============ */
.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 ============ */
.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; }
/* ---- VSCode風サイドバー: チャット履歴 + フォルダ ---- */
.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);
}
/* sidebar 用のフォルダツリー縮小サイズ上書き */
.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 group */
.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-grouped flat chat list (S-2) */
.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 + breadcrumb in header */
.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; }
/* keep .product-brand for backward compat (unused now) */
.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 ============ */
.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;
}
/* チャット入力欄の固定底バー (Claude風) */
.chat-bottom-bar {
flex-shrink:0;
padding:8px 0 16px;
background:linear-gradient(to bottom, rgba(255,255,255,0) 0%, white 16px);
}
/* ---- Empty state: 質問例エリア (上スクロール / 入力欄は底固定) ---- */
.chat-empty-hero {
flex:1; min-height:0; overflow-y:auto;
display:flex; flex-direction:column;
align-items:center;
padding:16px 0;
}
/* margin:auto 0 で短いコンテンツは中央寄せ、溢れたら通常スクロール */
.hero-block { width:100%; max-width:720px; margin:auto 0; }
/* チャット入力: 添付ゾーン + テキスト入力 を1枚のコンポーズエリアに統合 */
.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);
}
/* form は親の枠/背景を受け継ぐ */
.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; }
/* ---- Messages (M-1: bubble全廃) ---- */
.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; }
/* loading dots */
.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 FORM ============ */
.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);
}
/* I-2: スラッシュコマンド + フォーム内フィルターチップ */
.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;
}
/* ============ HOME SCREEN (Claude風 チャット初期画面) ============ */
/* 設定画面のみサイドバー非表示 */
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;
}
/* h1 内にインライン埋め込みする物件プルダウン */
.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;
}
/* 物件選択プルダウン (Claude の "Work" ピル相当) */
.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; }
/* チャット入力下の質問テンプレ (テーマ表示の2列グリッド) */
.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;
}
/* テンプレが1件しかないカテゴリは中央寄せ (タイル幅は2列時と揃える) */
.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 (旧) ============ */
.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;
}
/* G-1: 物件テーブル */
.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; }
/* ============ DOCUMENTS SCREEN ヘッダー共通スタイル ============ */
.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 (資料ビューア: 2カラム + モーダルプレビュー) ============ */
.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 を大きめに上書き */
.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);
}
/* フォルダツリー (type folder → year folder → file) */
.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;
}
/* file row inside year folder */
.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; }
/* legacy: badge reused in folder tree (FolderTree が「最新」用に参照) */
.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;
}
/* preview modal */
.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: documents preview 用に縦長サイズを上書き */
.mock-pdf-page {
aspect-ratio:auto;
max-width:720px;
min-height:920px;
padding:48px 56px;
}
/* upload modal */
.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 (ST-1: 2ペイン) ============ */
.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 settings button (bottom-left, matches sidebar menu-item) */
.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 (user management) */
.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; }
/* Properties pane: 物件名・MKCoreセル */
.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; }
/* ============ NEW SETTINGS PANES (neo-smart-chat 移植) ============ */
/* 基本設定: モード切替・入力設定行 */
.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 top header bar */
.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 ============ */
.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>;
};
// ---------- Mock data: 通知 ----------
const NOTIFICATIONS = [
{ id:5, unread:false, title:'システムメンテナンスのお知らせ', body:'5月20日 2:00〜4:00 にメンテナンスを実施します。', time:'昨日' },
];
// ---------- Mock data: 物件単位 ----------
// MKCore連携で取得できるドキュメント種別: 理事会議案書 / 総会議案書 / 理事会議事録 / 総会議事録
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' },
],
};
// MKCore から自動連携される資料の履歴(モック - 全物件共通サンプル)
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,
});
};
// 理事会議案書 (年最大6回)
[[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));
});
// 総会議案書 (年1回 / 期号は降順)
[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`,
});
});
// 種別ごとの最新1件にだけ latest フラグを付与
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] }));
})();
// 引用は基本的にサイドバーフォルダの資料を参照する。
// folderKeys は「クリック時に展開されるフォルダのキー」、docName は対象資料名。
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'] },
];
// 資料名から MKCore / 手動登録 のドキュメントオブジェクトを引く
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', // indigo
p2: '#10B981', // emerald
p3: '#F59E0B', // amber
p4: '#EC4899', // pink
p5: '#8B5CF6', // violet
p6: '#06B6D4', // cyan
};
// 日付バケットでフラット化したチャット履歴(S-2)
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] : [];
// 縦並びカテゴリ別の質問テンプレート(初期値・App stateの seed)
// 各 item は { theme:短いテーマ, text:実際に送信される質問文 }
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:'過去検討した際に実施を見送った修繕を項目と理由を教えてください。' },
]},
];
// ---------- Sidebar (VSCode風 フォルダツリー: 履歴 + 資料) ----------
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>
);
}
// ---------- Header (breadcrumb-driven) ----------
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>
);
}
// ---------- Home Screen (Claude風 チャット初期画面) ----------
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>
);
}
// カテゴリ別 質問テンプレ (テーマだけ表示 / 2列グリッド)
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>
);
}
// ---------- Upload Screen ----------
// ---------- Mock PDF page for viewer preview ----------
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>
);
}
// ---------- Documents Screen (資料ビューア: 2カラム + 年度フィルタ + クリックでプレビュー) ----------
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>
);
}
// ---------- Settings Screen (ST-1: 2ペイン) ----------
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({}); // { [groupId]: 'text' }
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>
);
}
// ---------- Bot answer rendering ----------
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>
);
}
// ---------- Right Panel ----------
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>
);
}
// default: 賃貸市場動向レポート
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>
);
}
// ---------- Question form ----------
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); // 'model' | 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>
);
}
// ---------- Mock answer generator ----------
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,
};
}
// Property-aware answers
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,
};
}
// Generic
return {
content: [
{ type:'p', text:`「${question}」について確認しました。フロントマネージャーとしてどのようにお手伝いしましょうか?` },
{ type:'ul', items:[
'物件の条件検索(エリア・間取り・予算など)',
'相場感の確認や物件比較',
'重要事項説明書・契約書のドラフト作成',
'内見・契約スケジュールの整理',
]},
{ type:'p', text:'ご希望のタスクを教えてください。' },
],
withCitations: false,
};
}
// ---------- App ----------
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() {
// 起動時に localStorage から最後にチャットした物件を復元
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'); // 'home' | 'chat' | 'settings'
const [notifOpen, setNotifOpen] = useState(false);
const [currentPropertyId, setCurrentPropertyId] = useState(initialPid);
// ホーム画面ではサイドバーを閉じた状態で開始
const [collapsed, setCollapsed] = useState(true);
const [activeChatKey, setActiveChatKey] = useState(null); // 'pid::chatTitle' or 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);
// サイドバーのフォルダ展開状態と資料ビューア (Appに引き上げて引用クリックから制御)
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);
};
// currentPropertyId 変更時に localStorage に保存
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('');
// currentPropertyId は維持 (ホームでも選択状態を保つ)
};
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>