@zaclinley/dsh-mailharbor 0.2.1 → 0.3.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}\n\n.mailharbor-list {\n display: flex;\n flex-direction: column;\n width: 360px;\n flex: none;\n border-right: 1px solid var(--m-line-soft);\n min-height: 0;\n}\n\n.mailharbor-reader {\n flex: 1;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n@media (max-width: 1000px) {\n .mailharbor-list {\n width: 300px;\n }\n}\n\n.mailharbor-narrow .mailharbor-body[data-mode='reader'] .mailharbor-list {\n display: none;\n}\n\n.mailharbor-narrow .mailharbor-body[data-mode='list'] .mailharbor-reader {\n display: none;\n}\n\n/* ---------- segmented control ---------- */\n\n.mailharbor-segment {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n padding: 2px;\n border-radius: 9px;\n background: var(--m-bg-2);\n}\n\n.mailharbor-segment button {\n height: 26px;\n padding: 0 12px;\n border-radius: 7px;\n color: var(--m-text-3);\n font-size: 12px;\n}\n\n.mailharbor-segment button:hover:not(.active) {\n color: var(--m-text-2);\n background: transparent;\n}\n\n.mailharbor-segment button.active {\n background: var(--m-bg-3);\n color: var(--m-text);\n}\n\n/* ---------- list ---------- */\n\n.mailharbor-filters {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-filters select {\n width: 128px;\n flex: none;\n font-size: 12px;\n}\n\n.mailharbor-filters input[type='search'] {\n flex: 1;\n height: 30px;\n font-size: 12.5px;\n}\n\n.mailharbor-rows {\n flex: 1;\n overflow: auto;\n min-height: 0;\n}\n\n.mailharbor-row {\n display: flex;\n flex-direction: column;\n gap: 3px;\n width: 100%;\n padding: 10px 14px;\n border: 0;\n border-bottom: 1px solid var(--m-line-soft);\n border-left: 2px solid transparent;\n border-radius: 0;\n background: transparent;\n color: inherit;\n text-align: left;\n cursor: pointer;\n user-select: none;\n}\n\n.mailharbor-row:hover {\n background: var(--m-hover);\n}\n\n.mailharbor-row.selected {\n background: var(--m-accent-soft);\n border-left-color: var(--m-accent);\n}\n\n.mailharbor-row .line-1,\n.mailharbor-row .line-2 {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n width: 100%;\n}\n\n.mailharbor-row .from {\n font-weight: 600;\n font-size: 12.5px;\n color: var(--m-text-2);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mailharbor-row .date {\n margin-left: auto;\n color: var(--m-caption);\n font-size: 11px;\n flex: none;\n}\n\n.mailharbor-row .subject {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--m-text-2);\n font-size: 12.5px;\n}\n\n.mailharbor-row .snippet {\n color: var(--m-caption);\n font-size: 11.5px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n width: 100%;\n}\n\n.mailharbor-row.unread .from,\n.mailharbor-row.unread .subject {\n color: var(--m-text);\n}\n\n.mailharbor-row .dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--m-accent);\n flex: none;\n}\n\n.mailharbor-row .paperclip {\n color: var(--m-caption);\n flex: none;\n display: inline-flex;\n}\n\n.mailharbor-list-foot {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-top: 1px solid var(--m-line-soft);\n color: var(--m-text-3);\n font-size: 11.5px;\n flex: none;\n}\n\n.mailharbor-empty {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 10px;\n padding: 48px 24px;\n color: var(--m-text-3);\n text-align: center;\n}\n\n.mailharbor-empty svg {\n opacity: 0.5;\n}\n\n.mailharbor-empty .title {\n color: var(--m-text-2);\n font-size: 13px;\n font-weight: 500;\n}\n\n/* ---------- reader ---------- */\n\n.mailharbor-reader-head {\n padding: 16px 20px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.mailharbor-reader-title {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n}\n\n.mailharbor-reader-title h2 {\n margin: 0;\n font-size: 17px;\n line-height: 1.35;\n font-weight: 600;\n letter-spacing: -0.01em;\n word-break: break-word;\n}\n\n.mailharbor-reader-meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 4px 10px;\n color: var(--m-text-3);\n font-size: 12px;\n}\n\n.mailharbor-reader-meta .who {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n color: var(--m-text-2);\n}\n\n.mailharbor-reader-meta .who strong {\n color: var(--m-text);\n font-weight: 600;\n}\n\n.mailharbor-reader-actions {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n}\n\n.mailharbor-reader-body {\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n padding: 14px 18px 18px;\n background: var(--m-bg-2);\n}\n\n.mailharbor-reader-body iframe {\n flex: 1;\n width: 100%;\n border: 0;\n border-radius: 10px;\n background: #fff;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n}\n\n.mailharbor-reader-body pre {\n flex: 1;\n overflow: auto;\n margin: 0;\n padding: 20px 22px;\n white-space: pre-wrap;\n word-break: break-word;\n background: #fff;\n border-radius: 10px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n color: #171a21;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 12.5px;\n line-height: 1.7;\n}\n\n.mailharbor-attachments {\n padding: 10px 18px;\n border-top: 1px solid var(--m-line-soft);\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n flex: none;\n background: var(--m-bg);\n}\n\n.mailharbor-attachment {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n height: 30px;\n padding: 0 10px;\n border: 1px solid var(--m-line);\n border-radius: var(--m-radius);\n font-size: 12px;\n text-decoration: none;\n color: var(--m-text-2);\n max-width: 260px;\n background: var(--m-bg-1);\n}\n\n.mailharbor-attachment:hover {\n border-color: var(--m-line-strong);\n color: var(--m-text);\n}\n\n.mailharbor-attachment .name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* ---------- AI summary ---------- */\n\n.mailharbor-divider {\n width: 1px;\n height: 18px;\n margin: 0 4px;\n background: var(--m-line);\n}\n\n.mailharbor-summary {\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n\n.mailharbor-summary section h4 {\n margin: 0 0 6px;\n font-size: 11.5px;\n font-weight: 600;\n color: var(--m-text-3);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n}\n\n.mailharbor-summary section p {\n margin: 0;\n white-space: pre-wrap;\n line-height: 1.75;\n color: var(--m-text);\n}\n\n/* ---------- modal / forms ---------- */\n\n.mailharbor-portal {\n position: fixed;\n inset: 0;\n z-index: 80;\n height: auto;\n background: transparent;\n pointer-events: none;\n}\n\n.mailharbor-portal .mailharbor-modal-mask {\n pointer-events: auto;\n}\n\n.mailharbor-modal-mask {\n position: fixed;\n inset: 0;\n z-index: 60;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: rgba(0, 0, 0, 0.55);\n backdrop-filter: blur(2px);\n}\n\n.mailharbor-modal {\n display: flex;\n flex-direction: column;\n width: min(720px, 100%);\n max-height: 86vh;\n overflow: hidden;\n border: 1px solid var(--m-line);\n border-radius: 14px;\n background: var(--m-bg-1);\n color: var(--m-text);\n box-shadow: var(--m-shadow);\n}\n\n.mailharbor-modal > header {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 52px;\n padding: 0 14px 0 18px;\n border-bottom: 1px solid var(--m-line-soft);\n font-size: 14px;\n font-weight: 600;\n}\n\n.mailharbor-modal .content {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 18px;\n overflow: auto;\n}\n\n.mailharbor-modal > footer {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 18px;\n border-top: 1px solid var(--m-line-soft);\n}\n\n.mailharbor-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n min-width: 0;\n}\n\n.mailharbor-field > label {\n font-size: 11.5px;\n color: var(--m-text-3);\n}\n\n.mailharbor-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 10px;\n}\n\n.mailharbor-grid.three {\n grid-template-columns: 2fr 90px auto;\n align-items: end;\n}\n\n.mailharbor-toggle {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n color: var(--m-text-2);\n font-size: 12.5px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.mailharbor-toggle input[type='checkbox'] {\n width: 14px;\n height: 14px;\n accent-color: var(--m-accent);\n}\n\n/* ---------- composer ---------- */\n\n.mailharbor-toolbar {\n display: flex;\n align-items: center;\n gap: 2px;\n flex-wrap: wrap;\n padding: 6px;\n border: 1px solid var(--m-line-soft);\n border-bottom: 0;\n border-radius: 10px 10px 0 0;\n background: var(--m-bg-2);\n}\n\n.mailharbor-toolbar button {\n height: 26px;\n padding: 0 9px;\n border-radius: 6px;\n color: var(--m-text-2);\n font-size: 12px;\n}\n\n.mailharbor-toolbar button:hover {\n background: var(--m-bg-3);\n color: var(--m-text);\n}\n\n.mailharbor-editor {\n min-height: 240px;\n max-height: 44vh;\n overflow: auto;\n padding: 14px 16px;\n border: 1px solid var(--m-line-soft);\n border-radius: 0 0 10px 10px;\n background: #fff;\n color: #171a21;\n outline: none;\n font-size: 13.5px;\n line-height: 1.7;\n}\n\n.mailharbor-editor:empty::before {\n content: attr(data-placeholder);\n color: #9aa0ab;\n}\n\n/* ---------- inline confirm ---------- */\n\n.mailharbor-confirm {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-radius: 8px;\n border: 1px solid var(--m-line);\n background: var(--m-bg-2);\n color: var(--m-text-2);\n font-size: 12.5px;\n}\n\n/* ---------- settings ---------- */\n\n.mailharbor-settings {\n padding: 20px 24px 48px;\n display: flex;\n flex-direction: column;\n gap: 16px;\n max-width: 880px;\n}\n\n.mailharbor-settings h2 {\n margin: 0;\n font-size: 18px;\n font-weight: 600;\n letter-spacing: -0.01em;\n}\n\n.mailharbor-settings h3 {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--m-text-3);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n}\n\n.mailharbor-card {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n border: 1px solid var(--m-line-soft);\n border-radius: 12px;\n background: var(--m-bg-1);\n}\n\n.mailharbor-account-row {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.mailharbor-account-row .meta {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n.mailharbor-account-row .meta strong {\n font-weight: 600;\n}\n\n.mailharbor-account-row .meta span {\n color: var(--m-text-3);\n font-size: 11.5px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.mailharbor .avatar {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n border-radius: 50%;\n background: var(--m-accent-soft);\n color: var(--m-accent);\n font-weight: 600;\n}\n\n.mailharbor-log {\n padding: 10px 12px;\n border-radius: 8px;\n background: var(--m-bg);\n border: 1px solid var(--m-line-soft);\n color: var(--m-text-2);\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11px;\n line-height: 1.6;\n max-height: 220px;\n overflow: auto;\n white-space: pre-wrap;\n}\n\n.mailharbor-kv {\n display: grid;\n grid-template-columns: minmax(120px, 170px) 1fr;\n gap: 6px 14px;\n font-size: 12px;\n margin: 0;\n}\n\n.mailharbor-kv dt {\n color: var(--m-text-3);\n}\n\n.mailharbor-kv dd {\n margin: 0;\n color: var(--m-text-2);\n word-break: break-all;\n}\n\n.mailharbor hr {\n border: 0;\n border-top: 1px solid var(--m-line-soft);\n margin: 2px 0;\n}\n";
38
38
 
39
39
  // src/client/icon.tsx
40
40
  var import_react = require("react");
@@ -123,6 +123,29 @@ 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
+ aiSummary: (payload) => request("ai/summary", {
129
+ method: "POST",
130
+ body: JSON.stringify(payload)
131
+ }),
132
+ aiReply: (payload) => request("ai/reply", {
133
+ method: "POST",
134
+ body: JSON.stringify(payload)
135
+ }),
136
+ aiTranslate: (payload) => request("ai/translate", {
137
+ method: "POST",
138
+ body: JSON.stringify(payload)
139
+ }),
140
+ aiStyle: (accountId) => request(`ai/style${query({ accountId: accountId ?? null })}`),
141
+ aiSaveStyle: (accountId, profile) => request(`ai/style${query({ accountId: accountId ?? null })}`, {
142
+ method: "PUT",
143
+ body: JSON.stringify({ accountId, profile })
144
+ }),
145
+ aiLearnStyle: (accountId) => request(`ai/style/learn${query({ accountId: accountId ?? null })}`, {
146
+ method: "POST",
147
+ body: JSON.stringify({ accountId: accountId ?? null })
148
+ }),
126
149
  settings: () => request("settings"),
127
150
  saveSettings: (patch) => request("settings", { method: "PATCH", body: JSON.stringify(patch) }),
128
151
  backupStatus: (accountId) => request(`backup/status${query({ accountId: accountId ?? null })}`),
@@ -637,6 +660,8 @@ function Composer({
637
660
  const [error, setError] = (0, import_react4.useState)("");
638
661
  const [confirming, setConfirming] = (0, import_react4.useState)(false);
639
662
  const [draftId, setDraftId] = (0, import_react4.useState)(initial.draftId);
663
+ const [translating, setTranslating] = (0, import_react4.useState)(null);
664
+ const [pendingLang, setPendingLang] = (0, import_react4.useState)(null);
640
665
  const editor = (0, import_react4.useRef)(null);
641
666
  (0, import_react4.useEffect)(() => {
642
667
  if (editor.current !== null) editor.current.innerHTML = initial.html;
@@ -647,6 +672,24 @@ function Composer({
647
672
  editor.current?.focus();
648
673
  document.execCommand(command, false, value);
649
674
  };
675
+ const translate = async (target) => {
676
+ if (editor.current === null) return;
677
+ if (html().trim() === "") {
678
+ setError("\u6B63\u6587\u4E3A\u7A7A\uFF0C\u65E0\u6CD5\u7FFB\u8BD1");
679
+ return;
680
+ }
681
+ setPendingLang(null);
682
+ setTranslating(target);
683
+ setError("");
684
+ try {
685
+ const result = await api.aiTranslate({ accountId: account.id, html: html(), target });
686
+ editor.current.innerHTML = result.html;
687
+ } catch (caught) {
688
+ setError(errorText2(caught));
689
+ } finally {
690
+ setTranslating(null);
691
+ }
692
+ };
650
693
  const addFiles = async (files) => {
651
694
  if (files === null || files.length === 0) return;
652
695
  setError("");
@@ -817,6 +860,16 @@ function Composer({
817
860
  ),
818
861
  (0, import_react4.createElement)("button", { onClick: () => exec("removeFormat"), title: "\u6E05\u9664\u683C\u5F0F" }, "\u6E05\u9664"),
819
862
  (0, import_react4.createElement)("span", { className: "spacer" }),
863
+ (0, import_react4.createElement)(
864
+ "button",
865
+ { onClick: () => setPendingLang("zh"), disabled: translating !== null, title: "AI \u7FFB\u8BD1\u6210\u4E2D\u6587" },
866
+ translating === "zh" ? "\u7FFB\u8BD1\u4E2D\u2026" : "\u4E2D"
867
+ ),
868
+ (0, import_react4.createElement)(
869
+ "button",
870
+ { onClick: () => setPendingLang("en"), disabled: translating !== null, title: "AI \u7FFB\u8BD1\u6210\u82F1\u6587" },
871
+ translating === "en" ? "\u2026" : "EN"
872
+ ),
820
873
  (0, import_react4.createElement)(
821
874
  "label",
822
875
  { className: "mailharbor-toggle small" },
@@ -832,6 +885,14 @@ function Composer({
832
885
  })
833
886
  )
834
887
  ),
888
+ pendingLang === null ? null : (0, import_react4.createElement)(
889
+ "div",
890
+ { className: "mailharbor-confirm" },
891
+ (0, import_react4.createElement)("span", null, pendingLang === "en" ? "\u628A\u6B63\u6587\u7FFB\u8BD1\u6210\u82F1\u6587\uFF1F" : "\u628A\u6B63\u6587\u7FFB\u8BD1\u6210\u4E2D\u6587\uFF1F"),
892
+ (0, import_react4.createElement)("span", { className: "spacer" }),
893
+ (0, import_react4.createElement)("button", { className: "ghost", onClick: () => setPendingLang(null) }, "\u53D6\u6D88"),
894
+ (0, import_react4.createElement)("button", { className: "primary", onClick: () => void translate(pendingLang) }, "\u7FFB\u8BD1")
895
+ ),
835
896
  attachments.length === 0 ? null : (0, import_react4.createElement)(
836
897
  "div",
837
898
  { className: "mailharbor-attachments", style: { padding: 0, border: 0 } },
@@ -950,25 +1011,43 @@ function srcDoc(detail, allowRemoteImages) {
950
1011
  function escapeHtml(value) {
951
1012
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
952
1013
  }
1014
+ function textToHtml(text) {
1015
+ return text.split(/\n{2,}/).map(
1016
+ (paragraph) => `<p>${paragraph.trim().replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll("\n", "<br>")}</p>`
1017
+ ).join("");
1018
+ }
953
1019
  function hasRemoteImages(detail) {
954
1020
  return detail.html !== null && /<img[^>]+src=["']https?:/i.test(detail.html);
955
1021
  }
956
1022
  function addressLine(list) {
957
1023
  return list.map((item) => item.name !== "" ? `${item.name} <${item.address}>` : item.address).join("\u3001");
958
1024
  }
1025
+ function errorText3(error) {
1026
+ return error instanceof Error ? error.message : String(error);
1027
+ }
959
1028
  function Reader({
960
1029
  detail,
961
- account,
962
1030
  source,
963
1031
  mailbox,
964
1032
  onChanged,
965
1033
  onReply,
1034
+ onAiReply,
966
1035
  onClose
967
1036
  }) {
968
1037
  const [plain, setPlain] = (0, import_react6.useState)(false);
969
1038
  const [loadImages, setLoadImages] = (0, import_react6.useState)(false);
970
1039
  const [busy, setBusy] = (0, import_react6.useState)(false);
971
1040
  const [error, setError] = (0, import_react6.useState)("");
1041
+ const [summaryOpen, setSummaryOpen] = (0, import_react6.useState)(false);
1042
+ const [summaryBusy, setSummaryBusy] = (0, import_react6.useState)(false);
1043
+ const [summaryError, setSummaryError] = (0, import_react6.useState)("");
1044
+ const [summaryBlocks, setSummaryBlocks] = (0, import_react6.useState)([]);
1045
+ const [replyOpen, setReplyOpen] = (0, import_react6.useState)(false);
1046
+ const [replyBusy, setReplyBusy] = (0, import_react6.useState)(false);
1047
+ const [replyError, setReplyError] = (0, import_react6.useState)("");
1048
+ const [replyInstruction, setReplyInstruction] = (0, import_react6.useState)("");
1049
+ const [replyLanguage, setReplyLanguage] = (0, import_react6.useState)("zh");
1050
+ const [replyText, setReplyText] = (0, import_react6.useState)("");
972
1051
  const remoteImages = (0, import_react6.useMemo)(() => hasRemoteImages(detail), [detail]);
973
1052
  const html = detail.html !== null && detail.html !== "";
974
1053
  const toggleRead = async () => {
@@ -977,18 +1056,74 @@ function Reader({
977
1056
  try {
978
1057
  const result = await api.setRead({
979
1058
  id: detail.id,
980
- accountId: account.id,
1059
+ accountId: detail.accountId,
981
1060
  source,
982
1061
  mailbox,
983
1062
  read: detail.unread
984
1063
  });
985
1064
  onChanged({ unread: !result.read });
986
1065
  } catch (caught) {
987
- setError(caught instanceof Error ? caught.message : String(caught));
1066
+ setError(errorText3(caught));
988
1067
  } finally {
989
1068
  setBusy(false);
990
1069
  }
991
1070
  };
1071
+ const openSummary = async () => {
1072
+ setSummaryOpen(true);
1073
+ setSummaryBusy(true);
1074
+ setSummaryError("");
1075
+ setSummaryBlocks([]);
1076
+ try {
1077
+ const result = await api.aiSummary({
1078
+ accountId: detail.accountId,
1079
+ source,
1080
+ id: detail.id,
1081
+ mailbox
1082
+ });
1083
+ setSummaryBlocks(result.blocks);
1084
+ } catch (caught) {
1085
+ setSummaryError(errorText3(caught));
1086
+ } finally {
1087
+ setSummaryBusy(false);
1088
+ }
1089
+ };
1090
+ const openReply = () => {
1091
+ setReplyOpen(true);
1092
+ setReplyText("");
1093
+ setReplyError("");
1094
+ setReplyInstruction("");
1095
+ setReplyLanguage("zh");
1096
+ };
1097
+ const generateReply = async () => {
1098
+ setReplyBusy(true);
1099
+ setReplyError("");
1100
+ try {
1101
+ const result = await api.aiReply({
1102
+ accountId: detail.accountId,
1103
+ source,
1104
+ id: detail.id,
1105
+ mailbox,
1106
+ instruction: replyInstruction,
1107
+ language: replyLanguage
1108
+ });
1109
+ setReplyText(result.text);
1110
+ } catch (caught) {
1111
+ setReplyError(errorText3(caught));
1112
+ } finally {
1113
+ setReplyBusy(false);
1114
+ }
1115
+ };
1116
+ const insertReply = () => {
1117
+ const subject = /^re:/i.test(detail.subject) ? detail.subject : `Re: ${detail.subject}`;
1118
+ onAiReply({
1119
+ to: detail.from.address,
1120
+ cc: "",
1121
+ subject,
1122
+ html: textToHtml(replyText),
1123
+ ...detail.messageId === "" ? {} : { inReplyTo: detail.messageId, references: [...detail.references, detail.messageId].join(" ") }
1124
+ });
1125
+ setReplyOpen(false);
1126
+ };
992
1127
  return (0, import_react6.createElement)(
993
1128
  "div",
994
1129
  { className: "mailharbor-reader" },
@@ -1056,6 +1191,9 @@ function Reader({
1056
1191
  (0, import_react6.createElement)(Icon, { name: "forward" }),
1057
1192
  "\u8F6C\u53D1"
1058
1193
  ),
1194
+ (0, import_react6.createElement)("span", { className: "mailharbor-divider" }),
1195
+ (0, import_react6.createElement)("button", { className: "ghost", onClick: () => void openSummary() }, "AI \u6458\u8981"),
1196
+ (0, import_react6.createElement)("button", { className: "ghost", onClick: openReply }, "AI \u56DE\u590D"),
1059
1197
  (0, import_react6.createElement)("span", { className: "spacer" }),
1060
1198
  html ? (0, import_react6.createElement)(
1061
1199
  "button",
@@ -1091,13 +1229,13 @@ function Reader({
1091
1229
  className: "mailharbor-attachment",
1092
1230
  key: file.id,
1093
1231
  href: source === "server" ? api.attachmentUrl({
1094
- accountId: account.id,
1232
+ accountId: detail.accountId,
1095
1233
  source: "server",
1096
1234
  mailbox,
1097
1235
  uid: detail.uid ?? "",
1098
1236
  part: file.id
1099
1237
  }) : api.attachmentUrl({
1100
- accountId: account.id,
1238
+ accountId: detail.accountId,
1101
1239
  source: "local",
1102
1240
  emailId: detail.id,
1103
1241
  attachmentId: file.id
@@ -1110,13 +1248,127 @@ function Reader({
1110
1248
  (0, import_react6.createElement)("span", { className: "muted small" }, formatBytes(file.size))
1111
1249
  )
1112
1250
  )
1113
- )
1251
+ ),
1252
+ summaryOpen ? (0, import_react6.createElement)(
1253
+ Modal,
1254
+ {
1255
+ title: "AI \u6458\u8981",
1256
+ onClose: () => setSummaryOpen(false),
1257
+ footer: (0, import_react6.createElement)(
1258
+ "div",
1259
+ { style: { display: "flex", gap: "8px", width: "100%", justifyContent: "flex-end" } },
1260
+ (0, import_react6.createElement)("button", { className: "ghost", onClick: () => setSummaryOpen(false) }, "\u5173\u95ED"),
1261
+ summaryBlocks.length === 0 ? null : (0, import_react6.createElement)(
1262
+ "button",
1263
+ {
1264
+ className: "primary",
1265
+ onClick: () => {
1266
+ void navigator.clipboard?.writeText(
1267
+ summaryBlocks.map((block) => `${block.title}
1268
+ ${block.body}`).join("\n\n")
1269
+ ).then(() => setSummaryOpen(false));
1270
+ }
1271
+ },
1272
+ "\u590D\u5236"
1273
+ )
1274
+ )
1275
+ },
1276
+ summaryBusy ? "\u6B63\u5728\u9605\u8BFB\u5E76\u603B\u7ED3\u2026" : null,
1277
+ summaryError === "" ? null : (0, import_react6.createElement)("div", { style: { color: "var(--m-danger)" } }, summaryError),
1278
+ summaryBlocks.length === 0 ? null : (0, import_react6.createElement)(
1279
+ "div",
1280
+ { className: "mailharbor-summary" },
1281
+ summaryBlocks.map(
1282
+ (block, index) => (0, import_react6.createElement)(
1283
+ "section",
1284
+ { key: `${block.title}:${String(index)}` },
1285
+ block.title === "" ? null : (0, import_react6.createElement)("h4", null, block.title),
1286
+ (0, import_react6.createElement)("p", null, block.body)
1287
+ )
1288
+ )
1289
+ )
1290
+ ) : null,
1291
+ replyOpen ? (0, import_react6.createElement)(
1292
+ Modal,
1293
+ {
1294
+ title: "AI \u56DE\u590D",
1295
+ onClose: () => setReplyOpen(false),
1296
+ footer: (0, import_react6.createElement)(
1297
+ "div",
1298
+ { style: { display: "flex", gap: "8px", width: "100%", alignItems: "center" } },
1299
+ (0, import_react6.createElement)("span", { className: "small muted" }, "\u751F\u6210\u540E\u53EF\u5728\u6B63\u6587\u91CC\u7EE7\u7EED\u4FEE\u6539\uFF0C\u53D1\u9001\u4ECD\u9700\u4F60\u786E\u8BA4"),
1300
+ (0, import_react6.createElement)("span", { className: "spacer" }),
1301
+ (0, import_react6.createElement)("button", { className: "ghost", onClick: () => setReplyOpen(false) }, "\u53D6\u6D88"),
1302
+ (0, import_react6.createElement)(
1303
+ "button",
1304
+ { className: "ghost", onClick: () => void generateReply(), disabled: replyBusy },
1305
+ replyBusy ? "\u751F\u6210\u4E2D\u2026" : replyText === "" ? "\u751F\u6210\u56DE\u590D" : "\u91CD\u65B0\u751F\u6210"
1306
+ ),
1307
+ (0, import_react6.createElement)(
1308
+ "button",
1309
+ {
1310
+ className: "primary",
1311
+ onClick: insertReply,
1312
+ disabled: replyBusy || replyText.trim() === ""
1313
+ },
1314
+ "\u63D2\u5165\u5230\u6B63\u6587"
1315
+ )
1316
+ )
1317
+ },
1318
+ (0, import_react6.createElement)(
1319
+ "div",
1320
+ { className: "mailharbor-grid" },
1321
+ (0, import_react6.createElement)(
1322
+ "div",
1323
+ { className: "mailharbor-field" },
1324
+ (0, import_react6.createElement)("label", null, "\u60F3\u8BA9 AI \u600E\u4E48\u5199\uFF08\u53EF\u7559\u7A7A\uFF09"),
1325
+ (0, import_react6.createElement)("input", {
1326
+ value: replyInstruction,
1327
+ placeholder: "\u4F8B\u5982\uFF1A\u786E\u8BA4\u6536\u5230\uFF0C\u8BF4\u660E\u5468\u4E94\u524D\u7ED9\u7B54\u590D",
1328
+ onChange: (event) => setReplyInstruction(event.target.value)
1329
+ })
1330
+ ),
1331
+ (0, import_react6.createElement)(
1332
+ "div",
1333
+ { className: "mailharbor-field" },
1334
+ (0, import_react6.createElement)("label", null, "\u8BED\u8A00"),
1335
+ (0, import_react6.createElement)(
1336
+ "div",
1337
+ { className: "mailharbor-segment" },
1338
+ (0, import_react6.createElement)(
1339
+ "button",
1340
+ {
1341
+ className: replyLanguage === "zh" ? "active" : "",
1342
+ onClick: () => setReplyLanguage("zh")
1343
+ },
1344
+ "\u4E2D\u6587"
1345
+ ),
1346
+ (0, import_react6.createElement)(
1347
+ "button",
1348
+ {
1349
+ className: replyLanguage === "en" ? "active" : "",
1350
+ onClick: () => setReplyLanguage("en")
1351
+ },
1352
+ "English"
1353
+ )
1354
+ )
1355
+ )
1356
+ ),
1357
+ replyError === "" ? null : (0, import_react6.createElement)("div", { style: { color: "var(--m-danger)" } }, replyError),
1358
+ (0, import_react6.createElement)("textarea", {
1359
+ rows: 12,
1360
+ value: replyText,
1361
+ placeholder: replyBusy ? "\u6B63\u5728\u751F\u6210\u2026" : "\u70B9\u300C\u751F\u6210\u56DE\u590D\u300D\u8BA9 AI \u8D77\u8349\uFF0C\u7ED3\u679C\u4F1A\u663E\u793A\u5728\u8FD9\u91CC",
1362
+ onChange: (event) => setReplyText(event.target.value)
1363
+ })
1364
+ ) : null
1114
1365
  );
1115
1366
  }
1116
1367
 
1117
1368
  // src/client/panel.tsx
1118
1369
  var EMPTY_PAGE = { items: [], total: 0, offset: 0, limit: 40 };
1119
- function errorText3(error) {
1370
+ var DRAFTS = "__drafts";
1371
+ function errorText4(error) {
1120
1372
  return error instanceof ApiError || error instanceof Error ? error.message : String(error);
1121
1373
  }
1122
1374
  function matchesNarrow() {
@@ -1129,12 +1381,14 @@ function quoteForReply(detail) {
1129
1381
  function MailPanel() {
1130
1382
  const [state, setState] = (0, import_react7.useState)(null);
1131
1383
  const [error, setError] = (0, import_react7.useState)("");
1384
+ const [accountScope, setAccountScope] = (0, import_react7.useState)(null);
1132
1385
  const [source, setSource] = (0, import_react7.useState)("local");
1133
1386
  const [mailbox, setMailbox] = (0, import_react7.useState)("all");
1134
1387
  const [folders, setFolders] = (0, import_react7.useState)([]);
1135
1388
  const [query2, setQuery] = (0, import_react7.useState)("");
1136
1389
  const [search, setSearch] = (0, import_react7.useState)("");
1137
1390
  const [page, setPage] = (0, import_react7.useState)(EMPTY_PAGE);
1391
+ const [drafts, setDrafts] = (0, import_react7.useState)([]);
1138
1392
  const [limit, setLimit] = (0, import_react7.useState)(40);
1139
1393
  const [loading, setLoading] = (0, import_react7.useState)(false);
1140
1394
  const [selected, setSelected] = (0, import_react7.useState)(null);
@@ -1145,8 +1399,11 @@ function MailPanel() {
1145
1399
  const [toast, setToast] = (0, import_react7.useState)("");
1146
1400
  const [narrow, setNarrow] = (0, import_react7.useState)(() => matchesNarrow());
1147
1401
  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;
1402
+ const accounts = state?.accounts ?? [];
1403
+ const isAll = accountScope === "all";
1404
+ const account = accounts.find((item) => item.id === accountScope) ?? null;
1405
+ const composeAccount = accounts.find((item) => item.id === compose?.accountId) ?? account ?? accounts[0] ?? null;
1406
+ const showingDrafts = mailbox === DRAFTS && source === "local";
1150
1407
  const refresh = (0, import_react7.useCallback)(() => {
1151
1408
  setTick((value) => value + 1);
1152
1409
  }, []);
@@ -1159,29 +1416,32 @@ function MailPanel() {
1159
1416
  (0, import_react7.useEffect)(() => {
1160
1417
  let cancelled = false;
1161
1418
  api.state().then((next) => {
1162
- if (!cancelled) {
1163
- setState(next);
1164
- if (next.settings.render.listLimit > 0) setLimit(next.settings.render.listLimit);
1165
- }
1419
+ if (cancelled) return;
1420
+ setState(next);
1421
+ if (next.settings.render.listLimit > 0) setLimit(next.settings.render.listLimit);
1422
+ setAccountScope((current) => current ?? next.activeAccountId);
1166
1423
  }).catch((caught) => {
1167
- if (!cancelled) setError(errorText3(caught));
1424
+ if (!cancelled) setError(errorText4(caught));
1168
1425
  });
1169
1426
  return () => {
1170
1427
  cancelled = true;
1171
1428
  };
1172
1429
  }, [tick]);
1173
1430
  (0, import_react7.useEffect)(() => {
1174
- if (accountId === null) return;
1431
+ if (accountScope === null) return;
1175
1432
  let cancelled = false;
1176
- api.folders({ accountId, source }).then((result) => {
1433
+ api.folders({ accountId: accountScope, source }).then((result) => {
1177
1434
  if (cancelled) return;
1178
1435
  const items2 = source === "local" ? [
1179
1436
  {
1180
1437
  name: "all",
1181
1438
  label: "\u5168\u90E8\u90AE\u4EF6",
1182
- count: result.items.reduce((sum, item) => sum + item.count, 0)
1439
+ count: result.items.filter((item) => item.name !== DRAFTS).reduce((sum, item) => sum + item.count, 0)
1183
1440
  },
1184
- ...result.items.map((item) => ({ ...item, label: item.label ?? item.name }))
1441
+ ...result.items.map((item) => ({
1442
+ ...item,
1443
+ label: item.name === DRAFTS ? "\u8349\u7A3F" : item.label ?? item.name
1444
+ }))
1185
1445
  ] : result.items.map((item) => ({ ...item, label: item.label ?? item.name }));
1186
1446
  setFolders(items2);
1187
1447
  }).catch(() => {
@@ -1190,26 +1450,56 @@ function MailPanel() {
1190
1450
  return () => {
1191
1451
  cancelled = true;
1192
1452
  };
1193
- }, [accountId, source, tick]);
1453
+ }, [accountScope, source, tick]);
1194
1454
  (0, import_react7.useEffect)(() => {
1195
1455
  setMailbox(source === "local" ? "all" : "INBOX");
1196
1456
  setSelected(null);
1457
+ setLimit(state?.settings.render.listLimit ?? 40);
1197
1458
  }, [source]);
1198
1459
  (0, import_react7.useEffect)(() => {
1199
- if (accountId === null) return;
1460
+ if (accountScope === "all" && source === "server") setSource("local");
1461
+ setSelected(null);
1462
+ }, [accountScope, source]);
1463
+ (0, import_react7.useEffect)(() => {
1464
+ if (accountScope === null) return;
1200
1465
  let cancelled = false;
1201
1466
  setLoading(true);
1202
- api.messages({ accountId, source, mailbox, query: search, limit }).then((result) => {
1203
- if (!cancelled) setPage(result);
1467
+ if (showingDrafts) {
1468
+ const targets = isAll ? accounts : account === null ? [] : [account];
1469
+ Promise.all(
1470
+ targets.map(async (target) => {
1471
+ try {
1472
+ const result = await api.drafts(target.id);
1473
+ return result.items.map((draft) => ({ ...draft, accountId: target.id }));
1474
+ } catch {
1475
+ return [];
1476
+ }
1477
+ })
1478
+ ).then((groups) => {
1479
+ if (cancelled) return;
1480
+ setDrafts(groups.flat().sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)));
1481
+ setPage(EMPTY_PAGE);
1482
+ }).finally(() => {
1483
+ if (!cancelled) setLoading(false);
1484
+ });
1485
+ return () => {
1486
+ cancelled = true;
1487
+ };
1488
+ }
1489
+ api.messages({ accountId: accountScope, source, mailbox, query: search, limit }).then((result) => {
1490
+ if (!cancelled) {
1491
+ setPage(result);
1492
+ setDrafts([]);
1493
+ }
1204
1494
  }).catch((caught) => {
1205
- if (!cancelled) setError(errorText3(caught));
1495
+ if (!cancelled) setError(errorText4(caught));
1206
1496
  }).finally(() => {
1207
1497
  if (!cancelled) setLoading(false);
1208
1498
  });
1209
1499
  return () => {
1210
1500
  cancelled = true;
1211
1501
  };
1212
- }, [accountId, source, mailbox, search, limit, tick]);
1502
+ }, [accountScope, source, mailbox, search, limit, tick]);
1213
1503
  (0, import_react7.useEffect)(() => {
1214
1504
  if (typeof window.matchMedia !== "function") return;
1215
1505
  const media = window.matchMedia("(max-width: 900px)");
@@ -1220,11 +1510,11 @@ function MailPanel() {
1220
1510
  };
1221
1511
  }, []);
1222
1512
  (0, import_react7.useEffect)(() => {
1223
- if (!showBackup || accountId === null) return;
1513
+ if (!showBackup || accountScope === null || isAll) return;
1224
1514
  let cancelled = false;
1225
1515
  const poll = async () => {
1226
1516
  try {
1227
- const status = await api.backupStatus(accountId);
1517
+ const status = await api.backupStatus(accountScope);
1228
1518
  if (!cancelled) setBackup(status);
1229
1519
  } catch {
1230
1520
  }
@@ -1237,16 +1527,17 @@ function MailPanel() {
1237
1527
  cancelled = true;
1238
1528
  window.clearInterval(timer);
1239
1529
  };
1240
- }, [showBackup, accountId]);
1530
+ }, [showBackup, accountScope, isAll]);
1241
1531
  const openMessage = async (item) => {
1242
- if (account === null) return;
1532
+ const owner = item.accountId;
1533
+ const ownerSource = source;
1243
1534
  setError("");
1244
1535
  try {
1245
- const detail = await api.message({ id: item.id, accountId: account.id, source, mailbox });
1536
+ const detail = await api.message({ id: item.id, accountId: owner, source: ownerSource, mailbox });
1246
1537
  setSelected(detail);
1247
1538
  if (detail.unread && state?.settings.read.markOnOpen === true) {
1248
1539
  try {
1249
- await api.setRead({ id: detail.id, accountId: account.id, source, mailbox, read: true });
1540
+ await api.setRead({ id: detail.id, accountId: owner, source: ownerSource, mailbox, read: true });
1250
1541
  setSelected({ ...detail, unread: false });
1251
1542
  setPage((current) => ({
1252
1543
  ...current,
@@ -1256,14 +1547,15 @@ function MailPanel() {
1256
1547
  }
1257
1548
  }
1258
1549
  } catch (caught) {
1259
- setError(errorText3(caught));
1550
+ setError(errorText4(caught));
1260
1551
  }
1261
1552
  };
1262
1553
  const startCompose = (mode) => {
1263
- if (account === null) return;
1554
+ const owner = account ?? accounts.find((item) => item.id === state?.activeAccountId) ?? accounts[0];
1555
+ if (owner === void 0) return;
1264
1556
  const detail = selected;
1265
1557
  if (mode === "new" || detail === null) {
1266
- setCompose({ mode: "new", to: "", cc: "", bcc: "", subject: "", html: "" });
1558
+ setCompose({ mode: "new", accountId: owner.id, to: "", cc: "", bcc: "", subject: "", html: "" });
1267
1559
  return;
1268
1560
  }
1269
1561
  const subjectPrefix = mode === "forward" ? "Fwd: " : "Re: ";
@@ -1271,6 +1563,7 @@ function MailPanel() {
1271
1563
  if (mode === "forward") {
1272
1564
  setCompose({
1273
1565
  mode,
1566
+ accountId: detail.accountId,
1274
1567
  to: "",
1275
1568
  cc: "",
1276
1569
  bcc: "",
@@ -1281,9 +1574,10 @@ function MailPanel() {
1281
1574
  return;
1282
1575
  }
1283
1576
  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(", ") : "";
1577
+ const cc = mode === "replyAll" ? [...detail.to, ...detail.cc].map((item) => item.address).filter((address) => address !== owner.email && address !== to).join(", ") : "";
1285
1578
  setCompose({
1286
1579
  mode,
1580
+ accountId: detail.accountId,
1287
1581
  to,
1288
1582
  cc,
1289
1583
  bcc: "",
@@ -1292,32 +1586,58 @@ function MailPanel() {
1292
1586
  ...detail.messageId === "" ? {} : { inReplyTo: detail.messageId, references: [...detail.references, detail.messageId].join(" ") }
1293
1587
  });
1294
1588
  };
1589
+ const openDraft = (draft) => {
1590
+ setCompose({
1591
+ mode: "new",
1592
+ accountId: draft.accountId,
1593
+ to: draft.to,
1594
+ cc: draft.cc,
1595
+ bcc: draft.bcc,
1596
+ subject: draft.subject,
1597
+ html: draft.html,
1598
+ attachments: draft.attachments,
1599
+ draftId: draft.id
1600
+ });
1601
+ };
1602
+ const removeDraft = async (id, accountId) => {
1603
+ try {
1604
+ await api.deleteDraft(id, accountId);
1605
+ setDrafts((current) => current.filter((draft) => draft.id !== id));
1606
+ notice("\u8349\u7A3F\u5DF2\u5220\u9664");
1607
+ refresh();
1608
+ } catch (caught) {
1609
+ setError(errorText4(caught));
1610
+ }
1611
+ };
1295
1612
  const switchAccount = async (id) => {
1296
1613
  try {
1297
- await api.activateAccount(id);
1614
+ if (id !== "all") await api.activateAccount(id);
1615
+ setAccountScope(id);
1298
1616
  setSelected(null);
1617
+ if (id === "all") setSource("local");
1299
1618
  refresh();
1300
1619
  } catch (caught) {
1301
- setError(errorText3(caught));
1620
+ setError(errorText4(caught));
1302
1621
  }
1303
1622
  };
1304
1623
  const startBackup = async () => {
1305
- if (accountId === null) return;
1624
+ const owner = accountScope === null || isAll ? accounts[0] : accounts.find((item) => item.id === accountScope);
1625
+ if (owner === void 0) return;
1306
1626
  try {
1307
- const result = await api.startBackup(accountId);
1627
+ const result = await api.startBackup(owner.id);
1308
1628
  setBackup(result.status);
1309
1629
  setShowBackup(true);
1310
1630
  } catch (caught) {
1311
- setError(errorText3(caught));
1631
+ setError(errorText4(caught));
1312
1632
  }
1313
1633
  };
1314
1634
  const stopBackup = async () => {
1315
- if (accountId === null) return;
1635
+ if (accountScope === null || isAll) return;
1316
1636
  try {
1317
- const result = await api.stopBackup(accountId);
1637
+ const result = await api.stopBackup(accountScope);
1318
1638
  setBackup(result.status);
1319
1639
  } catch (caught) {
1320
- setError(errorText3(caught));
1640
+ setError(errorText4(caught));
1321
1641
  }
1322
1642
  };
1323
1643
  const folderOptions = (0, import_react7.useMemo)(
@@ -1334,7 +1654,7 @@ function MailPanel() {
1334
1654
  (0, import_react7.createElement)("div", { className: "mailharbor-empty" }, error === "" ? "\u6B63\u5728\u52A0\u8F7D\u90AE\u7BB1\u2026" : error)
1335
1655
  );
1336
1656
  }
1337
- if (account === null) {
1657
+ if (accounts.length === 0) {
1338
1658
  return (0, import_react7.createElement)(
1339
1659
  "div",
1340
1660
  { className: "mailharbor" },
@@ -1351,13 +1671,16 @@ function MailPanel() {
1351
1671
  (0, import_react7.createElement)(AddAccountForm, {
1352
1672
  onDone: (next) => {
1353
1673
  setState(next);
1674
+ setAccountScope(next.activeAccountId);
1354
1675
  setSource("local");
1355
1676
  }
1356
1677
  })
1357
1678
  )
1358
1679
  );
1359
1680
  }
1360
- const needsLogin = source === "server" && state.loggedIn === false;
1681
+ const currentAccount = account ?? accounts[0] ?? null;
1682
+ const loggedIn = isAll ? accounts.some((item) => item.id === state.activeAccountId) : state.loggedIn;
1683
+ const needsLogin = source === "server" && !isAll && state.loggedIn === false;
1361
1684
  const items = page.items;
1362
1685
  return (0, import_react7.createElement)(
1363
1686
  "div",
@@ -1368,17 +1691,19 @@ function MailPanel() {
1368
1691
  (0, import_react7.createElement)(
1369
1692
  "div",
1370
1693
  { className: "mailharbor-account" },
1371
- (0, import_react7.createElement)(Avatar, { email: account.email, size: 26 }),
1372
- state.accounts.length > 1 ? (0, import_react7.createElement)(
1694
+ (0, import_react7.createElement)(Avatar, { email: isAll ? "all" : currentAccount?.email ?? "?", size: 26 }),
1695
+ accounts.length > 1 ? (0, import_react7.createElement)(
1373
1696
  "select",
1374
1697
  {
1375
- value: account.id,
1698
+ value: accountScope ?? "",
1376
1699
  onChange: (event) => void switchAccount(event.target.value),
1377
1700
  "aria-label": "\u8D26\u53F7"
1378
1701
  },
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
1702
+ (0, import_react7.createElement)("option", { key: "all", value: "all" }, "\u5168\u90E8\u8D26\u53F7"),
1703
+ accounts.map((item) => (0, import_react7.createElement)("option", { key: item.id, value: item.id }, item.label))
1704
+ ) : (0, import_react7.createElement)("span", { className: "name" }, currentAccount?.label ?? ""),
1705
+ isAll ? (0, import_react7.createElement)("span", { className: "badge" }, `${String(accounts.length)} \u4E2A\u8D26\u53F7`) : null,
1706
+ !isAll && state.loggedIn === false ? (0, import_react7.createElement)("span", { className: "badge warn" }, "\u672A\u767B\u5F55") : null
1382
1707
  ),
1383
1708
  (0, import_react7.createElement)(
1384
1709
  "div",
@@ -1390,7 +1715,18 @@ function MailPanel() {
1390
1715
  ),
1391
1716
  (0, import_react7.createElement)(
1392
1717
  "button",
1393
- { className: source === "server" ? "active" : "", onClick: () => setSource("server"), role: "tab" },
1718
+ {
1719
+ className: source === "server" ? "active" : "",
1720
+ onClick: () => {
1721
+ if (isAll) {
1722
+ 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");
1723
+ return;
1724
+ }
1725
+ setSource("server");
1726
+ },
1727
+ role: "tab",
1728
+ 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"
1729
+ },
1394
1730
  "\u670D\u52A1\u5668\u5B9E\u65F6"
1395
1731
  )
1396
1732
  ),
@@ -1412,7 +1748,11 @@ function MailPanel() {
1412
1748
  ),
1413
1749
  (0, import_react7.createElement)(
1414
1750
  "button",
1415
- { className: "ghost", onClick: () => void startBackup() },
1751
+ {
1752
+ className: "ghost",
1753
+ onClick: () => void startBackup(),
1754
+ title: isAll ? "\u5907\u4EFD\u5F53\u524D\u6D3B\u52A8\u8D26\u53F7" : "\u5907\u4EFD\u8FD9\u4E2A\u8D26\u53F7"
1755
+ },
1416
1756
  (0, import_react7.createElement)(Icon, { name: "archive" }),
1417
1757
  "\u5907\u4EFD"
1418
1758
  ),
@@ -1425,7 +1765,11 @@ function MailPanel() {
1425
1765
  ),
1426
1766
  toast === "" ? null : (0, import_react7.createElement)("div", { className: "small", style: { padding: "6px 14px", color: "var(--m-ok)" } }, toast),
1427
1767
  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)(
1768
+ needsLogin ? (0, import_react7.createElement)(
1769
+ "div",
1770
+ { className: "mailharbor-settings" },
1771
+ (0, import_react7.createElement)(LoginForm, { account: currentAccount, onDone: refresh })
1772
+ ) : (0, import_react7.createElement)(
1429
1773
  "div",
1430
1774
  { className: "mailharbor-body", "data-mode": narrow && selected !== null ? "reader" : "list" },
1431
1775
  (0, import_react7.createElement)(
@@ -1450,7 +1794,7 @@ function MailPanel() {
1450
1794
  ),
1451
1795
  (0, import_react7.createElement)("input", {
1452
1796
  type: "search",
1453
- placeholder: "\u641C\u7D22\u90AE\u4EF6",
1797
+ placeholder: showingDrafts ? "\u641C\u7D22\u8349\u7A3F" : "\u641C\u7D22\u90AE\u4EF6",
1454
1798
  "aria-label": "\u641C\u7D22\u90AE\u4EF6",
1455
1799
  value: query2,
1456
1800
  onChange: (event) => setQuery(event.target.value),
@@ -1478,7 +1822,73 @@ function MailPanel() {
1478
1822
  (0, import_react7.createElement)(
1479
1823
  "div",
1480
1824
  { 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)(
1825
+ loading && items.length === 0 && drafts.length === 0 ? (0, import_react7.createElement)("div", { className: "mailharbor-empty" }, "\u6B63\u5728\u8BFB\u53D6\u2026") : showingDrafts ? drafts.length === 0 ? (0, import_react7.createElement)(
1826
+ "div",
1827
+ { className: "mailharbor-empty" },
1828
+ (0, import_react7.createElement)(Icon, { name: "compose", size: 28 }),
1829
+ (0, import_react7.createElement)("div", { className: "title" }, "\u6CA1\u6709\u8349\u7A3F"),
1830
+ (0, import_react7.createElement)("div", { className: "small" }, "\u5199\u4FE1\u65F6\u70B9\u300C\u5B58\u8349\u7A3F\u300D\u4F1A\u51FA\u73B0\u5728\u8FD9\u91CC\u3002")
1831
+ ) : drafts.filter(
1832
+ (draft) => search === "" ? true : `${draft.subject} ${draft.to} ${draft.cc}`.toLowerCase().includes(search.toLowerCase())
1833
+ ).map(
1834
+ (draft) => (0, import_react7.createElement)(
1835
+ "div",
1836
+ {
1837
+ key: draft.id,
1838
+ className: "mailharbor-row",
1839
+ role: "button",
1840
+ tabIndex: 0,
1841
+ onClick: () => openDraft(draft),
1842
+ onKeyDown: (event) => {
1843
+ if (event.key === "Enter" || event.key === " ") {
1844
+ event.preventDefault();
1845
+ openDraft(draft);
1846
+ }
1847
+ }
1848
+ },
1849
+ (0, import_react7.createElement)(
1850
+ "span",
1851
+ { className: "line-1" },
1852
+ (0, import_react7.createElement)(
1853
+ "span",
1854
+ { className: "from" },
1855
+ `\u8349\u7A3F${draft.to === "" ? "" : ` \xB7 \u6536\u4EF6\u4EBA ${draft.to}`}`
1856
+ ),
1857
+ isAll ? (0, import_react7.createElement)(
1858
+ "span",
1859
+ { className: "badge" },
1860
+ accounts.find((item) => item.id === draft.accountId)?.label ?? ""
1861
+ ) : null,
1862
+ (0, import_react7.createElement)("span", { className: "date" }, shortDate(draft.updatedAt)),
1863
+ (0, import_react7.createElement)(
1864
+ "button",
1865
+ {
1866
+ className: "subtle",
1867
+ title: "\u5220\u9664\u8349\u7A3F",
1868
+ onClick: (event) => {
1869
+ event.stopPropagation();
1870
+ void removeDraft(draft.id, draft.accountId);
1871
+ }
1872
+ },
1873
+ (0, import_react7.createElement)(Icon, { name: "close", size: 13 })
1874
+ )
1875
+ ),
1876
+ (0, import_react7.createElement)(
1877
+ "span",
1878
+ { className: "line-2" },
1879
+ (0, import_react7.createElement)(
1880
+ "span",
1881
+ { className: "subject" },
1882
+ draft.subject === "" ? "(\u65E0\u4E3B\u9898)" : draft.subject
1883
+ )
1884
+ ),
1885
+ draft.html === "" ? null : (0, import_react7.createElement)(
1886
+ "span",
1887
+ { className: "snippet" },
1888
+ draft.html.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 120)
1889
+ )
1890
+ )
1891
+ ) : items.length === 0 ? (0, import_react7.createElement)(
1482
1892
  "div",
1483
1893
  { className: "mailharbor-empty" },
1484
1894
  (0, import_react7.createElement)(Icon, { name: "mail", size: 28 }),
@@ -1496,8 +1906,8 @@ function MailPanel() {
1496
1906
  (item) => (0, import_react7.createElement)(
1497
1907
  "div",
1498
1908
  {
1499
- key: item.id,
1500
- className: `mailharbor-row${item.unread ? " unread" : ""}${selected?.id === item.id ? " selected" : ""}`,
1909
+ key: `${item.accountId}:${item.id}`,
1910
+ className: `mailharbor-row${item.unread ? " unread" : ""}${selected?.id === item.id && selected?.accountId === item.accountId ? " selected" : ""}`,
1501
1911
  role: "button",
1502
1912
  tabIndex: 0,
1503
1913
  onClick: () => void openMessage(item),
@@ -1518,15 +1928,24 @@ function MailPanel() {
1518
1928
  item.from.name !== "" ? item.from.name : item.from.address
1519
1929
  ),
1520
1930
  item.hasAttachments ? (0, import_react7.createElement)(Icon, { name: "paperclip", size: 13 }) : null,
1931
+ isAll ? (0, import_react7.createElement)(
1932
+ "span",
1933
+ { className: "badge" },
1934
+ accounts.find((entry) => entry.id === item.accountId)?.label ?? ""
1935
+ ) : null,
1521
1936
  item.backedUp === false ? (0, import_react7.createElement)("span", { className: "badge warn" }, "\u672A\u5907\u4EFD") : null,
1522
1937
  (0, import_react7.createElement)("span", { className: "date" }, shortDate(item.date))
1523
1938
  ),
1524
- (0, import_react7.createElement)("span", { className: "line-2" }, (0, import_react7.createElement)("span", { className: "subject" }, item.subject)),
1939
+ (0, import_react7.createElement)(
1940
+ "span",
1941
+ { className: "line-2" },
1942
+ (0, import_react7.createElement)("span", { className: "subject" }, item.subject)
1943
+ ),
1525
1944
  item.snippet === "" ? null : (0, import_react7.createElement)("span", { className: "snippet" }, item.snippet)
1526
1945
  )
1527
1946
  )
1528
1947
  ),
1529
- items.length > 0 ? (0, import_react7.createElement)(
1948
+ !showingDrafts && items.length > 0 ? (0, import_react7.createElement)(
1530
1949
  "div",
1531
1950
  { className: "mailharbor-list-foot" },
1532
1951
  (0, import_react7.createElement)("span", null, `${String(items.length)} / ${String(page.total)} \u5C01`),
@@ -1536,6 +1955,10 @@ function MailPanel() {
1536
1955
  { className: "subtle", onClick: () => setLimit((current) => current + 40) },
1537
1956
  "\u52A0\u8F7D\u66F4\u591A"
1538
1957
  ) : null
1958
+ ) : showingDrafts && drafts.length > 0 ? (0, import_react7.createElement)(
1959
+ "div",
1960
+ { className: "mailharbor-list-foot" },
1961
+ (0, import_react7.createElement)("span", null, `${String(drafts.length)} \u5C01\u8349\u7A3F`)
1539
1962
  ) : null
1540
1963
  ),
1541
1964
  (0, import_react7.createElement)(
@@ -1549,15 +1972,25 @@ function MailPanel() {
1549
1972
  (0, import_react7.createElement)(
1550
1973
  "div",
1551
1974
  { 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"
1975
+ 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
1976
  )
1554
1977
  ) : (0, import_react7.createElement)(Reader, {
1555
1978
  detail: selected,
1556
- account,
1557
1979
  source,
1558
1980
  mailbox,
1559
1981
  onChanged: (patch) => setSelected({ ...selected, ...patch }),
1560
1982
  onReply: (mode) => startCompose(mode),
1983
+ onAiReply: (init) => setCompose({
1984
+ mode: "reply",
1985
+ accountId: selected.accountId,
1986
+ to: init.to,
1987
+ cc: init.cc,
1988
+ bcc: "",
1989
+ subject: init.subject,
1990
+ html: init.html,
1991
+ inReplyTo: init.inReplyTo,
1992
+ references: init.references
1993
+ }),
1561
1994
  onClose: narrow ? () => setSelected(null) : void 0
1562
1995
  })
1563
1996
  )
@@ -1594,8 +2027,8 @@ function MailPanel() {
1594
2027
  backup.output.length === 0 ? "\uFF08\u7B49\u5F85\u8F93\u51FA\u2026\uFF09" : backup.output.join("\n")
1595
2028
  )
1596
2029
  ) : null,
1597
- compose !== null ? (0, import_react7.createElement)(Composer, {
1598
- account,
2030
+ compose !== null && composeAccount !== null ? (0, import_react7.createElement)(Composer, {
2031
+ account: composeAccount,
1599
2032
  initial: compose,
1600
2033
  onClose: () => setCompose(null),
1601
2034
  onSent: (message) => notice(message)
@@ -1604,6 +2037,7 @@ function MailPanel() {
1604
2037
  onClose: () => setShowAdd(false),
1605
2038
  onDone: (next) => {
1606
2039
  setState(next);
2040
+ setAccountScope(next.activeAccountId);
1607
2041
  refresh();
1608
2042
  }
1609
2043
  }) : null
@@ -1612,7 +2046,7 @@ function MailPanel() {
1612
2046
 
1613
2047
  // src/client/settings.tsx
1614
2048
  var import_react8 = require("react");
1615
- function errorText4(error) {
2049
+ function errorText5(error) {
1616
2050
  return error instanceof ApiError || error instanceof Error ? error.message : String(error);
1617
2051
  }
1618
2052
  function MailSettings() {
@@ -1627,6 +2061,11 @@ function MailSettings() {
1627
2061
  const [aiEnabled, setAiEnabled] = (0, import_react8.useState)(true);
1628
2062
  const [aiProvider, setAiProvider] = (0, import_react8.useState)("deepseek-account");
1629
2063
  const [aiModel, setAiModel] = (0, import_react8.useState)("deepseek-flash");
2064
+ const [aiModels, setAiModels] = (0, import_react8.useState)({ providers: [] });
2065
+ const [aiStatus, setAiStatus] = (0, import_react8.useState)(null);
2066
+ const [styleText, setStyleText] = (0, import_react8.useState)("");
2067
+ const [styleMeta, setStyleMeta] = (0, import_react8.useState)({ updatedAt: "", sampleCount: 0 });
2068
+ const [styleBusy, setStyleBusy] = (0, import_react8.useState)(null);
1630
2069
  const [editing, setEditing] = (0, import_react8.useState)(null);
1631
2070
  const [checkResult, setCheckResult] = (0, import_react8.useState)("");
1632
2071
  const [adding, setAdding] = (0, import_react8.useState)(false);
@@ -1645,12 +2084,37 @@ function MailSettings() {
1645
2084
  setAiProvider(view.ai.provider);
1646
2085
  setAiModel(view.ai.model);
1647
2086
  }).catch((caught) => {
1648
- if (!cancelled) setError(errorText4(caught));
2087
+ if (!cancelled) setError(errorText5(caught));
1649
2088
  });
1650
2089
  return () => {
1651
2090
  cancelled = true;
1652
2091
  };
1653
2092
  }, [tick]);
2093
+ (0, import_react8.useEffect)(() => {
2094
+ let cancelled = false;
2095
+ const accountId = settings?.activeAccountId ?? state?.activeAccountId ?? null;
2096
+ if (accountId === null) return;
2097
+ Promise.all([api.aiStatus(accountId), api.aiModels(), api.aiStyle(accountId)]).then(([status, models, style]) => {
2098
+ if (cancelled) return;
2099
+ setAiStatus(status);
2100
+ setAiModels(models);
2101
+ setStyleText(style.style?.profile ?? "");
2102
+ setStyleMeta({
2103
+ updatedAt: style.style?.updatedAt ?? "",
2104
+ sampleCount: style.style?.sampleCount ?? 0
2105
+ });
2106
+ const preferred = models.providers.find((item) => item.provider === status.provider) ?? models.providers[0];
2107
+ if (preferred !== void 0 && status.provider === "") {
2108
+ setAiProvider(preferred.provider);
2109
+ const firstModel = preferred.models[0];
2110
+ if (firstModel !== void 0) setAiModel(firstModel.id);
2111
+ }
2112
+ }).catch(() => {
2113
+ });
2114
+ return () => {
2115
+ cancelled = true;
2116
+ };
2117
+ }, [tick, settings?.activeAccountId]);
1654
2118
  const reload = () => setTick((value) => value + 1);
1655
2119
  const notify = (message) => {
1656
2120
  setToast(message);
@@ -1664,7 +2128,7 @@ function MailSettings() {
1664
2128
  setSettings(result.settings);
1665
2129
  notify("\u8BBE\u7F6E\u5DF2\u4FDD\u5B58");
1666
2130
  } catch (caught) {
1667
- setError(errorText4(caught));
2131
+ setError(errorText5(caught));
1668
2132
  }
1669
2133
  };
1670
2134
  const account = settings?.accounts.find((item) => item.id === settings.activeAccountId) ?? null;
@@ -1716,7 +2180,7 @@ function MailSettings() {
1716
2180
  `IMAP \u6B63\u5E38\uFF08${String(result.imap.mailboxes.length)} \u4E2A\u6587\u4EF6\u5939\uFF09\xB7 SMTP ${result.smtp.ok ? "\u6B63\u5E38" : `\u5931\u8D25: ${result.smtp.error ?? ""}`}`
1717
2181
  );
1718
2182
  } catch (caught) {
1719
- setCheckResult(`\u5931\u8D25: ${errorText4(caught)}`);
2183
+ setCheckResult(`\u5931\u8D25: ${errorText5(caught)}`);
1720
2184
  }
1721
2185
  },
1722
2186
  onSave: async (patch) => {
@@ -1803,34 +2267,135 @@ function MailSettings() {
1803
2267
  setAiEnabled(value);
1804
2268
  void saveGeneral({ ai: { enabled: value } });
1805
2269
  },
1806
- label: "\u542F\u7528 AI\uFF08\u6458\u8981\u3001\u8D77\u8349\u3001\u6DA6\u8272\u3001\u81EA\u7136\u8BED\u8A00\u641C\u7D22\uFF09"
2270
+ label: "\u542F\u7528 AI\uFF08\u6458\u8981\u3001AI \u56DE\u590D\u3001\u7FFB\u8BD1\u3001\u5199\u4F5C\u98CE\u683C\uFF09"
1807
2271
  }),
2272
+ aiStatus === null ? null : (0, import_react8.createElement)(
2273
+ "div",
2274
+ { className: "small", style: { color: aiStatus.available ? "var(--m-ok)" : "var(--m-warn)" } },
2275
+ aiStatus.available ? `\u6A21\u578B\u670D\u52A1\u53EF\u7528 \xB7 ${aiStatus.provider} / ${aiStatus.model}` : aiStatus.error ?? "\u5F53\u524D DSH \u7EC4\u5408\u6CA1\u6709\u53EF\u7528\u7684\u6A21\u578B\u670D\u52A1"
2276
+ ),
1808
2277
  (0, import_react8.createElement)(
1809
2278
  "div",
1810
2279
  { className: "mailharbor-grid" },
1811
2280
  (0, import_react8.createElement)(
1812
2281
  Field,
1813
2282
  { label: "Provider" },
1814
- (0, import_react8.createElement)("input", {
2283
+ aiModels.providers.length === 0 ? (0, import_react8.createElement)("input", {
1815
2284
  value: aiProvider,
1816
2285
  onChange: (event) => setAiProvider(event.target.value),
1817
2286
  onBlur: () => void saveGeneral({ ai: { provider: aiProvider } })
1818
- })
2287
+ }) : (0, import_react8.createElement)(
2288
+ "select",
2289
+ {
2290
+ value: aiProvider,
2291
+ onChange: (event) => {
2292
+ setAiProvider(event.target.value);
2293
+ const provider = aiModels.providers.find((item) => item.provider === event.target.value);
2294
+ const first = provider?.models[0];
2295
+ if (first !== void 0) setAiModel(first.id);
2296
+ void saveGeneral({
2297
+ ai: {
2298
+ provider: event.target.value,
2299
+ ...first === void 0 ? {} : { model: first.id }
2300
+ }
2301
+ });
2302
+ }
2303
+ },
2304
+ aiModels.providers.map(
2305
+ (item) => (0, import_react8.createElement)("option", { key: item.provider, value: item.provider }, item.name)
2306
+ )
2307
+ )
1819
2308
  ),
1820
2309
  (0, import_react8.createElement)(
1821
2310
  Field,
1822
2311
  { 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
- })
2312
+ (() => {
2313
+ const provider = aiModels.providers.find((item) => item.provider === aiProvider);
2314
+ return provider === void 0 || provider.models.length === 0 ? (0, import_react8.createElement)("input", {
2315
+ value: aiModel,
2316
+ onChange: (event) => setAiModel(event.target.value),
2317
+ onBlur: () => void saveGeneral({ ai: { model: aiModel } })
2318
+ }) : (0, import_react8.createElement)(
2319
+ "select",
2320
+ {
2321
+ value: aiModel,
2322
+ onChange: (event) => {
2323
+ setAiModel(event.target.value);
2324
+ void saveGeneral({ ai: { model: event.target.value } });
2325
+ }
2326
+ },
2327
+ provider.models.map((item) => (0, import_react8.createElement)("option", { key: item.id, value: item.id }, item.name))
2328
+ );
2329
+ })()
2330
+ )
2331
+ ),
2332
+ (0, import_react8.createElement)("hr", null),
2333
+ (0, import_react8.createElement)(
2334
+ "div",
2335
+ { style: { display: "flex", alignItems: "center", gap: "8px" } },
2336
+ (0, import_react8.createElement)("strong", { style: { fontSize: "12.5px" } }, "\u5199\u4F5C\u98CE\u683C"),
2337
+ (0, import_react8.createElement)(
2338
+ "span",
2339
+ { className: "small muted" },
2340
+ styleMeta.updatedAt === "" ? "\u8FD8\u6CA1\u6709\u751F\u6210" : `${styleMeta.sampleCount} \u5C01\u5DF2\u53D1\u9001\u90AE\u4EF6 \xB7 ${new Date(styleMeta.updatedAt).toLocaleString()}`
2341
+ )
2342
+ ),
2343
+ (0, import_react8.createElement)(
2344
+ "p",
2345
+ { className: "small muted" },
2346
+ "\u4ECE\u5DF2\u53D1\u9001\u90AE\u4EF6\u5B66\u4E60\u4F60\u7684\u79F0\u547C\u3001\u7ED3\u5C3E\u3001\u8BED\u6C14\u548C\u5E38\u7528\u8868\u8FBE\uFF0CAI \u56DE\u590D\u65F6\u4F1A\u53C2\u8003\u5B83\u3002\u53EA\u4FDD\u5B58\u63D0\u70BC\u540E\u7684\u6863\u6848\uFF0C\u4E0D\u4FDD\u5B58\u90AE\u4EF6\u539F\u6587\u3002"
2347
+ ),
2348
+ (0, import_react8.createElement)("textarea", {
2349
+ rows: 6,
2350
+ value: styleText,
2351
+ placeholder: "\u70B9\u300C\u4ECE\u5DF2\u53D1\u9001\u90AE\u4EF6\u5B66\u4E60\u300D\u751F\u6210\uFF0C\u6216\u76F4\u63A5\u5199\u4E00\u6BB5\u4F60\u7684\u5199\u4F5C\u98CE\u683C",
2352
+ onChange: (event) => setStyleText(event.target.value)
2353
+ }),
2354
+ (0, import_react8.createElement)(
2355
+ "div",
2356
+ { style: { display: "flex", gap: "8px", flexWrap: "wrap" } },
2357
+ (0, import_react8.createElement)(
2358
+ "button",
2359
+ {
2360
+ className: "ghost",
2361
+ disabled: styleBusy !== null,
2362
+ onClick: () => {
2363
+ const accountId = settings.activeAccountId;
2364
+ if (accountId === null) return;
2365
+ setStyleBusy("learn");
2366
+ setError("");
2367
+ api.aiLearnStyle(accountId).then((result) => {
2368
+ setStyleText(result.style.profile);
2369
+ setStyleMeta({ updatedAt: result.style.updatedAt, sampleCount: result.style.sampleCount });
2370
+ notify("\u5DF2\u6839\u636E\u5DF2\u53D1\u9001\u90AE\u4EF6\u751F\u6210\u5199\u4F5C\u98CE\u683C");
2371
+ }).catch((caught) => setError(errorText5(caught))).finally(() => setStyleBusy(null));
2372
+ }
2373
+ },
2374
+ styleBusy === "learn" ? "\u5B66\u4E60\u4E2D\u2026" : "\u4ECE\u5DF2\u53D1\u9001\u90AE\u4EF6\u5B66\u4E60"
2375
+ ),
2376
+ (0, import_react8.createElement)(
2377
+ "button",
2378
+ {
2379
+ className: "primary",
2380
+ disabled: styleBusy !== null || styleText.trim() === "",
2381
+ onClick: () => {
2382
+ const accountId = settings.activeAccountId;
2383
+ if (accountId === null) return;
2384
+ setStyleBusy("save");
2385
+ setError("");
2386
+ api.aiSaveStyle(accountId, styleText).then((result) => {
2387
+ setStyleMeta({ updatedAt: result.style.updatedAt, sampleCount: result.style.sampleCount });
2388
+ notify("\u5199\u4F5C\u98CE\u683C\u5DF2\u4FDD\u5B58");
2389
+ }).catch((caught) => setError(errorText5(caught))).finally(() => setStyleBusy(null));
2390
+ }
2391
+ },
2392
+ "\u4FDD\u5B58\u98CE\u683C"
1828
2393
  )
1829
2394
  ),
1830
2395
  (0, import_react8.createElement)(
1831
2396
  "p",
1832
2397
  { 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"
2398
+ "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
2399
  )
1835
2400
  ),
1836
2401
  (0, import_react8.createElement)(
@@ -1845,7 +2410,11 @@ function MailSettings() {
1845
2410
  (0, import_react8.createElement)("dt", null, "\u914D\u7F6E\u6587\u4EF6"),
1846
2411
  (0, import_react8.createElement)("dd", null, settings.configFile),
1847
2412
  (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`),
2413
+ (0, import_react8.createElement)(
2414
+ "dd",
2415
+ null,
2416
+ account === null ? "\u2014" : `${account.dataDir.startsWith("/") ? account.dataDir : `${settings.dataDir}/${account.dataDir}`}/mail.db`
2417
+ ),
1849
2418
  (0, import_react8.createElement)("dt", null, "\u63D2\u4EF6\u7248\u672C"),
1850
2419
  (0, import_react8.createElement)("dd", null, state.version)
1851
2420
  ),
@@ -1862,7 +2431,7 @@ function MailSettings() {
1862
2431
  notify(status.status.message === "" ? "\u5907\u4EFD\u5DF2\u5F00\u59CB" : status.status.message);
1863
2432
  reload();
1864
2433
  } catch (caught) {
1865
- setError(errorText4(caught));
2434
+ setError(errorText5(caught));
1866
2435
  }
1867
2436
  }
1868
2437
  },
@@ -2073,7 +2642,7 @@ function AccountRow({
2073
2642
  }
2074
2643
 
2075
2644
  // src/client/index.tsx
2076
- var VERSION = "0.2.1";
2645
+ var VERSION = "0.3.0";
2077
2646
  var inject = ["slots"];
2078
2647
  var PANEL_ID = "mailharbor";
2079
2648
  var STYLE_ID = "mailharbor-style";