@zaclinley/dsh-mailharbor 0.2.1 → 0.4.0

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/* ---------- 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/* ---------- 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");
@@ -123,6 +123,44 @@ var api = {
123
123
  }),
124
124
  logout: (id) => request(`accounts/${encodeURIComponent(id)}/logout`, { method: "POST" }),
125
125
  check: (id) => request(`accounts/${encodeURIComponent(id)}/check`, { method: "POST" }),
126
+ aiStatus: (accountId) => request(`ai/status${query({ accountId: accountId ?? null })}`),
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
+ }),
143
+ aiSummary: (payload) => request("ai/summary", {
144
+ method: "POST",
145
+ body: JSON.stringify(payload)
146
+ }),
147
+ aiReply: (payload) => request("ai/reply", {
148
+ method: "POST",
149
+ body: JSON.stringify(payload)
150
+ }),
151
+ aiTranslate: (payload) => request("ai/translate", {
152
+ method: "POST",
153
+ body: JSON.stringify(payload)
154
+ }),
155
+ aiStyle: (accountId) => request(`ai/style${query({ accountId: accountId ?? null })}`),
156
+ aiSaveStyle: (accountId, profile) => request(`ai/style${query({ accountId: accountId ?? null })}`, {
157
+ method: "PUT",
158
+ body: JSON.stringify({ accountId, profile })
159
+ }),
160
+ aiLearnStyle: (accountId) => request(`ai/style/learn${query({ accountId: accountId ?? null })}`, {
161
+ method: "POST",
162
+ body: JSON.stringify({ accountId: accountId ?? null })
163
+ }),
126
164
  settings: () => request("settings"),
127
165
  saveSettings: (patch) => request("settings", { method: "PATCH", body: JSON.stringify(patch) }),
128
166
  backupStatus: (accountId) => request(`backup/status${query({ accountId: accountId ?? null })}`),
@@ -217,17 +255,451 @@ function MailIcon() {
217
255
  }
218
256
 
219
257
  // src/client/panel.tsx
220
- var import_react7 = require("react");
258
+ var import_react8 = require("react");
221
259
 
222
- // src/client/account-form.tsx
260
+ // src/client/ai-rail.tsx
223
261
  var import_react3 = require("react");
224
262
 
225
- // src/client/components.tsx
263
+ // src/client/icons.tsx
226
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");
227
699
  var import_react_dom = require("react-dom");
228
700
  function Avatar({ email, size = 26 }) {
229
701
  const letter = (email.trim()[0] ?? "?").toUpperCase();
230
- return (0, import_react2.createElement)(
702
+ return (0, import_react4.createElement)(
231
703
  "span",
232
704
  {
233
705
  className: "avatar",
@@ -246,12 +718,12 @@ function Field({
246
718
  children,
247
719
  hint
248
720
  }) {
249
- return (0, import_react2.createElement)(
721
+ return (0, import_react4.createElement)(
250
722
  "div",
251
723
  { className: "mailharbor-field" },
252
- (0, import_react2.createElement)("label", null, label),
724
+ (0, import_react4.createElement)("label", null, label),
253
725
  children,
254
- 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)
255
727
  );
256
728
  }
257
729
  function Toggle({
@@ -259,10 +731,10 @@ function Toggle({
259
731
  onChange,
260
732
  label
261
733
  }) {
262
- return (0, import_react2.createElement)(
734
+ return (0, import_react4.createElement)(
263
735
  "label",
264
736
  { className: "mailharbor-toggle" },
265
- (0, import_react2.createElement)("input", {
737
+ (0, import_react4.createElement)("input", {
266
738
  type: "checkbox",
267
739
  checked,
268
740
  onChange: (event) => {
@@ -288,7 +760,7 @@ function Modal({
288
760
  onClose,
289
761
  wide = false
290
762
  }) {
291
- (0, import_react2.useEffect)(() => {
763
+ (0, import_react4.useEffect)(() => {
292
764
  const onKey = (event) => {
293
765
  if (event.key === "Escape") onClose();
294
766
  };
@@ -298,7 +770,7 @@ function Modal({
298
770
  };
299
771
  }, [onClose]);
300
772
  return (0, import_react_dom.createPortal)(
301
- (0, import_react2.createElement)(
773
+ (0, import_react4.createElement)(
302
774
  "div",
303
775
  {
304
776
  className: "mailharbor-modal-mask",
@@ -306,7 +778,7 @@ function Modal({
306
778
  if (event.target === event.currentTarget) onClose();
307
779
  }
308
780
  },
309
- (0, import_react2.createElement)(
781
+ (0, import_react4.createElement)(
310
782
  "div",
311
783
  {
312
784
  className: "mailharbor-modal",
@@ -315,27 +787,27 @@ function Modal({
315
787
  "aria-modal": "true",
316
788
  "aria-label": title
317
789
  },
318
- (0, import_react2.createElement)(
790
+ (0, import_react4.createElement)(
319
791
  "header",
320
792
  null,
321
793
  title,
322
- (0, import_react2.createElement)("span", { className: "spacer" }),
323
- (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")
324
796
  ),
325
- (0, import_react2.createElement)("div", { className: "content" }, children),
326
- 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)
327
799
  )
328
800
  ),
329
801
  portalRoot()
330
802
  );
331
803
  }
332
804
  function ErrorBar({ message, onDismiss }) {
333
- return (0, import_react2.createElement)(
805
+ return (0, import_react4.createElement)(
334
806
  "div",
335
807
  { className: "error", role: "alert" },
336
- (0, import_react2.createElement)("span", null, message),
337
- (0, import_react2.createElement)("span", { className: "spacer" }),
338
- (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")
339
811
  );
340
812
  }
341
813
  function shortDate(iso) {
@@ -370,27 +842,27 @@ function formatBytes(size) {
370
842
  function domainOf(email) {
371
843
  return email.split("@")[1] ?? "example.com";
372
844
  }
373
- function errorText(error) {
845
+ function errorText2(error) {
374
846
  return error instanceof ApiError || error instanceof Error ? error.message : String(error);
375
847
  }
376
848
  function AddAccountForm({
377
849
  onDone,
378
850
  onCancel
379
851
  }) {
380
- const [email, setEmail] = (0, import_react3.useState)("");
381
- const [password, setPassword] = (0, import_react3.useState)("");
382
- const [label, setLabel] = (0, import_react3.useState)("");
383
- const [remember, setRemember] = (0, import_react3.useState)(true);
384
- const [advanced, setAdvanced] = (0, import_react3.useState)(false);
385
- const [imapHost, setImapHost] = (0, import_react3.useState)("");
386
- const [imapPort, setImapPort] = (0, import_react3.useState)("993");
387
- const [imapSecure, setImapSecure] = (0, import_react3.useState)(true);
388
- const [smtpHost, setSmtpHost] = (0, import_react3.useState)("");
389
- const [smtpPort, setSmtpPort] = (0, import_react3.useState)("465");
390
- const [smtpSecure, setSmtpSecure] = (0, import_react3.useState)(true);
391
- const [fromName, setFromName] = (0, import_react3.useState)("");
392
- const [busy, setBusy] = (0, import_react3.useState)(false);
393
- 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)("");
394
866
  const submit = async () => {
395
867
  setBusy(true);
396
868
  setError("");
@@ -417,22 +889,22 @@ function AddAccountForm({
417
889
  });
418
890
  onDone(state);
419
891
  } catch (caught) {
420
- setError(errorText(caught));
892
+ setError(errorText2(caught));
421
893
  } finally {
422
894
  setBusy(false);
423
895
  }
424
896
  };
425
- return (0, import_react3.createElement)(
897
+ return (0, import_react5.createElement)(
426
898
  "div",
427
899
  { className: "mailharbor-card" },
428
- (0, import_react3.createElement)("h3", null, "\u6DFB\u52A0\u90AE\u7BB1\u8D26\u53F7"),
429
- (0, import_react3.createElement)(
900
+ (0, import_react5.createElement)("h3", null, "\u6DFB\u52A0\u90AE\u7BB1\u8D26\u53F7"),
901
+ (0, import_react5.createElement)(
430
902
  "div",
431
903
  { className: "mailharbor-grid" },
432
- (0, import_react3.createElement)(
904
+ (0, import_react5.createElement)(
433
905
  Field,
434
906
  { label: "\u90AE\u7BB1\u5730\u5740" },
435
- (0, import_react3.createElement)("input", {
907
+ (0, import_react5.createElement)("input", {
436
908
  type: "email",
437
909
  value: email,
438
910
  placeholder: "you@example.com",
@@ -440,95 +912,95 @@ function AddAccountForm({
440
912
  autoFocus: true
441
913
  })
442
914
  ),
443
- (0, import_react3.createElement)(
915
+ (0, import_react5.createElement)(
444
916
  Field,
445
917
  { label: "\u5BC6\u7801 / \u5E94\u7528\u4E13\u7528\u5BC6\u7801" },
446
- (0, import_react3.createElement)("input", {
918
+ (0, import_react5.createElement)("input", {
447
919
  type: "password",
448
920
  value: password,
449
921
  onChange: (event) => setPassword(event.target.value)
450
922
  })
451
923
  )
452
924
  ),
453
- (0, import_react3.createElement)(
925
+ (0, import_react5.createElement)(
454
926
  Field,
455
927
  { label: "\u663E\u793A\u540D\u79F0\uFF08\u53EF\u9009\uFF09" },
456
- (0, import_react3.createElement)("input", {
928
+ (0, import_react5.createElement)("input", {
457
929
  value: label,
458
930
  placeholder: "\u5DE5\u4F5C\u90AE\u7BB1",
459
931
  onChange: (event) => setLabel(event.target.value)
460
932
  })
461
933
  ),
462
- (0, import_react3.createElement)(Toggle, { checked: remember, onChange: setRemember, label: "\u8BB0\u4F4F\u5BC6\u7801\uFF08\u5199\u5165\u7CFB\u7EDF\u94A5\u5319\u4E32\uFF09" }),
463
- (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)(
464
936
  "button",
465
937
  { className: "ghost", onClick: () => setAdvanced(!advanced) },
466
938
  advanced ? "\u9690\u85CF\u9AD8\u7EA7\u8BBE\u7F6E" : "\u9AD8\u7EA7\u8BBE\u7F6E\uFF08\u670D\u52A1\u5668\u4E0E\u7AEF\u53E3\uFF09"
467
939
  ),
468
- advanced ? (0, import_react3.createElement)(
940
+ advanced ? (0, import_react5.createElement)(
469
941
  "div",
470
942
  { style: { display: "flex", flexDirection: "column", gap: "10px" } },
471
- (0, import_react3.createElement)(
943
+ (0, import_react5.createElement)(
472
944
  "div",
473
945
  { className: "mailharbor-grid three" },
474
- (0, import_react3.createElement)(
946
+ (0, import_react5.createElement)(
475
947
  Field,
476
948
  { label: "IMAP \u4E3B\u673A" },
477
- (0, import_react3.createElement)("input", {
949
+ (0, import_react5.createElement)("input", {
478
950
  value: imapHost,
479
951
  placeholder: `imap.${domainOf(email)}`,
480
952
  onChange: (event) => setImapHost(event.target.value)
481
953
  })
482
954
  ),
483
- (0, import_react3.createElement)(
955
+ (0, import_react5.createElement)(
484
956
  Field,
485
957
  { label: "\u7AEF\u53E3" },
486
- (0, import_react3.createElement)("input", {
958
+ (0, import_react5.createElement)("input", {
487
959
  value: imapPort,
488
960
  inputMode: "numeric",
489
961
  onChange: (event) => setImapPort(event.target.value)
490
962
  })
491
963
  ),
492
- (0, import_react3.createElement)(Toggle, { checked: imapSecure, onChange: setImapSecure, label: "TLS" })
964
+ (0, import_react5.createElement)(Toggle, { checked: imapSecure, onChange: setImapSecure, label: "TLS" })
493
965
  ),
494
- (0, import_react3.createElement)(
966
+ (0, import_react5.createElement)(
495
967
  "div",
496
968
  { className: "mailharbor-grid three" },
497
- (0, import_react3.createElement)(
969
+ (0, import_react5.createElement)(
498
970
  Field,
499
971
  { label: "SMTP \u4E3B\u673A" },
500
- (0, import_react3.createElement)("input", {
972
+ (0, import_react5.createElement)("input", {
501
973
  value: smtpHost,
502
974
  placeholder: `smtp.${domainOf(email)}`,
503
975
  onChange: (event) => setSmtpHost(event.target.value)
504
976
  })
505
977
  ),
506
- (0, import_react3.createElement)(
978
+ (0, import_react5.createElement)(
507
979
  Field,
508
980
  { label: "\u7AEF\u53E3" },
509
- (0, import_react3.createElement)("input", {
981
+ (0, import_react5.createElement)("input", {
510
982
  value: smtpPort,
511
983
  inputMode: "numeric",
512
984
  onChange: (event) => setSmtpPort(event.target.value)
513
985
  })
514
986
  ),
515
- (0, import_react3.createElement)(Toggle, { checked: smtpSecure, onChange: setSmtpSecure, label: "TLS" })
987
+ (0, import_react5.createElement)(Toggle, { checked: smtpSecure, onChange: setSmtpSecure, label: "TLS" })
516
988
  ),
517
- (0, import_react3.createElement)(
989
+ (0, import_react5.createElement)(
518
990
  Field,
519
991
  { label: "\u53D1\u4EF6\u4EBA\u540D\u79F0\uFF08\u53EF\u9009\uFF09" },
520
- (0, import_react3.createElement)("input", {
992
+ (0, import_react5.createElement)("input", {
521
993
  value: fromName,
522
994
  onChange: (event) => setFromName(event.target.value)
523
995
  })
524
996
  )
525
997
  ) : null,
526
- error === "" ? null : (0, import_react3.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
527
- (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)(
528
1000
  "div",
529
1001
  { style: { display: "flex", gap: "8px", justifyContent: "flex-end" } },
530
- onCancel === void 0 ? null : (0, import_react3.createElement)("button", { className: "ghost", onClick: onCancel }, "\u53D6\u6D88"),
531
- (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)(
532
1004
  "button",
533
1005
  {
534
1006
  className: "primary",
@@ -541,10 +1013,10 @@ function AddAccountForm({
541
1013
  );
542
1014
  }
543
1015
  function LoginForm({ account, onDone }) {
544
- const [password, setPassword] = (0, import_react3.useState)("");
545
- const [remember, setRemember] = (0, import_react3.useState)(true);
546
- const [busy, setBusy] = (0, import_react3.useState)(false);
547
- 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)("");
548
1020
  const submit = async () => {
549
1021
  setBusy(true);
550
1022
  setError("");
@@ -552,24 +1024,24 @@ function LoginForm({ account, onDone }) {
552
1024
  await api.login(account.id, password, remember);
553
1025
  onDone();
554
1026
  } catch (caught) {
555
- setError(errorText(caught));
1027
+ setError(errorText2(caught));
556
1028
  } finally {
557
1029
  setBusy(false);
558
1030
  }
559
1031
  };
560
- return (0, import_react3.createElement)(
1032
+ return (0, import_react5.createElement)(
561
1033
  "div",
562
1034
  { className: "mailharbor-card" },
563
- (0, import_react3.createElement)("h3", null, `\u767B\u5F55 ${account.email}`),
564
- (0, import_react3.createElement)(
1035
+ (0, import_react5.createElement)("h3", null, `\u767B\u5F55 ${account.email}`),
1036
+ (0, import_react5.createElement)(
565
1037
  "p",
566
1038
  { className: "small muted" },
567
1039
  `${account.imap.host}:${String(account.imap.port)} \xB7 ${account.smtp.host}:${String(account.smtp.port)}`
568
1040
  ),
569
- (0, import_react3.createElement)(
1041
+ (0, import_react5.createElement)(
570
1042
  Field,
571
1043
  { label: "\u5BC6\u7801 / \u5E94\u7528\u4E13\u7528\u5BC6\u7801" },
572
- (0, import_react3.createElement)("input", {
1044
+ (0, import_react5.createElement)("input", {
573
1045
  type: "password",
574
1046
  value: password,
575
1047
  onChange: (event) => setPassword(event.target.value),
@@ -579,9 +1051,9 @@ function LoginForm({ account, onDone }) {
579
1051
  autoFocus: true
580
1052
  })
581
1053
  ),
582
- (0, import_react3.createElement)(Toggle, { checked: remember, onChange: setRemember, label: "\u8BB0\u4F4F\u5BC6\u7801\uFF08\u5199\u5165\u7CFB\u7EDF\u94A5\u5319\u4E32\uFF09" }),
583
- error === "" ? null : (0, import_react3.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
584
- (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)(
585
1057
  "button",
586
1058
  { className: "primary", onClick: () => void submit(), disabled: busy || password === "" },
587
1059
  busy ? "\u767B\u5F55\u4E2D\u2026" : "\u767B\u5F55"
@@ -592,10 +1064,10 @@ function AddAccountModal({
592
1064
  onClose,
593
1065
  onDone
594
1066
  }) {
595
- return (0, import_react3.createElement)(
1067
+ return (0, import_react5.createElement)(
596
1068
  Modal,
597
1069
  { title: "\u6DFB\u52A0\u90AE\u7BB1\u8D26\u53F7", onClose },
598
- (0, import_react3.createElement)(AddAccountForm, {
1070
+ (0, import_react5.createElement)(AddAccountForm, {
599
1071
  onDone: (state) => {
600
1072
  onDone(state);
601
1073
  onClose();
@@ -606,8 +1078,8 @@ function AddAccountModal({
606
1078
  }
607
1079
 
608
1080
  // src/client/composer.tsx
609
- var import_react4 = require("react");
610
- function errorText2(error) {
1081
+ var import_react6 = require("react");
1082
+ function errorText3(error) {
611
1083
  return error instanceof ApiError || error instanceof Error ? error.message : String(error);
612
1084
  }
613
1085
  function readFileBase64(file) {
@@ -627,25 +1099,138 @@ function Composer({
627
1099
  onClose,
628
1100
  onSent
629
1101
  }) {
630
- const [to, setTo] = (0, import_react4.useState)(initial.to);
631
- const [cc, setCc] = (0, import_react4.useState)(initial.cc);
632
- const [bcc, setBcc] = (0, import_react4.useState)(initial.bcc);
633
- const [subject, setSubject] = (0, import_react4.useState)(initial.subject);
634
- const [showCc, setShowCc] = (0, import_react4.useState)(initial.cc !== "" || initial.bcc !== "");
635
- const [attachments, setAttachments] = (0, import_react4.useState)(initial.attachments ?? []);
636
- const [busy, setBusy] = (0, import_react4.useState)(false);
637
- const [error, setError] = (0, import_react4.useState)("");
638
- const [confirming, setConfirming] = (0, import_react4.useState)(false);
639
- const [draftId, setDraftId] = (0, import_react4.useState)(initial.draftId);
640
- const editor = (0, import_react4.useRef)(null);
641
- (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)(() => {
642
1125
  if (editor.current !== null) editor.current.innerHTML = initial.html;
1126
+ setHasBody((editor.current?.innerHTML ?? "").trim() !== "");
643
1127
  }, []);
644
1128
  const html = () => editor.current?.innerHTML ?? "";
645
1129
  const text = () => editor.current?.innerText ?? "";
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
+ }
1211
+ };
1212
+ const translate = async (target) => {
1213
+ if (editor.current === null) return;
1214
+ if (html().trim() === "") {
1215
+ setError("\u6B63\u6587\u4E3A\u7A7A\uFF0C\u65E0\u6CD5\u7FFB\u8BD1");
1216
+ return;
1217
+ }
1218
+ setPendingLang(null);
1219
+ setTranslating(target);
1220
+ setError("");
1221
+ try {
1222
+ const result = await api.aiTranslate({ accountId: account.id, html: html(), target });
1223
+ pushVersion(result.html, target === "en" ? "\u7FFB\u8BD1 EN" : "\u7FFB\u8BD1 \u4E2D");
1224
+ } catch (caught) {
1225
+ setError(errorText3(caught));
1226
+ } finally {
1227
+ setTranslating(null);
1228
+ }
1229
+ };
646
1230
  const exec = (command, value) => {
647
1231
  editor.current?.focus();
648
1232
  document.execCommand(command, false, value);
1233
+ markDirty();
649
1234
  };
650
1235
  const addFiles = async (files) => {
651
1236
  if (files === null || files.length === 0) return;
@@ -662,29 +1247,15 @@ function Composer({
662
1247
  total += file.size;
663
1248
  }
664
1249
  setAttachments(next);
1250
+ markDirty();
665
1251
  };
666
1252
  const saveDraft = async () => {
667
1253
  setBusy(true);
668
1254
  setError("");
669
- try {
670
- const { draft } = await api.saveDraft(account.id, {
671
- ...draftId === void 0 ? {} : { id: draftId },
672
- to,
673
- cc,
674
- bcc,
675
- subject,
676
- html: html(),
677
- attachments,
678
- ...initial.inReplyTo === void 0 ? {} : { inReplyTo: initial.inReplyTo },
679
- ...initial.references === void 0 ? {} : { references: initial.references }
680
- });
681
- setDraftId(draft.id);
682
- onSent("\u8349\u7A3F\u5DF2\u4FDD\u5B58");
683
- } catch (caught) {
684
- setError(errorText2(caught));
685
- } finally {
686
- setBusy(false);
687
- }
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");
688
1259
  };
689
1260
  const send = async () => {
690
1261
  setBusy(true);
@@ -710,35 +1281,36 @@ function Composer({
710
1281
  onSent("\u90AE\u4EF6\u5DF2\u53D1\u9001");
711
1282
  onClose();
712
1283
  } catch (caught) {
713
- setError(errorText2(caught));
1284
+ setError(errorText3(caught));
714
1285
  } finally {
715
1286
  setBusy(false);
716
1287
  setConfirming(false);
717
1288
  }
718
1289
  };
719
- return (0, import_react4.createElement)(
1290
+ return (0, import_react6.createElement)(
720
1291
  Modal,
721
1292
  {
722
1293
  title: initial.mode === "reply" ? "\u56DE\u590D" : initial.mode === "replyAll" ? "\u5168\u90E8\u56DE\u590D" : initial.mode === "forward" ? "\u8F6C\u53D1" : "\u5199\u90AE\u4EF6",
723
- onClose,
1294
+ onClose: close,
724
1295
  wide: true,
725
- footer: (0, import_react4.createElement)(
1296
+ footer: (0, import_react6.createElement)(
726
1297
  "div",
727
- { style: { display: "flex", gap: "8px", alignItems: "center", width: "100%" } },
728
- (0, import_react4.createElement)("span", { className: "small muted" }, `\u53D1\u4EF6\u4EBA ${account.email}`),
729
- (0, import_react4.createElement)("span", { className: "spacer" }),
730
- (0, import_react4.createElement)("button", { className: "ghost", onClick: () => void saveDraft(), disabled: busy }, "\u5B58\u8349\u7A3F"),
731
- 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)(
732
1304
  "span",
733
1305
  { style: { display: "inline-flex", gap: "6px", alignItems: "center" } },
734
- (0, import_react4.createElement)("span", { className: "small" }, "\u786E\u5B9A\u53D1\u9001\uFF1F"),
735
- (0, import_react4.createElement)(
1306
+ (0, import_react6.createElement)("span", { className: "small" }, "\u786E\u5B9A\u53D1\u9001\uFF1F"),
1307
+ (0, import_react6.createElement)(
736
1308
  "button",
737
1309
  { className: "primary", onClick: () => void send(), disabled: busy },
738
1310
  busy ? "\u53D1\u9001\u4E2D\u2026" : "\u786E\u5B9A"
739
1311
  ),
740
- (0, import_react4.createElement)("button", { className: "ghost", onClick: () => setConfirming(false) }, "\u53D6\u6D88")
741
- ) : (0, import_react4.createElement)(
1312
+ (0, import_react6.createElement)("button", { className: "ghost", onClick: () => setConfirming(false) }, "\u53D6\u6D88")
1313
+ ) : (0, import_react6.createElement)(
742
1314
  "button",
743
1315
  {
744
1316
  className: "primary",
@@ -749,62 +1321,75 @@ function Composer({
749
1321
  )
750
1322
  )
751
1323
  },
752
- error === "" ? null : (0, import_react4.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
753
- (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)(
754
1326
  "div",
755
1327
  { className: "mailharbor-grid" },
756
- (0, import_react4.createElement)(
1328
+ (0, import_react6.createElement)(
757
1329
  Field,
758
1330
  { label: "\u6536\u4EF6\u4EBA" },
759
- (0, import_react4.createElement)("input", {
1331
+ (0, import_react6.createElement)("input", {
760
1332
  value: to,
761
- onChange: (event) => setTo(event.target.value),
1333
+ onChange: (event) => {
1334
+ setTo(event.target.value);
1335
+ markDirty();
1336
+ },
762
1337
  placeholder: "a@example.com, b@example.com"
763
1338
  })
764
1339
  ),
765
- (0, import_react4.createElement)(
1340
+ (0, import_react6.createElement)(
766
1341
  Field,
767
1342
  { label: "\u4E3B\u9898" },
768
- (0, import_react4.createElement)("input", {
1343
+ (0, import_react6.createElement)("input", {
769
1344
  value: subject,
770
- onChange: (event) => setSubject(event.target.value)
1345
+ onChange: (event) => {
1346
+ setSubject(event.target.value);
1347
+ markDirty();
1348
+ },
1349
+ placeholder: "\u90AE\u4EF6\u4E3B\u9898"
771
1350
  })
772
1351
  )
773
1352
  ),
774
- showCc ? (0, import_react4.createElement)(
1353
+ showCc ? (0, import_react6.createElement)(
775
1354
  "div",
776
1355
  { className: "mailharbor-grid" },
777
- (0, import_react4.createElement)(
1356
+ (0, import_react6.createElement)(
778
1357
  Field,
779
1358
  { label: "\u6284\u9001" },
780
- (0, import_react4.createElement)("input", {
1359
+ (0, import_react6.createElement)("input", {
781
1360
  value: cc,
782
- onChange: (event) => setCc(event.target.value)
1361
+ onChange: (event) => {
1362
+ setCc(event.target.value);
1363
+ markDirty();
1364
+ }
783
1365
  })
784
1366
  ),
785
- (0, import_react4.createElement)(
1367
+ (0, import_react6.createElement)(
786
1368
  Field,
787
1369
  { label: "\u5BC6\u9001" },
788
- (0, import_react4.createElement)("input", {
1370
+ (0, import_react6.createElement)("input", {
789
1371
  value: bcc,
790
- onChange: (event) => setBcc(event.target.value)
1372
+ onChange: (event) => {
1373
+ setBcc(event.target.value);
1374
+ markDirty();
1375
+ }
791
1376
  })
792
1377
  )
793
- ) : (0, import_react4.createElement)(
1378
+ ) : (0, import_react6.createElement)(
794
1379
  "button",
795
1380
  { className: "subtle small", style: { alignSelf: "flex-start" }, onClick: () => setShowCc(true) },
796
1381
  "\u6DFB\u52A0\u6284\u9001 / \u5BC6\u9001"
797
1382
  ),
798
- (0, import_react4.createElement)(
1383
+ (0, import_react6.createElement)(
799
1384
  "div",
800
1385
  { className: "mailharbor-toolbar" },
801
- (0, import_react4.createElement)("button", { onClick: () => exec("bold"), title: "\u52A0\u7C97" }, "B"),
802
- (0, import_react4.createElement)("button", { onClick: () => exec("italic"), title: "\u659C\u4F53" }, "I"),
803
- (0, import_react4.createElement)("button", { onClick: () => exec("underline"), title: "\u4E0B\u5212\u7EBF" }, "U"),
804
- (0, import_react4.createElement)("button", { onClick: () => exec("insertUnorderedList"), title: "\u65E0\u5E8F\u5217\u8868" }, "\u2022 \u5217\u8868"),
805
- (0, import_react4.createElement)("button", { onClick: () => exec("insertOrderedList"), title: "\u6709\u5E8F\u5217\u8868" }, "1. \u5217\u8868"),
806
- (0, import_react4.createElement)("button", { onClick: () => exec("formatBlock", "blockquote"), title: "\u5F15\u7528" }, "\u5F15\u7528"),
807
- (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)(
808
1393
  "button",
809
1394
  {
810
1395
  onClick: () => {
@@ -815,13 +1400,44 @@ function Composer({
815
1400
  },
816
1401
  "\u94FE\u63A5"
817
1402
  ),
818
- (0, import_react4.createElement)("button", { onClick: () => exec("removeFormat"), title: "\u6E05\u9664\u683C\u5F0F" }, "\u6E05\u9664"),
819
- (0, import_react4.createElement)("span", { className: "spacer" }),
820
- (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)(
1427
+ "button",
1428
+ { onClick: () => setPendingLang("zh"), disabled: translating !== null, title: "AI \u7FFB\u8BD1\u6210\u4E2D\u6587" },
1429
+ translating === "zh" ? "\u2026" : "\u4E2D"
1430
+ ),
1431
+ (0, import_react6.createElement)(
1432
+ "button",
1433
+ { onClick: () => setPendingLang("en"), disabled: translating !== null, title: "AI \u7FFB\u8BD1\u6210\u82F1\u6587" },
1434
+ translating === "en" ? "\u2026" : "EN"
1435
+ ),
1436
+ (0, import_react6.createElement)(
821
1437
  "label",
822
1438
  { className: "mailharbor-toggle small" },
823
1439
  "\u9644\u4EF6",
824
- (0, import_react4.createElement)("input", {
1440
+ (0, import_react6.createElement)("input", {
825
1441
  type: "file",
826
1442
  multiple: true,
827
1443
  style: { display: "none" },
@@ -832,93 +1448,92 @@ function Composer({
832
1448
  })
833
1449
  )
834
1450
  ),
835
- attachments.length === 0 ? 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)(
1470
+ "div",
1471
+ { className: "mailharbor-confirm" },
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")
1476
+ ),
1477
+ attachments.length === 0 ? null : (0, import_react6.createElement)(
836
1478
  "div",
837
1479
  { className: "mailharbor-attachments", style: { padding: 0, border: 0 } },
838
1480
  attachments.map(
839
- (file, index) => (0, import_react4.createElement)(
1481
+ (file, index) => (0, import_react6.createElement)(
840
1482
  "span",
841
1483
  { className: "mailharbor-attachment", key: `${file.filename}:${String(index)}` },
842
- (0, import_react4.createElement)("span", { className: "name" }, file.filename),
843
- (0, import_react4.createElement)(
1484
+ (0, import_react6.createElement)("span", { className: "name" }, file.filename),
1485
+ (0, import_react6.createElement)(
844
1486
  "button",
845
1487
  {
846
1488
  className: "subtle",
847
- onClick: () => setAttachments(attachments.filter((_item, position) => position !== index))
1489
+ onClick: () => {
1490
+ setAttachments(attachments.filter((_item, position) => position !== index));
1491
+ markDirty();
1492
+ }
848
1493
  },
849
1494
  "\u2715"
850
1495
  )
851
1496
  )
852
1497
  )
853
1498
  ),
854
- (0, import_react4.createElement)("div", {
1499
+ (0, import_react6.createElement)("div", {
855
1500
  className: "mailharbor-editor",
856
1501
  ref: editor,
857
1502
  contentEditable: true,
858
1503
  suppressContentEditableWarning: true,
859
1504
  "data-placeholder": "\u5199\u70B9\u4EC0\u4E48\u2026",
860
- spellCheck: true
861
- })
862
- );
863
- }
864
-
865
- // src/client/icons.tsx
866
- var import_react5 = require("react");
867
- var PATHS = {
868
- refresh: (0, import_react5.createElement)("path", { d: "M20 11a8 8 0 1 0-2.3 5.7M20 5v6h-6" }),
869
- plus: (0, import_react5.createElement)("path", { d: "M12 5v14M5 12h14" }),
870
- 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" }),
871
- archive: (0, import_react5.createElement)(
872
- "g",
873
- null,
874
- (0, import_react5.createElement)("rect", { x: 3, y: 4, width: 18, height: 5, rx: 1.5 }),
875
- (0, import_react5.createElement)("path", { d: "M5 9v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V9M10 13h4" })
876
- ),
877
- paperclip: (0, import_react5.createElement)("path", {
878
- 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"
879
- }),
880
- mail: (0, import_react5.createElement)(
881
- "g",
882
- null,
883
- (0, import_react5.createElement)("rect", { x: 3, y: 5, width: 18, height: 14, rx: 2.5 }),
884
- (0, import_react5.createElement)("path", { d: "m4 7 7.1 5a1.6 1.6 0 0 0 1.8 0L20 7" })
885
- ),
886
- 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" }),
887
- reply: (0, import_react5.createElement)("path", { d: "M9 7 4 12l5 5M4 12h9a6 6 0 0 1 6 6v1" }),
888
- 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" }),
889
- forward: (0, import_react5.createElement)("path", { d: "m15 7 5 5-5 5M20 12h-9a6 6 0 0 0-6 6v1" }),
890
- check: (0, import_react5.createElement)("path", { d: "m5 13 4 4L19 7" }),
891
- image: (0, import_react5.createElement)(
892
- "g",
893
- null,
894
- (0, import_react5.createElement)("rect", { x: 3, y: 4, width: 18, height: 16, rx: 2 }),
895
- (0, import_react5.createElement)("circle", { cx: 8.5, cy: 9.5, r: 1.5 }),
896
- (0, import_react5.createElement)("path", { d: "m4 18 5-5 4 4 3-3 4 4" })
897
- ),
898
- close: (0, import_react5.createElement)("path", { d: "M6 6l12 12M18 6 6 18" }),
899
- chevronLeft: (0, import_react5.createElement)("path", { d: "m14 6-6 6 6 6" })
900
- };
901
- function Icon({ name, size = 16 }) {
902
- return (0, import_react5.createElement)(
903
- "svg",
904
- {
905
- width: size,
906
- height: size,
907
- viewBox: "0 0 24 24",
908
- fill: "none",
909
- stroke: "currentColor",
910
- strokeWidth: 1.7,
911
- strokeLinecap: "round",
912
- strokeLinejoin: "round",
913
- "aria-hidden": "true",
914
- focusable: "false"
915
- },
916
- 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
+ )
917
1532
  );
918
1533
  }
919
1534
 
920
1535
  // src/client/reader.tsx
921
- var import_react6 = require("react");
1536
+ var import_react7 = require("react");
922
1537
  var IFRAME_STYLE = `
923
1538
  html, body { margin: 0; padding: 0; }
924
1539
  body {
@@ -956,20 +1571,23 @@ function hasRemoteImages(detail) {
956
1571
  function addressLine(list) {
957
1572
  return list.map((item) => item.name !== "" ? `${item.name} <${item.address}>` : item.address).join("\u3001");
958
1573
  }
1574
+ function errorText4(error) {
1575
+ return error instanceof Error ? error.message : String(error);
1576
+ }
959
1577
  function Reader({
960
1578
  detail,
961
- account,
962
1579
  source,
963
1580
  mailbox,
964
1581
  onChanged,
965
1582
  onReply,
1583
+ onAiAction,
966
1584
  onClose
967
1585
  }) {
968
- const [plain, setPlain] = (0, import_react6.useState)(false);
969
- const [loadImages, setLoadImages] = (0, import_react6.useState)(false);
970
- const [busy, setBusy] = (0, import_react6.useState)(false);
971
- const [error, setError] = (0, import_react6.useState)("");
972
- 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]);
973
1591
  const html = detail.html !== null && detail.html !== "";
974
1592
  const toggleRead = async () => {
975
1593
  setBusy(true);
@@ -977,34 +1595,34 @@ function Reader({
977
1595
  try {
978
1596
  const result = await api.setRead({
979
1597
  id: detail.id,
980
- accountId: account.id,
1598
+ accountId: detail.accountId,
981
1599
  source,
982
1600
  mailbox,
983
1601
  read: detail.unread
984
1602
  });
985
1603
  onChanged({ unread: !result.read });
986
1604
  } catch (caught) {
987
- setError(caught instanceof Error ? caught.message : String(caught));
1605
+ setError(errorText4(caught));
988
1606
  } finally {
989
1607
  setBusy(false);
990
1608
  }
991
1609
  };
992
- return (0, import_react6.createElement)(
1610
+ return (0, import_react7.createElement)(
993
1611
  "div",
994
1612
  { className: "mailharbor-reader" },
995
- (0, import_react6.createElement)(
1613
+ (0, import_react7.createElement)(
996
1614
  "div",
997
1615
  { className: "mailharbor-reader-head" },
998
- (0, import_react6.createElement)(
1616
+ (0, import_react7.createElement)(
999
1617
  "div",
1000
1618
  { className: "mailharbor-reader-title" },
1001
- onClose === void 0 ? null : (0, import_react6.createElement)(
1619
+ onClose === void 0 ? null : (0, import_react7.createElement)(
1002
1620
  "button",
1003
1621
  { className: "ghost icon", onClick: onClose, "aria-label": "\u8FD4\u56DE\u5217\u8868", title: "\u8FD4\u56DE\u5217\u8868" },
1004
- (0, import_react6.createElement)(Icon, { name: "chevronLeft" })
1622
+ (0, import_react7.createElement)(Icon, { name: "chevronLeft" })
1005
1623
  ),
1006
- (0, import_react6.createElement)("h2", { style: { flex: 1 } }, detail.subject),
1007
- (0, import_react6.createElement)(
1624
+ (0, import_react7.createElement)("h2", { style: { flex: 1 } }, detail.subject),
1625
+ (0, import_react7.createElement)(
1008
1626
  "button",
1009
1627
  {
1010
1628
  className: "ghost icon",
@@ -1013,91 +1631,100 @@ function Reader({
1013
1631
  title: detail.unread ? "\u6807\u4E3A\u5DF2\u8BFB" : "\u6807\u4E3A\u672A\u8BFB",
1014
1632
  "aria-label": detail.unread ? "\u6807\u4E3A\u5DF2\u8BFB" : "\u6807\u4E3A\u672A\u8BFB"
1015
1633
  },
1016
- (0, import_react6.createElement)(Icon, { name: "check" })
1634
+ (0, import_react7.createElement)(Icon, { name: "check" })
1017
1635
  )
1018
1636
  ),
1019
- (0, import_react6.createElement)(
1637
+ (0, import_react7.createElement)(
1020
1638
  "div",
1021
1639
  { className: "mailharbor-reader-meta" },
1022
- (0, import_react6.createElement)(
1640
+ (0, import_react7.createElement)(
1023
1641
  "span",
1024
1642
  { className: "who" },
1025
- (0, import_react6.createElement)(Avatar, { email: detail.from.address, size: 22 }),
1026
- (0, import_react6.createElement)("strong", null, detail.from.name !== "" ? detail.from.name : detail.from.address),
1027
- 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
1028
1646
  ),
1029
- (0, import_react6.createElement)("span", null, longDate(detail.date)),
1030
- 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
1031
1649
  ),
1032
- (0, import_react6.createElement)(
1650
+ (0, import_react7.createElement)(
1033
1651
  "div",
1034
1652
  { className: "mailharbor-reader-meta" },
1035
- (0, import_react6.createElement)("span", null, `\u6536\u4EF6\u4EBA: ${addressLine(detail.to) || "\u2014"}`),
1036
- 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)}`)
1037
1655
  ),
1038
- (0, import_react6.createElement)(
1656
+ (0, import_react7.createElement)(
1039
1657
  "div",
1040
1658
  { className: "mailharbor-reader-actions" },
1041
- (0, import_react6.createElement)(
1659
+ (0, import_react7.createElement)(
1042
1660
  "button",
1043
1661
  { className: "ghost", onClick: () => onReply("reply") },
1044
- (0, import_react6.createElement)(Icon, { name: "reply" }),
1662
+ (0, import_react7.createElement)(Icon, { name: "reply" }),
1045
1663
  "\u56DE\u590D"
1046
1664
  ),
1047
- (0, import_react6.createElement)(
1665
+ (0, import_react7.createElement)(
1048
1666
  "button",
1049
1667
  { className: "ghost", onClick: () => onReply("replyAll") },
1050
- (0, import_react6.createElement)(Icon, { name: "replyAll" }),
1668
+ (0, import_react7.createElement)(Icon, { name: "replyAll" }),
1051
1669
  "\u5168\u90E8\u56DE\u590D"
1052
1670
  ),
1053
- (0, import_react6.createElement)(
1671
+ (0, import_react7.createElement)(
1054
1672
  "button",
1055
1673
  { className: "ghost", onClick: () => onReply("forward") },
1056
- (0, import_react6.createElement)(Icon, { name: "forward" }),
1674
+ (0, import_react7.createElement)(Icon, { name: "forward" }),
1057
1675
  "\u8F6C\u53D1"
1058
1676
  ),
1059
- (0, import_react6.createElement)("span", { className: "spacer" }),
1060
- 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)(
1061
1688
  "button",
1062
1689
  { className: "subtle", onClick: () => setPlain(!plain) },
1063
1690
  plain ? "\u663E\u793A HTML" : "\u7EAF\u6587\u672C"
1064
1691
  ) : null,
1065
- remoteImages && !loadImages ? (0, import_react6.createElement)(
1692
+ remoteImages && !loadImages ? (0, import_react7.createElement)(
1066
1693
  "button",
1067
1694
  { className: "subtle", onClick: () => setLoadImages(true) },
1068
- (0, import_react6.createElement)(Icon, { name: "image", size: 14 }),
1695
+ (0, import_react7.createElement)(Icon, { name: "image", size: 14 }),
1069
1696
  "\u663E\u793A\u56FE\u7247"
1070
1697
  ) : null
1071
1698
  ),
1072
- 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)
1073
1700
  ),
1074
- (0, import_react6.createElement)(
1701
+ (0, import_react7.createElement)(
1075
1702
  "div",
1076
1703
  { className: "mailharbor-reader-body" },
1077
- html && !plain ? (0, import_react6.createElement)("iframe", {
1704
+ html && !plain ? (0, import_react7.createElement)("iframe", {
1078
1705
  title: "\u90AE\u4EF6\u6B63\u6587",
1079
1706
  sandbox: "",
1080
1707
  srcDoc: srcDoc(detail, loadImages),
1081
1708
  referrerPolicy: "no-referrer"
1082
- }) : (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")
1083
1710
  ),
1084
- detail.attachments.length === 0 ? null : (0, import_react6.createElement)(
1711
+ detail.attachments.length === 0 ? null : (0, import_react7.createElement)(
1085
1712
  "div",
1086
1713
  { className: "mailharbor-attachments" },
1087
1714
  detail.attachments.map(
1088
- (file) => (0, import_react6.createElement)(
1715
+ (file) => (0, import_react7.createElement)(
1089
1716
  "a",
1090
1717
  {
1091
1718
  className: "mailharbor-attachment",
1092
1719
  key: file.id,
1093
1720
  href: source === "server" ? api.attachmentUrl({
1094
- accountId: account.id,
1721
+ accountId: detail.accountId,
1095
1722
  source: "server",
1096
1723
  mailbox,
1097
1724
  uid: detail.uid ?? "",
1098
1725
  part: file.id
1099
1726
  }) : api.attachmentUrl({
1100
- accountId: account.id,
1727
+ accountId: detail.accountId,
1101
1728
  source: "local",
1102
1729
  emailId: detail.id,
1103
1730
  attachmentId: file.id
@@ -1105,9 +1732,9 @@ function Reader({
1105
1732
  download: file.filename,
1106
1733
  title: file.filename
1107
1734
  },
1108
- (0, import_react6.createElement)(Icon, { name: "paperclip", size: 14 }),
1109
- (0, import_react6.createElement)("span", { className: "name" }, file.filename),
1110
- (0, import_react6.createElement)("span", { className: "muted small" }, formatBytes(file.size))
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))
1111
1738
  )
1112
1739
  )
1113
1740
  )
@@ -1116,7 +1743,8 @@ function Reader({
1116
1743
 
1117
1744
  // src/client/panel.tsx
1118
1745
  var EMPTY_PAGE = { items: [], total: 0, offset: 0, limit: 40 };
1119
- function errorText3(error) {
1746
+ var DRAFTS = "__drafts";
1747
+ function errorText5(error) {
1120
1748
  return error instanceof ApiError || error instanceof Error ? error.message : String(error);
1121
1749
  }
1122
1750
  function matchesNarrow() {
@@ -1127,61 +1755,71 @@ function quoteForReply(detail) {
1127
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>`;
1128
1756
  }
1129
1757
  function MailPanel() {
1130
- const [state, setState] = (0, import_react7.useState)(null);
1131
- const [error, setError] = (0, import_react7.useState)("");
1132
- const [source, setSource] = (0, import_react7.useState)("local");
1133
- const [mailbox, setMailbox] = (0, import_react7.useState)("all");
1134
- const [folders, setFolders] = (0, import_react7.useState)([]);
1135
- const [query2, setQuery] = (0, import_react7.useState)("");
1136
- const [search, setSearch] = (0, import_react7.useState)("");
1137
- const [page, setPage] = (0, import_react7.useState)(EMPTY_PAGE);
1138
- const [limit, setLimit] = (0, import_react7.useState)(40);
1139
- const [loading, setLoading] = (0, import_react7.useState)(false);
1140
- const [selected, setSelected] = (0, import_react7.useState)(null);
1141
- const [compose, setCompose] = (0, import_react7.useState)(null);
1142
- const [showAdd, setShowAdd] = (0, import_react7.useState)(false);
1143
- const [showBackup, setShowBackup] = (0, import_react7.useState)(false);
1144
- const [backup, setBackup] = (0, import_react7.useState)(null);
1145
- const [toast, setToast] = (0, import_react7.useState)("");
1146
- const [narrow, setNarrow] = (0, import_react7.useState)(() => matchesNarrow());
1147
- const [tick, setTick] = (0, import_react7.useState)(0);
1148
- const account = state?.accounts.find((item) => item.id === state.activeAccountId) ?? null;
1149
- const accountId = account?.id ?? null;
1150
- const refresh = (0, import_react7.useCallback)(() => {
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);
1780
+ const accounts = state?.accounts ?? [];
1781
+ const isAll = accountScope === "all";
1782
+ const account = accounts.find((item) => item.id === accountScope) ?? null;
1783
+ const composeAccount = accounts.find((item) => item.id === compose?.accountId) ?? account ?? accounts[0] ?? null;
1784
+ const showingDrafts = mailbox === DRAFTS && source === "local";
1785
+ const refresh = (0, import_react8.useCallback)(() => {
1151
1786
  setTick((value) => value + 1);
1152
1787
  }, []);
1153
- const notice = (0, import_react7.useCallback)((message) => {
1788
+ const notice = (0, import_react8.useCallback)((message) => {
1154
1789
  setToast(message);
1155
1790
  window.setTimeout(() => {
1156
1791
  setToast("");
1157
1792
  }, 3200);
1158
1793
  }, []);
1159
- (0, import_react7.useEffect)(() => {
1794
+ (0, import_react8.useEffect)(() => {
1160
1795
  let cancelled = false;
1161
1796
  api.state().then((next) => {
1162
- if (!cancelled) {
1163
- setState(next);
1164
- if (next.settings.render.listLimit > 0) setLimit(next.settings.render.listLimit);
1165
- }
1797
+ if (cancelled) return;
1798
+ setState(next);
1799
+ if (next.settings.render.listLimit > 0) setLimit(next.settings.render.listLimit);
1800
+ setAccountScope((current) => current ?? next.activeAccountId);
1166
1801
  }).catch((caught) => {
1167
- if (!cancelled) setError(errorText3(caught));
1802
+ if (!cancelled) setError(errorText5(caught));
1168
1803
  });
1169
1804
  return () => {
1170
1805
  cancelled = true;
1171
1806
  };
1172
1807
  }, [tick]);
1173
- (0, import_react7.useEffect)(() => {
1174
- if (accountId === null) return;
1808
+ (0, import_react8.useEffect)(() => {
1809
+ if (accountScope === null) return;
1175
1810
  let cancelled = false;
1176
- api.folders({ accountId, source }).then((result) => {
1811
+ api.folders({ accountId: accountScope, source }).then((result) => {
1177
1812
  if (cancelled) return;
1178
1813
  const items2 = source === "local" ? [
1179
1814
  {
1180
1815
  name: "all",
1181
1816
  label: "\u5168\u90E8\u90AE\u4EF6",
1182
- count: result.items.reduce((sum, item) => sum + item.count, 0)
1817
+ count: result.items.filter((item) => item.name !== DRAFTS).reduce((sum, item) => sum + item.count, 0)
1183
1818
  },
1184
- ...result.items.map((item) => ({ ...item, label: item.label ?? item.name }))
1819
+ ...result.items.map((item) => ({
1820
+ ...item,
1821
+ label: item.name === DRAFTS ? "\u8349\u7A3F" : item.label ?? item.name
1822
+ }))
1185
1823
  ] : result.items.map((item) => ({ ...item, label: item.label ?? item.name }));
1186
1824
  setFolders(items2);
1187
1825
  }).catch(() => {
@@ -1190,27 +1828,57 @@ function MailPanel() {
1190
1828
  return () => {
1191
1829
  cancelled = true;
1192
1830
  };
1193
- }, [accountId, source, tick]);
1194
- (0, import_react7.useEffect)(() => {
1831
+ }, [accountScope, source, tick]);
1832
+ (0, import_react8.useEffect)(() => {
1195
1833
  setMailbox(source === "local" ? "all" : "INBOX");
1196
1834
  setSelected(null);
1835
+ setLimit(state?.settings.render.listLimit ?? 40);
1197
1836
  }, [source]);
1198
- (0, import_react7.useEffect)(() => {
1199
- if (accountId === null) return;
1837
+ (0, import_react8.useEffect)(() => {
1838
+ if (accountScope === "all" && source === "server") setSource("local");
1839
+ setSelected(null);
1840
+ }, [accountScope, source]);
1841
+ (0, import_react8.useEffect)(() => {
1842
+ if (accountScope === null) return;
1200
1843
  let cancelled = false;
1201
1844
  setLoading(true);
1202
- api.messages({ accountId, source, mailbox, query: search, limit }).then((result) => {
1203
- if (!cancelled) setPage(result);
1845
+ if (showingDrafts) {
1846
+ const targets = isAll ? accounts : account === null ? [] : [account];
1847
+ Promise.all(
1848
+ targets.map(async (target) => {
1849
+ try {
1850
+ const result = await api.drafts(target.id);
1851
+ return result.items.map((draft) => ({ ...draft, accountId: target.id }));
1852
+ } catch {
1853
+ return [];
1854
+ }
1855
+ })
1856
+ ).then((groups) => {
1857
+ if (cancelled) return;
1858
+ setDrafts(groups.flat().sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)));
1859
+ setPage(EMPTY_PAGE);
1860
+ }).finally(() => {
1861
+ if (!cancelled) setLoading(false);
1862
+ });
1863
+ return () => {
1864
+ cancelled = true;
1865
+ };
1866
+ }
1867
+ api.messages({ accountId: accountScope, source, mailbox, query: search, limit }).then((result) => {
1868
+ if (!cancelled) {
1869
+ setPage(result);
1870
+ setDrafts([]);
1871
+ }
1204
1872
  }).catch((caught) => {
1205
- if (!cancelled) setError(errorText3(caught));
1873
+ if (!cancelled) setError(errorText5(caught));
1206
1874
  }).finally(() => {
1207
1875
  if (!cancelled) setLoading(false);
1208
1876
  });
1209
1877
  return () => {
1210
1878
  cancelled = true;
1211
1879
  };
1212
- }, [accountId, source, mailbox, search, limit, tick]);
1213
- (0, import_react7.useEffect)(() => {
1880
+ }, [accountScope, source, mailbox, search, limit, tick]);
1881
+ (0, import_react8.useEffect)(() => {
1214
1882
  if (typeof window.matchMedia !== "function") return;
1215
1883
  const media = window.matchMedia("(max-width: 900px)");
1216
1884
  const onChange = () => setNarrow(media.matches);
@@ -1219,12 +1887,12 @@ function MailPanel() {
1219
1887
  media.removeEventListener("change", onChange);
1220
1888
  };
1221
1889
  }, []);
1222
- (0, import_react7.useEffect)(() => {
1223
- if (!showBackup || accountId === null) return;
1890
+ (0, import_react8.useEffect)(() => {
1891
+ if (!showBackup || accountScope === null || isAll) return;
1224
1892
  let cancelled = false;
1225
1893
  const poll = async () => {
1226
1894
  try {
1227
- const status = await api.backupStatus(accountId);
1895
+ const status = await api.backupStatus(accountScope);
1228
1896
  if (!cancelled) setBackup(status);
1229
1897
  } catch {
1230
1898
  }
@@ -1237,16 +1905,17 @@ function MailPanel() {
1237
1905
  cancelled = true;
1238
1906
  window.clearInterval(timer);
1239
1907
  };
1240
- }, [showBackup, accountId]);
1908
+ }, [showBackup, accountScope, isAll]);
1241
1909
  const openMessage = async (item) => {
1242
- if (account === null) return;
1910
+ const owner = item.accountId;
1911
+ const ownerSource = source;
1243
1912
  setError("");
1244
1913
  try {
1245
- const detail = await api.message({ id: item.id, accountId: account.id, source, mailbox });
1914
+ const detail = await api.message({ id: item.id, accountId: owner, source: ownerSource, mailbox });
1246
1915
  setSelected(detail);
1247
1916
  if (detail.unread && state?.settings.read.markOnOpen === true) {
1248
1917
  try {
1249
- await api.setRead({ id: detail.id, accountId: account.id, source, mailbox, read: true });
1918
+ await api.setRead({ id: detail.id, accountId: owner, source: ownerSource, mailbox, read: true });
1250
1919
  setSelected({ ...detail, unread: false });
1251
1920
  setPage((current) => ({
1252
1921
  ...current,
@@ -1256,14 +1925,15 @@ function MailPanel() {
1256
1925
  }
1257
1926
  }
1258
1927
  } catch (caught) {
1259
- setError(errorText3(caught));
1928
+ setError(errorText5(caught));
1260
1929
  }
1261
1930
  };
1262
1931
  const startCompose = (mode) => {
1263
- if (account === null) return;
1932
+ const owner = account ?? accounts.find((item) => item.id === state?.activeAccountId) ?? accounts[0];
1933
+ if (owner === void 0) return;
1264
1934
  const detail = selected;
1265
1935
  if (mode === "new" || detail === null) {
1266
- setCompose({ mode: "new", to: "", cc: "", bcc: "", subject: "", html: "" });
1936
+ setCompose({ mode: "new", accountId: owner.id, to: "", cc: "", bcc: "", subject: "", html: "" });
1267
1937
  return;
1268
1938
  }
1269
1939
  const subjectPrefix = mode === "forward" ? "Fwd: " : "Re: ";
@@ -1271,6 +1941,7 @@ function MailPanel() {
1271
1941
  if (mode === "forward") {
1272
1942
  setCompose({
1273
1943
  mode,
1944
+ accountId: detail.accountId,
1274
1945
  to: "",
1275
1946
  cc: "",
1276
1947
  bcc: "",
@@ -1281,9 +1952,10 @@ function MailPanel() {
1281
1952
  return;
1282
1953
  }
1283
1954
  const to = detail.from.address;
1284
- const cc = mode === "replyAll" ? [...detail.to, ...detail.cc].map((item) => item.address).filter((address) => address !== account.email && address !== to).join(", ") : "";
1955
+ const cc = mode === "replyAll" ? [...detail.to, ...detail.cc].map((item) => item.address).filter((address) => address !== owner.email && address !== to).join(", ") : "";
1285
1956
  setCompose({
1286
1957
  mode,
1958
+ accountId: detail.accountId,
1287
1959
  to,
1288
1960
  cc,
1289
1961
  bcc: "",
@@ -1292,35 +1964,98 @@ function MailPanel() {
1292
1964
  ...detail.messageId === "" ? {} : { inReplyTo: detail.messageId, references: [...detail.references, detail.messageId].join(" ") }
1293
1965
  });
1294
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
+ };
2004
+ const openDraft = (draft) => {
2005
+ setCompose({
2006
+ mode: "new",
2007
+ accountId: draft.accountId,
2008
+ to: draft.to,
2009
+ cc: draft.cc,
2010
+ bcc: draft.bcc,
2011
+ subject: draft.subject,
2012
+ html: draft.html,
2013
+ attachments: draft.attachments,
2014
+ draftId: draft.id
2015
+ });
2016
+ };
2017
+ const removeDraft = async (id, accountId) => {
2018
+ try {
2019
+ await api.deleteDraft(id, accountId);
2020
+ setDrafts((current) => current.filter((draft) => draft.id !== id));
2021
+ notice("\u8349\u7A3F\u5DF2\u5220\u9664");
2022
+ refresh();
2023
+ } catch (caught) {
2024
+ setError(errorText5(caught));
2025
+ }
2026
+ };
1295
2027
  const switchAccount = async (id) => {
1296
2028
  try {
1297
- await api.activateAccount(id);
2029
+ if (id !== "all") await api.activateAccount(id);
2030
+ setAccountScope(id);
1298
2031
  setSelected(null);
2032
+ if (id === "all") setSource("local");
1299
2033
  refresh();
1300
2034
  } catch (caught) {
1301
- setError(errorText3(caught));
2035
+ setError(errorText5(caught));
1302
2036
  }
1303
2037
  };
1304
2038
  const startBackup = async () => {
1305
- if (accountId === null) return;
2039
+ const owner = accountScope === null || isAll ? accounts[0] : accounts.find((item) => item.id === accountScope);
2040
+ if (owner === void 0) return;
1306
2041
  try {
1307
- const result = await api.startBackup(accountId);
2042
+ const result = await api.startBackup(owner.id);
1308
2043
  setBackup(result.status);
1309
2044
  setShowBackup(true);
1310
2045
  } catch (caught) {
1311
- setError(errorText3(caught));
2046
+ setError(errorText5(caught));
1312
2047
  }
1313
2048
  };
1314
2049
  const stopBackup = async () => {
1315
- if (accountId === null) return;
2050
+ if (accountScope === null || isAll) return;
1316
2051
  try {
1317
- const result = await api.stopBackup(accountId);
2052
+ const result = await api.stopBackup(accountScope);
1318
2053
  setBackup(result.status);
1319
2054
  } catch (caught) {
1320
- setError(errorText3(caught));
2055
+ setError(errorText5(caught));
1321
2056
  }
1322
2057
  };
1323
- const folderOptions = (0, import_react7.useMemo)(
2058
+ const folderOptions = (0, import_react8.useMemo)(
1324
2059
  () => folders.map((folder) => ({
1325
2060
  value: folder.name,
1326
2061
  label: `${folder.label ?? folder.name}${folder.count > 0 ? ` (${String(folder.count)})` : ""}`
@@ -1328,74 +2063,90 @@ function MailPanel() {
1328
2063
  [folders]
1329
2064
  );
1330
2065
  if (state === null) {
1331
- return (0, import_react7.createElement)(
2066
+ return (0, import_react8.createElement)(
1332
2067
  "div",
1333
2068
  { className: "mailharbor" },
1334
- (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)
1335
2070
  );
1336
2071
  }
1337
- if (account === null) {
1338
- return (0, import_react7.createElement)(
2072
+ if (accounts.length === 0) {
2073
+ return (0, import_react8.createElement)(
1339
2074
  "div",
1340
2075
  { className: "mailharbor" },
1341
- (0, import_react7.createElement)(
2076
+ (0, import_react8.createElement)(
1342
2077
  "div",
1343
2078
  { className: "mailharbor-settings" },
1344
- (0, import_react7.createElement)("h2", null, "\u6B22\u8FCE\u4F7F\u7528 MailHarbor"),
1345
- (0, import_react7.createElement)(
2079
+ (0, import_react8.createElement)("h2", null, "\u6B22\u8FCE\u4F7F\u7528 MailHarbor"),
2080
+ (0, import_react8.createElement)(
1346
2081
  "p",
1347
2082
  { className: "muted small" },
1348
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"
1349
2084
  ),
1350
- error === "" ? null : (0, import_react7.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
1351
- (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, {
1352
2087
  onDone: (next) => {
1353
2088
  setState(next);
2089
+ setAccountScope(next.activeAccountId);
1354
2090
  setSource("local");
1355
2091
  }
1356
2092
  })
1357
2093
  )
1358
2094
  );
1359
2095
  }
1360
- const needsLogin = source === "server" && state.loggedIn === false;
2096
+ const currentAccount = account ?? accounts[0] ?? null;
2097
+ const loggedIn = isAll ? accounts.some((item) => item.id === state.activeAccountId) : state.loggedIn;
2098
+ const needsLogin = source === "server" && !isAll && state.loggedIn === false;
1361
2099
  const items = page.items;
1362
- return (0, import_react7.createElement)(
2100
+ return (0, import_react8.createElement)(
1363
2101
  "div",
1364
2102
  { className: narrow ? "mailharbor mailharbor-narrow" : "mailharbor" },
1365
- (0, import_react7.createElement)(
2103
+ (0, import_react8.createElement)(
1366
2104
  "div",
1367
2105
  { className: "mailharbor-topbar" },
1368
- (0, import_react7.createElement)(
2106
+ (0, import_react8.createElement)(
1369
2107
  "div",
1370
2108
  { className: "mailharbor-account" },
1371
- (0, import_react7.createElement)(Avatar, { email: account.email, size: 26 }),
1372
- state.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)(
1373
2111
  "select",
1374
2112
  {
1375
- value: account.id,
2113
+ value: accountScope ?? "",
1376
2114
  onChange: (event) => void switchAccount(event.target.value),
1377
2115
  "aria-label": "\u8D26\u53F7"
1378
2116
  },
1379
- state.accounts.map((item) => (0, import_react7.createElement)("option", { key: item.id, value: item.id }, item.label))
1380
- ) : (0, import_react7.createElement)("span", { className: "name" }, account.label),
1381
- 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
1382
2122
  ),
1383
- (0, import_react7.createElement)(
2123
+ (0, import_react8.createElement)(
1384
2124
  "div",
1385
2125
  { className: "mailharbor-segment", role: "tablist" },
1386
- (0, import_react7.createElement)(
2126
+ (0, import_react8.createElement)(
1387
2127
  "button",
1388
2128
  { className: source === "local" ? "active" : "", onClick: () => setSource("local"), role: "tab" },
1389
2129
  "\u672C\u5730\u5907\u4EFD"
1390
2130
  ),
1391
- (0, import_react7.createElement)(
2131
+ (0, import_react8.createElement)(
1392
2132
  "button",
1393
- { className: source === "server" ? "active" : "", onClick: () => setSource("server"), role: "tab" },
2133
+ {
2134
+ className: source === "server" ? "active" : "",
2135
+ onClick: () => {
2136
+ if (isAll) {
2137
+ notice("\u7EDF\u4E00\u6536\u4EF6\u7BB1\u53EA\u652F\u6301\u672C\u5730\u5907\u4EFD\uFF0C\u5207\u6362\u5230\u5355\u4E2A\u8D26\u53F7\u53EF\u4F7F\u7528\u670D\u52A1\u5668\u5B9E\u65F6");
2138
+ return;
2139
+ }
2140
+ setSource("server");
2141
+ },
2142
+ role: "tab",
2143
+ title: isAll ? "\u7EDF\u4E00\u6536\u4EF6\u7BB1\u53EA\u652F\u6301\u672C\u5730\u5907\u4EFD" : "\u8BFB\u53D6\u670D\u52A1\u5668\u4E0A\u7684\u6700\u65B0\u90AE\u4EF6"
2144
+ },
1394
2145
  "\u670D\u52A1\u5668\u5B9E\u65F6"
1395
2146
  )
1396
2147
  ),
1397
- (0, import_react7.createElement)("span", { className: "spacer" }),
1398
- (0, import_react7.createElement)(
2148
+ (0, import_react8.createElement)("span", { className: "spacer" }),
2149
+ (0, import_react8.createElement)(
1399
2150
  "button",
1400
2151
  {
1401
2152
  className: "ghost icon",
@@ -1403,38 +2154,56 @@ function MailPanel() {
1403
2154
  "aria-label": "\u6DFB\u52A0\u8D26\u53F7",
1404
2155
  onClick: () => setShowAdd(true)
1405
2156
  },
1406
- (0, import_react7.createElement)(Icon, { name: "plus" })
2157
+ (0, import_react8.createElement)(Icon, { name: "plus" })
1407
2158
  ),
1408
- (0, import_react7.createElement)(
2159
+ (0, import_react8.createElement)(
1409
2160
  "button",
1410
2161
  { className: "ghost icon", title: "\u5237\u65B0\u5217\u8868", "aria-label": "\u5237\u65B0\u5217\u8868", onClick: refresh },
1411
- (0, import_react7.createElement)(Icon, { name: "refresh" })
2162
+ (0, import_react8.createElement)(Icon, { name: "refresh" })
1412
2163
  ),
1413
- (0, import_react7.createElement)(
2164
+ (0, import_react8.createElement)(
1414
2165
  "button",
1415
- { className: "ghost", onClick: () => void startBackup() },
1416
- (0, import_react7.createElement)(Icon, { name: "archive" }),
2166
+ {
2167
+ className: "ghost",
2168
+ onClick: () => void startBackup(),
2169
+ title: isAll ? "\u5907\u4EFD\u5F53\u524D\u6D3B\u52A8\u8D26\u53F7" : "\u5907\u4EFD\u8FD9\u4E2A\u8D26\u53F7"
2170
+ },
2171
+ (0, import_react8.createElement)(Icon, { name: "archive" }),
1417
2172
  "\u5907\u4EFD"
1418
2173
  ),
1419
- (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)(
1420
2185
  "button",
1421
2186
  { className: "primary", onClick: () => startCompose("new") },
1422
- (0, import_react7.createElement)(Icon, { name: "compose" }),
2187
+ (0, import_react8.createElement)(Icon, { name: "compose" }),
1423
2188
  "\u5199\u90AE\u4EF6"
1424
2189
  )
1425
2190
  ),
1426
- toast === "" ? null : (0, import_react7.createElement)("div", { className: "small", style: { padding: "6px 14px", color: "var(--m-ok)" } }, toast),
1427
- error === "" ? null : (0, import_react7.createElement)(ErrorBar, { message: error, onDismiss: () => setError("") }),
1428
- needsLogin ? (0, import_react7.createElement)("div", { className: "mailharbor-settings" }, (0, import_react7.createElement)(LoginForm, { account, onDone: refresh })) : (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)(
2194
+ "div",
2195
+ { className: "mailharbor-settings" },
2196
+ (0, import_react8.createElement)(LoginForm, { account: currentAccount, onDone: refresh })
2197
+ ) : (0, import_react8.createElement)(
1429
2198
  "div",
1430
2199
  { className: "mailharbor-body", "data-mode": narrow && selected !== null ? "reader" : "list" },
1431
- (0, import_react7.createElement)(
2200
+ (0, import_react8.createElement)(
1432
2201
  "div",
1433
2202
  { className: "mailharbor-list" },
1434
- (0, import_react7.createElement)(
2203
+ (0, import_react8.createElement)(
1435
2204
  "div",
1436
2205
  { className: "mailharbor-filters" },
1437
- (0, import_react7.createElement)(
2206
+ (0, import_react8.createElement)(
1438
2207
  "select",
1439
2208
  {
1440
2209
  value: mailbox,
@@ -1445,12 +2214,12 @@ function MailPanel() {
1445
2214
  "aria-label": "\u90AE\u7BB1\u6587\u4EF6\u5939"
1446
2215
  },
1447
2216
  folderOptions.map(
1448
- (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)
1449
2218
  )
1450
2219
  ),
1451
- (0, import_react7.createElement)("input", {
2220
+ (0, import_react8.createElement)("input", {
1452
2221
  type: "search",
1453
- placeholder: "\u641C\u7D22\u90AE\u4EF6",
2222
+ placeholder: showingDrafts ? "\u641C\u7D22\u8349\u7A3F" : "\u641C\u7D22\u90AE\u4EF6",
1454
2223
  "aria-label": "\u641C\u7D22\u90AE\u4EF6",
1455
2224
  value: query2,
1456
2225
  onChange: (event) => setQuery(event.target.value),
@@ -1462,7 +2231,7 @@ function MailPanel() {
1462
2231
  }
1463
2232
  }
1464
2233
  }),
1465
- query2 !== search || search !== "" ? (0, import_react7.createElement)(
2234
+ query2 !== search || search !== "" ? (0, import_react8.createElement)(
1466
2235
  "button",
1467
2236
  {
1468
2237
  className: "subtle",
@@ -1472,32 +2241,98 @@ function MailPanel() {
1472
2241
  setSearch("");
1473
2242
  }
1474
2243
  },
1475
- (0, import_react7.createElement)(Icon, { name: "close", size: 14 })
2244
+ (0, import_react8.createElement)(Icon, { name: "close", size: 14 })
1476
2245
  ) : null
1477
2246
  ),
1478
- (0, import_react7.createElement)(
2247
+ (0, import_react8.createElement)(
1479
2248
  "div",
1480
2249
  { className: "mailharbor-rows" },
1481
- loading && items.length === 0 ? (0, import_react7.createElement)("div", { className: "mailharbor-empty" }, "\u6B63\u5728\u8BFB\u53D6\u2026") : items.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)(
2251
+ "div",
2252
+ { className: "mailharbor-empty" },
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")
2256
+ ) : drafts.filter(
2257
+ (draft) => search === "" ? true : `${draft.subject} ${draft.to} ${draft.cc}`.toLowerCase().includes(search.toLowerCase())
2258
+ ).map(
2259
+ (draft) => (0, import_react8.createElement)(
2260
+ "div",
2261
+ {
2262
+ key: draft.id,
2263
+ className: "mailharbor-row",
2264
+ role: "button",
2265
+ tabIndex: 0,
2266
+ onClick: () => openDraft(draft),
2267
+ onKeyDown: (event) => {
2268
+ if (event.key === "Enter" || event.key === " ") {
2269
+ event.preventDefault();
2270
+ openDraft(draft);
2271
+ }
2272
+ }
2273
+ },
2274
+ (0, import_react8.createElement)(
2275
+ "span",
2276
+ { className: "line-1" },
2277
+ (0, import_react8.createElement)(
2278
+ "span",
2279
+ { className: "from" },
2280
+ `\u8349\u7A3F${draft.to === "" ? "" : ` \xB7 \u6536\u4EF6\u4EBA ${draft.to}`}`
2281
+ ),
2282
+ isAll ? (0, import_react8.createElement)(
2283
+ "span",
2284
+ { className: "badge" },
2285
+ accounts.find((item) => item.id === draft.accountId)?.label ?? ""
2286
+ ) : null,
2287
+ (0, import_react8.createElement)("span", { className: "date" }, shortDate(draft.updatedAt)),
2288
+ (0, import_react8.createElement)(
2289
+ "button",
2290
+ {
2291
+ className: "subtle",
2292
+ title: "\u5220\u9664\u8349\u7A3F",
2293
+ onClick: (event) => {
2294
+ event.stopPropagation();
2295
+ void removeDraft(draft.id, draft.accountId);
2296
+ }
2297
+ },
2298
+ (0, import_react8.createElement)(Icon, { name: "close", size: 13 })
2299
+ )
2300
+ ),
2301
+ (0, import_react8.createElement)(
2302
+ "span",
2303
+ { className: "line-2" },
2304
+ (0, import_react8.createElement)(
2305
+ "span",
2306
+ { className: "subject" },
2307
+ draft.subject === "" ? "(\u65E0\u4E3B\u9898)" : draft.subject
2308
+ )
2309
+ ),
2310
+ draft.html === "" ? null : (0, import_react8.createElement)(
2311
+ "span",
2312
+ { className: "snippet" },
2313
+ draft.html.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 120)
2314
+ )
2315
+ )
2316
+ ) : items.length === 0 ? (0, import_react8.createElement)(
1482
2317
  "div",
1483
2318
  { className: "mailharbor-empty" },
1484
- (0, import_react7.createElement)(Icon, { name: "mail", size: 28 }),
1485
- (0, import_react7.createElement)(
2319
+ (0, import_react8.createElement)(Icon, { name: "mail", size: 28 }),
2320
+ (0, import_react8.createElement)(
1486
2321
  "div",
1487
2322
  { className: "title" },
1488
2323
  source === "local" ? "\u672C\u5730\u5907\u4EFD\u91CC\u8FD8\u6CA1\u6709\u90AE\u4EF6" : "\u670D\u52A1\u5668\u4E0A\u6CA1\u6709\u90AE\u4EF6"
1489
2324
  ),
1490
- (0, import_react7.createElement)(
2325
+ (0, import_react8.createElement)(
1491
2326
  "div",
1492
2327
  { className: "small" },
1493
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"
1494
2329
  )
1495
2330
  ) : items.map(
1496
- (item) => (0, import_react7.createElement)(
2331
+ (item) => (0, import_react8.createElement)(
1497
2332
  "div",
1498
2333
  {
1499
- key: item.id,
1500
- className: `mailharbor-row${item.unread ? " unread" : ""}${selected?.id === item.id ? " selected" : ""}`,
2334
+ key: `${item.accountId}:${item.id}`,
2335
+ className: `mailharbor-row${item.unread ? " unread" : ""}${selected?.id === item.id && selected?.accountId === item.accountId ? " selected" : ""}`,
1501
2336
  role: "button",
1502
2337
  tabIndex: 0,
1503
2338
  onClick: () => void openMessage(item),
@@ -1508,102 +2343,127 @@ function MailPanel() {
1508
2343
  }
1509
2344
  }
1510
2345
  },
1511
- (0, import_react7.createElement)(
2346
+ (0, import_react8.createElement)(
1512
2347
  "span",
1513
2348
  { className: "line-1" },
1514
- item.unread ? (0, import_react7.createElement)("span", { className: "dot" }) : null,
1515
- (0, import_react7.createElement)(
2349
+ item.unread ? (0, import_react8.createElement)("span", { className: "dot" }) : null,
2350
+ (0, import_react8.createElement)(
1516
2351
  "span",
1517
2352
  { className: "from" },
1518
2353
  item.from.name !== "" ? item.from.name : item.from.address
1519
2354
  ),
1520
- item.hasAttachments ? (0, import_react7.createElement)(Icon, { name: "paperclip", size: 13 }) : null,
1521
- item.backedUp === false ? (0, import_react7.createElement)("span", { className: "badge warn" }, "\u672A\u5907\u4EFD") : null,
1522
- (0, import_react7.createElement)("span", { className: "date" }, shortDate(item.date))
2355
+ item.hasAttachments ? (0, import_react8.createElement)(Icon, { name: "paperclip", size: 13 }) : null,
2356
+ isAll ? (0, import_react8.createElement)(
2357
+ "span",
2358
+ { className: "badge" },
2359
+ accounts.find((entry) => entry.id === item.accountId)?.label ?? ""
2360
+ ) : null,
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))
2363
+ ),
2364
+ (0, import_react8.createElement)(
2365
+ "span",
2366
+ { className: "line-2" },
2367
+ (0, import_react8.createElement)("span", { className: "subject" }, item.subject)
1523
2368
  ),
1524
- (0, import_react7.createElement)("span", { className: "line-2" }, (0, import_react7.createElement)("span", { className: "subject" }, item.subject)),
1525
- 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)
1526
2370
  )
1527
2371
  )
1528
2372
  ),
1529
- items.length > 0 ? (0, import_react7.createElement)(
2373
+ !showingDrafts && items.length > 0 ? (0, import_react8.createElement)(
1530
2374
  "div",
1531
2375
  { className: "mailharbor-list-foot" },
1532
- (0, import_react7.createElement)("span", null, `${String(items.length)} / ${String(page.total)} \u5C01`),
1533
- (0, import_react7.createElement)("span", { className: "spacer" }),
1534
- 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)(
1535
2379
  "button",
1536
2380
  { className: "subtle", onClick: () => setLimit((current) => current + 40) },
1537
2381
  "\u52A0\u8F7D\u66F4\u591A"
1538
2382
  ) : null
2383
+ ) : showingDrafts && drafts.length > 0 ? (0, import_react8.createElement)(
2384
+ "div",
2385
+ { className: "mailharbor-list-foot" },
2386
+ (0, import_react8.createElement)("span", null, `${String(drafts.length)} \u5C01\u8349\u7A3F`)
1539
2387
  ) : null
1540
2388
  ),
1541
- (0, import_react7.createElement)(
2389
+ (0, import_react8.createElement)(
1542
2390
  "div",
1543
2391
  { className: "mailharbor-reader" },
1544
- selected === null ? (0, import_react7.createElement)(
2392
+ selected === null ? (0, import_react8.createElement)(
1545
2393
  "div",
1546
2394
  { className: "mailharbor-empty" },
1547
- (0, import_react7.createElement)(Icon, { name: "mail", size: 34 }),
1548
- (0, import_react7.createElement)("div", { className: "title" }, "\u9009\u62E9\u4E00\u5C01\u90AE\u4EF6\u5F00\u59CB\u9605\u8BFB"),
1549
- (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)(
1550
2398
  "div",
1551
2399
  { className: "small" },
1552
- state.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"
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"
1553
2401
  )
1554
- ) : (0, import_react7.createElement)(Reader, {
2402
+ ) : (0, import_react8.createElement)(Reader, {
1555
2403
  detail: selected,
1556
- account,
1557
2404
  source,
1558
2405
  mailbox,
1559
2406
  onChanged: (patch) => setSelected({ ...selected, ...patch }),
1560
2407
  onReply: (mode) => startCompose(mode),
2408
+ onAiAction: requestAi,
1561
2409
  onClose: narrow ? () => setSelected(null) : void 0
1562
2410
  })
1563
- )
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
1564
2423
  ),
1565
- showBackup && backup !== null ? (0, import_react7.createElement)(
2424
+ showBackup && backup !== null ? (0, import_react8.createElement)(
1566
2425
  Modal,
1567
2426
  {
1568
2427
  title: "\u5907\u4EFD",
1569
2428
  onClose: () => setShowBackup(false),
1570
- footer: (0, import_react7.createElement)(
2429
+ footer: (0, import_react8.createElement)(
1571
2430
  "div",
1572
2431
  { style: { display: "flex", gap: "8px", width: "100%", alignItems: "center" } },
1573
- (0, import_react7.createElement)("span", { className: "small muted" }, backup.running ? "\u5907\u4EFD\u8FDB\u884C\u4E2D\u2026" : backup.message),
1574
- (0, import_react7.createElement)("span", { className: "spacer" }),
1575
- 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"),
1576
- (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")
1577
2436
  )
1578
2437
  },
1579
- (0, import_react7.createElement)(
2438
+ (0, import_react8.createElement)(
1580
2439
  "div",
1581
2440
  { className: "mailharbor-kv" },
1582
- (0, import_react7.createElement)("dt", null, "\u5F00\u59CB\u65F6\u95F4"),
1583
- (0, import_react7.createElement)("dd", null, backup.startedAt ?? "\u2014"),
1584
- (0, import_react7.createElement)("dt", null, "\u4E0A\u6B21\u6210\u529F"),
1585
- (0, import_react7.createElement)("dd", null, backup.lastSuccessAt ?? "\u2014"),
1586
- (0, import_react7.createElement)("dt", null, "\u65B0\u589E"),
1587
- (0, import_react7.createElement)("dd", null, String(backup.added)),
1588
- (0, import_react7.createElement)("dt", null, "\u603B\u6570"),
1589
- (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))
1590
2449
  ),
1591
- (0, import_react7.createElement)(
2450
+ (0, import_react8.createElement)(
1592
2451
  "div",
1593
2452
  { className: "mailharbor-log" },
1594
2453
  backup.output.length === 0 ? "\uFF08\u7B49\u5F85\u8F93\u51FA\u2026\uFF09" : backup.output.join("\n")
1595
2454
  )
1596
2455
  ) : null,
1597
- compose !== null ? (0, import_react7.createElement)(Composer, {
1598
- account,
2456
+ compose !== null && composeAccount !== null ? (0, import_react8.createElement)(Composer, {
2457
+ account: composeAccount,
1599
2458
  initial: compose,
1600
2459
  onClose: () => setCompose(null),
1601
2460
  onSent: (message) => notice(message)
1602
2461
  }) : null,
1603
- showAdd ? (0, import_react7.createElement)(AddAccountModal, {
2462
+ showAdd ? (0, import_react8.createElement)(AddAccountModal, {
1604
2463
  onClose: () => setShowAdd(false),
1605
2464
  onDone: (next) => {
1606
2465
  setState(next);
2466
+ setAccountScope(next.activeAccountId);
1607
2467
  refresh();
1608
2468
  }
1609
2469
  }) : null
@@ -1611,27 +2471,32 @@ function MailPanel() {
1611
2471
  }
1612
2472
 
1613
2473
  // src/client/settings.tsx
1614
- var import_react8 = require("react");
1615
- function errorText4(error) {
2474
+ var import_react9 = require("react");
2475
+ function errorText6(error) {
1616
2476
  return error instanceof ApiError || error instanceof Error ? error.message : String(error);
1617
2477
  }
1618
2478
  function MailSettings() {
1619
- const [state, setState] = (0, import_react8.useState)(null);
1620
- const [settings, setSettings] = (0, import_react8.useState)(null);
1621
- const [error, setError] = (0, import_react8.useState)("");
1622
- const [toast, setToast] = (0, import_react8.useState)("");
1623
- const [signature, setSignature] = (0, import_react8.useState)("");
1624
- const [markOnOpen, setMarkOnOpen] = (0, import_react8.useState)(true);
1625
- const [pollSeconds, setPollSeconds] = (0, import_react8.useState)(60);
1626
- const [listLimit, setListLimit] = (0, import_react8.useState)(40);
1627
- const [aiEnabled, setAiEnabled] = (0, import_react8.useState)(true);
1628
- const [aiProvider, setAiProvider] = (0, import_react8.useState)("deepseek-account");
1629
- const [aiModel, setAiModel] = (0, import_react8.useState)("deepseek-flash");
1630
- const [editing, setEditing] = (0, import_react8.useState)(null);
1631
- const [checkResult, setCheckResult] = (0, import_react8.useState)("");
1632
- const [adding, setAdding] = (0, import_react8.useState)(false);
1633
- const [tick, setTick] = (0, import_react8.useState)(0);
1634
- (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 [checkResult, setCheckResult] = (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)(() => {
1635
2500
  let cancelled = false;
1636
2501
  Promise.all([api.state(), api.settings()]).then(([appState, view]) => {
1637
2502
  if (cancelled) return;
@@ -1645,12 +2510,37 @@ function MailSettings() {
1645
2510
  setAiProvider(view.ai.provider);
1646
2511
  setAiModel(view.ai.model);
1647
2512
  }).catch((caught) => {
1648
- if (!cancelled) setError(errorText4(caught));
2513
+ if (!cancelled) setError(errorText6(caught));
1649
2514
  });
1650
2515
  return () => {
1651
2516
  cancelled = true;
1652
2517
  };
1653
2518
  }, [tick]);
2519
+ (0, import_react9.useEffect)(() => {
2520
+ let cancelled = false;
2521
+ const accountId = settings?.activeAccountId ?? state?.activeAccountId ?? null;
2522
+ if (accountId === null) return;
2523
+ Promise.all([api.aiStatus(accountId), api.aiModels(), api.aiStyle(accountId)]).then(([status, models, style]) => {
2524
+ if (cancelled) return;
2525
+ setAiStatus(status);
2526
+ setAiModels(models);
2527
+ setStyleText(style.style?.profile ?? "");
2528
+ setStyleMeta({
2529
+ updatedAt: style.style?.updatedAt ?? "",
2530
+ sampleCount: style.style?.sampleCount ?? 0
2531
+ });
2532
+ const preferred = models.providers.find((item) => item.provider === status.provider) ?? models.providers[0];
2533
+ if (preferred !== void 0 && status.provider === "") {
2534
+ setAiProvider(preferred.provider);
2535
+ const firstModel = preferred.models[0];
2536
+ if (firstModel !== void 0) setAiModel(firstModel.id);
2537
+ }
2538
+ }).catch(() => {
2539
+ });
2540
+ return () => {
2541
+ cancelled = true;
2542
+ };
2543
+ }, [tick, settings?.activeAccountId]);
1654
2544
  const reload = () => setTick((value) => value + 1);
1655
2545
  const notify = (message) => {
1656
2546
  setToast(message);
@@ -1664,29 +2554,29 @@ function MailSettings() {
1664
2554
  setSettings(result.settings);
1665
2555
  notify("\u8BBE\u7F6E\u5DF2\u4FDD\u5B58");
1666
2556
  } catch (caught) {
1667
- setError(errorText4(caught));
2557
+ setError(errorText6(caught));
1668
2558
  }
1669
2559
  };
1670
2560
  const account = settings?.accounts.find((item) => item.id === settings.activeAccountId) ?? null;
1671
2561
  if (state === null || settings === null) {
1672
- 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);
1673
2563
  }
1674
- return (0, import_react8.createElement)(
2564
+ return (0, import_react9.createElement)(
1675
2565
  "div",
1676
2566
  { className: "mailharbor" },
1677
- (0, import_react8.createElement)(
2567
+ (0, import_react9.createElement)(
1678
2568
  "div",
1679
2569
  { className: "mailharbor-settings" },
1680
- (0, import_react8.createElement)("h2", null, "MailHarbor"),
1681
- (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"),
1682
- toast === "" ? null : (0, import_react8.createElement)("div", { className: "small", style: { color: "var(--m-ok)" } }, toast),
1683
- error === "" ? null : (0, import_react8.createElement)(ErrorBar, { message: error, onDismiss: () => setError("") }),
1684
- (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)(
1685
2575
  "div",
1686
2576
  { className: "mailharbor-card" },
1687
- (0, import_react8.createElement)("h3", null, "\u8D26\u53F7"),
1688
- settings.accounts.length === 0 ? (0, import_react8.createElement)(AddAccountForm, { onDone: () => reload() }) : settings.accounts.map(
1689
- (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, {
1690
2580
  key: item.id,
1691
2581
  account: item,
1692
2582
  active: item.id === settings.activeAccountId,
@@ -1716,7 +2606,7 @@ function MailSettings() {
1716
2606
  `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 ?? ""}`}`
1717
2607
  );
1718
2608
  } catch (caught) {
1719
- setCheckResult(`\u5931\u8D25: ${errorText4(caught)}`);
2609
+ setCheckResult(`\u5931\u8D25: ${errorText6(caught)}`);
1720
2610
  }
1721
2611
  },
1722
2612
  onSave: async (patch) => {
@@ -1730,32 +2620,32 @@ function MailSettings() {
1730
2620
  }
1731
2621
  })
1732
2622
  ),
1733
- adding ? (0, import_react8.createElement)(AddAccountForm, {
2623
+ adding ? (0, import_react9.createElement)(AddAccountForm, {
1734
2624
  onDone: () => {
1735
2625
  setAdding(false);
1736
2626
  reload();
1737
2627
  },
1738
2628
  onCancel: () => setAdding(false)
1739
- }) : (0, import_react8.createElement)("button", { className: "ghost", onClick: () => setAdding(true) }, "+ \u6DFB\u52A0\u8D26\u53F7")
2629
+ }) : (0, import_react9.createElement)("button", { className: "ghost", onClick: () => setAdding(true) }, "+ \u6DFB\u52A0\u8D26\u53F7")
1740
2630
  ),
1741
- (0, import_react8.createElement)(
2631
+ (0, import_react9.createElement)(
1742
2632
  "div",
1743
2633
  { className: "mailharbor-card" },
1744
- (0, import_react8.createElement)("h3", null, "\u5199\u4FE1\u4E0E\u9605\u8BFB"),
1745
- (0, import_react8.createElement)(
2634
+ (0, import_react9.createElement)("h3", null, "\u5199\u4FE1\u4E0E\u9605\u8BFB"),
2635
+ (0, import_react9.createElement)(
1746
2636
  Field,
1747
2637
  { label: "\u7B7E\u540D\uFF08\u7EAF\u6587\u672C\uFF0C\u53D1\u9001\u65F6\u8FFD\u52A0\u5230\u6B63\u6587\u672B\u5C3E\uFF09" },
1748
- (0, import_react8.createElement)("textarea", {
2638
+ (0, import_react9.createElement)("textarea", {
1749
2639
  rows: 4,
1750
2640
  value: signature,
1751
2641
  onChange: (event) => setSignature(event.target.value)
1752
2642
  })
1753
2643
  ),
1754
- (0, import_react8.createElement)(
2644
+ (0, import_react9.createElement)(
1755
2645
  "div",
1756
2646
  { style: { display: "flex", gap: "10px", alignItems: "center", flexWrap: "wrap" } },
1757
- (0, import_react8.createElement)("button", { className: "ghost", onClick: () => void saveGeneral({ signature }) }, "\u4FDD\u5B58\u7B7E\u540D"),
1758
- (0, import_react8.createElement)(Toggle, {
2647
+ (0, import_react9.createElement)("button", { className: "ghost", onClick: () => void saveGeneral({ signature }) }, "\u4FDD\u5B58\u7B7E\u540D"),
2648
+ (0, import_react9.createElement)(Toggle, {
1759
2649
  checked: markOnOpen,
1760
2650
  onChange: (value) => {
1761
2651
  setMarkOnOpen(value);
@@ -1764,13 +2654,13 @@ function MailSettings() {
1764
2654
  label: "\u6253\u5F00\u90AE\u4EF6\u65F6\u6807\u4E3A\u5DF2\u8BFB"
1765
2655
  })
1766
2656
  ),
1767
- (0, import_react8.createElement)(
2657
+ (0, import_react9.createElement)(
1768
2658
  "div",
1769
2659
  { className: "mailharbor-grid" },
1770
- (0, import_react8.createElement)(
2660
+ (0, import_react9.createElement)(
1771
2661
  Field,
1772
2662
  { label: "\u5217\u8868\u6BCF\u9875\u6570\u91CF" },
1773
- (0, import_react8.createElement)("input", {
2663
+ (0, import_react9.createElement)("input", {
1774
2664
  type: "number",
1775
2665
  min: 10,
1776
2666
  max: 200,
@@ -1779,10 +2669,10 @@ function MailSettings() {
1779
2669
  onBlur: () => void saveGeneral({ render: { listLimit } })
1780
2670
  })
1781
2671
  ),
1782
- (0, import_react8.createElement)(
2672
+ (0, import_react9.createElement)(
1783
2673
  Field,
1784
2674
  { label: "\u672A\u8BFB\u89D2\u6807\u8F6E\u8BE2\uFF08\u79D2\uFF09" },
1785
- (0, import_react8.createElement)("input", {
2675
+ (0, import_react9.createElement)("input", {
1786
2676
  type: "number",
1787
2677
  min: 15,
1788
2678
  max: 3600,
@@ -1793,66 +2683,171 @@ function MailSettings() {
1793
2683
  )
1794
2684
  )
1795
2685
  ),
1796
- (0, import_react8.createElement)(
2686
+ (0, import_react9.createElement)(
1797
2687
  "div",
1798
2688
  { className: "mailharbor-card" },
1799
- (0, import_react8.createElement)("h3", null, "AI"),
1800
- (0, import_react8.createElement)(Toggle, {
2689
+ (0, import_react9.createElement)("h3", null, "AI"),
2690
+ (0, import_react9.createElement)(Toggle, {
1801
2691
  checked: aiEnabled,
1802
2692
  onChange: (value) => {
1803
2693
  setAiEnabled(value);
1804
2694
  void saveGeneral({ ai: { enabled: value } });
1805
2695
  },
1806
- label: "\u542F\u7528 AI\uFF08\u6458\u8981\u3001\u8D77\u8349\u3001\u6DA6\u8272\u3001\u81EA\u7136\u8BED\u8A00\u641C\u7D22\uFF09"
2696
+ label: "\u542F\u7528 AI\uFF08\u6458\u8981\u3001AI \u56DE\u590D\u3001\u7FFB\u8BD1\u3001\u5199\u4F5C\u98CE\u683C\uFF09"
1807
2697
  }),
1808
- (0, import_react8.createElement)(
2698
+ aiStatus === null ? null : (0, import_react9.createElement)(
2699
+ "div",
2700
+ { className: "small", style: { color: aiStatus.available ? "var(--m-ok)" : "var(--m-warn)" } },
2701
+ 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"
2702
+ ),
2703
+ (0, import_react9.createElement)(
1809
2704
  "div",
1810
2705
  { className: "mailharbor-grid" },
1811
- (0, import_react8.createElement)(
2706
+ (0, import_react9.createElement)(
1812
2707
  Field,
1813
2708
  { label: "Provider" },
1814
- (0, import_react8.createElement)("input", {
2709
+ aiModels.providers.length === 0 ? (0, import_react9.createElement)("input", {
1815
2710
  value: aiProvider,
1816
2711
  onChange: (event) => setAiProvider(event.target.value),
1817
2712
  onBlur: () => void saveGeneral({ ai: { provider: aiProvider } })
1818
- })
2713
+ }) : (0, import_react9.createElement)(
2714
+ "select",
2715
+ {
2716
+ value: aiProvider,
2717
+ onChange: (event) => {
2718
+ setAiProvider(event.target.value);
2719
+ const provider = aiModels.providers.find((item) => item.provider === event.target.value);
2720
+ const first = provider?.models[0];
2721
+ if (first !== void 0) setAiModel(first.id);
2722
+ void saveGeneral({
2723
+ ai: {
2724
+ provider: event.target.value,
2725
+ ...first === void 0 ? {} : { model: first.id }
2726
+ }
2727
+ });
2728
+ }
2729
+ },
2730
+ aiModels.providers.map(
2731
+ (item) => (0, import_react9.createElement)("option", { key: item.provider, value: item.provider }, item.name)
2732
+ )
2733
+ )
1819
2734
  ),
1820
- (0, import_react8.createElement)(
2735
+ (0, import_react9.createElement)(
1821
2736
  Field,
1822
2737
  { label: "\u6A21\u578B" },
1823
- (0, import_react8.createElement)("input", {
1824
- value: aiModel,
1825
- onChange: (event) => setAiModel(event.target.value),
1826
- onBlur: () => void saveGeneral({ ai: { model: aiModel } })
1827
- })
2738
+ (() => {
2739
+ const provider = aiModels.providers.find((item) => item.provider === aiProvider);
2740
+ return provider === void 0 || provider.models.length === 0 ? (0, import_react9.createElement)("input", {
2741
+ value: aiModel,
2742
+ onChange: (event) => setAiModel(event.target.value),
2743
+ onBlur: () => void saveGeneral({ ai: { model: aiModel } })
2744
+ }) : (0, import_react9.createElement)(
2745
+ "select",
2746
+ {
2747
+ value: aiModel,
2748
+ onChange: (event) => {
2749
+ setAiModel(event.target.value);
2750
+ void saveGeneral({ ai: { model: event.target.value } });
2751
+ }
2752
+ },
2753
+ provider.models.map((item) => (0, import_react9.createElement)("option", { key: item.id, value: item.id }, item.name))
2754
+ );
2755
+ })()
1828
2756
  )
1829
2757
  ),
1830
- (0, import_react8.createElement)(
2758
+ (0, import_react9.createElement)("hr", null),
2759
+ (0, import_react9.createElement)(
2760
+ "div",
2761
+ { style: { display: "flex", alignItems: "center", gap: "8px" } },
2762
+ (0, import_react9.createElement)("strong", { style: { fontSize: "12.5px" } }, "\u5199\u4F5C\u98CE\u683C"),
2763
+ (0, import_react9.createElement)(
2764
+ "span",
2765
+ { className: "small muted" },
2766
+ styleMeta.updatedAt === "" ? "\u8FD8\u6CA1\u6709\u751F\u6210" : `${styleMeta.sampleCount} \u5C01\u5DF2\u53D1\u9001\u90AE\u4EF6 \xB7 ${new Date(styleMeta.updatedAt).toLocaleString()}`
2767
+ )
2768
+ ),
2769
+ (0, import_react9.createElement)(
2770
+ "p",
2771
+ { className: "small muted" },
2772
+ "\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"
2773
+ ),
2774
+ (0, import_react9.createElement)("textarea", {
2775
+ rows: 6,
2776
+ value: styleText,
2777
+ 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",
2778
+ onChange: (event) => setStyleText(event.target.value)
2779
+ }),
2780
+ (0, import_react9.createElement)(
2781
+ "div",
2782
+ { style: { display: "flex", gap: "8px", flexWrap: "wrap" } },
2783
+ (0, import_react9.createElement)(
2784
+ "button",
2785
+ {
2786
+ className: "ghost",
2787
+ disabled: styleBusy !== null,
2788
+ onClick: () => {
2789
+ const accountId = settings.activeAccountId;
2790
+ if (accountId === null) return;
2791
+ setStyleBusy("learn");
2792
+ setError("");
2793
+ api.aiLearnStyle(accountId).then((result) => {
2794
+ setStyleText(result.style.profile);
2795
+ setStyleMeta({ updatedAt: result.style.updatedAt, sampleCount: result.style.sampleCount });
2796
+ notify("\u5DF2\u6839\u636E\u5DF2\u53D1\u9001\u90AE\u4EF6\u751F\u6210\u5199\u4F5C\u98CE\u683C");
2797
+ }).catch((caught) => setError(errorText6(caught))).finally(() => setStyleBusy(null));
2798
+ }
2799
+ },
2800
+ styleBusy === "learn" ? "\u5B66\u4E60\u4E2D\u2026" : "\u4ECE\u5DF2\u53D1\u9001\u90AE\u4EF6\u5B66\u4E60"
2801
+ ),
2802
+ (0, import_react9.createElement)(
2803
+ "button",
2804
+ {
2805
+ className: "primary",
2806
+ disabled: styleBusy !== null || styleText.trim() === "",
2807
+ onClick: () => {
2808
+ const accountId = settings.activeAccountId;
2809
+ if (accountId === null) return;
2810
+ setStyleBusy("save");
2811
+ setError("");
2812
+ api.aiSaveStyle(accountId, styleText).then((result) => {
2813
+ setStyleMeta({ updatedAt: result.style.updatedAt, sampleCount: result.style.sampleCount });
2814
+ notify("\u5199\u4F5C\u98CE\u683C\u5DF2\u4FDD\u5B58");
2815
+ }).catch((caught) => setError(errorText6(caught))).finally(() => setStyleBusy(null));
2816
+ }
2817
+ },
2818
+ "\u4FDD\u5B58\u98CE\u683C"
2819
+ )
2820
+ ),
2821
+ (0, import_react9.createElement)(
1831
2822
  "p",
1832
2823
  { className: "small muted" },
1833
- "AI \u8C03\u7528\u8D70 DSH \u81EA\u5DF1\u7684\u6A21\u578B\u670D\u52A1\uFF0C\u4E0D\u989D\u5916\u4FDD\u5B58\u5BC6\u94A5\u3002AI \u9762\u677F\u529F\u80FD\u5C06\u5728 v0.2 \u63D0\u4F9B\u3002"
2824
+ "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"
1834
2825
  )
1835
2826
  ),
1836
- (0, import_react8.createElement)(
2827
+ (0, import_react9.createElement)(
1837
2828
  "div",
1838
2829
  { className: "mailharbor-card" },
1839
- (0, import_react8.createElement)("h3", null, "\u6570\u636E"),
1840
- (0, import_react8.createElement)(
2830
+ (0, import_react9.createElement)("h3", null, "\u6570\u636E"),
2831
+ (0, import_react9.createElement)(
1841
2832
  "dl",
1842
2833
  { className: "mailharbor-kv" },
1843
- (0, import_react8.createElement)("dt", null, "\u6570\u636E\u76EE\u5F55"),
1844
- (0, import_react8.createElement)("dd", null, settings.dataDir),
1845
- (0, import_react8.createElement)("dt", null, "\u914D\u7F6E\u6587\u4EF6"),
1846
- (0, import_react8.createElement)("dd", null, settings.configFile),
1847
- (0, import_react8.createElement)("dt", null, "\u5F53\u524D\u8D26\u53F7\u6570\u636E\u5E93"),
1848
- (0, import_react8.createElement)("dd", null, account === null ? "\u2014" : `${settings.dataDir}/accounts/${account.id}/mail.db`),
1849
- (0, import_react8.createElement)("dt", null, "\u63D2\u4EF6\u7248\u672C"),
1850
- (0, import_react8.createElement)("dd", null, state.version)
2834
+ (0, import_react9.createElement)("dt", null, "\u6570\u636E\u76EE\u5F55"),
2835
+ (0, import_react9.createElement)("dd", null, settings.dataDir),
2836
+ (0, import_react9.createElement)("dt", null, "\u914D\u7F6E\u6587\u4EF6"),
2837
+ (0, import_react9.createElement)("dd", null, settings.configFile),
2838
+ (0, import_react9.createElement)("dt", null, "\u5F53\u524D\u8D26\u53F7\u6570\u636E\u5E93"),
2839
+ (0, import_react9.createElement)(
2840
+ "dd",
2841
+ null,
2842
+ account === null ? "\u2014" : `${account.dataDir.startsWith("/") ? account.dataDir : `${settings.dataDir}/${account.dataDir}`}/mail.db`
2843
+ ),
2844
+ (0, import_react9.createElement)("dt", null, "\u63D2\u4EF6\u7248\u672C"),
2845
+ (0, import_react9.createElement)("dd", null, state.version)
1851
2846
  ),
1852
- (0, import_react8.createElement)(
2847
+ (0, import_react9.createElement)(
1853
2848
  "div",
1854
2849
  { style: { display: "flex", gap: "8px", flexWrap: "wrap" } },
1855
- account === null ? null : (0, import_react8.createElement)(
2850
+ account === null ? null : (0, import_react9.createElement)(
1856
2851
  "button",
1857
2852
  {
1858
2853
  className: "ghost",
@@ -1862,7 +2857,7 @@ function MailSettings() {
1862
2857
  notify(status.status.message === "" ? "\u5907\u4EFD\u5DF2\u5F00\u59CB" : status.status.message);
1863
2858
  reload();
1864
2859
  } catch (caught) {
1865
- setError(errorText4(caught));
2860
+ setError(errorText6(caught));
1866
2861
  }
1867
2862
  }
1868
2863
  },
@@ -1887,19 +2882,19 @@ function AccountRow({
1887
2882
  onSave,
1888
2883
  onRemove
1889
2884
  }) {
1890
- const [password, setPassword] = (0, import_react8.useState)("");
1891
- const [remember, setRemember] = (0, import_react8.useState)(true);
1892
- const [label, setLabel] = (0, import_react8.useState)(account.label);
1893
- const [username, setUsername] = (0, import_react8.useState)(account.username);
1894
- const [imapHost, setImapHost] = (0, import_react8.useState)(account.imap.host);
1895
- const [imapPort, setImapPort] = (0, import_react8.useState)(String(account.imap.port));
1896
- const [imapSecure, setImapSecure] = (0, import_react8.useState)(account.imap.secure);
1897
- const [smtpHost, setSmtpHost] = (0, import_react8.useState)(account.smtp.host);
1898
- const [smtpPort, setSmtpPort] = (0, import_react8.useState)(String(account.smtp.port));
1899
- const [smtpSecure, setSmtpSecure] = (0, import_react8.useState)(account.smtp.secure);
1900
- const [fromName, setFromName] = (0, import_react8.useState)(account.smtp.fromName);
1901
- const [busy, setBusy] = (0, import_react8.useState)(false);
1902
- (0, import_react8.useEffect)(() => {
2885
+ const [password, setPassword] = (0, import_react9.useState)("");
2886
+ const [remember, setRemember] = (0, import_react9.useState)(true);
2887
+ const [label, setLabel] = (0, import_react9.useState)(account.label);
2888
+ const [username, setUsername] = (0, import_react9.useState)(account.username);
2889
+ const [imapHost, setImapHost] = (0, import_react9.useState)(account.imap.host);
2890
+ const [imapPort, setImapPort] = (0, import_react9.useState)(String(account.imap.port));
2891
+ const [imapSecure, setImapSecure] = (0, import_react9.useState)(account.imap.secure);
2892
+ const [smtpHost, setSmtpHost] = (0, import_react9.useState)(account.smtp.host);
2893
+ const [smtpPort, setSmtpPort] = (0, import_react9.useState)(String(account.smtp.port));
2894
+ const [smtpSecure, setSmtpSecure] = (0, import_react9.useState)(account.smtp.secure);
2895
+ const [fromName, setFromName] = (0, import_react9.useState)(account.smtp.fromName);
2896
+ const [busy, setBusy] = (0, import_react9.useState)(false);
2897
+ (0, import_react9.useEffect)(() => {
1903
2898
  setLabel(account.label);
1904
2899
  setUsername(account.username);
1905
2900
  setImapHost(account.imap.host);
@@ -1918,103 +2913,103 @@ function AccountRow({
1918
2913
  setBusy(false);
1919
2914
  }
1920
2915
  };
1921
- return (0, import_react8.createElement)(
2916
+ return (0, import_react9.createElement)(
1922
2917
  "div",
1923
2918
  { className: "mailharbor-card", style: { background: "var(--m-bg-1)" } },
1924
- (0, import_react8.createElement)(
2919
+ (0, import_react9.createElement)(
1925
2920
  "div",
1926
2921
  { className: "mailharbor-account-row" },
1927
- (0, import_react8.createElement)(Avatar, { email: account.email, size: 30 }),
1928
- (0, import_react8.createElement)(
2922
+ (0, import_react9.createElement)(Avatar, { email: account.email, size: 30 }),
2923
+ (0, import_react9.createElement)(
1929
2924
  "div",
1930
2925
  { style: { display: "flex", flexDirection: "column", minWidth: 0 } },
1931
- (0, import_react8.createElement)("strong", null, account.label),
1932
- (0, import_react8.createElement)("span", { className: "small muted" }, account.email)
2926
+ (0, import_react9.createElement)("strong", null, account.label),
2927
+ (0, import_react9.createElement)("span", { className: "small muted" }, account.email)
1933
2928
  ),
1934
- active ? (0, import_react8.createElement)("span", { className: "badge ok" }, "\u5F53\u524D\u8D26\u53F7") : null,
1935
- loggedIn === false ? (0, import_react8.createElement)("span", { className: "badge warn" }, "\u672A\u767B\u5F55") : null,
1936
- (0, import_react8.createElement)("span", { className: "spacer" }),
1937
- active ? null : (0, import_react8.createElement)("button", { className: "ghost small", onClick: () => void wrap(onActivate) }, "\u8BBE\u4E3A\u5F53\u524D"),
1938
- (0, import_react8.createElement)("button", { className: "ghost small", onClick: () => void wrap(onCheck) }, "\u68C0\u67E5\u8FDE\u63A5"),
1939
- (0, import_react8.createElement)("button", { className: "ghost small", onClick: onToggleEdit }, editing ? "\u6536\u8D77" : "\u7F16\u8F91")
2929
+ active ? (0, import_react9.createElement)("span", { className: "badge ok" }, "\u5F53\u524D\u8D26\u53F7") : null,
2930
+ loggedIn === false ? (0, import_react9.createElement)("span", { className: "badge warn" }, "\u672A\u767B\u5F55") : null,
2931
+ (0, import_react9.createElement)("span", { className: "spacer" }),
2932
+ active ? null : (0, import_react9.createElement)("button", { className: "ghost small", onClick: () => void wrap(onActivate) }, "\u8BBE\u4E3A\u5F53\u524D"),
2933
+ (0, import_react9.createElement)("button", { className: "ghost small", onClick: () => void wrap(onCheck) }, "\u68C0\u67E5\u8FDE\u63A5"),
2934
+ (0, import_react9.createElement)("button", { className: "ghost small", onClick: onToggleEdit }, editing ? "\u6536\u8D77" : "\u7F16\u8F91")
1940
2935
  ),
1941
- editing ? (0, import_react8.createElement)(
2936
+ editing ? (0, import_react9.createElement)(
1942
2937
  "div",
1943
2938
  { style: { display: "flex", flexDirection: "column", gap: "10px" } },
1944
- (0, import_react8.createElement)(
2939
+ (0, import_react9.createElement)(
1945
2940
  "div",
1946
2941
  { className: "mailharbor-grid" },
1947
- (0, import_react8.createElement)(
2942
+ (0, import_react9.createElement)(
1948
2943
  Field,
1949
2944
  { label: "\u663E\u793A\u540D\u79F0" },
1950
- (0, import_react8.createElement)("input", {
2945
+ (0, import_react9.createElement)("input", {
1951
2946
  value: label,
1952
2947
  onChange: (event) => setLabel(event.target.value)
1953
2948
  })
1954
2949
  ),
1955
- (0, import_react8.createElement)(
2950
+ (0, import_react9.createElement)(
1956
2951
  Field,
1957
2952
  { label: "\u767B\u5F55\u7528\u6237\u540D" },
1958
- (0, import_react8.createElement)("input", {
2953
+ (0, import_react9.createElement)("input", {
1959
2954
  value: username,
1960
2955
  onChange: (event) => setUsername(event.target.value)
1961
2956
  })
1962
2957
  )
1963
2958
  ),
1964
- (0, import_react8.createElement)(
2959
+ (0, import_react9.createElement)(
1965
2960
  "div",
1966
2961
  { className: "mailharbor-grid three" },
1967
- (0, import_react8.createElement)(
2962
+ (0, import_react9.createElement)(
1968
2963
  Field,
1969
2964
  { label: "IMAP \u4E3B\u673A" },
1970
- (0, import_react8.createElement)("input", {
2965
+ (0, import_react9.createElement)("input", {
1971
2966
  value: imapHost,
1972
2967
  onChange: (event) => setImapHost(event.target.value)
1973
2968
  })
1974
2969
  ),
1975
- (0, import_react8.createElement)(
2970
+ (0, import_react9.createElement)(
1976
2971
  Field,
1977
2972
  { label: "\u7AEF\u53E3" },
1978
- (0, import_react8.createElement)("input", {
2973
+ (0, import_react9.createElement)("input", {
1979
2974
  value: imapPort,
1980
2975
  onChange: (event) => setImapPort(event.target.value)
1981
2976
  })
1982
2977
  ),
1983
- (0, import_react8.createElement)(Toggle, { checked: imapSecure, onChange: setImapSecure, label: "TLS" })
2978
+ (0, import_react9.createElement)(Toggle, { checked: imapSecure, onChange: setImapSecure, label: "TLS" })
1984
2979
  ),
1985
- (0, import_react8.createElement)(
2980
+ (0, import_react9.createElement)(
1986
2981
  "div",
1987
2982
  { className: "mailharbor-grid three" },
1988
- (0, import_react8.createElement)(
2983
+ (0, import_react9.createElement)(
1989
2984
  Field,
1990
2985
  { label: "SMTP \u4E3B\u673A" },
1991
- (0, import_react8.createElement)("input", {
2986
+ (0, import_react9.createElement)("input", {
1992
2987
  value: smtpHost,
1993
2988
  onChange: (event) => setSmtpHost(event.target.value)
1994
2989
  })
1995
2990
  ),
1996
- (0, import_react8.createElement)(
2991
+ (0, import_react9.createElement)(
1997
2992
  Field,
1998
2993
  { label: "\u7AEF\u53E3" },
1999
- (0, import_react8.createElement)("input", {
2994
+ (0, import_react9.createElement)("input", {
2000
2995
  value: smtpPort,
2001
2996
  onChange: (event) => setSmtpPort(event.target.value)
2002
2997
  })
2003
2998
  ),
2004
- (0, import_react8.createElement)(Toggle, { checked: smtpSecure, onChange: setSmtpSecure, label: "TLS" })
2999
+ (0, import_react9.createElement)(Toggle, { checked: smtpSecure, onChange: setSmtpSecure, label: "TLS" })
2005
3000
  ),
2006
- (0, import_react8.createElement)(
3001
+ (0, import_react9.createElement)(
2007
3002
  Field,
2008
3003
  { label: "\u53D1\u4EF6\u4EBA\u540D\u79F0" },
2009
- (0, import_react8.createElement)("input", {
3004
+ (0, import_react9.createElement)("input", {
2010
3005
  value: fromName,
2011
3006
  onChange: (event) => setFromName(event.target.value)
2012
3007
  })
2013
3008
  ),
2014
- (0, import_react8.createElement)(
3009
+ (0, import_react9.createElement)(
2015
3010
  "div",
2016
3011
  { style: { display: "flex", gap: "8px", alignItems: "center", flexWrap: "wrap" } },
2017
- (0, import_react8.createElement)(
3012
+ (0, import_react9.createElement)(
2018
3013
  "button",
2019
3014
  {
2020
3015
  className: "primary small",
@@ -2030,7 +3025,7 @@ function AccountRow({
2030
3025
  },
2031
3026
  "\u4FDD\u5B58\u8D26\u53F7"
2032
3027
  ),
2033
- (0, import_react8.createElement)(
3028
+ (0, import_react9.createElement)(
2034
3029
  "button",
2035
3030
  {
2036
3031
  className: "ghost small danger",
@@ -2039,21 +3034,21 @@ function AccountRow({
2039
3034
  "\u79FB\u9664\u8D26\u53F7"
2040
3035
  )
2041
3036
  ),
2042
- (0, import_react8.createElement)("hr", { style: { border: 0, borderTop: "1px solid var(--m-line)", width: "100%" } }),
2043
- (0, import_react8.createElement)(
3037
+ (0, import_react9.createElement)("hr", { style: { border: 0, borderTop: "1px solid var(--m-line)", width: "100%" } }),
3038
+ (0, import_react9.createElement)(
2044
3039
  Field,
2045
3040
  { label: "\u5BC6\u7801 / \u5E94\u7528\u4E13\u7528\u5BC6\u7801\uFF08\u4E0D\u5199\u5165\u914D\u7F6E\u6587\u4EF6\uFF09" },
2046
- (0, import_react8.createElement)("input", {
3041
+ (0, import_react9.createElement)("input", {
2047
3042
  type: "password",
2048
3043
  value: password,
2049
3044
  onChange: (event) => setPassword(event.target.value)
2050
3045
  })
2051
3046
  ),
2052
- (0, import_react8.createElement)(
3047
+ (0, import_react9.createElement)(
2053
3048
  "div",
2054
3049
  { style: { display: "flex", gap: "8px", alignItems: "center" } },
2055
- (0, import_react8.createElement)(Toggle, { checked: remember, onChange: setRemember, label: "\u8BB0\u4F4F\u5BC6\u7801" }),
2056
- (0, import_react8.createElement)(
3050
+ (0, import_react9.createElement)(Toggle, { checked: remember, onChange: setRemember, label: "\u8BB0\u4F4F\u5BC6\u7801" }),
3051
+ (0, import_react9.createElement)(
2057
3052
  "button",
2058
3053
  {
2059
3054
  className: "primary small",
@@ -2065,15 +3060,15 @@ function AccountRow({
2065
3060
  },
2066
3061
  "\u767B\u5F55"
2067
3062
  ),
2068
- (0, import_react8.createElement)("button", { className: "ghost small", onClick: () => void wrap(onLogout) }, "\u9000\u51FA\u767B\u5F55")
3063
+ (0, import_react9.createElement)("button", { className: "ghost small", onClick: () => void wrap(onLogout) }, "\u9000\u51FA\u767B\u5F55")
2069
3064
  ),
2070
- checkResult === "" ? null : (0, import_react8.createElement)("div", { className: "small muted" }, checkResult)
3065
+ checkResult === "" ? null : (0, import_react9.createElement)("div", { className: "small muted" }, checkResult)
2071
3066
  ) : null
2072
3067
  );
2073
3068
  }
2074
3069
 
2075
3070
  // src/client/index.tsx
2076
- var VERSION = "0.2.1";
3071
+ var VERSION = "0.4.0";
2077
3072
  var inject = ["slots"];
2078
3073
  var PANEL_ID = "mailharbor";
2079
3074
  var STYLE_ID = "mailharbor-style";