@zaclinley/dsh-mailharbor 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -34,7 +34,7 @@ __export(index_exports, {
34
34
  module.exports = __toCommonJS(index_exports);
35
35
 
36
36
  // src/client/styles.css
37
- var styles_default = "/* MailHarbor panel + settings.\n Tokens come from the DSH shell (dark by default); fallbacks match the\n default dark theme so the panel also renders in a bare host page.\n Component rules are all scoped under `.mailharbor` and the generic element\n reset lives in `:where()` so it cannot outrank a component class. */\n\n.mailharbor {\n --m-bg: var(--dsw-alias-bg-base, #151517);\n --m-bg-1: var(--dsw-alias-bg-layer-1, #232324);\n --m-bg-2: var(--dsw-alias-bg-layer-2, #2c2c2e);\n --m-bg-3: var(--dsw-alias-bg-layer-3, #353638);\n --m-line: var(--dsw-alias-border-l2, #ffffff1f);\n --m-line-soft: var(--dsw-alias-border-l1, #ffffff0f);\n --m-line-strong: var(--dsw-alias-border-l3, #ffffff29);\n --m-text: var(--dsw-alias-label-primary, #f9fafb);\n --m-text-2: var(--dsw-alias-label-secondary, #cfd3d6);\n --m-text-3: var(--dsw-alias-label-tertiary, #adb2b8);\n --m-caption: var(--dsw-alias-label-caption, #81858c);\n --m-accent: #5686fe;\n --m-accent-soft: rgba(86, 134, 254, 0.14);\n --m-primary-bg: var(--dsw-alias-brand-primary, #f9fafb);\n --m-primary-fg: var(--dsw-alias-label-primary-foreground, #0f1115);\n --m-hover: var(--dsw-alias-interactive-bg-hover, #ffffff14);\n --m-danger: #f25a5a;\n --m-ok: #3fbf7f;\n --m-warn: var(--dsw-alias-state-warn-label, #dd8629);\n --m-radius: 8px;\n --m-shadow: 0 18px 60px rgba(0, 0, 0, 0.5);\n\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n background: var(--m-bg);\n color: var(--m-text);\n font-size: 13px;\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n}\n\n.mailharbor *,\n.mailharbor *::before,\n.mailharbor *::after {\n box-sizing: border-box;\n}\n\n/* ---------- base controls ---------- */\n\n.mailharbor :where(button) {\n font: inherit;\n color: inherit;\n background: transparent;\n border: 0;\n margin: 0;\n cursor: pointer;\n}\n\n.mailharbor button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n height: 30px;\n padding: 0 12px;\n border-radius: var(--m-radius);\n border: 1px solid transparent;\n color: var(--m-text-2);\n font-size: 12.5px;\n font-weight: 500;\n white-space: nowrap;\n transition:\n background 120ms ease,\n color 120ms ease,\n border-color 120ms ease;\n}\n\n.mailharbor button:hover:not(:disabled) {\n background: var(--m-hover);\n color: var(--m-text);\n}\n\n.mailharbor button:disabled {\n opacity: 0.45;\n cursor: default;\n}\n\n.mailharbor button.primary {\n background: var(--m-primary-bg);\n color: var(--m-primary-fg);\n border-color: transparent;\n}\n\n.mailharbor button.primary:hover:not(:disabled) {\n background: var(--m-primary-bg);\n filter: brightness(0.94);\n color: var(--m-primary-fg);\n}\n\n.mailharbor button.ghost {\n border-color: var(--m-line);\n color: var(--m-text-2);\n}\n\n.mailharbor button.ghost:hover:not(:disabled) {\n border-color: var(--m-line-strong);\n color: var(--m-text);\n}\n\n.mailharbor button.subtle {\n color: var(--m-text-3);\n padding: 0 8px;\n}\n\n.mailharbor button.icon {\n width: 30px;\n padding: 0;\n}\n\n.mailharbor button.danger {\n color: var(--m-danger);\n}\n\n.mailharbor input,\n.mailharbor select,\n.mailharbor textarea {\n font: inherit;\n color: var(--m-text);\n background: var(--m-bg-1);\n border: 1px solid var(--m-line);\n border-radius: var(--m-radius);\n padding: 0 10px;\n height: 32px;\n min-width: 0;\n}\n\n.mailharbor textarea {\n height: auto;\n padding: 8px 10px;\n resize: vertical;\n line-height: 1.6;\n}\n\n.mailharbor select {\n padding-right: 26px;\n appearance: none;\n background-image:\n linear-gradient(45deg, transparent 50%, var(--m-text-3) 50%),\n linear-gradient(135deg, var(--m-text-3) 50%, transparent 50%);\n background-position:\n calc(100% - 15px) 14px,\n calc(100% - 10px) 14px;\n background-size:\n 5px 5px,\n 5px 5px;\n background-repeat: no-repeat;\n}\n\n.mailharbor ::placeholder {\n color: var(--m-caption);\n}\n\n.mailharbor input:focus-visible,\n.mailharbor select:focus-visible,\n.mailharbor textarea:focus-visible,\n.mailharbor button:focus-visible,\n.mailharbor [contenteditable='true']:focus-visible {\n outline: 2px solid rgba(86, 134, 254, 0.55);\n outline-offset: -1px;\n}\n\n.mailharbor .spacer {\n flex: 1;\n}\n\n.mailharbor .muted {\n color: var(--m-text-3);\n}\n\n.mailharbor .small {\n font-size: 12px;\n}\n\n.mailharbor .badge {\n display: inline-flex;\n align-items: center;\n height: 18px;\n padding: 0 6px;\n border-radius: 999px;\n font-size: 10.5px;\n font-weight: 500;\n background: var(--m-bg-3);\n color: var(--m-text-2);\n}\n\n.mailharbor .badge.ok {\n background: rgba(63, 191, 127, 0.16);\n color: var(--m-ok);\n}\n\n.mailharbor .badge.warn {\n background: rgba(221, 134, 41, 0.16);\n color: var(--m-warn);\n}\n\n.mailharbor .error {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 14px;\n color: var(--m-danger);\n background: rgba(242, 90, 90, 0.1);\n border-bottom: 1px solid rgba(242, 90, 90, 0.2);\n font-size: 12.5px;\n}\n\n/* ---------- shell ---------- */\n\n.mailharbor-topbar {\n display: flex;\n align-items: center;\n gap: 10px;\n height: 52px;\n padding: 0 14px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-account {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n max-width: 220px;\n padding: 4px 8px 4px 4px;\n border-radius: 999px;\n border: 1px solid transparent;\n cursor: default;\n}\n\n.mailharbor-account:hover {\n background: var(--m-hover);\n}\n\n.mailharbor-account .name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 500;\n}\n\n.mailharbor-account select {\n height: 26px;\n background: transparent;\n border-color: transparent;\n font-weight: 500;\n}\n\n.mailharbor-body {\n display: flex;\n flex: 1;\n min-height: 0;\n}\n\n.mailharbor-list {\n display: flex;\n flex-direction: column;\n width: 360px;\n flex: none;\n border-right: 1px solid var(--m-line-soft);\n min-height: 0;\n}\n\n.mailharbor-reader {\n flex: 1;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n@media (max-width: 1000px) {\n .mailharbor-list {\n width: 300px;\n }\n}\n\n.mailharbor-narrow .mailharbor-body[data-mode='reader'] .mailharbor-list {\n display: none;\n}\n\n.mailharbor-narrow .mailharbor-body[data-mode='list'] .mailharbor-reader {\n display: none;\n}\n\n/* ---------- segmented control ---------- */\n\n.mailharbor-segment {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n padding: 2px;\n border-radius: 9px;\n background: var(--m-bg-2);\n}\n\n.mailharbor-segment button {\n height: 26px;\n padding: 0 12px;\n border-radius: 7px;\n color: var(--m-text-3);\n font-size: 12px;\n}\n\n.mailharbor-segment button:hover:not(.active) {\n color: var(--m-text-2);\n background: transparent;\n}\n\n.mailharbor-segment button.active {\n background: var(--m-bg-3);\n color: var(--m-text);\n}\n\n/* ---------- list ---------- */\n\n.mailharbor-filters {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-filters select {\n width: 128px;\n flex: none;\n font-size: 12px;\n}\n\n.mailharbor-filters input[type='search'] {\n flex: 1;\n height: 30px;\n font-size: 12.5px;\n}\n\n.mailharbor-rows {\n flex: 1;\n overflow: auto;\n min-height: 0;\n}\n\n.mailharbor-row {\n display: flex;\n flex-direction: column;\n gap: 3px;\n width: 100%;\n padding: 10px 14px;\n border: 0;\n border-bottom: 1px solid var(--m-line-soft);\n border-left: 2px solid transparent;\n border-radius: 0;\n background: transparent;\n color: inherit;\n text-align: left;\n cursor: pointer;\n user-select: none;\n}\n\n.mailharbor-row:hover {\n background: var(--m-hover);\n}\n\n.mailharbor-row.selected {\n background: var(--m-accent-soft);\n border-left-color: var(--m-accent);\n}\n\n.mailharbor-row .line-1,\n.mailharbor-row .line-2 {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n width: 100%;\n}\n\n.mailharbor-row .from {\n font-weight: 600;\n font-size: 12.5px;\n color: var(--m-text-2);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mailharbor-row .date {\n margin-left: auto;\n color: var(--m-caption);\n font-size: 11px;\n flex: none;\n}\n\n.mailharbor-row .subject {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--m-text-2);\n font-size: 12.5px;\n}\n\n.mailharbor-row .snippet {\n color: var(--m-caption);\n font-size: 11.5px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n width: 100%;\n}\n\n.mailharbor-row.unread .from,\n.mailharbor-row.unread .subject {\n color: var(--m-text);\n}\n\n.mailharbor-row .dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--m-accent);\n flex: none;\n}\n\n.mailharbor-row .paperclip {\n color: var(--m-caption);\n flex: none;\n display: inline-flex;\n}\n\n.mailharbor-list-foot {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-top: 1px solid var(--m-line-soft);\n color: var(--m-text-3);\n font-size: 11.5px;\n flex: none;\n}\n\n.mailharbor-empty {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 10px;\n padding: 48px 24px;\n color: var(--m-text-3);\n text-align: center;\n}\n\n.mailharbor-empty svg {\n opacity: 0.5;\n}\n\n.mailharbor-empty .title {\n color: var(--m-text-2);\n font-size: 13px;\n font-weight: 500;\n}\n\n/* ---------- reader ---------- */\n\n.mailharbor-reader-head {\n padding: 16px 20px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.mailharbor-reader-title {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n}\n\n.mailharbor-reader-title h2 {\n margin: 0;\n font-size: 17px;\n line-height: 1.35;\n font-weight: 600;\n letter-spacing: -0.01em;\n word-break: break-word;\n}\n\n.mailharbor-reader-meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 4px 10px;\n color: var(--m-text-3);\n font-size: 12px;\n}\n\n.mailharbor-reader-meta .who {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n color: var(--m-text-2);\n}\n\n.mailharbor-reader-meta .who strong {\n color: var(--m-text);\n font-weight: 600;\n}\n\n.mailharbor-reader-actions {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n}\n\n.mailharbor-reader-body {\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n padding: 14px 18px 18px;\n background: var(--m-bg-2);\n}\n\n.mailharbor-reader-body iframe {\n flex: 1;\n width: 100%;\n border: 0;\n border-radius: 10px;\n background: #fff;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n}\n\n.mailharbor-reader-body pre {\n flex: 1;\n overflow: auto;\n margin: 0;\n padding: 20px 22px;\n white-space: pre-wrap;\n word-break: break-word;\n background: #fff;\n border-radius: 10px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n color: #171a21;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 12.5px;\n line-height: 1.7;\n}\n\n.mailharbor-attachments {\n padding: 10px 18px;\n border-top: 1px solid var(--m-line-soft);\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n flex: none;\n background: var(--m-bg);\n}\n\n.mailharbor-attachment {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n height: 30px;\n padding: 0 10px;\n border: 1px solid var(--m-line);\n border-radius: var(--m-radius);\n font-size: 12px;\n text-decoration: none;\n color: var(--m-text-2);\n max-width: 260px;\n background: var(--m-bg-1);\n}\n\n.mailharbor-attachment:hover {\n border-color: var(--m-line-strong);\n color: var(--m-text);\n}\n\n.mailharbor-attachment .name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* ---------- AI summary ---------- */\n\n.mailharbor-divider {\n width: 1px;\n height: 18px;\n margin: 0 4px;\n background: var(--m-line);\n}\n\n.mailharbor-summary {\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n\n.mailharbor-summary section h4 {\n margin: 0 0 6px;\n font-size: 11.5px;\n font-weight: 600;\n color: var(--m-text-3);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n}\n\n.mailharbor-summary section p {\n margin: 0;\n white-space: pre-wrap;\n line-height: 1.75;\n color: var(--m-text);\n}\n\n/* ---------- modal / forms ---------- */\n\n.mailharbor-portal {\n position: fixed;\n inset: 0;\n z-index: 80;\n height: auto;\n background: transparent;\n pointer-events: none;\n}\n\n.mailharbor-portal .mailharbor-modal-mask {\n pointer-events: auto;\n}\n\n.mailharbor-modal-mask {\n position: fixed;\n inset: 0;\n z-index: 60;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: rgba(0, 0, 0, 0.55);\n backdrop-filter: blur(2px);\n}\n\n.mailharbor-modal {\n display: flex;\n flex-direction: column;\n width: min(720px, 100%);\n max-height: 86vh;\n overflow: hidden;\n border: 1px solid var(--m-line);\n border-radius: 14px;\n background: var(--m-bg-1);\n color: var(--m-text);\n box-shadow: var(--m-shadow);\n}\n\n.mailharbor-modal > header {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 52px;\n padding: 0 14px 0 18px;\n border-bottom: 1px solid var(--m-line-soft);\n font-size: 14px;\n font-weight: 600;\n}\n\n.mailharbor-modal .content {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 18px;\n overflow: auto;\n}\n\n.mailharbor-modal > footer {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 18px;\n border-top: 1px solid var(--m-line-soft);\n}\n\n.mailharbor-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n min-width: 0;\n}\n\n.mailharbor-field > label {\n font-size: 11.5px;\n color: var(--m-text-3);\n}\n\n.mailharbor-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 10px;\n}\n\n.mailharbor-grid.three {\n grid-template-columns: 2fr 90px auto;\n align-items: end;\n}\n\n.mailharbor-toggle {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n color: var(--m-text-2);\n font-size: 12.5px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.mailharbor-toggle input[type='checkbox'] {\n width: 14px;\n height: 14px;\n accent-color: var(--m-accent);\n}\n\n/* ---------- composer ---------- */\n\n.mailharbor-toolbar {\n display: flex;\n align-items: center;\n gap: 2px;\n flex-wrap: wrap;\n padding: 6px;\n border: 1px solid var(--m-line-soft);\n border-bottom: 0;\n border-radius: 10px 10px 0 0;\n background: var(--m-bg-2);\n}\n\n.mailharbor-toolbar button {\n height: 26px;\n padding: 0 9px;\n border-radius: 6px;\n color: var(--m-text-2);\n font-size: 12px;\n}\n\n.mailharbor-toolbar button:hover {\n background: var(--m-bg-3);\n color: var(--m-text);\n}\n\n.mailharbor-editor {\n min-height: 240px;\n max-height: 44vh;\n overflow: auto;\n padding: 14px 16px;\n border: 1px solid var(--m-line-soft);\n border-radius: 0 0 10px 10px;\n background: #fff;\n color: #171a21;\n outline: none;\n font-size: 13.5px;\n line-height: 1.7;\n}\n\n.mailharbor-editor:empty::before {\n content: attr(data-placeholder);\n color: #9aa0ab;\n}\n\n/* ---------- inline confirm ---------- */\n\n.mailharbor-confirm {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-radius: 8px;\n border: 1px solid var(--m-line);\n background: var(--m-bg-2);\n color: var(--m-text-2);\n font-size: 12.5px;\n}\n\n/* ---------- settings ---------- */\n\n.mailharbor-settings {\n padding: 20px 24px 48px;\n display: flex;\n flex-direction: column;\n gap: 16px;\n max-width: 880px;\n}\n\n.mailharbor-settings h2 {\n margin: 0;\n font-size: 18px;\n font-weight: 600;\n letter-spacing: -0.01em;\n}\n\n.mailharbor-settings h3 {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--m-text-3);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n}\n\n.mailharbor-card {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n border: 1px solid var(--m-line-soft);\n border-radius: 12px;\n background: var(--m-bg-1);\n}\n\n.mailharbor-account-row {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.mailharbor-account-row .meta {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n.mailharbor-account-row .meta strong {\n font-weight: 600;\n}\n\n.mailharbor-account-row .meta span {\n color: var(--m-text-3);\n font-size: 11.5px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.mailharbor .avatar {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n border-radius: 50%;\n background: var(--m-accent-soft);\n color: var(--m-accent);\n font-weight: 600;\n}\n\n.mailharbor-log {\n padding: 10px 12px;\n border-radius: 8px;\n background: var(--m-bg);\n border: 1px solid var(--m-line-soft);\n color: var(--m-text-2);\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11px;\n line-height: 1.6;\n max-height: 220px;\n overflow: auto;\n white-space: pre-wrap;\n}\n\n.mailharbor-kv {\n display: grid;\n grid-template-columns: minmax(120px, 170px) 1fr;\n gap: 6px 14px;\n font-size: 12px;\n margin: 0;\n}\n\n.mailharbor-kv dt {\n color: var(--m-text-3);\n}\n\n.mailharbor-kv dd {\n margin: 0;\n color: var(--m-text-2);\n word-break: break-all;\n}\n\n.mailharbor hr {\n border: 0;\n border-top: 1px solid var(--m-line-soft);\n margin: 2px 0;\n}\n";
37
+ var styles_default = "/* MailHarbor panel + settings.\n Tokens come from the DSH shell (dark by default); fallbacks match the\n default dark theme so the panel also renders in a bare host page.\n Component rules are all scoped under `.mailharbor` and the generic element\n reset lives in `:where()` so it cannot outrank a component class. */\n\n.mailharbor {\n --m-bg: var(--dsw-alias-bg-base, #151517);\n --m-bg-1: var(--dsw-alias-bg-layer-1, #232324);\n --m-bg-2: var(--dsw-alias-bg-layer-2, #2c2c2e);\n --m-bg-3: var(--dsw-alias-bg-layer-3, #353638);\n --m-line: var(--dsw-alias-border-l2, #ffffff1f);\n --m-line-soft: var(--dsw-alias-border-l1, #ffffff0f);\n --m-line-strong: var(--dsw-alias-border-l3, #ffffff29);\n --m-text: var(--dsw-alias-label-primary, #f9fafb);\n --m-text-2: var(--dsw-alias-label-secondary, #cfd3d6);\n --m-text-3: var(--dsw-alias-label-tertiary, #adb2b8);\n --m-caption: var(--dsw-alias-label-caption, #81858c);\n --m-accent: #5686fe;\n --m-accent-soft: rgba(86, 134, 254, 0.14);\n --m-primary-bg: var(--dsw-alias-brand-primary, #f9fafb);\n --m-primary-fg: var(--dsw-alias-label-primary-foreground, #0f1115);\n --m-hover: var(--dsw-alias-interactive-bg-hover, #ffffff14);\n --m-danger: #f25a5a;\n --m-ok: #3fbf7f;\n --m-warn: var(--dsw-alias-state-warn-label, #dd8629);\n --m-radius: 8px;\n --m-shadow: 0 18px 60px rgba(0, 0, 0, 0.5);\n\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n background: var(--m-bg);\n color: var(--m-text);\n font-size: 13px;\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n}\n\n.mailharbor *,\n.mailharbor *::before,\n.mailharbor *::after {\n box-sizing: border-box;\n}\n\n/* ---------- base controls ---------- */\n\n.mailharbor :where(button) {\n font: inherit;\n color: inherit;\n background: transparent;\n border: 0;\n margin: 0;\n cursor: pointer;\n}\n\n.mailharbor button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n height: 30px;\n padding: 0 12px;\n border-radius: var(--m-radius);\n border: 1px solid transparent;\n color: var(--m-text-2);\n font-size: 12.5px;\n font-weight: 500;\n white-space: nowrap;\n transition:\n background 120ms ease,\n color 120ms ease,\n border-color 120ms ease;\n}\n\n.mailharbor button:hover:not(:disabled) {\n background: var(--m-hover);\n color: var(--m-text);\n}\n\n.mailharbor button:disabled {\n opacity: 0.45;\n cursor: default;\n}\n\n.mailharbor button.primary {\n background: var(--m-primary-bg);\n color: var(--m-primary-fg);\n border-color: transparent;\n}\n\n.mailharbor button.primary:hover:not(:disabled) {\n background: var(--m-primary-bg);\n filter: brightness(0.94);\n color: var(--m-primary-fg);\n}\n\n.mailharbor button.ghost {\n border-color: var(--m-line);\n color: var(--m-text-2);\n}\n\n.mailharbor button.ghost:hover:not(:disabled) {\n border-color: var(--m-line-strong);\n color: var(--m-text);\n}\n\n.mailharbor button.subtle {\n color: var(--m-text-3);\n padding: 0 8px;\n}\n\n.mailharbor button.icon {\n width: 30px;\n padding: 0;\n}\n\n.mailharbor button.danger {\n color: var(--m-danger);\n}\n\n.mailharbor input,\n.mailharbor select,\n.mailharbor textarea {\n font: inherit;\n color: var(--m-text);\n background: var(--m-bg-1);\n border: 1px solid var(--m-line);\n border-radius: var(--m-radius);\n padding: 0 10px;\n height: 32px;\n min-width: 0;\n}\n\n.mailharbor textarea {\n height: auto;\n padding: 8px 10px;\n resize: vertical;\n line-height: 1.6;\n}\n\n.mailharbor select {\n padding-right: 26px;\n appearance: none;\n background-image:\n linear-gradient(45deg, transparent 50%, var(--m-text-3) 50%),\n linear-gradient(135deg, var(--m-text-3) 50%, transparent 50%);\n background-position:\n calc(100% - 15px) 14px,\n calc(100% - 10px) 14px;\n background-size:\n 5px 5px,\n 5px 5px;\n background-repeat: no-repeat;\n}\n\n.mailharbor ::placeholder {\n color: var(--m-caption);\n}\n\n.mailharbor input:focus-visible,\n.mailharbor select:focus-visible,\n.mailharbor textarea:focus-visible,\n.mailharbor button:focus-visible,\n.mailharbor [contenteditable='true']:focus-visible {\n outline: 2px solid rgba(86, 134, 254, 0.55);\n outline-offset: -1px;\n}\n\n.mailharbor .spacer {\n flex: 1;\n}\n\n.mailharbor .muted {\n color: var(--m-text-3);\n}\n\n.mailharbor .small {\n font-size: 12px;\n}\n\n.mailharbor .badge {\n display: inline-flex;\n align-items: center;\n height: 18px;\n padding: 0 6px;\n border-radius: 999px;\n font-size: 10.5px;\n font-weight: 500;\n background: var(--m-bg-3);\n color: var(--m-text-2);\n}\n\n.mailharbor .badge.ok {\n background: rgba(63, 191, 127, 0.16);\n color: var(--m-ok);\n}\n\n.mailharbor .badge.warn {\n background: rgba(221, 134, 41, 0.16);\n color: var(--m-warn);\n}\n\n.mailharbor .error {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 14px;\n color: var(--m-danger);\n background: rgba(242, 90, 90, 0.1);\n border-bottom: 1px solid rgba(242, 90, 90, 0.2);\n font-size: 12.5px;\n}\n\n/* ---------- shell ---------- */\n\n.mailharbor-topbar {\n display: flex;\n align-items: center;\n gap: 10px;\n height: 52px;\n padding: 0 14px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-account {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n max-width: 220px;\n padding: 4px 8px 4px 4px;\n border-radius: 999px;\n border: 1px solid transparent;\n cursor: default;\n}\n\n.mailharbor-account:hover {\n background: var(--m-hover);\n}\n\n.mailharbor-account .name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 500;\n}\n\n.mailharbor-account select {\n height: 26px;\n background: transparent;\n border-color: transparent;\n font-weight: 500;\n}\n\n.mailharbor-body {\n display: flex;\n flex: 1;\n min-height: 0;\n position: relative;\n}\n\n.mailharbor button.active {\n background: var(--m-accent-soft);\n color: var(--m-text);\n}\n\n.mailharbor-list {\n display: flex;\n flex-direction: column;\n width: 360px;\n flex: none;\n border-right: 1px solid var(--m-line-soft);\n min-height: 0;\n}\n\n.mailharbor-reader {\n flex: 1;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n@media (max-width: 1000px) {\n .mailharbor-list {\n width: 300px;\n }\n}\n\n.mailharbor-narrow .mailharbor-body[data-mode='reader'] .mailharbor-list {\n display: none;\n}\n\n.mailharbor-narrow .mailharbor-body[data-mode='list'] .mailharbor-reader {\n display: none;\n}\n\n/* ---------- segmented control ---------- */\n\n.mailharbor-segment {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n padding: 2px;\n border-radius: 9px;\n background: var(--m-bg-2);\n}\n\n.mailharbor-segment button {\n height: 26px;\n padding: 0 12px;\n border-radius: 7px;\n color: var(--m-text-3);\n font-size: 12px;\n}\n\n.mailharbor-segment button:hover:not(.active) {\n color: var(--m-text-2);\n background: transparent;\n}\n\n.mailharbor-segment button.active {\n background: var(--m-bg-3);\n color: var(--m-text);\n}\n\n/* ---------- list ---------- */\n\n.mailharbor-filters {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-filters select {\n width: 128px;\n flex: none;\n font-size: 12px;\n}\n\n.mailharbor-filters input[type='search'] {\n flex: 1;\n height: 30px;\n font-size: 12.5px;\n}\n\n.mailharbor-rows {\n flex: 1;\n overflow: auto;\n min-height: 0;\n}\n\n.mailharbor-row {\n display: flex;\n flex-direction: column;\n gap: 3px;\n width: 100%;\n padding: 10px 14px;\n border: 0;\n border-bottom: 1px solid var(--m-line-soft);\n border-left: 2px solid transparent;\n border-radius: 0;\n background: transparent;\n color: inherit;\n text-align: left;\n cursor: pointer;\n user-select: none;\n}\n\n.mailharbor-row:hover {\n background: var(--m-hover);\n}\n\n.mailharbor-row.selected {\n background: var(--m-accent-soft);\n border-left-color: var(--m-accent);\n}\n\n.mailharbor-row .line-1,\n.mailharbor-row .line-2 {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n width: 100%;\n}\n\n.mailharbor-row .from {\n font-weight: 600;\n font-size: 12.5px;\n color: var(--m-text-2);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mailharbor-row .date {\n margin-left: auto;\n color: var(--m-caption);\n font-size: 11px;\n flex: none;\n}\n\n.mailharbor-row .subject {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--m-text-2);\n font-size: 12.5px;\n}\n\n.mailharbor-row .snippet {\n color: var(--m-caption);\n font-size: 11.5px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n width: 100%;\n}\n\n.mailharbor-row.unread .from,\n.mailharbor-row.unread .subject {\n color: var(--m-text);\n}\n\n.mailharbor-row .dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--m-accent);\n flex: none;\n}\n\n.mailharbor-row .paperclip {\n color: var(--m-caption);\n flex: none;\n display: inline-flex;\n}\n\n.mailharbor-list-foot {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-top: 1px solid var(--m-line-soft);\n color: var(--m-text-3);\n font-size: 11.5px;\n flex: none;\n}\n\n.mailharbor-empty {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 10px;\n padding: 48px 24px;\n color: var(--m-text-3);\n text-align: center;\n}\n\n.mailharbor-empty svg {\n opacity: 0.5;\n}\n\n.mailharbor-empty .title {\n color: var(--m-text-2);\n font-size: 13px;\n font-weight: 500;\n}\n\n/* ---------- reader ---------- */\n\n.mailharbor-reader-head {\n padding: 16px 20px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.mailharbor-reader-title {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n}\n\n.mailharbor-reader-title h2 {\n margin: 0;\n font-size: 17px;\n line-height: 1.35;\n font-weight: 600;\n letter-spacing: -0.01em;\n word-break: break-word;\n}\n\n.mailharbor-reader-meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 4px 10px;\n color: var(--m-text-3);\n font-size: 12px;\n}\n\n.mailharbor-reader-meta .who {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n color: var(--m-text-2);\n}\n\n.mailharbor-reader-meta .who strong {\n color: var(--m-text);\n font-weight: 600;\n}\n\n.mailharbor-reader-actions {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n}\n\n.mailharbor-reader-body {\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n padding: 14px 18px 18px;\n background: var(--m-bg-2);\n}\n\n.mailharbor-reader-body iframe {\n flex: 1;\n width: 100%;\n border: 0;\n border-radius: 10px;\n background: #fff;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n}\n\n.mailharbor-reader-body pre {\n flex: 1;\n overflow: auto;\n margin: 0;\n padding: 20px 22px;\n white-space: pre-wrap;\n word-break: break-word;\n background: #fff;\n border-radius: 10px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n color: #171a21;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 12.5px;\n line-height: 1.7;\n}\n\n.mailharbor-attachments {\n padding: 10px 18px;\n border-top: 1px solid var(--m-line-soft);\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n flex: none;\n background: var(--m-bg);\n}\n\n.mailharbor-attachment {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n height: 30px;\n padding: 0 10px;\n border: 1px solid var(--m-line);\n border-radius: var(--m-radius);\n font-size: 12px;\n text-decoration: none;\n color: var(--m-text-2);\n max-width: 260px;\n background: var(--m-bg-1);\n}\n\n.mailharbor-attachment:hover {\n border-color: var(--m-line-strong);\n color: var(--m-text);\n}\n\n.mailharbor-attachment .name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* ---------- AI rail ---------- */\n\n.mailharbor-ai {\n display: flex;\n flex-direction: column;\n width: 340px;\n flex: none;\n min-height: 0;\n border-left: 1px solid var(--m-line-soft);\n background: var(--m-bg);\n}\n\n.mailharbor-ai-head {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 46px;\n padding: 0 8px 0 14px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-head strong {\n font-size: 13px;\n font-weight: 600;\n}\n\n.mailharbor-ai-confirm {\n display: inline-flex;\n gap: 2px;\n}\n\n.mailharbor-ai-context {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 9px 14px 10px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-context strong {\n font-size: 12.5px;\n font-weight: 600;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mailharbor-ai-warn {\n padding: 8px 14px;\n color: var(--m-warn);\n background: rgba(221, 134, 41, 0.1);\n font-size: 12px;\n flex: none;\n}\n\n.mailharbor-ai-quick {\n display: flex;\n gap: 6px;\n padding: 10px 14px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-quick button {\n flex: 1;\n padding: 0 6px;\n}\n\n.mailharbor-ai-list {\n flex: 1;\n min-height: 0;\n overflow: auto;\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 12px 14px;\n}\n\n.mailharbor-ai-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 30px 12px;\n text-align: center;\n color: var(--m-text-3);\n}\n\n.mailharbor-ai-empty .title {\n color: var(--m-text-2);\n font-weight: 500;\n font-size: 12.5px;\n}\n\n.mailharbor-ai-card {\n display: flex;\n flex-direction: column;\n border: 1px solid var(--m-line-soft);\n border-radius: 10px;\n background: var(--m-bg-1);\n overflow: hidden;\n}\n\n.mailharbor-ai-card.generating {\n opacity: 0.7;\n}\n\n.mailharbor-ai-card-head {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 8px 7px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n font-size: 11.5px;\n color: var(--m-text-3);\n}\n\n.mailharbor-ai-card-head > span:first-child {\n color: var(--m-text-2);\n font-weight: 600;\n}\n\n.mailharbor-ai-summary {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 10px 12px;\n}\n\n.mailharbor-ai-summary section h4 {\n margin: 0 0 3px;\n font-size: 11px;\n color: var(--m-text-3);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\n\n.mailharbor-ai-summary section p {\n margin: 0;\n white-space: pre-wrap;\n font-size: 12.5px;\n line-height: 1.65;\n color: var(--m-text);\n}\n\n.mailharbor-ai-text {\n padding: 10px 12px;\n max-height: 320px;\n overflow: auto;\n white-space: pre-wrap;\n font-size: 12.5px;\n line-height: 1.65;\n color: var(--m-text);\n}\n\n.mailharbor-ai-card-actions {\n display: flex;\n align-items: center;\n gap: 4px;\n flex-wrap: wrap;\n padding: 6px 8px;\n border-top: 1px solid var(--m-line-soft);\n}\n\n.mailharbor-ai-card-actions button {\n height: 26px;\n padding: 0 8px;\n font-size: 11.5px;\n}\n\n.mailharbor-ai-ask {\n display: flex;\n gap: 6px;\n padding: 10px 12px;\n border-top: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-ask input {\n flex: 1;\n height: 32px;\n}\n\n.mailharbor-ai-error {\n color: var(--m-danger);\n font-size: 12px;\n padding: 0 2px;\n}\n\n@media (max-width: 1280px) {\n .mailharbor-ai {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n width: min(420px, 94%);\n z-index: 6;\n border-left: 1px solid var(--m-line);\n box-shadow: -12px 0 40px rgba(0, 0, 0, 0.45);\n }\n}\n\n/* ---------- AI summary ---------- */\n\n.mailharbor-divider {\n width: 1px;\n height: 18px;\n margin: 0 4px;\n background: var(--m-line);\n}\n\n.mailharbor-summary {\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n\n.mailharbor-summary section h4 {\n margin: 0 0 6px;\n font-size: 11.5px;\n font-weight: 600;\n color: var(--m-text-3);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n}\n\n.mailharbor-summary section p {\n margin: 0;\n white-space: pre-wrap;\n line-height: 1.75;\n color: var(--m-text);\n}\n\n/* ---------- modal / forms ---------- */\n\n.mailharbor-portal {\n position: fixed;\n inset: 0;\n z-index: 80;\n height: auto;\n background: transparent;\n pointer-events: none;\n}\n\n.mailharbor-portal .mailharbor-modal-mask {\n pointer-events: auto;\n}\n\n.mailharbor-modal-mask {\n position: fixed;\n inset: 0;\n z-index: 60;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: rgba(0, 0, 0, 0.55);\n backdrop-filter: blur(2px);\n}\n\n.mailharbor-modal {\n display: flex;\n flex-direction: column;\n width: min(720px, 100%);\n max-height: 86vh;\n overflow: hidden;\n border: 1px solid var(--m-line);\n border-radius: 14px;\n background: var(--m-bg-1);\n color: var(--m-text);\n box-shadow: var(--m-shadow);\n}\n\n.mailharbor-modal > header {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 52px;\n padding: 0 14px 0 18px;\n border-bottom: 1px solid var(--m-line-soft);\n font-size: 14px;\n font-weight: 600;\n}\n\n.mailharbor-modal .content {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 18px;\n overflow: auto;\n}\n\n.mailharbor-modal > footer {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 18px;\n border-top: 1px solid var(--m-line-soft);\n}\n\n.mailharbor-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n min-width: 0;\n}\n\n.mailharbor-field > label {\n font-size: 11.5px;\n color: var(--m-text-3);\n}\n\n.mailharbor-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 10px;\n}\n\n.mailharbor-grid.three {\n grid-template-columns: 2fr 90px auto;\n align-items: end;\n}\n\n.mailharbor-toggle {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n color: var(--m-text-2);\n font-size: 12.5px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.mailharbor-toggle input[type='checkbox'] {\n width: 14px;\n height: 14px;\n accent-color: var(--m-accent);\n}\n\n/* ---------- composer ---------- */\n\n.mailharbor-toolbar {\n display: flex;\n align-items: center;\n gap: 2px;\n flex-wrap: wrap;\n padding: 6px;\n border: 1px solid var(--m-line-soft);\n border-bottom: 0;\n border-radius: 10px 10px 0 0;\n background: var(--m-bg-2);\n}\n\n.mailharbor-toolbar button {\n height: 26px;\n padding: 0 9px;\n border-radius: 6px;\n color: var(--m-text-2);\n font-size: 12px;\n}\n\n.mailharbor-toolbar button:hover {\n background: var(--m-bg-3);\n color: var(--m-text);\n}\n\n.mailharbor-editor {\n min-height: 240px;\n max-height: 44vh;\n overflow: auto;\n padding: 14px 16px;\n border: 1px solid var(--m-line-soft);\n border-radius: 0 0 10px 10px;\n background: #fff;\n color: #171a21;\n outline: none;\n font-size: 13.5px;\n line-height: 1.7;\n}\n\n.mailharbor-editor:empty::before {\n content: attr(data-placeholder);\n color: #9aa0ab;\n}\n\n/* ---------- inline confirm ---------- */\n\n.mailharbor-confirm {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-radius: 8px;\n border: 1px solid var(--m-line);\n background: var(--m-bg-2);\n color: var(--m-text-2);\n font-size: 12.5px;\n}\n\n/* ---------- composer AI ---------- */\n\n.mailharbor-toolbar button.ai {\n color: var(--m-accent);\n}\n\n.mailharbor-compose-foot {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n flex-wrap: wrap;\n}\n\n.mailharbor-versions {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n padding-top: 8px;\n}\n\n.mailharbor-versions .chip {\n height: 22px;\n padding: 0 8px;\n border: 1px solid var(--m-line);\n border-radius: 999px;\n color: var(--m-text-3);\n font-size: 11px;\n}\n\n.mailharbor-versions .chip.active {\n background: var(--m-accent-soft);\n border-color: transparent;\n color: var(--m-text);\n}\n\n/* ---------- settings ---------- */\n\n.mailharbor-settings {\n padding: 20px 24px 48px;\n display: flex;\n flex-direction: column;\n gap: 16px;\n max-width: 880px;\n}\n\n.mailharbor-settings h2 {\n margin: 0;\n font-size: 18px;\n font-weight: 600;\n letter-spacing: -0.01em;\n}\n\n.mailharbor-settings h3 {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--m-text-3);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n}\n\n.mailharbor-card {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n border: 1px solid var(--m-line-soft);\n border-radius: 12px;\n background: var(--m-bg-1);\n}\n\n.mailharbor-account-row {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.mailharbor-account-row .meta {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n.mailharbor-account-row .meta strong {\n font-weight: 600;\n}\n\n.mailharbor-account-row .meta span {\n color: var(--m-text-3);\n font-size: 11.5px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.mailharbor .avatar {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n border-radius: 50%;\n background: var(--m-accent-soft);\n color: var(--m-accent);\n font-weight: 600;\n}\n\n.mailharbor-log {\n padding: 10px 12px;\n border-radius: 8px;\n background: var(--m-bg);\n border: 1px solid var(--m-line-soft);\n color: var(--m-text-2);\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11px;\n line-height: 1.6;\n max-height: 220px;\n overflow: auto;\n white-space: pre-wrap;\n}\n\n.mailharbor-kv {\n display: grid;\n grid-template-columns: minmax(120px, 170px) 1fr;\n gap: 6px 14px;\n font-size: 12px;\n margin: 0;\n}\n\n.mailharbor-kv dt {\n color: var(--m-text-3);\n}\n\n.mailharbor-kv dd {\n margin: 0;\n color: var(--m-text-2);\n word-break: break-all;\n}\n\n.mailharbor hr {\n border: 0;\n border-top: 1px solid var(--m-line-soft);\n margin: 2px 0;\n}\n";
38
38
 
39
39
  // src/client/icon.tsx
40
40
  var import_react = require("react");
@@ -125,6 +125,21 @@ var api = {
125
125
  check: (id) => request(`accounts/${encodeURIComponent(id)}/check`, { method: "POST" }),
126
126
  aiStatus: (accountId) => request(`ai/status${query({ accountId: accountId ?? null })}`),
127
127
  aiModels: () => request("ai/models"),
128
+ aiThread: (accountId, key) => request(`ai/thread${query({ accountId: accountId ?? null, key })}`),
129
+ aiAddCard: (payload) => request("ai/cards", {
130
+ method: "POST",
131
+ body: JSON.stringify(payload)
132
+ }),
133
+ aiDeleteCard: (accountId, key, cardId) => request(`ai/cards${query({ accountId: accountId ?? null, key, cardId })}`, {
134
+ method: "DELETE"
135
+ }),
136
+ aiClearThread: (accountId, key) => request(`ai/thread${query({ accountId: accountId ?? null, key })}`, {
137
+ method: "DELETE"
138
+ }),
139
+ aiCompose: (payload) => request("ai/compose", {
140
+ method: "POST",
141
+ body: JSON.stringify(payload)
142
+ }),
128
143
  aiSummary: (payload) => request("ai/summary", {
129
144
  method: "POST",
130
145
  body: JSON.stringify(payload)
@@ -240,17 +255,451 @@ function MailIcon() {
240
255
  }
241
256
 
242
257
  // src/client/panel.tsx
243
- var import_react7 = require("react");
258
+ var import_react8 = require("react");
244
259
 
245
- // src/client/account-form.tsx
260
+ // src/client/ai-rail.tsx
246
261
  var import_react3 = require("react");
247
262
 
248
- // src/client/components.tsx
263
+ // src/client/icons.tsx
249
264
  var import_react2 = require("react");
265
+ var PATHS = {
266
+ refresh: (0, import_react2.createElement)("path", { d: "M20 11a8 8 0 1 0-2.3 5.7M20 5v6h-6" }),
267
+ plus: (0, import_react2.createElement)("path", { d: "M12 5v14M5 12h14" }),
268
+ compose: (0, import_react2.createElement)("path", { d: "M4 20h4l10.5-10.5a2.1 2.1 0 0 0-3-3L5 17v3zM13.5 6.5l3 3" }),
269
+ archive: (0, import_react2.createElement)(
270
+ "g",
271
+ null,
272
+ (0, import_react2.createElement)("rect", { x: 3, y: 4, width: 18, height: 5, rx: 1.5 }),
273
+ (0, import_react2.createElement)("path", { d: "M5 9v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V9M10 13h4" })
274
+ ),
275
+ paperclip: (0, import_react2.createElement)("path", {
276
+ d: "M20 11.5 12 19.5a5 5 0 0 1-7-7l8-8a3.3 3.3 0 0 1 4.7 4.7l-8 8a1.7 1.7 0 0 1-2.4-2.4l7.3-7.3"
277
+ }),
278
+ mail: (0, import_react2.createElement)(
279
+ "g",
280
+ null,
281
+ (0, import_react2.createElement)("rect", { x: 3, y: 5, width: 18, height: 14, rx: 2.5 }),
282
+ (0, import_react2.createElement)("path", { d: "m4 7 7.1 5a1.6 1.6 0 0 0 1.8 0L20 7" })
283
+ ),
284
+ search: (0, import_react2.createElement)("path", { d: "M11 18a7 7 0 1 0 0-14 7 7 0 0 0 0 14zM20 20l-4-4" }),
285
+ reply: (0, import_react2.createElement)("path", { d: "M9 7 4 12l5 5M4 12h9a6 6 0 0 1 6 6v1" }),
286
+ replyAll: (0, import_react2.createElement)("path", { d: "m9 7-5 5 5 5M14 7l-5 5 5 5M13 12h5a5 5 0 0 1 5 5" }),
287
+ forward: (0, import_react2.createElement)("path", { d: "m15 7 5 5-5 5M20 12h-9a6 6 0 0 0-6 6v1" }),
288
+ check: (0, import_react2.createElement)("path", { d: "m5 13 4 4L19 7" }),
289
+ image: (0, import_react2.createElement)(
290
+ "g",
291
+ null,
292
+ (0, import_react2.createElement)("rect", { x: 3, y: 4, width: 18, height: 16, rx: 2 }),
293
+ (0, import_react2.createElement)("circle", { cx: 8.5, cy: 9.5, r: 1.5 }),
294
+ (0, import_react2.createElement)("path", { d: "m4 18 5-5 4 4 3-3 4 4" })
295
+ ),
296
+ close: (0, import_react2.createElement)("path", { d: "M6 6l12 12M18 6 6 18" }),
297
+ chevronLeft: (0, import_react2.createElement)("path", { d: "m14 6-6 6 6 6" }),
298
+ chevronRight: (0, import_react2.createElement)("path", { d: "m10 6 6 6-6 6" }),
299
+ sparkles: (0, import_react2.createElement)(
300
+ "g",
301
+ null,
302
+ (0, import_react2.createElement)("path", { d: "m12 3 1.8 4.6L18.5 9l-4.7 1.4L12 15l-1.8-4.6L5.5 9l4.7-1.4z" }),
303
+ (0, import_react2.createElement)("path", { d: "M18.5 15.5 19.3 18l2.2.8-2.2.7-.8 2.5-.8-2.5-2.2-.7 2.2-.8z" })
304
+ ),
305
+ trash: (0, import_react2.createElement)(
306
+ "g",
307
+ null,
308
+ (0, import_react2.createElement)("path", {
309
+ d: "M4 7h16M9 7V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2M6.5 7l.8 12a1 1 0 0 0 1 .9h7.4a1 1 0 0 0 1-.9l.8-12"
310
+ }),
311
+ (0, import_react2.createElement)("path", { d: "M10 11v6M14 11v6" })
312
+ ),
313
+ send: (0, import_react2.createElement)("path", { d: "m4 4 17 8-17 8 3.6-8z M7.6 12H21" })
314
+ };
315
+ function Icon({ name, size = 16 }) {
316
+ return (0, import_react2.createElement)(
317
+ "svg",
318
+ {
319
+ width: size,
320
+ height: size,
321
+ viewBox: "0 0 24 24",
322
+ fill: "none",
323
+ stroke: "currentColor",
324
+ strokeWidth: 1.7,
325
+ strokeLinecap: "round",
326
+ strokeLinejoin: "round",
327
+ "aria-hidden": "true",
328
+ focusable: "false"
329
+ },
330
+ PATHS[name] ?? null
331
+ );
332
+ }
333
+
334
+ // src/client/ai-rail.tsx
335
+ function errorText(error) {
336
+ return error instanceof ApiError || error instanceof Error ? error.message : String(error);
337
+ }
338
+ function textToHtml(text) {
339
+ return text.split(/\n{2,}/).map(
340
+ (paragraph) => `<p>${paragraph.trim().replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll("\n", "<br>")}</p>`
341
+ ).join("");
342
+ }
343
+ function summaryBlocks(text) {
344
+ const blocks = [];
345
+ let current = { title: "", lines: [] };
346
+ const push = () => {
347
+ const body = current.lines.join("\n").trim();
348
+ if (current.title !== "" || body !== "") blocks.push({ title: current.title, body });
349
+ current = { title: "", lines: [] };
350
+ };
351
+ for (const line of text.split("\n")) {
352
+ const heading = /^#{1,4}\s+(.*)$/.exec(line) ?? /^(结论|需要你做什么|关键信息|建议回复要点)\s*$/.exec(line);
353
+ if (heading !== null) {
354
+ push();
355
+ current.title = heading[1] ?? "";
356
+ } else {
357
+ current.lines.push(line);
358
+ }
359
+ }
360
+ push();
361
+ return blocks;
362
+ }
363
+ function suggestionsOf(summary) {
364
+ const match = /建议回复要点[::]?\s*\n([\s\S]*)$/.exec(summary);
365
+ if (match === null) return "\u6839\u636E\u8FD9\u5C01\u90AE\u4EF6\u5199\u4E00\u5C01\u5F97\u4F53\u7684\u56DE\u590D\u3002";
366
+ return `\u6839\u636E\u8FD9\u4E9B\u5EFA\u8BAE\u5199\u56DE\u590D\uFF1A
367
+ ${(match[1] ?? "").trim()}`;
368
+ }
369
+ var KIND_LABEL = {
370
+ summary: "AI \u6458\u8981",
371
+ reply: "AI \u56DE\u590D",
372
+ translate: "AI \u7FFB\u8BD1",
373
+ answer: "AI \u56DE\u7B54"
374
+ };
375
+ function threadKey(detail, source) {
376
+ return detail.messageId !== void 0 && detail.messageId !== "" ? `mid:${detail.messageId}` : `${source}:${detail.id}`;
377
+ }
378
+ function AiRail({
379
+ account,
380
+ detail,
381
+ source,
382
+ mailbox,
383
+ request: request2,
384
+ onClose,
385
+ onInsert,
386
+ onSaveDraft,
387
+ onNotice
388
+ }) {
389
+ const [status, setStatus] = (0, import_react3.useState)(null);
390
+ const [thread, setThread] = (0, import_react3.useState)(null);
391
+ const [loading, setLoading] = (0, import_react3.useState)(false);
392
+ const [generating, setGenerating] = (0, import_react3.useState)(null);
393
+ const [error, setError] = (0, import_react3.useState)("");
394
+ const [question, setQuestion] = (0, import_react3.useState)("");
395
+ const [confirmClear, setConfirmClear] = (0, import_react3.useState)(false);
396
+ const list = (0, import_react3.useRef)(null);
397
+ const lastNonce = (0, import_react3.useRef)(0);
398
+ const key = detail === null ? "" : threadKey(detail, source);
399
+ (0, import_react3.useEffect)(() => {
400
+ if (account === null) return;
401
+ let cancelled = false;
402
+ api.aiStatus(account.id).then((next) => {
403
+ if (!cancelled) setStatus(next);
404
+ }).catch(() => {
405
+ if (!cancelled)
406
+ setStatus({ available: false, enabled: false, provider: "", model: "", error: "\u65E0\u6CD5\u8BFB\u53D6 AI \u72B6\u6001" });
407
+ });
408
+ return () => {
409
+ cancelled = true;
410
+ };
411
+ }, [account]);
412
+ (0, import_react3.useEffect)(() => {
413
+ if (account === null || key === "") {
414
+ setThread(null);
415
+ return;
416
+ }
417
+ let cancelled = false;
418
+ setLoading(true);
419
+ api.aiThread(account.id, key).then((result) => {
420
+ if (!cancelled) setThread(result.thread);
421
+ }).catch(() => {
422
+ if (!cancelled) setThread(null);
423
+ }).finally(() => {
424
+ if (!cancelled) setLoading(false);
425
+ });
426
+ return () => {
427
+ cancelled = true;
428
+ };
429
+ }, [account, key]);
430
+ const generate = (0, import_react3.useCallback)(
431
+ async (kind, extra = {}) => {
432
+ if (detail === null || account === null) return;
433
+ setGenerating(kind);
434
+ setError("");
435
+ try {
436
+ const result = await api.aiAddCard({
437
+ accountId: detail.accountId,
438
+ key,
439
+ kind,
440
+ source,
441
+ id: detail.id,
442
+ mailbox,
443
+ ...extra
444
+ });
445
+ setThread(result.thread);
446
+ } catch (caught) {
447
+ setError(errorText(caught));
448
+ } finally {
449
+ setGenerating(null);
450
+ }
451
+ },
452
+ [detail, account, key, source, mailbox]
453
+ );
454
+ (0, import_react3.useEffect)(() => {
455
+ if (request2 === null || request2.nonce === lastNonce.current) return;
456
+ lastNonce.current = request2.nonce;
457
+ if (detail !== null) void generate(request2.kind);
458
+ }, [request2]);
459
+ (0, import_react3.useEffect)(() => {
460
+ const element = list.current;
461
+ if (element !== null) element.scrollTop = element.scrollHeight;
462
+ }, [thread, generating]);
463
+ const replyInit = (card) => {
464
+ const subject = card.meta?.subject ?? (detail === null ? "" : /^re:/i.test(detail.subject) ? detail.subject : `Re: ${detail.subject}`);
465
+ return {
466
+ to: card.meta?.to ?? detail?.from.address ?? "",
467
+ cc: "",
468
+ subject,
469
+ html: card.html ?? textToHtml(card.output),
470
+ ...card.meta?.inReplyTo !== void 0 && card.meta.inReplyTo !== "" ? { inReplyTo: card.meta.inReplyTo } : {},
471
+ ...card.meta?.references !== void 0 && card.meta.references !== "" ? { references: card.meta.references } : {}
472
+ };
473
+ };
474
+ const copy = (card) => {
475
+ void navigator.clipboard?.writeText(card.output).then(() => onNotice("\u5DF2\u590D\u5236"));
476
+ };
477
+ const remove = async (card) => {
478
+ if (account === null) return;
479
+ try {
480
+ const result = await api.aiDeleteCard(account.id, key, card.id);
481
+ setThread(result.thread);
482
+ } catch (caught) {
483
+ setError(errorText(caught));
484
+ }
485
+ };
486
+ const clear = async () => {
487
+ if (account === null || key === "") return;
488
+ try {
489
+ await api.aiClearThread(account.id, key);
490
+ setThread(null);
491
+ setConfirmClear(false);
492
+ onNotice("\u5DF2\u6E05\u9664\u8FD9\u5C01\u90AE\u4EF6\u7684 AI \u8BB0\u5F55");
493
+ } catch (caught) {
494
+ setError(errorText(caught));
495
+ }
496
+ };
497
+ const ask = async () => {
498
+ const text = question.trim();
499
+ if (text === "" || generating !== null) return;
500
+ setQuestion("");
501
+ await generate("answer", { instruction: text });
502
+ };
503
+ const disabled = status !== null && (!status.available || !status.enabled);
504
+ const cards = thread?.cards ?? [];
505
+ return (0, import_react3.createElement)(
506
+ "aside",
507
+ { className: "mailharbor-ai", "aria-label": "AI \u52A9\u624B" },
508
+ (0, import_react3.createElement)(
509
+ "div",
510
+ { className: "mailharbor-ai-head" },
511
+ (0, import_react3.createElement)(Icon, { name: "sparkles", size: 15 }),
512
+ (0, import_react3.createElement)("strong", null, "AI \u52A9\u624B"),
513
+ status === null || status.model === "" ? null : (0, import_react3.createElement)("span", { className: "badge" }, status.model),
514
+ (0, import_react3.createElement)("span", { className: "spacer" }),
515
+ confirmClear ? (0, import_react3.createElement)(
516
+ "span",
517
+ { className: "mailharbor-ai-confirm" },
518
+ (0, import_react3.createElement)("button", { className: "subtle", onClick: () => void clear() }, "\u786E\u8BA4\u6E05\u9664"),
519
+ (0, import_react3.createElement)("button", { className: "subtle", onClick: () => setConfirmClear(false) }, "\u53D6\u6D88")
520
+ ) : (0, import_react3.createElement)(
521
+ "button",
522
+ {
523
+ className: "subtle icon",
524
+ title: cards.length === 0 ? "\u5173\u95ED" : "\u6E05\u9664\u8FD9\u5C01\u90AE\u4EF6\u7684\u8BB0\u5F55",
525
+ onClick: () => cards.length === 0 ? onClose() : setConfirmClear(true)
526
+ },
527
+ (0, import_react3.createElement)(Icon, { name: cards.length === 0 ? "close" : "trash", size: 14 })
528
+ ),
529
+ (0, import_react3.createElement)(
530
+ "button",
531
+ { className: "subtle icon", title: "\u6536\u8D77", onClick: onClose },
532
+ (0, import_react3.createElement)(Icon, { name: "chevronRight", size: 14 })
533
+ )
534
+ ),
535
+ detail === null ? (0, import_react3.createElement)(
536
+ "div",
537
+ { className: "mailharbor-ai-empty" },
538
+ (0, import_react3.createElement)(Icon, { name: "sparkles", size: 26 }),
539
+ (0, import_react3.createElement)("div", { className: "title" }, "\u5148\u9009\u4E00\u5C01\u90AE\u4EF6"),
540
+ (0, import_react3.createElement)("div", { className: "small" }, "\u9009\u4E2D\u90AE\u4EF6\u540E\uFF0C\u53EF\u5728\u8FD9\u91CC\u751F\u6210\u6458\u8981\u3001\u56DE\u590D\u548C\u7FFB\u8BD1\uFF0C\u8BB0\u5F55\u4F1A\u4E00\u76F4\u4FDD\u7559\u3002")
541
+ ) : (0, import_react3.createElement)(
542
+ "div",
543
+ { className: "mailharbor-ai-context" },
544
+ (0, import_react3.createElement)("span", { className: "small" }, "\u8DDF\u968F\u5F53\u524D\u90AE\u4EF6"),
545
+ (0, import_react3.createElement)("strong", null, detail.subject),
546
+ (0, import_react3.createElement)("span", { className: "small muted" }, "AI \u53EA\u770B\u5230\u8FD9\u5C01\u90AE\u4EF6")
547
+ ),
548
+ disabled && detail !== null ? (0, import_react3.createElement)("div", { className: "mailharbor-ai-warn" }, status?.error ?? "AI \u5DF2\u5173\u95ED\uFF0C\u8BF7\u5230\u8BBE\u7F6E\u91CC\u5F00\u542F") : null,
549
+ detail === null ? null : (0, import_react3.createElement)(
550
+ "div",
551
+ { className: "mailharbor-ai-quick" },
552
+ (0, import_react3.createElement)(
553
+ "button",
554
+ {
555
+ className: "ghost",
556
+ disabled: disabled || generating !== null,
557
+ onClick: () => void generate("summary")
558
+ },
559
+ generating === "summary" ? "\u603B\u7ED3\u4E2D\u2026" : "\u6458\u8981"
560
+ ),
561
+ (0, import_react3.createElement)(
562
+ "button",
563
+ {
564
+ className: "ghost",
565
+ disabled: disabled || generating !== null,
566
+ onClick: () => void generate("reply")
567
+ },
568
+ generating === "reply" ? "\u8D77\u8349\u4E2D\u2026" : "\u56DE\u590D"
569
+ ),
570
+ (0, import_react3.createElement)(
571
+ "button",
572
+ {
573
+ className: "ghost",
574
+ disabled: disabled || generating !== null,
575
+ onClick: () => void generate("translate", { target: "en" })
576
+ },
577
+ generating === "translate" ? "\u7FFB\u8BD1\u4E2D\u2026" : "\u7FFB\u8BD1"
578
+ )
579
+ ),
580
+ (0, import_react3.createElement)(
581
+ "div",
582
+ { className: "mailharbor-ai-list", ref: list },
583
+ loading ? (0, import_react3.createElement)("div", { className: "mailharbor-ai-empty small" }, "\u6B63\u5728\u8BFB\u53D6\u8BB0\u5F55\u2026") : null,
584
+ !loading && detail !== null && cards.length === 0 && generating === null ? (0, import_react3.createElement)(
585
+ "div",
586
+ { className: "mailharbor-ai-empty" },
587
+ (0, import_react3.createElement)("div", { className: "title" }, "\u8FD9\u5C01\u90AE\u4EF6\u8FD8\u6CA1\u6709 AI \u8BB0\u5F55"),
588
+ (0, import_react3.createElement)(
589
+ "div",
590
+ { className: "small" },
591
+ "\u70B9\u4E0A\u9762\u7684\u300C\u6458\u8981\u300D\u5F00\u59CB\uFF1B\u751F\u6210\u7684\u5185\u5BB9\u4F1A\u4FDD\u5B58\u5728\u8FD9\u91CC\uFF0C\u5173\u6389\u9762\u677F\u4E5F\u4E0D\u4F1A\u4E22\u3002"
592
+ )
593
+ ) : null,
594
+ cards.map(
595
+ (card) => (0, import_react3.createElement)(
596
+ "article",
597
+ { className: "mailharbor-ai-card", key: card.id },
598
+ (0, import_react3.createElement)(
599
+ "div",
600
+ { className: "mailharbor-ai-card-head" },
601
+ (0, import_react3.createElement)("span", null, KIND_LABEL[card.kind] ?? "AI"),
602
+ (0, import_react3.createElement)("span", { className: "muted small" }, new Date(card.createdAt).toLocaleTimeString()),
603
+ (0, import_react3.createElement)("span", { className: "spacer" }),
604
+ (0, import_react3.createElement)(
605
+ "button",
606
+ {
607
+ className: "subtle icon",
608
+ title: "\u5220\u9664",
609
+ onClick: () => void remove(card)
610
+ },
611
+ (0, import_react3.createElement)(Icon, { name: "close", size: 12 })
612
+ )
613
+ ),
614
+ card.kind === "summary" ? (0, import_react3.createElement)(
615
+ "div",
616
+ { className: "mailharbor-ai-summary" },
617
+ summaryBlocks(card.output).map(
618
+ (block, index) => (0, import_react3.createElement)(
619
+ "section",
620
+ { key: `${block.title}:${String(index)}` },
621
+ block.title === "" ? null : (0, import_react3.createElement)("h4", null, block.title),
622
+ (0, import_react3.createElement)("p", null, block.body)
623
+ )
624
+ )
625
+ ) : (0, import_react3.createElement)("div", { className: "mailharbor-ai-text" }, card.output),
626
+ (0, import_react3.createElement)(
627
+ "div",
628
+ { className: "mailharbor-ai-card-actions" },
629
+ card.kind === "summary" ? (0, import_react3.createElement)(
630
+ "button",
631
+ {
632
+ className: "ghost",
633
+ disabled: generating !== null,
634
+ onClick: () => void generate("reply", { instruction: suggestionsOf(card.output) })
635
+ },
636
+ "\u7528\u8FD9\u4E9B\u8981\u70B9\u56DE\u590D"
637
+ ) : null,
638
+ card.kind === "reply" || card.kind === "translate" ? (0, import_react3.createElement)(
639
+ "button",
640
+ { className: "ghost", onClick: () => onInsert(replyInit(card)) },
641
+ card.kind === "translate" ? "\u63D2\u5165\u56DE\u590D" : "\u63D2\u5165\u6B63\u6587"
642
+ ) : null,
643
+ card.kind === "reply" ? (0, import_react3.createElement)("button", { className: "ghost", onClick: () => onSaveDraft(replyInit(card)) }, "\u5B58\u8349\u7A3F") : null,
644
+ card.kind === "answer" ? (0, import_react3.createElement)(
645
+ "button",
646
+ {
647
+ className: "ghost",
648
+ onClick: () => setQuestion(card.output.slice(0, 80))
649
+ },
650
+ "\u7EE7\u7EED\u8FFD\u95EE"
651
+ ) : null,
652
+ (0, import_react3.createElement)("span", { className: "spacer" }),
653
+ (0, import_react3.createElement)("button", { className: "subtle", onClick: () => copy(card) }, "\u590D\u5236")
654
+ )
655
+ )
656
+ ),
657
+ generating !== null ? (0, import_react3.createElement)(
658
+ "article",
659
+ { className: "mailharbor-ai-card generating" },
660
+ (0, import_react3.createElement)("div", { className: "mailharbor-ai-card-head" }, (0, import_react3.createElement)("span", null, KIND_LABEL[generating])),
661
+ (0, import_react3.createElement)("div", { className: "mailharbor-ai-text muted" }, "\u6B63\u5728\u751F\u6210\u2026")
662
+ ) : null,
663
+ error === "" ? null : (0, import_react3.createElement)("div", { className: "mailharbor-ai-error" }, error)
664
+ ),
665
+ detail === null ? null : (0, import_react3.createElement)(
666
+ "form",
667
+ {
668
+ className: "mailharbor-ai-ask",
669
+ onSubmit: (event) => {
670
+ event.preventDefault();
671
+ void ask();
672
+ }
673
+ },
674
+ (0, import_react3.createElement)("input", {
675
+ value: question,
676
+ placeholder: "\u95EE\u8FD9\u5C01\u90AE\u4EF6\uFF0C\u4F8B\u5982\uFF1A\u8C01\u9700\u8981\u5728\u4EC0\u4E48\u65F6\u5019\u505A\u4EC0\u4E48\uFF1F",
677
+ "aria-label": "\u95EE AI",
678
+ disabled: disabled || generating !== null,
679
+ onChange: (event) => setQuestion(event.target.value)
680
+ }),
681
+ (0, import_react3.createElement)(
682
+ "button",
683
+ {
684
+ className: "primary icon",
685
+ type: "submit",
686
+ disabled: disabled || generating !== null || question.trim() === ""
687
+ },
688
+ (0, import_react3.createElement)(Icon, { name: "send", size: 14 })
689
+ )
690
+ )
691
+ );
692
+ }
693
+
694
+ // src/client/account-form.tsx
695
+ var import_react5 = require("react");
696
+
697
+ // src/client/components.tsx
698
+ var import_react4 = require("react");
250
699
  var import_react_dom = require("react-dom");
251
700
  function Avatar({ email, size = 26 }) {
252
701
  const letter = (email.trim()[0] ?? "?").toUpperCase();
253
- return (0, import_react2.createElement)(
702
+ return (0, import_react4.createElement)(
254
703
  "span",
255
704
  {
256
705
  className: "avatar",
@@ -269,12 +718,12 @@ function Field({
269
718
  children,
270
719
  hint
271
720
  }) {
272
- return (0, import_react2.createElement)(
721
+ return (0, import_react4.createElement)(
273
722
  "div",
274
723
  { className: "mailharbor-field" },
275
- (0, import_react2.createElement)("label", null, label),
724
+ (0, import_react4.createElement)("label", null, label),
276
725
  children,
277
- hint === void 0 ? null : (0, import_react2.createElement)("span", { className: "small muted" }, hint)
726
+ hint === void 0 ? null : (0, import_react4.createElement)("span", { className: "small muted" }, hint)
278
727
  );
279
728
  }
280
729
  function Toggle({
@@ -282,10 +731,10 @@ function Toggle({
282
731
  onChange,
283
732
  label
284
733
  }) {
285
- return (0, import_react2.createElement)(
734
+ return (0, import_react4.createElement)(
286
735
  "label",
287
736
  { className: "mailharbor-toggle" },
288
- (0, import_react2.createElement)("input", {
737
+ (0, import_react4.createElement)("input", {
289
738
  type: "checkbox",
290
739
  checked,
291
740
  onChange: (event) => {
@@ -311,7 +760,7 @@ function Modal({
311
760
  onClose,
312
761
  wide = false
313
762
  }) {
314
- (0, import_react2.useEffect)(() => {
763
+ (0, import_react4.useEffect)(() => {
315
764
  const onKey = (event) => {
316
765
  if (event.key === "Escape") onClose();
317
766
  };
@@ -321,7 +770,7 @@ function Modal({
321
770
  };
322
771
  }, [onClose]);
323
772
  return (0, import_react_dom.createPortal)(
324
- (0, import_react2.createElement)(
773
+ (0, import_react4.createElement)(
325
774
  "div",
326
775
  {
327
776
  className: "mailharbor-modal-mask",
@@ -329,7 +778,7 @@ function Modal({
329
778
  if (event.target === event.currentTarget) onClose();
330
779
  }
331
780
  },
332
- (0, import_react2.createElement)(
781
+ (0, import_react4.createElement)(
333
782
  "div",
334
783
  {
335
784
  className: "mailharbor-modal",
@@ -338,27 +787,27 @@ function Modal({
338
787
  "aria-modal": "true",
339
788
  "aria-label": title
340
789
  },
341
- (0, import_react2.createElement)(
790
+ (0, import_react4.createElement)(
342
791
  "header",
343
792
  null,
344
793
  title,
345
- (0, import_react2.createElement)("span", { className: "spacer" }),
346
- (0, import_react2.createElement)("button", { className: "subtle", onClick: onClose, "aria-label": "\u5173\u95ED" }, "\u2715")
794
+ (0, import_react4.createElement)("span", { className: "spacer" }),
795
+ (0, import_react4.createElement)("button", { className: "subtle", onClick: onClose, "aria-label": "\u5173\u95ED" }, "\u2715")
347
796
  ),
348
- (0, import_react2.createElement)("div", { className: "content" }, children),
349
- footer === void 0 ? null : (0, import_react2.createElement)("footer", null, footer)
797
+ (0, import_react4.createElement)("div", { className: "content" }, children),
798
+ footer === void 0 ? null : (0, import_react4.createElement)("footer", null, footer)
350
799
  )
351
800
  ),
352
801
  portalRoot()
353
802
  );
354
803
  }
355
804
  function ErrorBar({ message, onDismiss }) {
356
- return (0, import_react2.createElement)(
805
+ return (0, import_react4.createElement)(
357
806
  "div",
358
807
  { className: "error", role: "alert" },
359
- (0, import_react2.createElement)("span", null, message),
360
- (0, import_react2.createElement)("span", { className: "spacer" }),
361
- (0, import_react2.createElement)("button", { className: "subtle", onClick: onDismiss }, "\u5173\u95ED")
808
+ (0, import_react4.createElement)("span", null, message),
809
+ (0, import_react4.createElement)("span", { className: "spacer" }),
810
+ (0, import_react4.createElement)("button", { className: "subtle", onClick: onDismiss }, "\u5173\u95ED")
362
811
  );
363
812
  }
364
813
  function shortDate(iso) {
@@ -393,27 +842,27 @@ function formatBytes(size) {
393
842
  function domainOf(email) {
394
843
  return email.split("@")[1] ?? "example.com";
395
844
  }
396
- function errorText(error) {
845
+ function errorText2(error) {
397
846
  return error instanceof ApiError || error instanceof Error ? error.message : String(error);
398
847
  }
399
848
  function AddAccountForm({
400
849
  onDone,
401
850
  onCancel
402
851
  }) {
403
- const [email, setEmail] = (0, import_react3.useState)("");
404
- const [password, setPassword] = (0, import_react3.useState)("");
405
- const [label, setLabel] = (0, import_react3.useState)("");
406
- const [remember, setRemember] = (0, import_react3.useState)(true);
407
- const [advanced, setAdvanced] = (0, import_react3.useState)(false);
408
- const [imapHost, setImapHost] = (0, import_react3.useState)("");
409
- const [imapPort, setImapPort] = (0, import_react3.useState)("993");
410
- const [imapSecure, setImapSecure] = (0, import_react3.useState)(true);
411
- const [smtpHost, setSmtpHost] = (0, import_react3.useState)("");
412
- const [smtpPort, setSmtpPort] = (0, import_react3.useState)("465");
413
- const [smtpSecure, setSmtpSecure] = (0, import_react3.useState)(true);
414
- const [fromName, setFromName] = (0, import_react3.useState)("");
415
- const [busy, setBusy] = (0, import_react3.useState)(false);
416
- const [error, setError] = (0, import_react3.useState)("");
852
+ const [email, setEmail] = (0, import_react5.useState)("");
853
+ const [password, setPassword] = (0, import_react5.useState)("");
854
+ const [label, setLabel] = (0, import_react5.useState)("");
855
+ const [remember, setRemember] = (0, import_react5.useState)(true);
856
+ const [advanced, setAdvanced] = (0, import_react5.useState)(false);
857
+ const [imapHost, setImapHost] = (0, import_react5.useState)("");
858
+ const [imapPort, setImapPort] = (0, import_react5.useState)("993");
859
+ const [imapSecure, setImapSecure] = (0, import_react5.useState)(true);
860
+ const [smtpHost, setSmtpHost] = (0, import_react5.useState)("");
861
+ const [smtpPort, setSmtpPort] = (0, import_react5.useState)("465");
862
+ const [smtpSecure, setSmtpSecure] = (0, import_react5.useState)(true);
863
+ const [fromName, setFromName] = (0, import_react5.useState)("");
864
+ const [busy, setBusy] = (0, import_react5.useState)(false);
865
+ const [error, setError] = (0, import_react5.useState)("");
417
866
  const submit = async () => {
418
867
  setBusy(true);
419
868
  setError("");
@@ -440,22 +889,22 @@ function AddAccountForm({
440
889
  });
441
890
  onDone(state);
442
891
  } catch (caught) {
443
- setError(errorText(caught));
892
+ setError(errorText2(caught));
444
893
  } finally {
445
894
  setBusy(false);
446
895
  }
447
896
  };
448
- return (0, import_react3.createElement)(
897
+ return (0, import_react5.createElement)(
449
898
  "div",
450
899
  { className: "mailharbor-card" },
451
- (0, import_react3.createElement)("h3", null, "\u6DFB\u52A0\u90AE\u7BB1\u8D26\u53F7"),
452
- (0, import_react3.createElement)(
900
+ (0, import_react5.createElement)("h3", null, "\u6DFB\u52A0\u90AE\u7BB1\u8D26\u53F7"),
901
+ (0, import_react5.createElement)(
453
902
  "div",
454
903
  { className: "mailharbor-grid" },
455
- (0, import_react3.createElement)(
904
+ (0, import_react5.createElement)(
456
905
  Field,
457
906
  { label: "\u90AE\u7BB1\u5730\u5740" },
458
- (0, import_react3.createElement)("input", {
907
+ (0, import_react5.createElement)("input", {
459
908
  type: "email",
460
909
  value: email,
461
910
  placeholder: "you@example.com",
@@ -463,95 +912,95 @@ function AddAccountForm({
463
912
  autoFocus: true
464
913
  })
465
914
  ),
466
- (0, import_react3.createElement)(
915
+ (0, import_react5.createElement)(
467
916
  Field,
468
917
  { label: "\u5BC6\u7801 / \u5E94\u7528\u4E13\u7528\u5BC6\u7801" },
469
- (0, import_react3.createElement)("input", {
918
+ (0, import_react5.createElement)("input", {
470
919
  type: "password",
471
920
  value: password,
472
921
  onChange: (event) => setPassword(event.target.value)
473
922
  })
474
923
  )
475
924
  ),
476
- (0, import_react3.createElement)(
925
+ (0, import_react5.createElement)(
477
926
  Field,
478
927
  { label: "\u663E\u793A\u540D\u79F0\uFF08\u53EF\u9009\uFF09" },
479
- (0, import_react3.createElement)("input", {
928
+ (0, import_react5.createElement)("input", {
480
929
  value: label,
481
930
  placeholder: "\u5DE5\u4F5C\u90AE\u7BB1",
482
931
  onChange: (event) => setLabel(event.target.value)
483
932
  })
484
933
  ),
485
- (0, import_react3.createElement)(Toggle, { checked: remember, onChange: setRemember, label: "\u8BB0\u4F4F\u5BC6\u7801\uFF08\u5199\u5165\u7CFB\u7EDF\u94A5\u5319\u4E32\uFF09" }),
486
- (0, import_react3.createElement)(
934
+ (0, import_react5.createElement)(Toggle, { checked: remember, onChange: setRemember, label: "\u8BB0\u4F4F\u5BC6\u7801\uFF08\u5199\u5165\u7CFB\u7EDF\u94A5\u5319\u4E32\uFF09" }),
935
+ (0, import_react5.createElement)(
487
936
  "button",
488
937
  { className: "ghost", onClick: () => setAdvanced(!advanced) },
489
938
  advanced ? "\u9690\u85CF\u9AD8\u7EA7\u8BBE\u7F6E" : "\u9AD8\u7EA7\u8BBE\u7F6E\uFF08\u670D\u52A1\u5668\u4E0E\u7AEF\u53E3\uFF09"
490
939
  ),
491
- advanced ? (0, import_react3.createElement)(
940
+ advanced ? (0, import_react5.createElement)(
492
941
  "div",
493
942
  { style: { display: "flex", flexDirection: "column", gap: "10px" } },
494
- (0, import_react3.createElement)(
943
+ (0, import_react5.createElement)(
495
944
  "div",
496
945
  { className: "mailharbor-grid three" },
497
- (0, import_react3.createElement)(
946
+ (0, import_react5.createElement)(
498
947
  Field,
499
948
  { label: "IMAP \u4E3B\u673A" },
500
- (0, import_react3.createElement)("input", {
949
+ (0, import_react5.createElement)("input", {
501
950
  value: imapHost,
502
951
  placeholder: `imap.${domainOf(email)}`,
503
952
  onChange: (event) => setImapHost(event.target.value)
504
953
  })
505
954
  ),
506
- (0, import_react3.createElement)(
955
+ (0, import_react5.createElement)(
507
956
  Field,
508
957
  { label: "\u7AEF\u53E3" },
509
- (0, import_react3.createElement)("input", {
958
+ (0, import_react5.createElement)("input", {
510
959
  value: imapPort,
511
960
  inputMode: "numeric",
512
961
  onChange: (event) => setImapPort(event.target.value)
513
962
  })
514
963
  ),
515
- (0, import_react3.createElement)(Toggle, { checked: imapSecure, onChange: setImapSecure, label: "TLS" })
964
+ (0, import_react5.createElement)(Toggle, { checked: imapSecure, onChange: setImapSecure, label: "TLS" })
516
965
  ),
517
- (0, import_react3.createElement)(
966
+ (0, import_react5.createElement)(
518
967
  "div",
519
968
  { className: "mailharbor-grid three" },
520
- (0, import_react3.createElement)(
969
+ (0, import_react5.createElement)(
521
970
  Field,
522
971
  { label: "SMTP \u4E3B\u673A" },
523
- (0, import_react3.createElement)("input", {
972
+ (0, import_react5.createElement)("input", {
524
973
  value: smtpHost,
525
974
  placeholder: `smtp.${domainOf(email)}`,
526
975
  onChange: (event) => setSmtpHost(event.target.value)
527
976
  })
528
977
  ),
529
- (0, import_react3.createElement)(
978
+ (0, import_react5.createElement)(
530
979
  Field,
531
980
  { label: "\u7AEF\u53E3" },
532
- (0, import_react3.createElement)("input", {
981
+ (0, import_react5.createElement)("input", {
533
982
  value: smtpPort,
534
983
  inputMode: "numeric",
535
984
  onChange: (event) => setSmtpPort(event.target.value)
536
985
  })
537
986
  ),
538
- (0, import_react3.createElement)(Toggle, { checked: smtpSecure, onChange: setSmtpSecure, label: "TLS" })
987
+ (0, import_react5.createElement)(Toggle, { checked: smtpSecure, onChange: setSmtpSecure, label: "TLS" })
539
988
  ),
540
- (0, import_react3.createElement)(
989
+ (0, import_react5.createElement)(
541
990
  Field,
542
991
  { label: "\u53D1\u4EF6\u4EBA\u540D\u79F0\uFF08\u53EF\u9009\uFF09" },
543
- (0, import_react3.createElement)("input", {
992
+ (0, import_react5.createElement)("input", {
544
993
  value: fromName,
545
994
  onChange: (event) => setFromName(event.target.value)
546
995
  })
547
996
  )
548
997
  ) : null,
549
- error === "" ? null : (0, import_react3.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
550
- (0, import_react3.createElement)(
998
+ error === "" ? null : (0, import_react5.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
999
+ (0, import_react5.createElement)(
551
1000
  "div",
552
1001
  { style: { display: "flex", gap: "8px", justifyContent: "flex-end" } },
553
- onCancel === void 0 ? null : (0, import_react3.createElement)("button", { className: "ghost", onClick: onCancel }, "\u53D6\u6D88"),
554
- (0, import_react3.createElement)(
1002
+ onCancel === void 0 ? null : (0, import_react5.createElement)("button", { className: "ghost", onClick: onCancel }, "\u53D6\u6D88"),
1003
+ (0, import_react5.createElement)(
555
1004
  "button",
556
1005
  {
557
1006
  className: "primary",
@@ -564,10 +1013,10 @@ function AddAccountForm({
564
1013
  );
565
1014
  }
566
1015
  function LoginForm({ account, onDone }) {
567
- const [password, setPassword] = (0, import_react3.useState)("");
568
- const [remember, setRemember] = (0, import_react3.useState)(true);
569
- const [busy, setBusy] = (0, import_react3.useState)(false);
570
- const [error, setError] = (0, import_react3.useState)("");
1016
+ const [password, setPassword] = (0, import_react5.useState)("");
1017
+ const [remember, setRemember] = (0, import_react5.useState)(true);
1018
+ const [busy, setBusy] = (0, import_react5.useState)(false);
1019
+ const [error, setError] = (0, import_react5.useState)("");
571
1020
  const submit = async () => {
572
1021
  setBusy(true);
573
1022
  setError("");
@@ -575,24 +1024,24 @@ function LoginForm({ account, onDone }) {
575
1024
  await api.login(account.id, password, remember);
576
1025
  onDone();
577
1026
  } catch (caught) {
578
- setError(errorText(caught));
1027
+ setError(errorText2(caught));
579
1028
  } finally {
580
1029
  setBusy(false);
581
1030
  }
582
1031
  };
583
- return (0, import_react3.createElement)(
1032
+ return (0, import_react5.createElement)(
584
1033
  "div",
585
1034
  { className: "mailharbor-card" },
586
- (0, import_react3.createElement)("h3", null, `\u767B\u5F55 ${account.email}`),
587
- (0, import_react3.createElement)(
1035
+ (0, import_react5.createElement)("h3", null, `\u767B\u5F55 ${account.email}`),
1036
+ (0, import_react5.createElement)(
588
1037
  "p",
589
1038
  { className: "small muted" },
590
1039
  `${account.imap.host}:${String(account.imap.port)} \xB7 ${account.smtp.host}:${String(account.smtp.port)}`
591
1040
  ),
592
- (0, import_react3.createElement)(
1041
+ (0, import_react5.createElement)(
593
1042
  Field,
594
1043
  { label: "\u5BC6\u7801 / \u5E94\u7528\u4E13\u7528\u5BC6\u7801" },
595
- (0, import_react3.createElement)("input", {
1044
+ (0, import_react5.createElement)("input", {
596
1045
  type: "password",
597
1046
  value: password,
598
1047
  onChange: (event) => setPassword(event.target.value),
@@ -602,9 +1051,9 @@ function LoginForm({ account, onDone }) {
602
1051
  autoFocus: true
603
1052
  })
604
1053
  ),
605
- (0, import_react3.createElement)(Toggle, { checked: remember, onChange: setRemember, label: "\u8BB0\u4F4F\u5BC6\u7801\uFF08\u5199\u5165\u7CFB\u7EDF\u94A5\u5319\u4E32\uFF09" }),
606
- error === "" ? null : (0, import_react3.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
607
- (0, import_react3.createElement)(
1054
+ (0, import_react5.createElement)(Toggle, { checked: remember, onChange: setRemember, label: "\u8BB0\u4F4F\u5BC6\u7801\uFF08\u5199\u5165\u7CFB\u7EDF\u94A5\u5319\u4E32\uFF09" }),
1055
+ error === "" ? null : (0, import_react5.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
1056
+ (0, import_react5.createElement)(
608
1057
  "button",
609
1058
  { className: "primary", onClick: () => void submit(), disabled: busy || password === "" },
610
1059
  busy ? "\u767B\u5F55\u4E2D\u2026" : "\u767B\u5F55"
@@ -615,10 +1064,10 @@ function AddAccountModal({
615
1064
  onClose,
616
1065
  onDone
617
1066
  }) {
618
- return (0, import_react3.createElement)(
1067
+ return (0, import_react5.createElement)(
619
1068
  Modal,
620
1069
  { title: "\u6DFB\u52A0\u90AE\u7BB1\u8D26\u53F7", onClose },
621
- (0, import_react3.createElement)(AddAccountForm, {
1070
+ (0, import_react5.createElement)(AddAccountForm, {
622
1071
  onDone: (state) => {
623
1072
  onDone(state);
624
1073
  onClose();
@@ -629,8 +1078,8 @@ function AddAccountModal({
629
1078
  }
630
1079
 
631
1080
  // src/client/composer.tsx
632
- var import_react4 = require("react");
633
- function errorText2(error) {
1081
+ var import_react6 = require("react");
1082
+ function errorText3(error) {
634
1083
  return error instanceof ApiError || error instanceof Error ? error.message : String(error);
635
1084
  }
636
1085
  function readFileBase64(file) {
@@ -650,27 +1099,115 @@ function Composer({
650
1099
  onClose,
651
1100
  onSent
652
1101
  }) {
653
- const [to, setTo] = (0, import_react4.useState)(initial.to);
654
- const [cc, setCc] = (0, import_react4.useState)(initial.cc);
655
- const [bcc, setBcc] = (0, import_react4.useState)(initial.bcc);
656
- const [subject, setSubject] = (0, import_react4.useState)(initial.subject);
657
- const [showCc, setShowCc] = (0, import_react4.useState)(initial.cc !== "" || initial.bcc !== "");
658
- const [attachments, setAttachments] = (0, import_react4.useState)(initial.attachments ?? []);
659
- const [busy, setBusy] = (0, import_react4.useState)(false);
660
- const [error, setError] = (0, import_react4.useState)("");
661
- const [confirming, setConfirming] = (0, import_react4.useState)(false);
662
- const [draftId, setDraftId] = (0, import_react4.useState)(initial.draftId);
663
- const [translating, setTranslating] = (0, import_react4.useState)(null);
664
- const [pendingLang, setPendingLang] = (0, import_react4.useState)(null);
665
- const editor = (0, import_react4.useRef)(null);
666
- (0, import_react4.useEffect)(() => {
1102
+ const [to, setTo] = (0, import_react6.useState)(initial.to);
1103
+ const [cc, setCc] = (0, import_react6.useState)(initial.cc);
1104
+ const [bcc, setBcc] = (0, import_react6.useState)(initial.bcc);
1105
+ const [subject, setSubject] = (0, import_react6.useState)(initial.subject);
1106
+ const [showCc, setShowCc] = (0, import_react6.useState)(initial.cc !== "" || initial.bcc !== "");
1107
+ const [attachments, setAttachments] = (0, import_react6.useState)(initial.attachments ?? []);
1108
+ const [busy, setBusy] = (0, import_react6.useState)(false);
1109
+ const [error, setError] = (0, import_react6.useState)("");
1110
+ const [confirming, setConfirming] = (0, import_react6.useState)(false);
1111
+ const [draftId, setDraftId] = (0, import_react6.useState)(initial.draftId);
1112
+ const [savedAt, setSavedAt] = (0, import_react6.useState)(null);
1113
+ const [dirty, setDirty] = (0, import_react6.useState)(0);
1114
+ const [translating, setTranslating] = (0, import_react6.useState)(null);
1115
+ const [pendingLang, setPendingLang] = (0, import_react6.useState)(null);
1116
+ const [versions, setVersions] = (0, import_react6.useState)([{ html: initial.html, label: "\u539F\u59CB" }]);
1117
+ const [versionIndex, setVersionIndex] = (0, import_react6.useState)(0);
1118
+ const [aiBusy, setAiBusy] = (0, import_react6.useState)(null);
1119
+ const [aiDraftOpen, setAiDraftOpen] = (0, import_react6.useState)(false);
1120
+ const [aiInstruction, setAiInstruction] = (0, import_react6.useState)("");
1121
+ const [hasBody, setHasBody] = (0, import_react6.useState)(initial.html.trim() !== "");
1122
+ const editor = (0, import_react6.useRef)(null);
1123
+ const closing = (0, import_react6.useRef)(false);
1124
+ (0, import_react6.useEffect)(() => {
667
1125
  if (editor.current !== null) editor.current.innerHTML = initial.html;
1126
+ setHasBody((editor.current?.innerHTML ?? "").trim() !== "");
668
1127
  }, []);
669
1128
  const html = () => editor.current?.innerHTML ?? "";
670
1129
  const text = () => editor.current?.innerText ?? "";
671
- const exec = (command, value) => {
672
- editor.current?.focus();
673
- document.execCommand(command, false, value);
1130
+ const markDirty = () => {
1131
+ setDirty((value) => value + 1);
1132
+ setHasBody((editor.current?.innerHTML ?? "").trim() !== "");
1133
+ };
1134
+ const saveNow = async () => {
1135
+ if (to.trim() === "" && subject.trim() === "" && html().trim() === "") return false;
1136
+ try {
1137
+ const { draft } = await api.saveDraft(account.id, {
1138
+ ...draftId === void 0 ? {} : { id: draftId },
1139
+ to,
1140
+ cc,
1141
+ bcc,
1142
+ subject,
1143
+ html: html(),
1144
+ attachments,
1145
+ ...initial.inReplyTo === void 0 ? {} : { inReplyTo: initial.inReplyTo },
1146
+ ...initial.references === void 0 ? {} : { references: initial.references }
1147
+ });
1148
+ setDraftId(draft.id);
1149
+ setSavedAt(/* @__PURE__ */ new Date());
1150
+ setDirty(0);
1151
+ return true;
1152
+ } catch {
1153
+ return false;
1154
+ }
1155
+ };
1156
+ (0, import_react6.useEffect)(() => {
1157
+ if (dirty === 0) return;
1158
+ const timer = window.setTimeout(() => {
1159
+ void saveNow();
1160
+ }, 2e3);
1161
+ return () => {
1162
+ window.clearTimeout(timer);
1163
+ };
1164
+ }, [dirty, to, cc, bcc, subject, attachments]);
1165
+ const close = () => {
1166
+ if (closing.current) return;
1167
+ closing.current = true;
1168
+ void saveNow().finally(() => {
1169
+ setDirty(0);
1170
+ closing.current = false;
1171
+ onClose();
1172
+ });
1173
+ };
1174
+ const pushVersion = (nextHtml, label) => {
1175
+ const base = versions.slice(0, versionIndex + 1);
1176
+ if (base[base.length - 1]?.html !== html()) base.push({ html: html(), label: "\u624B\u52A8" });
1177
+ base.push({ html: nextHtml, label });
1178
+ setVersions(base.slice(-10));
1179
+ setVersionIndex(Math.min(base.length - 1, 9));
1180
+ if (editor.current !== null) editor.current.innerHTML = nextHtml;
1181
+ setHasBody(nextHtml.trim() !== "");
1182
+ markDirty();
1183
+ };
1184
+ const restoreVersion = (index) => {
1185
+ const version = versions[index];
1186
+ if (version === void 0 || editor.current === null) return;
1187
+ editor.current.innerHTML = version.html;
1188
+ setVersionIndex(index);
1189
+ setHasBody(version.html.trim() !== "");
1190
+ markDirty();
1191
+ };
1192
+ const runAi = async (mode) => {
1193
+ setAiBusy(mode);
1194
+ setError("");
1195
+ try {
1196
+ const result = await api.aiCompose({
1197
+ accountId: account.id,
1198
+ mode,
1199
+ instruction: aiInstruction,
1200
+ html: html(),
1201
+ language: "zh"
1202
+ });
1203
+ pushVersion(result.html, mode === "draft" ? "AI \u8D77\u8349" : mode === "polish" ? "AI \u6DA6\u8272" : "AI \u7F29\u77ED");
1204
+ setAiDraftOpen(false);
1205
+ setAiInstruction("");
1206
+ } catch (caught) {
1207
+ setError(errorText3(caught));
1208
+ } finally {
1209
+ setAiBusy(null);
1210
+ }
674
1211
  };
675
1212
  const translate = async (target) => {
676
1213
  if (editor.current === null) return;
@@ -683,13 +1220,18 @@ function Composer({
683
1220
  setError("");
684
1221
  try {
685
1222
  const result = await api.aiTranslate({ accountId: account.id, html: html(), target });
686
- editor.current.innerHTML = result.html;
1223
+ pushVersion(result.html, target === "en" ? "\u7FFB\u8BD1 EN" : "\u7FFB\u8BD1 \u4E2D");
687
1224
  } catch (caught) {
688
- setError(errorText2(caught));
1225
+ setError(errorText3(caught));
689
1226
  } finally {
690
1227
  setTranslating(null);
691
1228
  }
692
1229
  };
1230
+ const exec = (command, value) => {
1231
+ editor.current?.focus();
1232
+ document.execCommand(command, false, value);
1233
+ markDirty();
1234
+ };
693
1235
  const addFiles = async (files) => {
694
1236
  if (files === null || files.length === 0) return;
695
1237
  setError("");
@@ -705,29 +1247,15 @@ function Composer({
705
1247
  total += file.size;
706
1248
  }
707
1249
  setAttachments(next);
1250
+ markDirty();
708
1251
  };
709
1252
  const saveDraft = async () => {
710
1253
  setBusy(true);
711
1254
  setError("");
712
- try {
713
- const { draft } = await api.saveDraft(account.id, {
714
- ...draftId === void 0 ? {} : { id: draftId },
715
- to,
716
- cc,
717
- bcc,
718
- subject,
719
- html: html(),
720
- attachments,
721
- ...initial.inReplyTo === void 0 ? {} : { inReplyTo: initial.inReplyTo },
722
- ...initial.references === void 0 ? {} : { references: initial.references }
723
- });
724
- setDraftId(draft.id);
725
- onSent("\u8349\u7A3F\u5DF2\u4FDD\u5B58");
726
- } catch (caught) {
727
- setError(errorText2(caught));
728
- } finally {
729
- setBusy(false);
730
- }
1255
+ const ok = await saveNow();
1256
+ setBusy(false);
1257
+ if (ok) onSent("\u8349\u7A3F\u5DF2\u4FDD\u5B58");
1258
+ else setError("\u8349\u7A3F\u4FDD\u5B58\u5931\u8D25");
731
1259
  };
732
1260
  const send = async () => {
733
1261
  setBusy(true);
@@ -753,35 +1281,36 @@ function Composer({
753
1281
  onSent("\u90AE\u4EF6\u5DF2\u53D1\u9001");
754
1282
  onClose();
755
1283
  } catch (caught) {
756
- setError(errorText2(caught));
1284
+ setError(errorText3(caught));
757
1285
  } finally {
758
1286
  setBusy(false);
759
1287
  setConfirming(false);
760
1288
  }
761
1289
  };
762
- return (0, import_react4.createElement)(
1290
+ return (0, import_react6.createElement)(
763
1291
  Modal,
764
1292
  {
765
1293
  title: initial.mode === "reply" ? "\u56DE\u590D" : initial.mode === "replyAll" ? "\u5168\u90E8\u56DE\u590D" : initial.mode === "forward" ? "\u8F6C\u53D1" : "\u5199\u90AE\u4EF6",
766
- onClose,
1294
+ onClose: close,
767
1295
  wide: true,
768
- footer: (0, import_react4.createElement)(
1296
+ footer: (0, import_react6.createElement)(
769
1297
  "div",
770
- { style: { display: "flex", gap: "8px", alignItems: "center", width: "100%" } },
771
- (0, import_react4.createElement)("span", { className: "small muted" }, `\u53D1\u4EF6\u4EBA ${account.email}`),
772
- (0, import_react4.createElement)("span", { className: "spacer" }),
773
- (0, import_react4.createElement)("button", { className: "ghost", onClick: () => void saveDraft(), disabled: busy }, "\u5B58\u8349\u7A3F"),
774
- confirming ? (0, import_react4.createElement)(
1298
+ { className: "mailharbor-compose-foot" },
1299
+ (0, import_react6.createElement)("span", { className: "small muted" }, `\u53D1\u4EF6\u4EBA ${account.email}`),
1300
+ savedAt === null ? null : (0, import_react6.createElement)("span", { className: "small muted" }, `\u5DF2\u81EA\u52A8\u4FDD\u5B58 ${savedAt.toLocaleTimeString()}`),
1301
+ (0, import_react6.createElement)("span", { className: "spacer" }),
1302
+ (0, import_react6.createElement)("button", { className: "ghost", onClick: () => void saveDraft(), disabled: busy }, "\u5B58\u8349\u7A3F"),
1303
+ confirming ? (0, import_react6.createElement)(
775
1304
  "span",
776
1305
  { style: { display: "inline-flex", gap: "6px", alignItems: "center" } },
777
- (0, import_react4.createElement)("span", { className: "small" }, "\u786E\u5B9A\u53D1\u9001\uFF1F"),
778
- (0, import_react4.createElement)(
1306
+ (0, import_react6.createElement)("span", { className: "small" }, "\u786E\u5B9A\u53D1\u9001\uFF1F"),
1307
+ (0, import_react6.createElement)(
779
1308
  "button",
780
1309
  { className: "primary", onClick: () => void send(), disabled: busy },
781
1310
  busy ? "\u53D1\u9001\u4E2D\u2026" : "\u786E\u5B9A"
782
1311
  ),
783
- (0, import_react4.createElement)("button", { className: "ghost", onClick: () => setConfirming(false) }, "\u53D6\u6D88")
784
- ) : (0, import_react4.createElement)(
1312
+ (0, import_react6.createElement)("button", { className: "ghost", onClick: () => setConfirming(false) }, "\u53D6\u6D88")
1313
+ ) : (0, import_react6.createElement)(
785
1314
  "button",
786
1315
  {
787
1316
  className: "primary",
@@ -792,62 +1321,75 @@ function Composer({
792
1321
  )
793
1322
  )
794
1323
  },
795
- error === "" ? null : (0, import_react4.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
796
- (0, import_react4.createElement)(
1324
+ error === "" ? null : (0, import_react6.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
1325
+ (0, import_react6.createElement)(
797
1326
  "div",
798
1327
  { className: "mailharbor-grid" },
799
- (0, import_react4.createElement)(
1328
+ (0, import_react6.createElement)(
800
1329
  Field,
801
1330
  { label: "\u6536\u4EF6\u4EBA" },
802
- (0, import_react4.createElement)("input", {
1331
+ (0, import_react6.createElement)("input", {
803
1332
  value: to,
804
- onChange: (event) => setTo(event.target.value),
1333
+ onChange: (event) => {
1334
+ setTo(event.target.value);
1335
+ markDirty();
1336
+ },
805
1337
  placeholder: "a@example.com, b@example.com"
806
1338
  })
807
1339
  ),
808
- (0, import_react4.createElement)(
1340
+ (0, import_react6.createElement)(
809
1341
  Field,
810
1342
  { label: "\u4E3B\u9898" },
811
- (0, import_react4.createElement)("input", {
1343
+ (0, import_react6.createElement)("input", {
812
1344
  value: subject,
813
- onChange: (event) => setSubject(event.target.value)
1345
+ onChange: (event) => {
1346
+ setSubject(event.target.value);
1347
+ markDirty();
1348
+ },
1349
+ placeholder: "\u90AE\u4EF6\u4E3B\u9898"
814
1350
  })
815
1351
  )
816
1352
  ),
817
- showCc ? (0, import_react4.createElement)(
1353
+ showCc ? (0, import_react6.createElement)(
818
1354
  "div",
819
1355
  { className: "mailharbor-grid" },
820
- (0, import_react4.createElement)(
1356
+ (0, import_react6.createElement)(
821
1357
  Field,
822
1358
  { label: "\u6284\u9001" },
823
- (0, import_react4.createElement)("input", {
1359
+ (0, import_react6.createElement)("input", {
824
1360
  value: cc,
825
- onChange: (event) => setCc(event.target.value)
1361
+ onChange: (event) => {
1362
+ setCc(event.target.value);
1363
+ markDirty();
1364
+ }
826
1365
  })
827
1366
  ),
828
- (0, import_react4.createElement)(
1367
+ (0, import_react6.createElement)(
829
1368
  Field,
830
1369
  { label: "\u5BC6\u9001" },
831
- (0, import_react4.createElement)("input", {
1370
+ (0, import_react6.createElement)("input", {
832
1371
  value: bcc,
833
- onChange: (event) => setBcc(event.target.value)
1372
+ onChange: (event) => {
1373
+ setBcc(event.target.value);
1374
+ markDirty();
1375
+ }
834
1376
  })
835
1377
  )
836
- ) : (0, import_react4.createElement)(
1378
+ ) : (0, import_react6.createElement)(
837
1379
  "button",
838
1380
  { className: "subtle small", style: { alignSelf: "flex-start" }, onClick: () => setShowCc(true) },
839
1381
  "\u6DFB\u52A0\u6284\u9001 / \u5BC6\u9001"
840
1382
  ),
841
- (0, import_react4.createElement)(
1383
+ (0, import_react6.createElement)(
842
1384
  "div",
843
1385
  { className: "mailharbor-toolbar" },
844
- (0, import_react4.createElement)("button", { onClick: () => exec("bold"), title: "\u52A0\u7C97" }, "B"),
845
- (0, import_react4.createElement)("button", { onClick: () => exec("italic"), title: "\u659C\u4F53" }, "I"),
846
- (0, import_react4.createElement)("button", { onClick: () => exec("underline"), title: "\u4E0B\u5212\u7EBF" }, "U"),
847
- (0, import_react4.createElement)("button", { onClick: () => exec("insertUnorderedList"), title: "\u65E0\u5E8F\u5217\u8868" }, "\u2022 \u5217\u8868"),
848
- (0, import_react4.createElement)("button", { onClick: () => exec("insertOrderedList"), title: "\u6709\u5E8F\u5217\u8868" }, "1. \u5217\u8868"),
849
- (0, import_react4.createElement)("button", { onClick: () => exec("formatBlock", "blockquote"), title: "\u5F15\u7528" }, "\u5F15\u7528"),
850
- (0, import_react4.createElement)(
1386
+ (0, import_react6.createElement)("button", { onClick: () => exec("bold"), title: "\u52A0\u7C97" }, "B"),
1387
+ (0, import_react6.createElement)("button", { onClick: () => exec("italic"), title: "\u659C\u4F53" }, "I"),
1388
+ (0, import_react6.createElement)("button", { onClick: () => exec("underline"), title: "\u4E0B\u5212\u7EBF" }, "U"),
1389
+ (0, import_react6.createElement)("button", { onClick: () => exec("insertUnorderedList"), title: "\u65E0\u5E8F\u5217\u8868" }, "\u2022 \u5217\u8868"),
1390
+ (0, import_react6.createElement)("button", { onClick: () => exec("insertOrderedList"), title: "\u6709\u5E8F\u5217\u8868" }, "1. \u5217\u8868"),
1391
+ (0, import_react6.createElement)("button", { onClick: () => exec("formatBlock", "blockquote"), title: "\u5F15\u7528" }, "\u5F15\u7528"),
1392
+ (0, import_react6.createElement)(
851
1393
  "button",
852
1394
  {
853
1395
  onClick: () => {
@@ -858,23 +1400,44 @@ function Composer({
858
1400
  },
859
1401
  "\u94FE\u63A5"
860
1402
  ),
861
- (0, import_react4.createElement)("button", { onClick: () => exec("removeFormat"), title: "\u6E05\u9664\u683C\u5F0F" }, "\u6E05\u9664"),
862
- (0, import_react4.createElement)("span", { className: "spacer" }),
863
- (0, import_react4.createElement)(
1403
+ (0, import_react6.createElement)("button", { onClick: () => exec("removeFormat"), title: "\u6E05\u9664\u683C\u5F0F" }, "\u6E05\u9664"),
1404
+ (0, import_react6.createElement)("span", { className: "spacer" }),
1405
+ (0, import_react6.createElement)(
1406
+ "button",
1407
+ {
1408
+ className: "ai",
1409
+ onClick: () => setAiDraftOpen(!aiDraftOpen),
1410
+ disabled: aiBusy !== null,
1411
+ title: "\u8BA9 AI \u8D77\u8349\u6B63\u6587"
1412
+ },
1413
+ (0, import_react6.createElement)(Icon, { name: "sparkles", size: 13 }),
1414
+ "\u8D77\u8349"
1415
+ ),
1416
+ (0, import_react6.createElement)(
1417
+ "button",
1418
+ { className: "ai", onClick: () => void runAi("polish"), disabled: aiBusy !== null || !hasBody },
1419
+ aiBusy === "polish" ? "\u6DA6\u8272\u4E2D\u2026" : "\u6DA6\u8272"
1420
+ ),
1421
+ (0, import_react6.createElement)(
1422
+ "button",
1423
+ { className: "ai", onClick: () => void runAi("shorten"), disabled: aiBusy !== null || !hasBody },
1424
+ aiBusy === "shorten" ? "\u7F29\u77ED\u4E2D\u2026" : "\u7F29\u77ED"
1425
+ ),
1426
+ (0, import_react6.createElement)(
864
1427
  "button",
865
1428
  { onClick: () => setPendingLang("zh"), disabled: translating !== null, title: "AI \u7FFB\u8BD1\u6210\u4E2D\u6587" },
866
- translating === "zh" ? "\u7FFB\u8BD1\u4E2D\u2026" : "\u4E2D"
1429
+ translating === "zh" ? "\u2026" : "\u4E2D"
867
1430
  ),
868
- (0, import_react4.createElement)(
1431
+ (0, import_react6.createElement)(
869
1432
  "button",
870
1433
  { onClick: () => setPendingLang("en"), disabled: translating !== null, title: "AI \u7FFB\u8BD1\u6210\u82F1\u6587" },
871
1434
  translating === "en" ? "\u2026" : "EN"
872
1435
  ),
873
- (0, import_react4.createElement)(
1436
+ (0, import_react6.createElement)(
874
1437
  "label",
875
1438
  { className: "mailharbor-toggle small" },
876
1439
  "\u9644\u4EF6",
877
- (0, import_react4.createElement)("input", {
1440
+ (0, import_react6.createElement)("input", {
878
1441
  type: "file",
879
1442
  multiple: true,
880
1443
  style: { display: "none" },
@@ -885,101 +1448,92 @@ function Composer({
885
1448
  })
886
1449
  )
887
1450
  ),
888
- pendingLang === null ? null : (0, import_react4.createElement)(
1451
+ aiDraftOpen ? (0, import_react6.createElement)(
1452
+ "div",
1453
+ { className: "mailharbor-confirm" },
1454
+ (0, import_react6.createElement)("input", {
1455
+ value: aiInstruction,
1456
+ placeholder: "\u60F3\u8BA9 AI \u5199\u4EC0\u4E48\uFF1F\u4F8B\u5982\uFF1A\u786E\u8BA4\u6536\u5230\uFF0C\u5468\u4E94\u524D\u7ED9\u7B54\u590D",
1457
+ onChange: (event) => setAiInstruction(event.target.value),
1458
+ onKeyDown: (event) => {
1459
+ if (event.key === "Enter") void runAi("draft");
1460
+ }
1461
+ }),
1462
+ (0, import_react6.createElement)(
1463
+ "button",
1464
+ { className: "primary", onClick: () => void runAi("draft"), disabled: aiBusy !== null },
1465
+ aiBusy === "draft" ? "\u751F\u6210\u4E2D\u2026" : "\u751F\u6210"
1466
+ ),
1467
+ (0, import_react6.createElement)("button", { className: "ghost", onClick: () => setAiDraftOpen(false) }, "\u53D6\u6D88")
1468
+ ) : null,
1469
+ pendingLang === null ? null : (0, import_react6.createElement)(
889
1470
  "div",
890
1471
  { className: "mailharbor-confirm" },
891
- (0, import_react4.createElement)("span", null, pendingLang === "en" ? "\u628A\u6B63\u6587\u7FFB\u8BD1\u6210\u82F1\u6587\uFF1F" : "\u628A\u6B63\u6587\u7FFB\u8BD1\u6210\u4E2D\u6587\uFF1F"),
892
- (0, import_react4.createElement)("span", { className: "spacer" }),
893
- (0, import_react4.createElement)("button", { className: "ghost", onClick: () => setPendingLang(null) }, "\u53D6\u6D88"),
894
- (0, import_react4.createElement)("button", { className: "primary", onClick: () => void translate(pendingLang) }, "\u7FFB\u8BD1")
1472
+ (0, import_react6.createElement)("span", null, pendingLang === "en" ? "\u628A\u6B63\u6587\u7FFB\u8BD1\u6210\u82F1\u6587\uFF1F" : "\u628A\u6B63\u6587\u7FFB\u8BD1\u6210\u4E2D\u6587\uFF1F"),
1473
+ (0, import_react6.createElement)("span", { className: "spacer" }),
1474
+ (0, import_react6.createElement)("button", { className: "ghost", onClick: () => setPendingLang(null) }, "\u53D6\u6D88"),
1475
+ (0, import_react6.createElement)("button", { className: "primary", onClick: () => void translate(pendingLang) }, "\u7FFB\u8BD1")
895
1476
  ),
896
- attachments.length === 0 ? null : (0, import_react4.createElement)(
1477
+ attachments.length === 0 ? null : (0, import_react6.createElement)(
897
1478
  "div",
898
1479
  { className: "mailharbor-attachments", style: { padding: 0, border: 0 } },
899
1480
  attachments.map(
900
- (file, index) => (0, import_react4.createElement)(
1481
+ (file, index) => (0, import_react6.createElement)(
901
1482
  "span",
902
1483
  { className: "mailharbor-attachment", key: `${file.filename}:${String(index)}` },
903
- (0, import_react4.createElement)("span", { className: "name" }, file.filename),
904
- (0, import_react4.createElement)(
1484
+ (0, import_react6.createElement)("span", { className: "name" }, file.filename),
1485
+ (0, import_react6.createElement)(
905
1486
  "button",
906
1487
  {
907
1488
  className: "subtle",
908
- onClick: () => setAttachments(attachments.filter((_item, position) => position !== index))
1489
+ onClick: () => {
1490
+ setAttachments(attachments.filter((_item, position) => position !== index));
1491
+ markDirty();
1492
+ }
909
1493
  },
910
1494
  "\u2715"
911
1495
  )
912
1496
  )
913
1497
  )
914
1498
  ),
915
- (0, import_react4.createElement)("div", {
1499
+ (0, import_react6.createElement)("div", {
916
1500
  className: "mailharbor-editor",
917
1501
  ref: editor,
918
1502
  contentEditable: true,
919
1503
  suppressContentEditableWarning: true,
920
1504
  "data-placeholder": "\u5199\u70B9\u4EC0\u4E48\u2026",
921
- spellCheck: true
922
- })
923
- );
924
- }
925
-
926
- // src/client/icons.tsx
927
- var import_react5 = require("react");
928
- var PATHS = {
929
- refresh: (0, import_react5.createElement)("path", { d: "M20 11a8 8 0 1 0-2.3 5.7M20 5v6h-6" }),
930
- plus: (0, import_react5.createElement)("path", { d: "M12 5v14M5 12h14" }),
931
- compose: (0, import_react5.createElement)("path", { d: "M4 20h4l10.5-10.5a2.1 2.1 0 0 0-3-3L5 17v3zM13.5 6.5l3 3" }),
932
- archive: (0, import_react5.createElement)(
933
- "g",
934
- null,
935
- (0, import_react5.createElement)("rect", { x: 3, y: 4, width: 18, height: 5, rx: 1.5 }),
936
- (0, import_react5.createElement)("path", { d: "M5 9v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V9M10 13h4" })
937
- ),
938
- paperclip: (0, import_react5.createElement)("path", {
939
- d: "M20 11.5 12 19.5a5 5 0 0 1-7-7l8-8a3.3 3.3 0 0 1 4.7 4.7l-8 8a1.7 1.7 0 0 1-2.4-2.4l7.3-7.3"
940
- }),
941
- mail: (0, import_react5.createElement)(
942
- "g",
943
- null,
944
- (0, import_react5.createElement)("rect", { x: 3, y: 5, width: 18, height: 14, rx: 2.5 }),
945
- (0, import_react5.createElement)("path", { d: "m4 7 7.1 5a1.6 1.6 0 0 0 1.8 0L20 7" })
946
- ),
947
- search: (0, import_react5.createElement)("path", { d: "M11 18a7 7 0 1 0 0-14 7 7 0 0 0 0 14zM20 20l-4-4" }),
948
- reply: (0, import_react5.createElement)("path", { d: "M9 7 4 12l5 5M4 12h9a6 6 0 0 1 6 6v1" }),
949
- replyAll: (0, import_react5.createElement)("path", { d: "m9 7-5 5 5 5M14 7l-5 5 5 5M13 12h5a5 5 0 0 1 5 5" }),
950
- forward: (0, import_react5.createElement)("path", { d: "m15 7 5 5-5 5M20 12h-9a6 6 0 0 0-6 6v1" }),
951
- check: (0, import_react5.createElement)("path", { d: "m5 13 4 4L19 7" }),
952
- image: (0, import_react5.createElement)(
953
- "g",
954
- null,
955
- (0, import_react5.createElement)("rect", { x: 3, y: 4, width: 18, height: 16, rx: 2 }),
956
- (0, import_react5.createElement)("circle", { cx: 8.5, cy: 9.5, r: 1.5 }),
957
- (0, import_react5.createElement)("path", { d: "m4 18 5-5 4 4 3-3 4 4" })
958
- ),
959
- close: (0, import_react5.createElement)("path", { d: "M6 6l12 12M18 6 6 18" }),
960
- chevronLeft: (0, import_react5.createElement)("path", { d: "m14 6-6 6 6 6" })
961
- };
962
- function Icon({ name, size = 16 }) {
963
- return (0, import_react5.createElement)(
964
- "svg",
965
- {
966
- width: size,
967
- height: size,
968
- viewBox: "0 0 24 24",
969
- fill: "none",
970
- stroke: "currentColor",
971
- strokeWidth: 1.7,
972
- strokeLinecap: "round",
973
- strokeLinejoin: "round",
974
- "aria-hidden": "true",
975
- focusable: "false"
976
- },
977
- PATHS[name] ?? null
1505
+ spellCheck: true,
1506
+ onInput: markDirty
1507
+ }),
1508
+ (0, import_react6.createElement)(
1509
+ "div",
1510
+ { className: "mailharbor-versions" },
1511
+ (0, import_react6.createElement)("span", { className: "small muted" }, "\u7248\u672C"),
1512
+ versions.map(
1513
+ (version, index) => (0, import_react6.createElement)(
1514
+ "button",
1515
+ {
1516
+ key: `${version.label}:${String(index)}`,
1517
+ className: index === versionIndex ? "chip active" : "chip",
1518
+ title: version.label,
1519
+ onClick: () => restoreVersion(index)
1520
+ },
1521
+ String(index + 1)
1522
+ )
1523
+ ),
1524
+ versions.length > 1 ? (0, import_react6.createElement)(
1525
+ "button",
1526
+ { className: "subtle small", onClick: () => restoreVersion(Math.max(0, versionIndex - 1)) },
1527
+ "\u6062\u590D\u4E0A\u4E00\u7248"
1528
+ ) : null,
1529
+ (0, import_react6.createElement)("span", { className: "spacer" }),
1530
+ (0, import_react6.createElement)("span", { className: "small muted" }, versions[versionIndex]?.label ?? "")
1531
+ )
978
1532
  );
979
1533
  }
980
1534
 
981
1535
  // src/client/reader.tsx
982
- var import_react6 = require("react");
1536
+ var import_react7 = require("react");
983
1537
  var IFRAME_STYLE = `
984
1538
  html, body { margin: 0; padding: 0; }
985
1539
  body {
@@ -1011,18 +1565,13 @@ function srcDoc(detail, allowRemoteImages) {
1011
1565
  function escapeHtml(value) {
1012
1566
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
1013
1567
  }
1014
- function textToHtml(text) {
1015
- return text.split(/\n{2,}/).map(
1016
- (paragraph) => `<p>${paragraph.trim().replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll("\n", "<br>")}</p>`
1017
- ).join("");
1018
- }
1019
1568
  function hasRemoteImages(detail) {
1020
1569
  return detail.html !== null && /<img[^>]+src=["']https?:/i.test(detail.html);
1021
1570
  }
1022
1571
  function addressLine(list) {
1023
1572
  return list.map((item) => item.name !== "" ? `${item.name} <${item.address}>` : item.address).join("\u3001");
1024
1573
  }
1025
- function errorText3(error) {
1574
+ function errorText4(error) {
1026
1575
  return error instanceof Error ? error.message : String(error);
1027
1576
  }
1028
1577
  function Reader({
@@ -1031,115 +1580,49 @@ function Reader({
1031
1580
  mailbox,
1032
1581
  onChanged,
1033
1582
  onReply,
1034
- onAiReply,
1583
+ onAiAction,
1035
1584
  onClose
1036
1585
  }) {
1037
- const [plain, setPlain] = (0, import_react6.useState)(false);
1038
- const [loadImages, setLoadImages] = (0, import_react6.useState)(false);
1039
- const [busy, setBusy] = (0, import_react6.useState)(false);
1040
- const [error, setError] = (0, import_react6.useState)("");
1041
- const [summaryOpen, setSummaryOpen] = (0, import_react6.useState)(false);
1042
- const [summaryBusy, setSummaryBusy] = (0, import_react6.useState)(false);
1043
- const [summaryError, setSummaryError] = (0, import_react6.useState)("");
1044
- const [summaryBlocks, setSummaryBlocks] = (0, import_react6.useState)([]);
1045
- const [replyOpen, setReplyOpen] = (0, import_react6.useState)(false);
1046
- const [replyBusy, setReplyBusy] = (0, import_react6.useState)(false);
1047
- const [replyError, setReplyError] = (0, import_react6.useState)("");
1048
- const [replyInstruction, setReplyInstruction] = (0, import_react6.useState)("");
1049
- const [replyLanguage, setReplyLanguage] = (0, import_react6.useState)("zh");
1050
- const [replyText, setReplyText] = (0, import_react6.useState)("");
1051
- const remoteImages = (0, import_react6.useMemo)(() => hasRemoteImages(detail), [detail]);
1586
+ const [plain, setPlain] = (0, import_react7.useState)(false);
1587
+ const [loadImages, setLoadImages] = (0, import_react7.useState)(false);
1588
+ const [busy, setBusy] = (0, import_react7.useState)(false);
1589
+ const [error, setError] = (0, import_react7.useState)("");
1590
+ const remoteImages = (0, import_react7.useMemo)(() => hasRemoteImages(detail), [detail]);
1052
1591
  const html = detail.html !== null && detail.html !== "";
1053
1592
  const toggleRead = async () => {
1054
- setBusy(true);
1055
- setError("");
1056
- try {
1057
- const result = await api.setRead({
1058
- id: detail.id,
1059
- accountId: detail.accountId,
1060
- source,
1061
- mailbox,
1062
- read: detail.unread
1063
- });
1064
- onChanged({ unread: !result.read });
1065
- } catch (caught) {
1066
- setError(errorText3(caught));
1067
- } finally {
1068
- setBusy(false);
1069
- }
1070
- };
1071
- const openSummary = async () => {
1072
- setSummaryOpen(true);
1073
- setSummaryBusy(true);
1074
- setSummaryError("");
1075
- setSummaryBlocks([]);
1076
- try {
1077
- const result = await api.aiSummary({
1078
- accountId: detail.accountId,
1079
- source,
1080
- id: detail.id,
1081
- mailbox
1082
- });
1083
- setSummaryBlocks(result.blocks);
1084
- } catch (caught) {
1085
- setSummaryError(errorText3(caught));
1086
- } finally {
1087
- setSummaryBusy(false);
1088
- }
1089
- };
1090
- const openReply = () => {
1091
- setReplyOpen(true);
1092
- setReplyText("");
1093
- setReplyError("");
1094
- setReplyInstruction("");
1095
- setReplyLanguage("zh");
1096
- };
1097
- const generateReply = async () => {
1098
- setReplyBusy(true);
1099
- setReplyError("");
1593
+ setBusy(true);
1594
+ setError("");
1100
1595
  try {
1101
- const result = await api.aiReply({
1596
+ const result = await api.setRead({
1597
+ id: detail.id,
1102
1598
  accountId: detail.accountId,
1103
1599
  source,
1104
- id: detail.id,
1105
1600
  mailbox,
1106
- instruction: replyInstruction,
1107
- language: replyLanguage
1601
+ read: detail.unread
1108
1602
  });
1109
- setReplyText(result.text);
1603
+ onChanged({ unread: !result.read });
1110
1604
  } catch (caught) {
1111
- setReplyError(errorText3(caught));
1605
+ setError(errorText4(caught));
1112
1606
  } finally {
1113
- setReplyBusy(false);
1607
+ setBusy(false);
1114
1608
  }
1115
1609
  };
1116
- const insertReply = () => {
1117
- const subject = /^re:/i.test(detail.subject) ? detail.subject : `Re: ${detail.subject}`;
1118
- onAiReply({
1119
- to: detail.from.address,
1120
- cc: "",
1121
- subject,
1122
- html: textToHtml(replyText),
1123
- ...detail.messageId === "" ? {} : { inReplyTo: detail.messageId, references: [...detail.references, detail.messageId].join(" ") }
1124
- });
1125
- setReplyOpen(false);
1126
- };
1127
- return (0, import_react6.createElement)(
1610
+ return (0, import_react7.createElement)(
1128
1611
  "div",
1129
1612
  { className: "mailharbor-reader" },
1130
- (0, import_react6.createElement)(
1613
+ (0, import_react7.createElement)(
1131
1614
  "div",
1132
1615
  { className: "mailharbor-reader-head" },
1133
- (0, import_react6.createElement)(
1616
+ (0, import_react7.createElement)(
1134
1617
  "div",
1135
1618
  { className: "mailharbor-reader-title" },
1136
- onClose === void 0 ? null : (0, import_react6.createElement)(
1619
+ onClose === void 0 ? null : (0, import_react7.createElement)(
1137
1620
  "button",
1138
1621
  { className: "ghost icon", onClick: onClose, "aria-label": "\u8FD4\u56DE\u5217\u8868", title: "\u8FD4\u56DE\u5217\u8868" },
1139
- (0, import_react6.createElement)(Icon, { name: "chevronLeft" })
1622
+ (0, import_react7.createElement)(Icon, { name: "chevronLeft" })
1140
1623
  ),
1141
- (0, import_react6.createElement)("h2", { style: { flex: 1 } }, detail.subject),
1142
- (0, import_react6.createElement)(
1624
+ (0, import_react7.createElement)("h2", { style: { flex: 1 } }, detail.subject),
1625
+ (0, import_react7.createElement)(
1143
1626
  "button",
1144
1627
  {
1145
1628
  className: "ghost icon",
@@ -1148,82 +1631,88 @@ function Reader({
1148
1631
  title: detail.unread ? "\u6807\u4E3A\u5DF2\u8BFB" : "\u6807\u4E3A\u672A\u8BFB",
1149
1632
  "aria-label": detail.unread ? "\u6807\u4E3A\u5DF2\u8BFB" : "\u6807\u4E3A\u672A\u8BFB"
1150
1633
  },
1151
- (0, import_react6.createElement)(Icon, { name: "check" })
1634
+ (0, import_react7.createElement)(Icon, { name: "check" })
1152
1635
  )
1153
1636
  ),
1154
- (0, import_react6.createElement)(
1637
+ (0, import_react7.createElement)(
1155
1638
  "div",
1156
1639
  { className: "mailharbor-reader-meta" },
1157
- (0, import_react6.createElement)(
1640
+ (0, import_react7.createElement)(
1158
1641
  "span",
1159
1642
  { className: "who" },
1160
- (0, import_react6.createElement)(Avatar, { email: detail.from.address, size: 22 }),
1161
- (0, import_react6.createElement)("strong", null, detail.from.name !== "" ? detail.from.name : detail.from.address),
1162
- detail.from.name !== "" ? (0, import_react6.createElement)("span", null, `<${detail.from.address}>`) : null
1643
+ (0, import_react7.createElement)(Avatar, { email: detail.from.address, size: 22 }),
1644
+ (0, import_react7.createElement)("strong", null, detail.from.name !== "" ? detail.from.name : detail.from.address),
1645
+ detail.from.name !== "" ? (0, import_react7.createElement)("span", null, `<${detail.from.address}>`) : null
1163
1646
  ),
1164
- (0, import_react6.createElement)("span", null, longDate(detail.date)),
1165
- detail.backedUp === false ? (0, import_react6.createElement)("span", { className: "badge warn" }, "\u672A\u5907\u4EFD") : null
1647
+ (0, import_react7.createElement)("span", null, longDate(detail.date)),
1648
+ detail.backedUp === false ? (0, import_react7.createElement)("span", { className: "badge warn" }, "\u672A\u5907\u4EFD") : null
1166
1649
  ),
1167
- (0, import_react6.createElement)(
1650
+ (0, import_react7.createElement)(
1168
1651
  "div",
1169
1652
  { className: "mailharbor-reader-meta" },
1170
- (0, import_react6.createElement)("span", null, `\u6536\u4EF6\u4EBA: ${addressLine(detail.to) || "\u2014"}`),
1171
- detail.cc.length === 0 ? null : (0, import_react6.createElement)("span", null, `\u6284\u9001: ${addressLine(detail.cc)}`)
1653
+ (0, import_react7.createElement)("span", null, `\u6536\u4EF6\u4EBA: ${addressLine(detail.to) || "\u2014"}`),
1654
+ detail.cc.length === 0 ? null : (0, import_react7.createElement)("span", null, `\u6284\u9001: ${addressLine(detail.cc)}`)
1172
1655
  ),
1173
- (0, import_react6.createElement)(
1656
+ (0, import_react7.createElement)(
1174
1657
  "div",
1175
1658
  { className: "mailharbor-reader-actions" },
1176
- (0, import_react6.createElement)(
1659
+ (0, import_react7.createElement)(
1177
1660
  "button",
1178
1661
  { className: "ghost", onClick: () => onReply("reply") },
1179
- (0, import_react6.createElement)(Icon, { name: "reply" }),
1662
+ (0, import_react7.createElement)(Icon, { name: "reply" }),
1180
1663
  "\u56DE\u590D"
1181
1664
  ),
1182
- (0, import_react6.createElement)(
1665
+ (0, import_react7.createElement)(
1183
1666
  "button",
1184
1667
  { className: "ghost", onClick: () => onReply("replyAll") },
1185
- (0, import_react6.createElement)(Icon, { name: "replyAll" }),
1668
+ (0, import_react7.createElement)(Icon, { name: "replyAll" }),
1186
1669
  "\u5168\u90E8\u56DE\u590D"
1187
1670
  ),
1188
- (0, import_react6.createElement)(
1671
+ (0, import_react7.createElement)(
1189
1672
  "button",
1190
1673
  { className: "ghost", onClick: () => onReply("forward") },
1191
- (0, import_react6.createElement)(Icon, { name: "forward" }),
1674
+ (0, import_react7.createElement)(Icon, { name: "forward" }),
1192
1675
  "\u8F6C\u53D1"
1193
1676
  ),
1194
- (0, import_react6.createElement)("span", { className: "mailharbor-divider" }),
1195
- (0, import_react6.createElement)("button", { className: "ghost", onClick: () => void openSummary() }, "AI \u6458\u8981"),
1196
- (0, import_react6.createElement)("button", { className: "ghost", onClick: openReply }, "AI \u56DE\u590D"),
1197
- (0, import_react6.createElement)("span", { className: "spacer" }),
1198
- html ? (0, import_react6.createElement)(
1677
+ (0, import_react7.createElement)("span", { className: "mailharbor-divider" }),
1678
+ (0, import_react7.createElement)(
1679
+ "button",
1680
+ { className: "ghost", onClick: () => onAiAction("summary") },
1681
+ (0, import_react7.createElement)(Icon, { name: "sparkles", size: 14 }),
1682
+ "AI \u6458\u8981"
1683
+ ),
1684
+ (0, import_react7.createElement)("button", { className: "ghost", onClick: () => onAiAction("reply") }, "AI \u56DE\u590D"),
1685
+ (0, import_react7.createElement)("button", { className: "ghost", onClick: () => onAiAction("translate") }, "AI \u7FFB\u8BD1"),
1686
+ (0, import_react7.createElement)("span", { className: "spacer" }),
1687
+ html ? (0, import_react7.createElement)(
1199
1688
  "button",
1200
1689
  { className: "subtle", onClick: () => setPlain(!plain) },
1201
1690
  plain ? "\u663E\u793A HTML" : "\u7EAF\u6587\u672C"
1202
1691
  ) : null,
1203
- remoteImages && !loadImages ? (0, import_react6.createElement)(
1692
+ remoteImages && !loadImages ? (0, import_react7.createElement)(
1204
1693
  "button",
1205
1694
  { className: "subtle", onClick: () => setLoadImages(true) },
1206
- (0, import_react6.createElement)(Icon, { name: "image", size: 14 }),
1695
+ (0, import_react7.createElement)(Icon, { name: "image", size: 14 }),
1207
1696
  "\u663E\u793A\u56FE\u7247"
1208
1697
  ) : null
1209
1698
  ),
1210
- error === "" ? null : (0, import_react6.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error)
1699
+ error === "" ? null : (0, import_react7.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error)
1211
1700
  ),
1212
- (0, import_react6.createElement)(
1701
+ (0, import_react7.createElement)(
1213
1702
  "div",
1214
1703
  { className: "mailharbor-reader-body" },
1215
- html && !plain ? (0, import_react6.createElement)("iframe", {
1704
+ html && !plain ? (0, import_react7.createElement)("iframe", {
1216
1705
  title: "\u90AE\u4EF6\u6B63\u6587",
1217
1706
  sandbox: "",
1218
1707
  srcDoc: srcDoc(detail, loadImages),
1219
1708
  referrerPolicy: "no-referrer"
1220
- }) : (0, import_react6.createElement)("pre", null, detail.text !== "" ? detail.text : "\uFF08\u65E0\u6B63\u6587\uFF09")
1709
+ }) : (0, import_react7.createElement)("pre", null, detail.text !== "" ? detail.text : "\uFF08\u65E0\u6B63\u6587\uFF09")
1221
1710
  ),
1222
- detail.attachments.length === 0 ? null : (0, import_react6.createElement)(
1711
+ detail.attachments.length === 0 ? null : (0, import_react7.createElement)(
1223
1712
  "div",
1224
1713
  { className: "mailharbor-attachments" },
1225
1714
  detail.attachments.map(
1226
- (file) => (0, import_react6.createElement)(
1715
+ (file) => (0, import_react7.createElement)(
1227
1716
  "a",
1228
1717
  {
1229
1718
  className: "mailharbor-attachment",
@@ -1243,132 +1732,19 @@ function Reader({
1243
1732
  download: file.filename,
1244
1733
  title: file.filename
1245
1734
  },
1246
- (0, import_react6.createElement)(Icon, { name: "paperclip", size: 14 }),
1247
- (0, import_react6.createElement)("span", { className: "name" }, file.filename),
1248
- (0, import_react6.createElement)("span", { className: "muted small" }, formatBytes(file.size))
1249
- )
1250
- )
1251
- ),
1252
- summaryOpen ? (0, import_react6.createElement)(
1253
- Modal,
1254
- {
1255
- title: "AI \u6458\u8981",
1256
- onClose: () => setSummaryOpen(false),
1257
- footer: (0, import_react6.createElement)(
1258
- "div",
1259
- { style: { display: "flex", gap: "8px", width: "100%", justifyContent: "flex-end" } },
1260
- (0, import_react6.createElement)("button", { className: "ghost", onClick: () => setSummaryOpen(false) }, "\u5173\u95ED"),
1261
- summaryBlocks.length === 0 ? null : (0, import_react6.createElement)(
1262
- "button",
1263
- {
1264
- className: "primary",
1265
- onClick: () => {
1266
- void navigator.clipboard?.writeText(
1267
- summaryBlocks.map((block) => `${block.title}
1268
- ${block.body}`).join("\n\n")
1269
- ).then(() => setSummaryOpen(false));
1270
- }
1271
- },
1272
- "\u590D\u5236"
1273
- )
1274
- )
1275
- },
1276
- summaryBusy ? "\u6B63\u5728\u9605\u8BFB\u5E76\u603B\u7ED3\u2026" : null,
1277
- summaryError === "" ? null : (0, import_react6.createElement)("div", { style: { color: "var(--m-danger)" } }, summaryError),
1278
- summaryBlocks.length === 0 ? null : (0, import_react6.createElement)(
1279
- "div",
1280
- { className: "mailharbor-summary" },
1281
- summaryBlocks.map(
1282
- (block, index) => (0, import_react6.createElement)(
1283
- "section",
1284
- { key: `${block.title}:${String(index)}` },
1285
- block.title === "" ? null : (0, import_react6.createElement)("h4", null, block.title),
1286
- (0, import_react6.createElement)("p", null, block.body)
1287
- )
1735
+ (0, import_react7.createElement)(Icon, { name: "paperclip", size: 14 }),
1736
+ (0, import_react7.createElement)("span", { className: "name" }, file.filename),
1737
+ (0, import_react7.createElement)("span", { className: "muted small" }, formatBytes(file.size))
1288
1738
  )
1289
1739
  )
1290
- ) : null,
1291
- replyOpen ? (0, import_react6.createElement)(
1292
- Modal,
1293
- {
1294
- title: "AI \u56DE\u590D",
1295
- onClose: () => setReplyOpen(false),
1296
- footer: (0, import_react6.createElement)(
1297
- "div",
1298
- { style: { display: "flex", gap: "8px", width: "100%", alignItems: "center" } },
1299
- (0, import_react6.createElement)("span", { className: "small muted" }, "\u751F\u6210\u540E\u53EF\u5728\u6B63\u6587\u91CC\u7EE7\u7EED\u4FEE\u6539\uFF0C\u53D1\u9001\u4ECD\u9700\u4F60\u786E\u8BA4"),
1300
- (0, import_react6.createElement)("span", { className: "spacer" }),
1301
- (0, import_react6.createElement)("button", { className: "ghost", onClick: () => setReplyOpen(false) }, "\u53D6\u6D88"),
1302
- (0, import_react6.createElement)(
1303
- "button",
1304
- { className: "ghost", onClick: () => void generateReply(), disabled: replyBusy },
1305
- replyBusy ? "\u751F\u6210\u4E2D\u2026" : replyText === "" ? "\u751F\u6210\u56DE\u590D" : "\u91CD\u65B0\u751F\u6210"
1306
- ),
1307
- (0, import_react6.createElement)(
1308
- "button",
1309
- {
1310
- className: "primary",
1311
- onClick: insertReply,
1312
- disabled: replyBusy || replyText.trim() === ""
1313
- },
1314
- "\u63D2\u5165\u5230\u6B63\u6587"
1315
- )
1316
- )
1317
- },
1318
- (0, import_react6.createElement)(
1319
- "div",
1320
- { className: "mailharbor-grid" },
1321
- (0, import_react6.createElement)(
1322
- "div",
1323
- { className: "mailharbor-field" },
1324
- (0, import_react6.createElement)("label", null, "\u60F3\u8BA9 AI \u600E\u4E48\u5199\uFF08\u53EF\u7559\u7A7A\uFF09"),
1325
- (0, import_react6.createElement)("input", {
1326
- value: replyInstruction,
1327
- placeholder: "\u4F8B\u5982\uFF1A\u786E\u8BA4\u6536\u5230\uFF0C\u8BF4\u660E\u5468\u4E94\u524D\u7ED9\u7B54\u590D",
1328
- onChange: (event) => setReplyInstruction(event.target.value)
1329
- })
1330
- ),
1331
- (0, import_react6.createElement)(
1332
- "div",
1333
- { className: "mailharbor-field" },
1334
- (0, import_react6.createElement)("label", null, "\u8BED\u8A00"),
1335
- (0, import_react6.createElement)(
1336
- "div",
1337
- { className: "mailharbor-segment" },
1338
- (0, import_react6.createElement)(
1339
- "button",
1340
- {
1341
- className: replyLanguage === "zh" ? "active" : "",
1342
- onClick: () => setReplyLanguage("zh")
1343
- },
1344
- "\u4E2D\u6587"
1345
- ),
1346
- (0, import_react6.createElement)(
1347
- "button",
1348
- {
1349
- className: replyLanguage === "en" ? "active" : "",
1350
- onClick: () => setReplyLanguage("en")
1351
- },
1352
- "English"
1353
- )
1354
- )
1355
- )
1356
- ),
1357
- replyError === "" ? null : (0, import_react6.createElement)("div", { style: { color: "var(--m-danger)" } }, replyError),
1358
- (0, import_react6.createElement)("textarea", {
1359
- rows: 12,
1360
- value: replyText,
1361
- placeholder: replyBusy ? "\u6B63\u5728\u751F\u6210\u2026" : "\u70B9\u300C\u751F\u6210\u56DE\u590D\u300D\u8BA9 AI \u8D77\u8349\uFF0C\u7ED3\u679C\u4F1A\u663E\u793A\u5728\u8FD9\u91CC",
1362
- onChange: (event) => setReplyText(event.target.value)
1363
- })
1364
- ) : null
1740
+ )
1365
1741
  );
1366
1742
  }
1367
1743
 
1368
1744
  // src/client/panel.tsx
1369
1745
  var EMPTY_PAGE = { items: [], total: 0, offset: 0, limit: 40 };
1370
1746
  var DRAFTS = "__drafts";
1371
- function errorText4(error) {
1747
+ function errorText5(error) {
1372
1748
  return error instanceof ApiError || error instanceof Error ? error.message : String(error);
1373
1749
  }
1374
1750
  function matchesNarrow() {
@@ -1379,41 +1755,43 @@ function quoteForReply(detail) {
1379
1755
  return `<p><br></p><p>------------------ \u539F\u59CB\u90AE\u4EF6 ------------------</p><blockquote style="margin:0 0 0 12px;padding-left:12px;border-left:2px solid #d7dbe3;color:#565c67">${body}</blockquote>`;
1380
1756
  }
1381
1757
  function MailPanel() {
1382
- const [state, setState] = (0, import_react7.useState)(null);
1383
- const [error, setError] = (0, import_react7.useState)("");
1384
- const [accountScope, setAccountScope] = (0, import_react7.useState)(null);
1385
- const [source, setSource] = (0, import_react7.useState)("local");
1386
- const [mailbox, setMailbox] = (0, import_react7.useState)("all");
1387
- const [folders, setFolders] = (0, import_react7.useState)([]);
1388
- const [query2, setQuery] = (0, import_react7.useState)("");
1389
- const [search, setSearch] = (0, import_react7.useState)("");
1390
- const [page, setPage] = (0, import_react7.useState)(EMPTY_PAGE);
1391
- const [drafts, setDrafts] = (0, import_react7.useState)([]);
1392
- const [limit, setLimit] = (0, import_react7.useState)(40);
1393
- const [loading, setLoading] = (0, import_react7.useState)(false);
1394
- const [selected, setSelected] = (0, import_react7.useState)(null);
1395
- const [compose, setCompose] = (0, import_react7.useState)(null);
1396
- const [showAdd, setShowAdd] = (0, import_react7.useState)(false);
1397
- const [showBackup, setShowBackup] = (0, import_react7.useState)(false);
1398
- const [backup, setBackup] = (0, import_react7.useState)(null);
1399
- const [toast, setToast] = (0, import_react7.useState)("");
1400
- const [narrow, setNarrow] = (0, import_react7.useState)(() => matchesNarrow());
1401
- const [tick, setTick] = (0, import_react7.useState)(0);
1758
+ const [state, setState] = (0, import_react8.useState)(null);
1759
+ const [error, setError] = (0, import_react8.useState)("");
1760
+ const [accountScope, setAccountScope] = (0, import_react8.useState)(null);
1761
+ const [source, setSource] = (0, import_react8.useState)("local");
1762
+ const [mailbox, setMailbox] = (0, import_react8.useState)("all");
1763
+ const [folders, setFolders] = (0, import_react8.useState)([]);
1764
+ const [query2, setQuery] = (0, import_react8.useState)("");
1765
+ const [search, setSearch] = (0, import_react8.useState)("");
1766
+ const [page, setPage] = (0, import_react8.useState)(EMPTY_PAGE);
1767
+ const [drafts, setDrafts] = (0, import_react8.useState)([]);
1768
+ const [limit, setLimit] = (0, import_react8.useState)(40);
1769
+ const [loading, setLoading] = (0, import_react8.useState)(false);
1770
+ const [selected, setSelected] = (0, import_react8.useState)(null);
1771
+ const [compose, setCompose] = (0, import_react8.useState)(null);
1772
+ const [showAdd, setShowAdd] = (0, import_react8.useState)(false);
1773
+ const [showBackup, setShowBackup] = (0, import_react8.useState)(false);
1774
+ const [backup, setBackup] = (0, import_react8.useState)(null);
1775
+ const [toast, setToast] = (0, import_react8.useState)("");
1776
+ const [narrow, setNarrow] = (0, import_react8.useState)(() => matchesNarrow());
1777
+ const [railOpen, setRailOpen] = (0, import_react8.useState)(false);
1778
+ const [aiRequest, setAiRequest] = (0, import_react8.useState)(null);
1779
+ const [tick, setTick] = (0, import_react8.useState)(0);
1402
1780
  const accounts = state?.accounts ?? [];
1403
1781
  const isAll = accountScope === "all";
1404
1782
  const account = accounts.find((item) => item.id === accountScope) ?? null;
1405
1783
  const composeAccount = accounts.find((item) => item.id === compose?.accountId) ?? account ?? accounts[0] ?? null;
1406
1784
  const showingDrafts = mailbox === DRAFTS && source === "local";
1407
- const refresh = (0, import_react7.useCallback)(() => {
1785
+ const refresh = (0, import_react8.useCallback)(() => {
1408
1786
  setTick((value) => value + 1);
1409
1787
  }, []);
1410
- const notice = (0, import_react7.useCallback)((message) => {
1788
+ const notice = (0, import_react8.useCallback)((message) => {
1411
1789
  setToast(message);
1412
1790
  window.setTimeout(() => {
1413
1791
  setToast("");
1414
1792
  }, 3200);
1415
1793
  }, []);
1416
- (0, import_react7.useEffect)(() => {
1794
+ (0, import_react8.useEffect)(() => {
1417
1795
  let cancelled = false;
1418
1796
  api.state().then((next) => {
1419
1797
  if (cancelled) return;
@@ -1421,13 +1799,13 @@ function MailPanel() {
1421
1799
  if (next.settings.render.listLimit > 0) setLimit(next.settings.render.listLimit);
1422
1800
  setAccountScope((current) => current ?? next.activeAccountId);
1423
1801
  }).catch((caught) => {
1424
- if (!cancelled) setError(errorText4(caught));
1802
+ if (!cancelled) setError(errorText5(caught));
1425
1803
  });
1426
1804
  return () => {
1427
1805
  cancelled = true;
1428
1806
  };
1429
1807
  }, [tick]);
1430
- (0, import_react7.useEffect)(() => {
1808
+ (0, import_react8.useEffect)(() => {
1431
1809
  if (accountScope === null) return;
1432
1810
  let cancelled = false;
1433
1811
  api.folders({ accountId: accountScope, source }).then((result) => {
@@ -1451,16 +1829,16 @@ function MailPanel() {
1451
1829
  cancelled = true;
1452
1830
  };
1453
1831
  }, [accountScope, source, tick]);
1454
- (0, import_react7.useEffect)(() => {
1832
+ (0, import_react8.useEffect)(() => {
1455
1833
  setMailbox(source === "local" ? "all" : "INBOX");
1456
1834
  setSelected(null);
1457
1835
  setLimit(state?.settings.render.listLimit ?? 40);
1458
1836
  }, [source]);
1459
- (0, import_react7.useEffect)(() => {
1837
+ (0, import_react8.useEffect)(() => {
1460
1838
  if (accountScope === "all" && source === "server") setSource("local");
1461
1839
  setSelected(null);
1462
1840
  }, [accountScope, source]);
1463
- (0, import_react7.useEffect)(() => {
1841
+ (0, import_react8.useEffect)(() => {
1464
1842
  if (accountScope === null) return;
1465
1843
  let cancelled = false;
1466
1844
  setLoading(true);
@@ -1492,7 +1870,7 @@ function MailPanel() {
1492
1870
  setDrafts([]);
1493
1871
  }
1494
1872
  }).catch((caught) => {
1495
- if (!cancelled) setError(errorText4(caught));
1873
+ if (!cancelled) setError(errorText5(caught));
1496
1874
  }).finally(() => {
1497
1875
  if (!cancelled) setLoading(false);
1498
1876
  });
@@ -1500,7 +1878,7 @@ function MailPanel() {
1500
1878
  cancelled = true;
1501
1879
  };
1502
1880
  }, [accountScope, source, mailbox, search, limit, tick]);
1503
- (0, import_react7.useEffect)(() => {
1881
+ (0, import_react8.useEffect)(() => {
1504
1882
  if (typeof window.matchMedia !== "function") return;
1505
1883
  const media = window.matchMedia("(max-width: 900px)");
1506
1884
  const onChange = () => setNarrow(media.matches);
@@ -1509,7 +1887,7 @@ function MailPanel() {
1509
1887
  media.removeEventListener("change", onChange);
1510
1888
  };
1511
1889
  }, []);
1512
- (0, import_react7.useEffect)(() => {
1890
+ (0, import_react8.useEffect)(() => {
1513
1891
  if (!showBackup || accountScope === null || isAll) return;
1514
1892
  let cancelled = false;
1515
1893
  const poll = async () => {
@@ -1547,7 +1925,7 @@ function MailPanel() {
1547
1925
  }
1548
1926
  }
1549
1927
  } catch (caught) {
1550
- setError(errorText4(caught));
1928
+ setError(errorText5(caught));
1551
1929
  }
1552
1930
  };
1553
1931
  const startCompose = (mode) => {
@@ -1586,6 +1964,43 @@ function MailPanel() {
1586
1964
  ...detail.messageId === "" ? {} : { inReplyTo: detail.messageId, references: [...detail.references, detail.messageId].join(" ") }
1587
1965
  });
1588
1966
  };
1967
+ const requestAi = (kind) => {
1968
+ setRailOpen(true);
1969
+ setAiRequest({ kind, nonce: Date.now() });
1970
+ };
1971
+ const insertReply = (init) => {
1972
+ if (selected === null) return;
1973
+ setCompose({
1974
+ mode: "reply",
1975
+ accountId: selected.accountId,
1976
+ to: init.to,
1977
+ cc: init.cc,
1978
+ bcc: "",
1979
+ subject: init.subject,
1980
+ html: init.html,
1981
+ ...init.inReplyTo === void 0 ? {} : { inReplyTo: init.inReplyTo },
1982
+ ...init.references === void 0 ? {} : { references: init.references }
1983
+ });
1984
+ };
1985
+ const saveReplyDraft = async (init) => {
1986
+ if (selected === null) return;
1987
+ try {
1988
+ await api.saveDraft(selected.accountId, {
1989
+ to: init.to,
1990
+ cc: init.cc,
1991
+ bcc: "",
1992
+ subject: init.subject,
1993
+ html: init.html,
1994
+ attachments: [],
1995
+ ...init.inReplyTo === void 0 ? {} : { inReplyTo: init.inReplyTo },
1996
+ ...init.references === void 0 ? {} : { references: init.references }
1997
+ });
1998
+ notice("\u5DF2\u5B58\u4E3A\u8349\u7A3F");
1999
+ refresh();
2000
+ } catch (caught) {
2001
+ setError(errorText5(caught));
2002
+ }
2003
+ };
1589
2004
  const openDraft = (draft) => {
1590
2005
  setCompose({
1591
2006
  mode: "new",
@@ -1606,7 +2021,7 @@ function MailPanel() {
1606
2021
  notice("\u8349\u7A3F\u5DF2\u5220\u9664");
1607
2022
  refresh();
1608
2023
  } catch (caught) {
1609
- setError(errorText4(caught));
2024
+ setError(errorText5(caught));
1610
2025
  }
1611
2026
  };
1612
2027
  const switchAccount = async (id) => {
@@ -1617,7 +2032,7 @@ function MailPanel() {
1617
2032
  if (id === "all") setSource("local");
1618
2033
  refresh();
1619
2034
  } catch (caught) {
1620
- setError(errorText4(caught));
2035
+ setError(errorText5(caught));
1621
2036
  }
1622
2037
  };
1623
2038
  const startBackup = async () => {
@@ -1628,7 +2043,7 @@ function MailPanel() {
1628
2043
  setBackup(result.status);
1629
2044
  setShowBackup(true);
1630
2045
  } catch (caught) {
1631
- setError(errorText4(caught));
2046
+ setError(errorText5(caught));
1632
2047
  }
1633
2048
  };
1634
2049
  const stopBackup = async () => {
@@ -1637,10 +2052,10 @@ function MailPanel() {
1637
2052
  const result = await api.stopBackup(accountScope);
1638
2053
  setBackup(result.status);
1639
2054
  } catch (caught) {
1640
- setError(errorText4(caught));
2055
+ setError(errorText5(caught));
1641
2056
  }
1642
2057
  };
1643
- const folderOptions = (0, import_react7.useMemo)(
2058
+ const folderOptions = (0, import_react8.useMemo)(
1644
2059
  () => folders.map((folder) => ({
1645
2060
  value: folder.name,
1646
2061
  label: `${folder.label ?? folder.name}${folder.count > 0 ? ` (${String(folder.count)})` : ""}`
@@ -1648,27 +2063,27 @@ function MailPanel() {
1648
2063
  [folders]
1649
2064
  );
1650
2065
  if (state === null) {
1651
- return (0, import_react7.createElement)(
2066
+ return (0, import_react8.createElement)(
1652
2067
  "div",
1653
2068
  { className: "mailharbor" },
1654
- (0, import_react7.createElement)("div", { className: "mailharbor-empty" }, error === "" ? "\u6B63\u5728\u52A0\u8F7D\u90AE\u7BB1\u2026" : error)
2069
+ (0, import_react8.createElement)("div", { className: "mailharbor-empty" }, error === "" ? "\u6B63\u5728\u52A0\u8F7D\u90AE\u7BB1\u2026" : error)
1655
2070
  );
1656
2071
  }
1657
2072
  if (accounts.length === 0) {
1658
- return (0, import_react7.createElement)(
2073
+ return (0, import_react8.createElement)(
1659
2074
  "div",
1660
2075
  { className: "mailharbor" },
1661
- (0, import_react7.createElement)(
2076
+ (0, import_react8.createElement)(
1662
2077
  "div",
1663
2078
  { className: "mailharbor-settings" },
1664
- (0, import_react7.createElement)("h2", null, "\u6B22\u8FCE\u4F7F\u7528 MailHarbor"),
1665
- (0, import_react7.createElement)(
2079
+ (0, import_react8.createElement)("h2", null, "\u6B22\u8FCE\u4F7F\u7528 MailHarbor"),
2080
+ (0, import_react8.createElement)(
1666
2081
  "p",
1667
2082
  { className: "muted small" },
1668
2083
  "\u628A\u4EFB\u610F IMAP/SMTP \u90AE\u7BB1\u63A5\u8FDB DeepSeek Harness\uFF1A\u672C\u5730\u5907\u4EFD\u3001\u5B9E\u65F6\u8BFB\u4FE1\u3001\u5199\u90AE\u4EF6\u4E0E Agent \u5DE5\u5177\u3002"
1669
2084
  ),
1670
- error === "" ? null : (0, import_react7.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
1671
- (0, import_react7.createElement)(AddAccountForm, {
2085
+ error === "" ? null : (0, import_react8.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
2086
+ (0, import_react8.createElement)(AddAccountForm, {
1672
2087
  onDone: (next) => {
1673
2088
  setState(next);
1674
2089
  setAccountScope(next.activeAccountId);
@@ -1682,38 +2097,38 @@ function MailPanel() {
1682
2097
  const loggedIn = isAll ? accounts.some((item) => item.id === state.activeAccountId) : state.loggedIn;
1683
2098
  const needsLogin = source === "server" && !isAll && state.loggedIn === false;
1684
2099
  const items = page.items;
1685
- return (0, import_react7.createElement)(
2100
+ return (0, import_react8.createElement)(
1686
2101
  "div",
1687
2102
  { className: narrow ? "mailharbor mailharbor-narrow" : "mailharbor" },
1688
- (0, import_react7.createElement)(
2103
+ (0, import_react8.createElement)(
1689
2104
  "div",
1690
2105
  { className: "mailharbor-topbar" },
1691
- (0, import_react7.createElement)(
2106
+ (0, import_react8.createElement)(
1692
2107
  "div",
1693
2108
  { className: "mailharbor-account" },
1694
- (0, import_react7.createElement)(Avatar, { email: isAll ? "all" : currentAccount?.email ?? "?", size: 26 }),
1695
- accounts.length > 1 ? (0, import_react7.createElement)(
2109
+ (0, import_react8.createElement)(Avatar, { email: isAll ? "all" : currentAccount?.email ?? "?", size: 26 }),
2110
+ accounts.length > 1 ? (0, import_react8.createElement)(
1696
2111
  "select",
1697
2112
  {
1698
2113
  value: accountScope ?? "",
1699
2114
  onChange: (event) => void switchAccount(event.target.value),
1700
2115
  "aria-label": "\u8D26\u53F7"
1701
2116
  },
1702
- (0, import_react7.createElement)("option", { key: "all", value: "all" }, "\u5168\u90E8\u8D26\u53F7"),
1703
- accounts.map((item) => (0, import_react7.createElement)("option", { key: item.id, value: item.id }, item.label))
1704
- ) : (0, import_react7.createElement)("span", { className: "name" }, currentAccount?.label ?? ""),
1705
- isAll ? (0, import_react7.createElement)("span", { className: "badge" }, `${String(accounts.length)} \u4E2A\u8D26\u53F7`) : null,
1706
- !isAll && state.loggedIn === false ? (0, import_react7.createElement)("span", { className: "badge warn" }, "\u672A\u767B\u5F55") : null
2117
+ (0, import_react8.createElement)("option", { key: "all", value: "all" }, "\u5168\u90E8\u8D26\u53F7"),
2118
+ accounts.map((item) => (0, import_react8.createElement)("option", { key: item.id, value: item.id }, item.label))
2119
+ ) : (0, import_react8.createElement)("span", { className: "name" }, currentAccount?.label ?? ""),
2120
+ isAll ? (0, import_react8.createElement)("span", { className: "badge" }, `${String(accounts.length)} \u4E2A\u8D26\u53F7`) : null,
2121
+ !isAll && state.loggedIn === false ? (0, import_react8.createElement)("span", { className: "badge warn" }, "\u672A\u767B\u5F55") : null
1707
2122
  ),
1708
- (0, import_react7.createElement)(
2123
+ (0, import_react8.createElement)(
1709
2124
  "div",
1710
2125
  { className: "mailharbor-segment", role: "tablist" },
1711
- (0, import_react7.createElement)(
2126
+ (0, import_react8.createElement)(
1712
2127
  "button",
1713
2128
  { className: source === "local" ? "active" : "", onClick: () => setSource("local"), role: "tab" },
1714
2129
  "\u672C\u5730\u5907\u4EFD"
1715
2130
  ),
1716
- (0, import_react7.createElement)(
2131
+ (0, import_react8.createElement)(
1717
2132
  "button",
1718
2133
  {
1719
2134
  className: source === "server" ? "active" : "",
@@ -1730,8 +2145,8 @@ function MailPanel() {
1730
2145
  "\u670D\u52A1\u5668\u5B9E\u65F6"
1731
2146
  )
1732
2147
  ),
1733
- (0, import_react7.createElement)("span", { className: "spacer" }),
1734
- (0, import_react7.createElement)(
2148
+ (0, import_react8.createElement)("span", { className: "spacer" }),
2149
+ (0, import_react8.createElement)(
1735
2150
  "button",
1736
2151
  {
1737
2152
  className: "ghost icon",
@@ -1739,46 +2154,56 @@ function MailPanel() {
1739
2154
  "aria-label": "\u6DFB\u52A0\u8D26\u53F7",
1740
2155
  onClick: () => setShowAdd(true)
1741
2156
  },
1742
- (0, import_react7.createElement)(Icon, { name: "plus" })
2157
+ (0, import_react8.createElement)(Icon, { name: "plus" })
1743
2158
  ),
1744
- (0, import_react7.createElement)(
2159
+ (0, import_react8.createElement)(
1745
2160
  "button",
1746
2161
  { className: "ghost icon", title: "\u5237\u65B0\u5217\u8868", "aria-label": "\u5237\u65B0\u5217\u8868", onClick: refresh },
1747
- (0, import_react7.createElement)(Icon, { name: "refresh" })
2162
+ (0, import_react8.createElement)(Icon, { name: "refresh" })
1748
2163
  ),
1749
- (0, import_react7.createElement)(
2164
+ (0, import_react8.createElement)(
1750
2165
  "button",
1751
2166
  {
1752
2167
  className: "ghost",
1753
2168
  onClick: () => void startBackup(),
1754
2169
  title: isAll ? "\u5907\u4EFD\u5F53\u524D\u6D3B\u52A8\u8D26\u53F7" : "\u5907\u4EFD\u8FD9\u4E2A\u8D26\u53F7"
1755
2170
  },
1756
- (0, import_react7.createElement)(Icon, { name: "archive" }),
2171
+ (0, import_react8.createElement)(Icon, { name: "archive" }),
1757
2172
  "\u5907\u4EFD"
1758
2173
  ),
1759
- (0, import_react7.createElement)(
2174
+ (0, import_react8.createElement)(
2175
+ "button",
2176
+ {
2177
+ className: railOpen ? "ghost active" : "ghost",
2178
+ onClick: () => setRailOpen(!railOpen),
2179
+ title: "AI \u5DE5\u4F5C\u53F0"
2180
+ },
2181
+ (0, import_react8.createElement)(Icon, { name: "sparkles" }),
2182
+ "AI"
2183
+ ),
2184
+ (0, import_react8.createElement)(
1760
2185
  "button",
1761
2186
  { className: "primary", onClick: () => startCompose("new") },
1762
- (0, import_react7.createElement)(Icon, { name: "compose" }),
2187
+ (0, import_react8.createElement)(Icon, { name: "compose" }),
1763
2188
  "\u5199\u90AE\u4EF6"
1764
2189
  )
1765
2190
  ),
1766
- toast === "" ? null : (0, import_react7.createElement)("div", { className: "small", style: { padding: "6px 14px", color: "var(--m-ok)" } }, toast),
1767
- error === "" ? null : (0, import_react7.createElement)(ErrorBar, { message: error, onDismiss: () => setError("") }),
1768
- needsLogin ? (0, import_react7.createElement)(
2191
+ toast === "" ? null : (0, import_react8.createElement)("div", { className: "small", style: { padding: "6px 14px", color: "var(--m-ok)" } }, toast),
2192
+ error === "" ? null : (0, import_react8.createElement)(ErrorBar, { message: error, onDismiss: () => setError("") }),
2193
+ needsLogin ? (0, import_react8.createElement)(
1769
2194
  "div",
1770
2195
  { className: "mailharbor-settings" },
1771
- (0, import_react7.createElement)(LoginForm, { account: currentAccount, onDone: refresh })
1772
- ) : (0, import_react7.createElement)(
2196
+ (0, import_react8.createElement)(LoginForm, { account: currentAccount, onDone: refresh })
2197
+ ) : (0, import_react8.createElement)(
1773
2198
  "div",
1774
2199
  { className: "mailharbor-body", "data-mode": narrow && selected !== null ? "reader" : "list" },
1775
- (0, import_react7.createElement)(
2200
+ (0, import_react8.createElement)(
1776
2201
  "div",
1777
2202
  { className: "mailharbor-list" },
1778
- (0, import_react7.createElement)(
2203
+ (0, import_react8.createElement)(
1779
2204
  "div",
1780
2205
  { className: "mailharbor-filters" },
1781
- (0, import_react7.createElement)(
2206
+ (0, import_react8.createElement)(
1782
2207
  "select",
1783
2208
  {
1784
2209
  value: mailbox,
@@ -1789,10 +2214,10 @@ function MailPanel() {
1789
2214
  "aria-label": "\u90AE\u7BB1\u6587\u4EF6\u5939"
1790
2215
  },
1791
2216
  folderOptions.map(
1792
- (option) => (0, import_react7.createElement)("option", { key: option.value, value: option.value }, option.label)
2217
+ (option) => (0, import_react8.createElement)("option", { key: option.value, value: option.value }, option.label)
1793
2218
  )
1794
2219
  ),
1795
- (0, import_react7.createElement)("input", {
2220
+ (0, import_react8.createElement)("input", {
1796
2221
  type: "search",
1797
2222
  placeholder: showingDrafts ? "\u641C\u7D22\u8349\u7A3F" : "\u641C\u7D22\u90AE\u4EF6",
1798
2223
  "aria-label": "\u641C\u7D22\u90AE\u4EF6",
@@ -1806,7 +2231,7 @@ function MailPanel() {
1806
2231
  }
1807
2232
  }
1808
2233
  }),
1809
- query2 !== search || search !== "" ? (0, import_react7.createElement)(
2234
+ query2 !== search || search !== "" ? (0, import_react8.createElement)(
1810
2235
  "button",
1811
2236
  {
1812
2237
  className: "subtle",
@@ -1816,22 +2241,22 @@ function MailPanel() {
1816
2241
  setSearch("");
1817
2242
  }
1818
2243
  },
1819
- (0, import_react7.createElement)(Icon, { name: "close", size: 14 })
2244
+ (0, import_react8.createElement)(Icon, { name: "close", size: 14 })
1820
2245
  ) : null
1821
2246
  ),
1822
- (0, import_react7.createElement)(
2247
+ (0, import_react8.createElement)(
1823
2248
  "div",
1824
2249
  { className: "mailharbor-rows" },
1825
- loading && items.length === 0 && drafts.length === 0 ? (0, import_react7.createElement)("div", { className: "mailharbor-empty" }, "\u6B63\u5728\u8BFB\u53D6\u2026") : showingDrafts ? drafts.length === 0 ? (0, import_react7.createElement)(
2250
+ loading && items.length === 0 && drafts.length === 0 ? (0, import_react8.createElement)("div", { className: "mailharbor-empty" }, "\u6B63\u5728\u8BFB\u53D6\u2026") : showingDrafts ? drafts.length === 0 ? (0, import_react8.createElement)(
1826
2251
  "div",
1827
2252
  { className: "mailharbor-empty" },
1828
- (0, import_react7.createElement)(Icon, { name: "compose", size: 28 }),
1829
- (0, import_react7.createElement)("div", { className: "title" }, "\u6CA1\u6709\u8349\u7A3F"),
1830
- (0, import_react7.createElement)("div", { className: "small" }, "\u5199\u4FE1\u65F6\u70B9\u300C\u5B58\u8349\u7A3F\u300D\u4F1A\u51FA\u73B0\u5728\u8FD9\u91CC\u3002")
2253
+ (0, import_react8.createElement)(Icon, { name: "compose", size: 28 }),
2254
+ (0, import_react8.createElement)("div", { className: "title" }, "\u6CA1\u6709\u8349\u7A3F"),
2255
+ (0, import_react8.createElement)("div", { className: "small" }, "\u5199\u4FE1\u65F6\u70B9\u300C\u5B58\u8349\u7A3F\u300D\u4F1A\u51FA\u73B0\u5728\u8FD9\u91CC\u3002")
1831
2256
  ) : drafts.filter(
1832
2257
  (draft) => search === "" ? true : `${draft.subject} ${draft.to} ${draft.cc}`.toLowerCase().includes(search.toLowerCase())
1833
2258
  ).map(
1834
- (draft) => (0, import_react7.createElement)(
2259
+ (draft) => (0, import_react8.createElement)(
1835
2260
  "div",
1836
2261
  {
1837
2262
  key: draft.id,
@@ -1846,21 +2271,21 @@ function MailPanel() {
1846
2271
  }
1847
2272
  }
1848
2273
  },
1849
- (0, import_react7.createElement)(
2274
+ (0, import_react8.createElement)(
1850
2275
  "span",
1851
2276
  { className: "line-1" },
1852
- (0, import_react7.createElement)(
2277
+ (0, import_react8.createElement)(
1853
2278
  "span",
1854
2279
  { className: "from" },
1855
2280
  `\u8349\u7A3F${draft.to === "" ? "" : ` \xB7 \u6536\u4EF6\u4EBA ${draft.to}`}`
1856
2281
  ),
1857
- isAll ? (0, import_react7.createElement)(
2282
+ isAll ? (0, import_react8.createElement)(
1858
2283
  "span",
1859
2284
  { className: "badge" },
1860
2285
  accounts.find((item) => item.id === draft.accountId)?.label ?? ""
1861
2286
  ) : null,
1862
- (0, import_react7.createElement)("span", { className: "date" }, shortDate(draft.updatedAt)),
1863
- (0, import_react7.createElement)(
2287
+ (0, import_react8.createElement)("span", { className: "date" }, shortDate(draft.updatedAt)),
2288
+ (0, import_react8.createElement)(
1864
2289
  "button",
1865
2290
  {
1866
2291
  className: "subtle",
@@ -1870,40 +2295,40 @@ function MailPanel() {
1870
2295
  void removeDraft(draft.id, draft.accountId);
1871
2296
  }
1872
2297
  },
1873
- (0, import_react7.createElement)(Icon, { name: "close", size: 13 })
2298
+ (0, import_react8.createElement)(Icon, { name: "close", size: 13 })
1874
2299
  )
1875
2300
  ),
1876
- (0, import_react7.createElement)(
2301
+ (0, import_react8.createElement)(
1877
2302
  "span",
1878
2303
  { className: "line-2" },
1879
- (0, import_react7.createElement)(
2304
+ (0, import_react8.createElement)(
1880
2305
  "span",
1881
2306
  { className: "subject" },
1882
2307
  draft.subject === "" ? "(\u65E0\u4E3B\u9898)" : draft.subject
1883
2308
  )
1884
2309
  ),
1885
- draft.html === "" ? null : (0, import_react7.createElement)(
2310
+ draft.html === "" ? null : (0, import_react8.createElement)(
1886
2311
  "span",
1887
2312
  { className: "snippet" },
1888
2313
  draft.html.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 120)
1889
2314
  )
1890
2315
  )
1891
- ) : items.length === 0 ? (0, import_react7.createElement)(
2316
+ ) : items.length === 0 ? (0, import_react8.createElement)(
1892
2317
  "div",
1893
2318
  { className: "mailharbor-empty" },
1894
- (0, import_react7.createElement)(Icon, { name: "mail", size: 28 }),
1895
- (0, import_react7.createElement)(
2319
+ (0, import_react8.createElement)(Icon, { name: "mail", size: 28 }),
2320
+ (0, import_react8.createElement)(
1896
2321
  "div",
1897
2322
  { className: "title" },
1898
2323
  source === "local" ? "\u672C\u5730\u5907\u4EFD\u91CC\u8FD8\u6CA1\u6709\u90AE\u4EF6" : "\u670D\u52A1\u5668\u4E0A\u6CA1\u6709\u90AE\u4EF6"
1899
2324
  ),
1900
- (0, import_react7.createElement)(
2325
+ (0, import_react8.createElement)(
1901
2326
  "div",
1902
2327
  { className: "small" },
1903
2328
  source === "local" ? "\u70B9\u9876\u90E8\u300C\u5907\u4EFD\u300D\u628A\u670D\u52A1\u5668\u90AE\u4EF6\u540C\u6B65\u4E0B\u6765\u3002" : "\u6362\u4E00\u4E2A\u6587\u4EF6\u5939\u6216\u7A0D\u540E\u5237\u65B0\u3002"
1904
2329
  )
1905
2330
  ) : items.map(
1906
- (item) => (0, import_react7.createElement)(
2331
+ (item) => (0, import_react8.createElement)(
1907
2332
  "div",
1908
2333
  {
1909
2334
  key: `${item.accountId}:${item.id}`,
@@ -1918,122 +2343,123 @@ function MailPanel() {
1918
2343
  }
1919
2344
  }
1920
2345
  },
1921
- (0, import_react7.createElement)(
2346
+ (0, import_react8.createElement)(
1922
2347
  "span",
1923
2348
  { className: "line-1" },
1924
- item.unread ? (0, import_react7.createElement)("span", { className: "dot" }) : null,
1925
- (0, import_react7.createElement)(
2349
+ item.unread ? (0, import_react8.createElement)("span", { className: "dot" }) : null,
2350
+ (0, import_react8.createElement)(
1926
2351
  "span",
1927
2352
  { className: "from" },
1928
2353
  item.from.name !== "" ? item.from.name : item.from.address
1929
2354
  ),
1930
- item.hasAttachments ? (0, import_react7.createElement)(Icon, { name: "paperclip", size: 13 }) : null,
1931
- isAll ? (0, import_react7.createElement)(
2355
+ item.hasAttachments ? (0, import_react8.createElement)(Icon, { name: "paperclip", size: 13 }) : null,
2356
+ isAll ? (0, import_react8.createElement)(
1932
2357
  "span",
1933
2358
  { className: "badge" },
1934
2359
  accounts.find((entry) => entry.id === item.accountId)?.label ?? ""
1935
2360
  ) : null,
1936
- item.backedUp === false ? (0, import_react7.createElement)("span", { className: "badge warn" }, "\u672A\u5907\u4EFD") : null,
1937
- (0, import_react7.createElement)("span", { className: "date" }, shortDate(item.date))
2361
+ item.backedUp === false ? (0, import_react8.createElement)("span", { className: "badge warn" }, "\u672A\u5907\u4EFD") : null,
2362
+ (0, import_react8.createElement)("span", { className: "date" }, shortDate(item.date))
1938
2363
  ),
1939
- (0, import_react7.createElement)(
2364
+ (0, import_react8.createElement)(
1940
2365
  "span",
1941
2366
  { className: "line-2" },
1942
- (0, import_react7.createElement)("span", { className: "subject" }, item.subject)
2367
+ (0, import_react8.createElement)("span", { className: "subject" }, item.subject)
1943
2368
  ),
1944
- item.snippet === "" ? null : (0, import_react7.createElement)("span", { className: "snippet" }, item.snippet)
2369
+ item.snippet === "" ? null : (0, import_react8.createElement)("span", { className: "snippet" }, item.snippet)
1945
2370
  )
1946
2371
  )
1947
2372
  ),
1948
- !showingDrafts && items.length > 0 ? (0, import_react7.createElement)(
2373
+ !showingDrafts && items.length > 0 ? (0, import_react8.createElement)(
1949
2374
  "div",
1950
2375
  { className: "mailharbor-list-foot" },
1951
- (0, import_react7.createElement)("span", null, `${String(items.length)} / ${String(page.total)} \u5C01`),
1952
- (0, import_react7.createElement)("span", { className: "spacer" }),
1953
- page.total > items.length ? (0, import_react7.createElement)(
2376
+ (0, import_react8.createElement)("span", null, `${String(items.length)} / ${String(page.total)} \u5C01`),
2377
+ (0, import_react8.createElement)("span", { className: "spacer" }),
2378
+ page.total > items.length ? (0, import_react8.createElement)(
1954
2379
  "button",
1955
2380
  { className: "subtle", onClick: () => setLimit((current) => current + 40) },
1956
2381
  "\u52A0\u8F7D\u66F4\u591A"
1957
2382
  ) : null
1958
- ) : showingDrafts && drafts.length > 0 ? (0, import_react7.createElement)(
2383
+ ) : showingDrafts && drafts.length > 0 ? (0, import_react8.createElement)(
1959
2384
  "div",
1960
2385
  { className: "mailharbor-list-foot" },
1961
- (0, import_react7.createElement)("span", null, `${String(drafts.length)} \u5C01\u8349\u7A3F`)
2386
+ (0, import_react8.createElement)("span", null, `${String(drafts.length)} \u5C01\u8349\u7A3F`)
1962
2387
  ) : null
1963
2388
  ),
1964
- (0, import_react7.createElement)(
2389
+ (0, import_react8.createElement)(
1965
2390
  "div",
1966
2391
  { className: "mailharbor-reader" },
1967
- selected === null ? (0, import_react7.createElement)(
2392
+ selected === null ? (0, import_react8.createElement)(
1968
2393
  "div",
1969
2394
  { className: "mailharbor-empty" },
1970
- (0, import_react7.createElement)(Icon, { name: "mail", size: 34 }),
1971
- (0, import_react7.createElement)("div", { className: "title" }, "\u9009\u62E9\u4E00\u5C01\u90AE\u4EF6\u5F00\u59CB\u9605\u8BFB"),
1972
- (0, import_react7.createElement)(
2395
+ (0, import_react8.createElement)(Icon, { name: "mail", size: 34 }),
2396
+ (0, import_react8.createElement)("div", { className: "title" }, "\u9009\u62E9\u4E00\u5C01\u90AE\u4EF6\u5F00\u59CB\u9605\u8BFB"),
2397
+ (0, import_react8.createElement)(
1973
2398
  "div",
1974
2399
  { className: "small" },
1975
2400
  loggedIn ? "\u5DF2\u767B\u5F55\uFF0C\u53EF\u5207\u6362\u300C\u670D\u52A1\u5668\u5B9E\u65F6\u300D\u67E5\u770B\u6700\u65B0\u90AE\u4EF6\u3002" : "\u6D4F\u89C8\u672C\u5730\u5907\u4EFD\u4E0D\u9700\u8981\u767B\u5F55\u3002"
1976
2401
  )
1977
- ) : (0, import_react7.createElement)(Reader, {
2402
+ ) : (0, import_react8.createElement)(Reader, {
1978
2403
  detail: selected,
1979
2404
  source,
1980
2405
  mailbox,
1981
2406
  onChanged: (patch) => setSelected({ ...selected, ...patch }),
1982
2407
  onReply: (mode) => startCompose(mode),
1983
- onAiReply: (init) => setCompose({
1984
- mode: "reply",
1985
- accountId: selected.accountId,
1986
- to: init.to,
1987
- cc: init.cc,
1988
- bcc: "",
1989
- subject: init.subject,
1990
- html: init.html,
1991
- inReplyTo: init.inReplyTo,
1992
- references: init.references
1993
- }),
2408
+ onAiAction: requestAi,
1994
2409
  onClose: narrow ? () => setSelected(null) : void 0
1995
2410
  })
1996
- )
2411
+ ),
2412
+ railOpen ? (0, import_react8.createElement)(AiRail, {
2413
+ account: accounts.find((item) => item.id === (selected?.accountId ?? accountScope)) ?? currentAccount,
2414
+ detail: selected,
2415
+ source,
2416
+ mailbox,
2417
+ request: aiRequest,
2418
+ onClose: () => setRailOpen(false),
2419
+ onInsert: insertReply,
2420
+ onSaveDraft: (init) => void saveReplyDraft(init),
2421
+ onNotice: notice
2422
+ }) : null
1997
2423
  ),
1998
- showBackup && backup !== null ? (0, import_react7.createElement)(
2424
+ showBackup && backup !== null ? (0, import_react8.createElement)(
1999
2425
  Modal,
2000
2426
  {
2001
2427
  title: "\u5907\u4EFD",
2002
2428
  onClose: () => setShowBackup(false),
2003
- footer: (0, import_react7.createElement)(
2429
+ footer: (0, import_react8.createElement)(
2004
2430
  "div",
2005
2431
  { style: { display: "flex", gap: "8px", width: "100%", alignItems: "center" } },
2006
- (0, import_react7.createElement)("span", { className: "small muted" }, backup.running ? "\u5907\u4EFD\u8FDB\u884C\u4E2D\u2026" : backup.message),
2007
- (0, import_react7.createElement)("span", { className: "spacer" }),
2008
- backup.running ? (0, import_react7.createElement)("button", { className: "ghost danger", onClick: () => void stopBackup() }, "\u505C\u6B62\u5907\u4EFD") : (0, import_react7.createElement)("button", { className: "primary", onClick: () => void startBackup() }, "\u5F00\u59CB\u5907\u4EFD"),
2009
- (0, import_react7.createElement)("button", { className: "ghost", onClick: () => setShowBackup(false) }, "\u5173\u95ED")
2432
+ (0, import_react8.createElement)("span", { className: "small muted" }, backup.running ? "\u5907\u4EFD\u8FDB\u884C\u4E2D\u2026" : backup.message),
2433
+ (0, import_react8.createElement)("span", { className: "spacer" }),
2434
+ backup.running ? (0, import_react8.createElement)("button", { className: "ghost danger", onClick: () => void stopBackup() }, "\u505C\u6B62\u5907\u4EFD") : (0, import_react8.createElement)("button", { className: "primary", onClick: () => void startBackup() }, "\u5F00\u59CB\u5907\u4EFD"),
2435
+ (0, import_react8.createElement)("button", { className: "ghost", onClick: () => setShowBackup(false) }, "\u5173\u95ED")
2010
2436
  )
2011
2437
  },
2012
- (0, import_react7.createElement)(
2438
+ (0, import_react8.createElement)(
2013
2439
  "div",
2014
2440
  { className: "mailharbor-kv" },
2015
- (0, import_react7.createElement)("dt", null, "\u5F00\u59CB\u65F6\u95F4"),
2016
- (0, import_react7.createElement)("dd", null, backup.startedAt ?? "\u2014"),
2017
- (0, import_react7.createElement)("dt", null, "\u4E0A\u6B21\u6210\u529F"),
2018
- (0, import_react7.createElement)("dd", null, backup.lastSuccessAt ?? "\u2014"),
2019
- (0, import_react7.createElement)("dt", null, "\u65B0\u589E"),
2020
- (0, import_react7.createElement)("dd", null, String(backup.added)),
2021
- (0, import_react7.createElement)("dt", null, "\u603B\u6570"),
2022
- (0, import_react7.createElement)("dd", null, String(backup.total))
2441
+ (0, import_react8.createElement)("dt", null, "\u5F00\u59CB\u65F6\u95F4"),
2442
+ (0, import_react8.createElement)("dd", null, backup.startedAt ?? "\u2014"),
2443
+ (0, import_react8.createElement)("dt", null, "\u4E0A\u6B21\u6210\u529F"),
2444
+ (0, import_react8.createElement)("dd", null, backup.lastSuccessAt ?? "\u2014"),
2445
+ (0, import_react8.createElement)("dt", null, "\u65B0\u589E"),
2446
+ (0, import_react8.createElement)("dd", null, String(backup.added)),
2447
+ (0, import_react8.createElement)("dt", null, "\u603B\u6570"),
2448
+ (0, import_react8.createElement)("dd", null, String(backup.total))
2023
2449
  ),
2024
- (0, import_react7.createElement)(
2450
+ (0, import_react8.createElement)(
2025
2451
  "div",
2026
2452
  { className: "mailharbor-log" },
2027
2453
  backup.output.length === 0 ? "\uFF08\u7B49\u5F85\u8F93\u51FA\u2026\uFF09" : backup.output.join("\n")
2028
2454
  )
2029
2455
  ) : null,
2030
- compose !== null && composeAccount !== null ? (0, import_react7.createElement)(Composer, {
2456
+ compose !== null && composeAccount !== null ? (0, import_react8.createElement)(Composer, {
2031
2457
  account: composeAccount,
2032
2458
  initial: compose,
2033
2459
  onClose: () => setCompose(null),
2034
2460
  onSent: (message) => notice(message)
2035
2461
  }) : null,
2036
- showAdd ? (0, import_react7.createElement)(AddAccountModal, {
2462
+ showAdd ? (0, import_react8.createElement)(AddAccountModal, {
2037
2463
  onClose: () => setShowAdd(false),
2038
2464
  onDone: (next) => {
2039
2465
  setState(next);
@@ -2045,32 +2471,32 @@ function MailPanel() {
2045
2471
  }
2046
2472
 
2047
2473
  // src/client/settings.tsx
2048
- var import_react8 = require("react");
2049
- function errorText5(error) {
2474
+ var import_react9 = require("react");
2475
+ function errorText6(error) {
2050
2476
  return error instanceof ApiError || error instanceof Error ? error.message : String(error);
2051
2477
  }
2052
2478
  function MailSettings() {
2053
- const [state, setState] = (0, import_react8.useState)(null);
2054
- const [settings, setSettings] = (0, import_react8.useState)(null);
2055
- const [error, setError] = (0, import_react8.useState)("");
2056
- const [toast, setToast] = (0, import_react8.useState)("");
2057
- const [signature, setSignature] = (0, import_react8.useState)("");
2058
- const [markOnOpen, setMarkOnOpen] = (0, import_react8.useState)(true);
2059
- const [pollSeconds, setPollSeconds] = (0, import_react8.useState)(60);
2060
- const [listLimit, setListLimit] = (0, import_react8.useState)(40);
2061
- const [aiEnabled, setAiEnabled] = (0, import_react8.useState)(true);
2062
- const [aiProvider, setAiProvider] = (0, import_react8.useState)("deepseek-account");
2063
- const [aiModel, setAiModel] = (0, import_react8.useState)("deepseek-flash");
2064
- const [aiModels, setAiModels] = (0, import_react8.useState)({ providers: [] });
2065
- const [aiStatus, setAiStatus] = (0, import_react8.useState)(null);
2066
- const [styleText, setStyleText] = (0, import_react8.useState)("");
2067
- const [styleMeta, setStyleMeta] = (0, import_react8.useState)({ updatedAt: "", sampleCount: 0 });
2068
- const [styleBusy, setStyleBusy] = (0, import_react8.useState)(null);
2069
- const [editing, setEditing] = (0, import_react8.useState)(null);
2070
- const [checkResult, setCheckResult] = (0, import_react8.useState)("");
2071
- const [adding, setAdding] = (0, import_react8.useState)(false);
2072
- const [tick, setTick] = (0, import_react8.useState)(0);
2073
- (0, import_react8.useEffect)(() => {
2479
+ const [state, setState] = (0, import_react9.useState)(null);
2480
+ const [settings, setSettings] = (0, import_react9.useState)(null);
2481
+ const [error, setError] = (0, import_react9.useState)("");
2482
+ const [toast, setToast] = (0, import_react9.useState)("");
2483
+ const [signature, setSignature] = (0, import_react9.useState)("");
2484
+ const [markOnOpen, setMarkOnOpen] = (0, import_react9.useState)(true);
2485
+ const [pollSeconds, setPollSeconds] = (0, import_react9.useState)(60);
2486
+ const [listLimit, setListLimit] = (0, import_react9.useState)(40);
2487
+ const [aiEnabled, setAiEnabled] = (0, import_react9.useState)(true);
2488
+ const [aiProvider, setAiProvider] = (0, import_react9.useState)("deepseek-account");
2489
+ const [aiModel, setAiModel] = (0, import_react9.useState)("deepseek-flash");
2490
+ const [aiModels, setAiModels] = (0, import_react9.useState)({ providers: [] });
2491
+ const [aiStatus, setAiStatus] = (0, import_react9.useState)(null);
2492
+ const [styleText, setStyleText] = (0, import_react9.useState)("");
2493
+ const [styleMeta, setStyleMeta] = (0, import_react9.useState)({ updatedAt: "", sampleCount: 0 });
2494
+ const [styleBusy, setStyleBusy] = (0, import_react9.useState)(null);
2495
+ const [editing, setEditing] = (0, import_react9.useState)(null);
2496
+ const [checkResults, setCheckResults] = (0, import_react9.useState)({});
2497
+ const [adding, setAdding] = (0, import_react9.useState)(false);
2498
+ const [tick, setTick] = (0, import_react9.useState)(0);
2499
+ (0, import_react9.useEffect)(() => {
2074
2500
  let cancelled = false;
2075
2501
  Promise.all([api.state(), api.settings()]).then(([appState, view]) => {
2076
2502
  if (cancelled) return;
@@ -2084,13 +2510,13 @@ function MailSettings() {
2084
2510
  setAiProvider(view.ai.provider);
2085
2511
  setAiModel(view.ai.model);
2086
2512
  }).catch((caught) => {
2087
- if (!cancelled) setError(errorText5(caught));
2513
+ if (!cancelled) setError(errorText6(caught));
2088
2514
  });
2089
2515
  return () => {
2090
2516
  cancelled = true;
2091
2517
  };
2092
2518
  }, [tick]);
2093
- (0, import_react8.useEffect)(() => {
2519
+ (0, import_react9.useEffect)(() => {
2094
2520
  let cancelled = false;
2095
2521
  const accountId = settings?.activeAccountId ?? state?.activeAccountId ?? null;
2096
2522
  if (accountId === null) return;
@@ -2128,38 +2554,38 @@ function MailSettings() {
2128
2554
  setSettings(result.settings);
2129
2555
  notify("\u8BBE\u7F6E\u5DF2\u4FDD\u5B58");
2130
2556
  } catch (caught) {
2131
- setError(errorText5(caught));
2557
+ setError(errorText6(caught));
2132
2558
  }
2133
2559
  };
2134
2560
  const account = settings?.accounts.find((item) => item.id === settings.activeAccountId) ?? null;
2135
2561
  if (state === null || settings === null) {
2136
- return (0, import_react8.createElement)("div", { className: "mailharbor mailharbor-settings" }, error === "" ? "\u6B63\u5728\u52A0\u8F7D\u8BBE\u7F6E\u2026" : error);
2562
+ return (0, import_react9.createElement)("div", { className: "mailharbor mailharbor-settings" }, error === "" ? "\u6B63\u5728\u52A0\u8F7D\u8BBE\u7F6E\u2026" : error);
2137
2563
  }
2138
- return (0, import_react8.createElement)(
2564
+ return (0, import_react9.createElement)(
2139
2565
  "div",
2140
2566
  { className: "mailharbor" },
2141
- (0, import_react8.createElement)(
2567
+ (0, import_react9.createElement)(
2142
2568
  "div",
2143
2569
  { className: "mailharbor-settings" },
2144
- (0, import_react8.createElement)("h2", null, "MailHarbor"),
2145
- (0, import_react8.createElement)("p", { className: "small muted" }, "\u591A\u8D26\u53F7 IMAP/SMTP \u90AE\u7BB1\u3001\u672C\u5730\u5907\u4EFD\u3001AI \u4E0E\u4EFB\u52A1\u3002\u8BBE\u7F6E\u4F1A\u7ACB\u5373\u751F\u6548\u3002"),
2146
- toast === "" ? null : (0, import_react8.createElement)("div", { className: "small", style: { color: "var(--m-ok)" } }, toast),
2147
- error === "" ? null : (0, import_react8.createElement)(ErrorBar, { message: error, onDismiss: () => setError("") }),
2148
- (0, import_react8.createElement)(
2570
+ (0, import_react9.createElement)("h2", null, "MailHarbor"),
2571
+ (0, import_react9.createElement)("p", { className: "small muted" }, "\u591A\u8D26\u53F7 IMAP/SMTP \u90AE\u7BB1\u3001\u672C\u5730\u5907\u4EFD\u3001AI \u4E0E\u4EFB\u52A1\u3002\u8BBE\u7F6E\u4F1A\u7ACB\u5373\u751F\u6548\u3002"),
2572
+ toast === "" ? null : (0, import_react9.createElement)("div", { className: "small", style: { color: "var(--m-ok)" } }, toast),
2573
+ error === "" ? null : (0, import_react9.createElement)(ErrorBar, { message: error, onDismiss: () => setError("") }),
2574
+ (0, import_react9.createElement)(
2149
2575
  "div",
2150
2576
  { className: "mailharbor-card" },
2151
- (0, import_react8.createElement)("h3", null, "\u8D26\u53F7"),
2152
- settings.accounts.length === 0 ? (0, import_react8.createElement)(AddAccountForm, { onDone: () => reload() }) : settings.accounts.map(
2153
- (item) => (0, import_react8.createElement)(AccountRow, {
2577
+ (0, import_react9.createElement)("h3", null, "\u8D26\u53F7"),
2578
+ settings.accounts.length === 0 ? (0, import_react9.createElement)(AddAccountForm, { onDone: () => reload() }) : settings.accounts.map(
2579
+ (item) => (0, import_react9.createElement)(AccountRow, {
2154
2580
  key: item.id,
2155
2581
  account: item,
2156
2582
  active: item.id === settings.activeAccountId,
2157
2583
  loggedIn: item.id === settings.activeAccountId ? state.loggedIn : void 0,
2158
2584
  editing: editing === item.id,
2159
- checkResult: editing === item.id ? checkResult : "",
2585
+ checkResult: checkResults[item.id] ?? "",
2160
2586
  onToggleEdit: () => {
2161
2587
  setEditing(editing === item.id ? null : item.id);
2162
- setCheckResult("");
2588
+ setCheckResults((prev) => ({ ...prev, [item.id]: "" }));
2163
2589
  },
2164
2590
  onActivate: async () => {
2165
2591
  await api.activateAccount(item.id);
@@ -2176,11 +2602,12 @@ function MailSettings() {
2176
2602
  onCheck: async () => {
2177
2603
  try {
2178
2604
  const result = await api.check(item.id);
2179
- setCheckResult(
2180
- `IMAP \u6B63\u5E38\uFF08${String(result.imap.mailboxes.length)} \u4E2A\u6587\u4EF6\u5939\uFF09\xB7 SMTP ${result.smtp.ok ? "\u6B63\u5E38" : `\u5931\u8D25: ${result.smtp.error ?? ""}`}`
2181
- );
2605
+ setCheckResults((prev) => ({
2606
+ ...prev,
2607
+ [item.id]: `IMAP \u6B63\u5E38\uFF08${String(result.imap.mailboxes.length)} \u4E2A\u6587\u4EF6\u5939\uFF09\xB7 SMTP ${result.smtp.ok ? "\u6B63\u5E38" : `\u5931\u8D25: ${result.smtp.error ?? ""}`}`
2608
+ }));
2182
2609
  } catch (caught) {
2183
- setCheckResult(`\u5931\u8D25: ${errorText5(caught)}`);
2610
+ setCheckResults((prev) => ({ ...prev, [item.id]: `\u5931\u8D25: ${errorText6(caught)}` }));
2184
2611
  }
2185
2612
  },
2186
2613
  onSave: async (patch) => {
@@ -2194,32 +2621,32 @@ function MailSettings() {
2194
2621
  }
2195
2622
  })
2196
2623
  ),
2197
- adding ? (0, import_react8.createElement)(AddAccountForm, {
2624
+ adding ? (0, import_react9.createElement)(AddAccountForm, {
2198
2625
  onDone: () => {
2199
2626
  setAdding(false);
2200
2627
  reload();
2201
2628
  },
2202
2629
  onCancel: () => setAdding(false)
2203
- }) : (0, import_react8.createElement)("button", { className: "ghost", onClick: () => setAdding(true) }, "+ \u6DFB\u52A0\u8D26\u53F7")
2630
+ }) : (0, import_react9.createElement)("button", { className: "ghost", onClick: () => setAdding(true) }, "+ \u6DFB\u52A0\u8D26\u53F7")
2204
2631
  ),
2205
- (0, import_react8.createElement)(
2632
+ (0, import_react9.createElement)(
2206
2633
  "div",
2207
2634
  { className: "mailharbor-card" },
2208
- (0, import_react8.createElement)("h3", null, "\u5199\u4FE1\u4E0E\u9605\u8BFB"),
2209
- (0, import_react8.createElement)(
2635
+ (0, import_react9.createElement)("h3", null, "\u5199\u4FE1\u4E0E\u9605\u8BFB"),
2636
+ (0, import_react9.createElement)(
2210
2637
  Field,
2211
2638
  { label: "\u7B7E\u540D\uFF08\u7EAF\u6587\u672C\uFF0C\u53D1\u9001\u65F6\u8FFD\u52A0\u5230\u6B63\u6587\u672B\u5C3E\uFF09" },
2212
- (0, import_react8.createElement)("textarea", {
2639
+ (0, import_react9.createElement)("textarea", {
2213
2640
  rows: 4,
2214
2641
  value: signature,
2215
2642
  onChange: (event) => setSignature(event.target.value)
2216
2643
  })
2217
2644
  ),
2218
- (0, import_react8.createElement)(
2645
+ (0, import_react9.createElement)(
2219
2646
  "div",
2220
2647
  { style: { display: "flex", gap: "10px", alignItems: "center", flexWrap: "wrap" } },
2221
- (0, import_react8.createElement)("button", { className: "ghost", onClick: () => void saveGeneral({ signature }) }, "\u4FDD\u5B58\u7B7E\u540D"),
2222
- (0, import_react8.createElement)(Toggle, {
2648
+ (0, import_react9.createElement)("button", { className: "ghost", onClick: () => void saveGeneral({ signature }) }, "\u4FDD\u5B58\u7B7E\u540D"),
2649
+ (0, import_react9.createElement)(Toggle, {
2223
2650
  checked: markOnOpen,
2224
2651
  onChange: (value) => {
2225
2652
  setMarkOnOpen(value);
@@ -2228,13 +2655,13 @@ function MailSettings() {
2228
2655
  label: "\u6253\u5F00\u90AE\u4EF6\u65F6\u6807\u4E3A\u5DF2\u8BFB"
2229
2656
  })
2230
2657
  ),
2231
- (0, import_react8.createElement)(
2658
+ (0, import_react9.createElement)(
2232
2659
  "div",
2233
2660
  { className: "mailharbor-grid" },
2234
- (0, import_react8.createElement)(
2661
+ (0, import_react9.createElement)(
2235
2662
  Field,
2236
2663
  { label: "\u5217\u8868\u6BCF\u9875\u6570\u91CF" },
2237
- (0, import_react8.createElement)("input", {
2664
+ (0, import_react9.createElement)("input", {
2238
2665
  type: "number",
2239
2666
  min: 10,
2240
2667
  max: 200,
@@ -2243,10 +2670,10 @@ function MailSettings() {
2243
2670
  onBlur: () => void saveGeneral({ render: { listLimit } })
2244
2671
  })
2245
2672
  ),
2246
- (0, import_react8.createElement)(
2673
+ (0, import_react9.createElement)(
2247
2674
  Field,
2248
2675
  { label: "\u672A\u8BFB\u89D2\u6807\u8F6E\u8BE2\uFF08\u79D2\uFF09" },
2249
- (0, import_react8.createElement)("input", {
2676
+ (0, import_react9.createElement)("input", {
2250
2677
  type: "number",
2251
2678
  min: 15,
2252
2679
  max: 3600,
@@ -2257,11 +2684,11 @@ function MailSettings() {
2257
2684
  )
2258
2685
  )
2259
2686
  ),
2260
- (0, import_react8.createElement)(
2687
+ (0, import_react9.createElement)(
2261
2688
  "div",
2262
2689
  { className: "mailharbor-card" },
2263
- (0, import_react8.createElement)("h3", null, "AI"),
2264
- (0, import_react8.createElement)(Toggle, {
2690
+ (0, import_react9.createElement)("h3", null, "AI"),
2691
+ (0, import_react9.createElement)(Toggle, {
2265
2692
  checked: aiEnabled,
2266
2693
  onChange: (value) => {
2267
2694
  setAiEnabled(value);
@@ -2269,22 +2696,22 @@ function MailSettings() {
2269
2696
  },
2270
2697
  label: "\u542F\u7528 AI\uFF08\u6458\u8981\u3001AI \u56DE\u590D\u3001\u7FFB\u8BD1\u3001\u5199\u4F5C\u98CE\u683C\uFF09"
2271
2698
  }),
2272
- aiStatus === null ? null : (0, import_react8.createElement)(
2699
+ aiStatus === null ? null : (0, import_react9.createElement)(
2273
2700
  "div",
2274
2701
  { className: "small", style: { color: aiStatus.available ? "var(--m-ok)" : "var(--m-warn)" } },
2275
2702
  aiStatus.available ? `\u6A21\u578B\u670D\u52A1\u53EF\u7528 \xB7 ${aiStatus.provider} / ${aiStatus.model}` : aiStatus.error ?? "\u5F53\u524D DSH \u7EC4\u5408\u6CA1\u6709\u53EF\u7528\u7684\u6A21\u578B\u670D\u52A1"
2276
2703
  ),
2277
- (0, import_react8.createElement)(
2704
+ (0, import_react9.createElement)(
2278
2705
  "div",
2279
2706
  { className: "mailharbor-grid" },
2280
- (0, import_react8.createElement)(
2707
+ (0, import_react9.createElement)(
2281
2708
  Field,
2282
2709
  { label: "Provider" },
2283
- aiModels.providers.length === 0 ? (0, import_react8.createElement)("input", {
2710
+ aiModels.providers.length === 0 ? (0, import_react9.createElement)("input", {
2284
2711
  value: aiProvider,
2285
2712
  onChange: (event) => setAiProvider(event.target.value),
2286
2713
  onBlur: () => void saveGeneral({ ai: { provider: aiProvider } })
2287
- }) : (0, import_react8.createElement)(
2714
+ }) : (0, import_react9.createElement)(
2288
2715
  "select",
2289
2716
  {
2290
2717
  value: aiProvider,
@@ -2302,20 +2729,20 @@ function MailSettings() {
2302
2729
  }
2303
2730
  },
2304
2731
  aiModels.providers.map(
2305
- (item) => (0, import_react8.createElement)("option", { key: item.provider, value: item.provider }, item.name)
2732
+ (item) => (0, import_react9.createElement)("option", { key: item.provider, value: item.provider }, item.name)
2306
2733
  )
2307
2734
  )
2308
2735
  ),
2309
- (0, import_react8.createElement)(
2736
+ (0, import_react9.createElement)(
2310
2737
  Field,
2311
2738
  { label: "\u6A21\u578B" },
2312
2739
  (() => {
2313
2740
  const provider = aiModels.providers.find((item) => item.provider === aiProvider);
2314
- return provider === void 0 || provider.models.length === 0 ? (0, import_react8.createElement)("input", {
2741
+ return provider === void 0 || provider.models.length === 0 ? (0, import_react9.createElement)("input", {
2315
2742
  value: aiModel,
2316
2743
  onChange: (event) => setAiModel(event.target.value),
2317
2744
  onBlur: () => void saveGeneral({ ai: { model: aiModel } })
2318
- }) : (0, import_react8.createElement)(
2745
+ }) : (0, import_react9.createElement)(
2319
2746
  "select",
2320
2747
  {
2321
2748
  value: aiModel,
@@ -2324,37 +2751,37 @@ function MailSettings() {
2324
2751
  void saveGeneral({ ai: { model: event.target.value } });
2325
2752
  }
2326
2753
  },
2327
- provider.models.map((item) => (0, import_react8.createElement)("option", { key: item.id, value: item.id }, item.name))
2754
+ provider.models.map((item) => (0, import_react9.createElement)("option", { key: item.id, value: item.id }, item.name))
2328
2755
  );
2329
2756
  })()
2330
2757
  )
2331
2758
  ),
2332
- (0, import_react8.createElement)("hr", null),
2333
- (0, import_react8.createElement)(
2759
+ (0, import_react9.createElement)("hr", null),
2760
+ (0, import_react9.createElement)(
2334
2761
  "div",
2335
2762
  { style: { display: "flex", alignItems: "center", gap: "8px" } },
2336
- (0, import_react8.createElement)("strong", { style: { fontSize: "12.5px" } }, "\u5199\u4F5C\u98CE\u683C"),
2337
- (0, import_react8.createElement)(
2763
+ (0, import_react9.createElement)("strong", { style: { fontSize: "12.5px" } }, "\u5199\u4F5C\u98CE\u683C"),
2764
+ (0, import_react9.createElement)(
2338
2765
  "span",
2339
2766
  { className: "small muted" },
2340
2767
  styleMeta.updatedAt === "" ? "\u8FD8\u6CA1\u6709\u751F\u6210" : `${styleMeta.sampleCount} \u5C01\u5DF2\u53D1\u9001\u90AE\u4EF6 \xB7 ${new Date(styleMeta.updatedAt).toLocaleString()}`
2341
2768
  )
2342
2769
  ),
2343
- (0, import_react8.createElement)(
2770
+ (0, import_react9.createElement)(
2344
2771
  "p",
2345
2772
  { className: "small muted" },
2346
2773
  "\u4ECE\u5DF2\u53D1\u9001\u90AE\u4EF6\u5B66\u4E60\u4F60\u7684\u79F0\u547C\u3001\u7ED3\u5C3E\u3001\u8BED\u6C14\u548C\u5E38\u7528\u8868\u8FBE\uFF0CAI \u56DE\u590D\u65F6\u4F1A\u53C2\u8003\u5B83\u3002\u53EA\u4FDD\u5B58\u63D0\u70BC\u540E\u7684\u6863\u6848\uFF0C\u4E0D\u4FDD\u5B58\u90AE\u4EF6\u539F\u6587\u3002"
2347
2774
  ),
2348
- (0, import_react8.createElement)("textarea", {
2775
+ (0, import_react9.createElement)("textarea", {
2349
2776
  rows: 6,
2350
2777
  value: styleText,
2351
2778
  placeholder: "\u70B9\u300C\u4ECE\u5DF2\u53D1\u9001\u90AE\u4EF6\u5B66\u4E60\u300D\u751F\u6210\uFF0C\u6216\u76F4\u63A5\u5199\u4E00\u6BB5\u4F60\u7684\u5199\u4F5C\u98CE\u683C",
2352
2779
  onChange: (event) => setStyleText(event.target.value)
2353
2780
  }),
2354
- (0, import_react8.createElement)(
2781
+ (0, import_react9.createElement)(
2355
2782
  "div",
2356
2783
  { style: { display: "flex", gap: "8px", flexWrap: "wrap" } },
2357
- (0, import_react8.createElement)(
2784
+ (0, import_react9.createElement)(
2358
2785
  "button",
2359
2786
  {
2360
2787
  className: "ghost",
@@ -2368,12 +2795,12 @@ function MailSettings() {
2368
2795
  setStyleText(result.style.profile);
2369
2796
  setStyleMeta({ updatedAt: result.style.updatedAt, sampleCount: result.style.sampleCount });
2370
2797
  notify("\u5DF2\u6839\u636E\u5DF2\u53D1\u9001\u90AE\u4EF6\u751F\u6210\u5199\u4F5C\u98CE\u683C");
2371
- }).catch((caught) => setError(errorText5(caught))).finally(() => setStyleBusy(null));
2798
+ }).catch((caught) => setError(errorText6(caught))).finally(() => setStyleBusy(null));
2372
2799
  }
2373
2800
  },
2374
2801
  styleBusy === "learn" ? "\u5B66\u4E60\u4E2D\u2026" : "\u4ECE\u5DF2\u53D1\u9001\u90AE\u4EF6\u5B66\u4E60"
2375
2802
  ),
2376
- (0, import_react8.createElement)(
2803
+ (0, import_react9.createElement)(
2377
2804
  "button",
2378
2805
  {
2379
2806
  className: "primary",
@@ -2386,42 +2813,42 @@ function MailSettings() {
2386
2813
  api.aiSaveStyle(accountId, styleText).then((result) => {
2387
2814
  setStyleMeta({ updatedAt: result.style.updatedAt, sampleCount: result.style.sampleCount });
2388
2815
  notify("\u5199\u4F5C\u98CE\u683C\u5DF2\u4FDD\u5B58");
2389
- }).catch((caught) => setError(errorText5(caught))).finally(() => setStyleBusy(null));
2816
+ }).catch((caught) => setError(errorText6(caught))).finally(() => setStyleBusy(null));
2390
2817
  }
2391
2818
  },
2392
2819
  "\u4FDD\u5B58\u98CE\u683C"
2393
2820
  )
2394
2821
  ),
2395
- (0, import_react8.createElement)(
2822
+ (0, import_react9.createElement)(
2396
2823
  "p",
2397
2824
  { className: "small muted" },
2398
2825
  "AI \u8C03\u7528\u8D70 DSH \u81EA\u5DF1\u7684\u6A21\u578B\u670D\u52A1\uFF0C\u4E0D\u989D\u5916\u4FDD\u5B58\u5BC6\u94A5\uFF1B\u53D1\u9001\u4ECD\u7136\u53EA\u7531\u4F60\u786E\u8BA4\u3002"
2399
2826
  )
2400
2827
  ),
2401
- (0, import_react8.createElement)(
2828
+ (0, import_react9.createElement)(
2402
2829
  "div",
2403
2830
  { className: "mailharbor-card" },
2404
- (0, import_react8.createElement)("h3", null, "\u6570\u636E"),
2405
- (0, import_react8.createElement)(
2831
+ (0, import_react9.createElement)("h3", null, "\u6570\u636E"),
2832
+ (0, import_react9.createElement)(
2406
2833
  "dl",
2407
2834
  { className: "mailharbor-kv" },
2408
- (0, import_react8.createElement)("dt", null, "\u6570\u636E\u76EE\u5F55"),
2409
- (0, import_react8.createElement)("dd", null, settings.dataDir),
2410
- (0, import_react8.createElement)("dt", null, "\u914D\u7F6E\u6587\u4EF6"),
2411
- (0, import_react8.createElement)("dd", null, settings.configFile),
2412
- (0, import_react8.createElement)("dt", null, "\u5F53\u524D\u8D26\u53F7\u6570\u636E\u5E93"),
2413
- (0, import_react8.createElement)(
2835
+ (0, import_react9.createElement)("dt", null, "\u6570\u636E\u76EE\u5F55"),
2836
+ (0, import_react9.createElement)("dd", null, settings.dataDir),
2837
+ (0, import_react9.createElement)("dt", null, "\u914D\u7F6E\u6587\u4EF6"),
2838
+ (0, import_react9.createElement)("dd", null, settings.configFile),
2839
+ (0, import_react9.createElement)("dt", null, "\u5F53\u524D\u8D26\u53F7\u6570\u636E\u5E93"),
2840
+ (0, import_react9.createElement)(
2414
2841
  "dd",
2415
2842
  null,
2416
2843
  account === null ? "\u2014" : `${account.dataDir.startsWith("/") ? account.dataDir : `${settings.dataDir}/${account.dataDir}`}/mail.db`
2417
2844
  ),
2418
- (0, import_react8.createElement)("dt", null, "\u63D2\u4EF6\u7248\u672C"),
2419
- (0, import_react8.createElement)("dd", null, state.version)
2845
+ (0, import_react9.createElement)("dt", null, "\u63D2\u4EF6\u7248\u672C"),
2846
+ (0, import_react9.createElement)("dd", null, state.version)
2420
2847
  ),
2421
- (0, import_react8.createElement)(
2848
+ (0, import_react9.createElement)(
2422
2849
  "div",
2423
2850
  { style: { display: "flex", gap: "8px", flexWrap: "wrap" } },
2424
- account === null ? null : (0, import_react8.createElement)(
2851
+ account === null ? null : (0, import_react9.createElement)(
2425
2852
  "button",
2426
2853
  {
2427
2854
  className: "ghost",
@@ -2431,7 +2858,7 @@ function MailSettings() {
2431
2858
  notify(status.status.message === "" ? "\u5907\u4EFD\u5DF2\u5F00\u59CB" : status.status.message);
2432
2859
  reload();
2433
2860
  } catch (caught) {
2434
- setError(errorText5(caught));
2861
+ setError(errorText6(caught));
2435
2862
  }
2436
2863
  }
2437
2864
  },
@@ -2456,19 +2883,20 @@ function AccountRow({
2456
2883
  onSave,
2457
2884
  onRemove
2458
2885
  }) {
2459
- const [password, setPassword] = (0, import_react8.useState)("");
2460
- const [remember, setRemember] = (0, import_react8.useState)(true);
2461
- const [label, setLabel] = (0, import_react8.useState)(account.label);
2462
- const [username, setUsername] = (0, import_react8.useState)(account.username);
2463
- const [imapHost, setImapHost] = (0, import_react8.useState)(account.imap.host);
2464
- const [imapPort, setImapPort] = (0, import_react8.useState)(String(account.imap.port));
2465
- const [imapSecure, setImapSecure] = (0, import_react8.useState)(account.imap.secure);
2466
- const [smtpHost, setSmtpHost] = (0, import_react8.useState)(account.smtp.host);
2467
- const [smtpPort, setSmtpPort] = (0, import_react8.useState)(String(account.smtp.port));
2468
- const [smtpSecure, setSmtpSecure] = (0, import_react8.useState)(account.smtp.secure);
2469
- const [fromName, setFromName] = (0, import_react8.useState)(account.smtp.fromName);
2470
- const [busy, setBusy] = (0, import_react8.useState)(false);
2471
- (0, import_react8.useEffect)(() => {
2886
+ const [password, setPassword] = (0, import_react9.useState)("");
2887
+ const [remember, setRemember] = (0, import_react9.useState)(true);
2888
+ const [label, setLabel] = (0, import_react9.useState)(account.label);
2889
+ const [username, setUsername] = (0, import_react9.useState)(account.username);
2890
+ const [imapHost, setImapHost] = (0, import_react9.useState)(account.imap.host);
2891
+ const [imapPort, setImapPort] = (0, import_react9.useState)(String(account.imap.port));
2892
+ const [imapSecure, setImapSecure] = (0, import_react9.useState)(account.imap.secure);
2893
+ const [smtpHost, setSmtpHost] = (0, import_react9.useState)(account.smtp.host);
2894
+ const [smtpPort, setSmtpPort] = (0, import_react9.useState)(String(account.smtp.port));
2895
+ const [smtpSecure, setSmtpSecure] = (0, import_react9.useState)(account.smtp.secure);
2896
+ const [fromName, setFromName] = (0, import_react9.useState)(account.smtp.fromName);
2897
+ const [busy, setBusy] = (0, import_react9.useState)(false);
2898
+ const [checking, setChecking] = (0, import_react9.useState)(false);
2899
+ (0, import_react9.useEffect)(() => {
2472
2900
  setLabel(account.label);
2473
2901
  setUsername(account.username);
2474
2902
  setImapHost(account.imap.host);
@@ -2487,103 +2915,122 @@ function AccountRow({
2487
2915
  setBusy(false);
2488
2916
  }
2489
2917
  };
2490
- return (0, import_react8.createElement)(
2918
+ return (0, import_react9.createElement)(
2491
2919
  "div",
2492
2920
  { className: "mailharbor-card", style: { background: "var(--m-bg-1)" } },
2493
- (0, import_react8.createElement)(
2921
+ (0, import_react9.createElement)(
2494
2922
  "div",
2495
2923
  { className: "mailharbor-account-row" },
2496
- (0, import_react8.createElement)(Avatar, { email: account.email, size: 30 }),
2497
- (0, import_react8.createElement)(
2924
+ (0, import_react9.createElement)(Avatar, { email: account.email, size: 30 }),
2925
+ (0, import_react9.createElement)(
2498
2926
  "div",
2499
2927
  { style: { display: "flex", flexDirection: "column", minWidth: 0 } },
2500
- (0, import_react8.createElement)("strong", null, account.label),
2501
- (0, import_react8.createElement)("span", { className: "small muted" }, account.email)
2928
+ (0, import_react9.createElement)("strong", null, account.label),
2929
+ (0, import_react9.createElement)("span", { className: "small muted" }, account.email)
2502
2930
  ),
2503
- active ? (0, import_react8.createElement)("span", { className: "badge ok" }, "\u5F53\u524D\u8D26\u53F7") : null,
2504
- loggedIn === false ? (0, import_react8.createElement)("span", { className: "badge warn" }, "\u672A\u767B\u5F55") : null,
2505
- (0, import_react8.createElement)("span", { className: "spacer" }),
2506
- active ? null : (0, import_react8.createElement)("button", { className: "ghost small", onClick: () => void wrap(onActivate) }, "\u8BBE\u4E3A\u5F53\u524D"),
2507
- (0, import_react8.createElement)("button", { className: "ghost small", onClick: () => void wrap(onCheck) }, "\u68C0\u67E5\u8FDE\u63A5"),
2508
- (0, import_react8.createElement)("button", { className: "ghost small", onClick: onToggleEdit }, editing ? "\u6536\u8D77" : "\u7F16\u8F91")
2931
+ active ? (0, import_react9.createElement)("span", { className: "badge ok" }, "\u5F53\u524D\u8D26\u53F7") : null,
2932
+ loggedIn === false ? (0, import_react9.createElement)("span", { className: "badge warn" }, "\u672A\u767B\u5F55") : null,
2933
+ (0, import_react9.createElement)("span", { className: "spacer" }),
2934
+ active ? null : (0, import_react9.createElement)("button", { className: "ghost small", onClick: () => void wrap(onActivate) }, "\u8BBE\u4E3A\u5F53\u524D"),
2935
+ (0, import_react9.createElement)(
2936
+ "button",
2937
+ {
2938
+ className: "ghost small",
2939
+ disabled: checking,
2940
+ onClick: () => {
2941
+ setChecking(true);
2942
+ void wrap(onCheck).finally(() => setChecking(false));
2943
+ }
2944
+ },
2945
+ checking ? "\u68C0\u67E5\u4E2D\u2026" : "\u68C0\u67E5\u8FDE\u63A5"
2946
+ ),
2947
+ (0, import_react9.createElement)("button", { className: "ghost small", onClick: onToggleEdit }, editing ? "\u6536\u8D77" : "\u7F16\u8F91")
2948
+ ),
2949
+ checkResult === "" ? null : (0, import_react9.createElement)(
2950
+ "div",
2951
+ {
2952
+ className: "mailharbor-check",
2953
+ style: { color: checkResult.startsWith("\u5931\u8D25") ? "var(--m-danger)" : "var(--m-ok)" }
2954
+ },
2955
+ checkResult
2509
2956
  ),
2510
- editing ? (0, import_react8.createElement)(
2957
+ editing ? (0, import_react9.createElement)(
2511
2958
  "div",
2512
2959
  { style: { display: "flex", flexDirection: "column", gap: "10px" } },
2513
- (0, import_react8.createElement)(
2960
+ (0, import_react9.createElement)(
2514
2961
  "div",
2515
2962
  { className: "mailharbor-grid" },
2516
- (0, import_react8.createElement)(
2963
+ (0, import_react9.createElement)(
2517
2964
  Field,
2518
2965
  { label: "\u663E\u793A\u540D\u79F0" },
2519
- (0, import_react8.createElement)("input", {
2966
+ (0, import_react9.createElement)("input", {
2520
2967
  value: label,
2521
2968
  onChange: (event) => setLabel(event.target.value)
2522
2969
  })
2523
2970
  ),
2524
- (0, import_react8.createElement)(
2971
+ (0, import_react9.createElement)(
2525
2972
  Field,
2526
2973
  { label: "\u767B\u5F55\u7528\u6237\u540D" },
2527
- (0, import_react8.createElement)("input", {
2974
+ (0, import_react9.createElement)("input", {
2528
2975
  value: username,
2529
2976
  onChange: (event) => setUsername(event.target.value)
2530
2977
  })
2531
2978
  )
2532
2979
  ),
2533
- (0, import_react8.createElement)(
2980
+ (0, import_react9.createElement)(
2534
2981
  "div",
2535
2982
  { className: "mailharbor-grid three" },
2536
- (0, import_react8.createElement)(
2983
+ (0, import_react9.createElement)(
2537
2984
  Field,
2538
2985
  { label: "IMAP \u4E3B\u673A" },
2539
- (0, import_react8.createElement)("input", {
2986
+ (0, import_react9.createElement)("input", {
2540
2987
  value: imapHost,
2541
2988
  onChange: (event) => setImapHost(event.target.value)
2542
2989
  })
2543
2990
  ),
2544
- (0, import_react8.createElement)(
2991
+ (0, import_react9.createElement)(
2545
2992
  Field,
2546
2993
  { label: "\u7AEF\u53E3" },
2547
- (0, import_react8.createElement)("input", {
2994
+ (0, import_react9.createElement)("input", {
2548
2995
  value: imapPort,
2549
2996
  onChange: (event) => setImapPort(event.target.value)
2550
2997
  })
2551
2998
  ),
2552
- (0, import_react8.createElement)(Toggle, { checked: imapSecure, onChange: setImapSecure, label: "TLS" })
2999
+ (0, import_react9.createElement)(Toggle, { checked: imapSecure, onChange: setImapSecure, label: "TLS" })
2553
3000
  ),
2554
- (0, import_react8.createElement)(
3001
+ (0, import_react9.createElement)(
2555
3002
  "div",
2556
3003
  { className: "mailharbor-grid three" },
2557
- (0, import_react8.createElement)(
3004
+ (0, import_react9.createElement)(
2558
3005
  Field,
2559
3006
  { label: "SMTP \u4E3B\u673A" },
2560
- (0, import_react8.createElement)("input", {
3007
+ (0, import_react9.createElement)("input", {
2561
3008
  value: smtpHost,
2562
3009
  onChange: (event) => setSmtpHost(event.target.value)
2563
3010
  })
2564
3011
  ),
2565
- (0, import_react8.createElement)(
3012
+ (0, import_react9.createElement)(
2566
3013
  Field,
2567
3014
  { label: "\u7AEF\u53E3" },
2568
- (0, import_react8.createElement)("input", {
3015
+ (0, import_react9.createElement)("input", {
2569
3016
  value: smtpPort,
2570
3017
  onChange: (event) => setSmtpPort(event.target.value)
2571
3018
  })
2572
3019
  ),
2573
- (0, import_react8.createElement)(Toggle, { checked: smtpSecure, onChange: setSmtpSecure, label: "TLS" })
3020
+ (0, import_react9.createElement)(Toggle, { checked: smtpSecure, onChange: setSmtpSecure, label: "TLS" })
2574
3021
  ),
2575
- (0, import_react8.createElement)(
3022
+ (0, import_react9.createElement)(
2576
3023
  Field,
2577
3024
  { label: "\u53D1\u4EF6\u4EBA\u540D\u79F0" },
2578
- (0, import_react8.createElement)("input", {
3025
+ (0, import_react9.createElement)("input", {
2579
3026
  value: fromName,
2580
3027
  onChange: (event) => setFromName(event.target.value)
2581
3028
  })
2582
3029
  ),
2583
- (0, import_react8.createElement)(
3030
+ (0, import_react9.createElement)(
2584
3031
  "div",
2585
3032
  { style: { display: "flex", gap: "8px", alignItems: "center", flexWrap: "wrap" } },
2586
- (0, import_react8.createElement)(
3033
+ (0, import_react9.createElement)(
2587
3034
  "button",
2588
3035
  {
2589
3036
  className: "primary small",
@@ -2599,7 +3046,7 @@ function AccountRow({
2599
3046
  },
2600
3047
  "\u4FDD\u5B58\u8D26\u53F7"
2601
3048
  ),
2602
- (0, import_react8.createElement)(
3049
+ (0, import_react9.createElement)(
2603
3050
  "button",
2604
3051
  {
2605
3052
  className: "ghost small danger",
@@ -2608,21 +3055,21 @@ function AccountRow({
2608
3055
  "\u79FB\u9664\u8D26\u53F7"
2609
3056
  )
2610
3057
  ),
2611
- (0, import_react8.createElement)("hr", { style: { border: 0, borderTop: "1px solid var(--m-line)", width: "100%" } }),
2612
- (0, import_react8.createElement)(
3058
+ (0, import_react9.createElement)("hr", { style: { border: 0, borderTop: "1px solid var(--m-line)", width: "100%" } }),
3059
+ (0, import_react9.createElement)(
2613
3060
  Field,
2614
3061
  { label: "\u5BC6\u7801 / \u5E94\u7528\u4E13\u7528\u5BC6\u7801\uFF08\u4E0D\u5199\u5165\u914D\u7F6E\u6587\u4EF6\uFF09" },
2615
- (0, import_react8.createElement)("input", {
3062
+ (0, import_react9.createElement)("input", {
2616
3063
  type: "password",
2617
3064
  value: password,
2618
3065
  onChange: (event) => setPassword(event.target.value)
2619
3066
  })
2620
3067
  ),
2621
- (0, import_react8.createElement)(
3068
+ (0, import_react9.createElement)(
2622
3069
  "div",
2623
3070
  { style: { display: "flex", gap: "8px", alignItems: "center" } },
2624
- (0, import_react8.createElement)(Toggle, { checked: remember, onChange: setRemember, label: "\u8BB0\u4F4F\u5BC6\u7801" }),
2625
- (0, import_react8.createElement)(
3071
+ (0, import_react9.createElement)(Toggle, { checked: remember, onChange: setRemember, label: "\u8BB0\u4F4F\u5BC6\u7801" }),
3072
+ (0, import_react9.createElement)(
2626
3073
  "button",
2627
3074
  {
2628
3075
  className: "primary small",
@@ -2634,15 +3081,14 @@ function AccountRow({
2634
3081
  },
2635
3082
  "\u767B\u5F55"
2636
3083
  ),
2637
- (0, import_react8.createElement)("button", { className: "ghost small", onClick: () => void wrap(onLogout) }, "\u9000\u51FA\u767B\u5F55")
2638
- ),
2639
- checkResult === "" ? null : (0, import_react8.createElement)("div", { className: "small muted" }, checkResult)
3084
+ (0, import_react9.createElement)("button", { className: "ghost small", onClick: () => void wrap(onLogout) }, "\u9000\u51FA\u767B\u5F55")
3085
+ )
2640
3086
  ) : null
2641
3087
  );
2642
3088
  }
2643
3089
 
2644
3090
  // src/client/index.tsx
2645
- var VERSION = "0.3.0";
3091
+ var VERSION = "0.4.1";
2646
3092
  var inject = ["slots"];
2647
3093
  var PANEL_ID = "mailharbor";
2648
3094
  var STYLE_ID = "mailharbor-style";