@zaclinley/dsh-mailharbor 0.2.0 → 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 })}`),
@@ -163,16 +186,24 @@ function MailIcon() {
163
186
  return (0, import_react.createElement)(
164
187
  "span",
165
188
  {
166
- style: { position: "relative", display: "inline-flex", alignItems: "center", justifyContent: "center" }
189
+ style: {
190
+ position: "relative",
191
+ display: "inline-flex",
192
+ alignItems: "center",
193
+ justifyContent: "center",
194
+ width: "18px",
195
+ height: "18px",
196
+ boxSizing: "border-box"
197
+ }
167
198
  },
168
199
  (0, import_react.createElement)(
169
200
  "svg",
170
- { width: 18, height: 18, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true" },
171
- (0, import_react.createElement)("rect", { x: 2.5, y: 5, width: 19, height: 14, rx: 2.5, stroke: "currentColor", strokeWidth: 1.6 }),
201
+ { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true" },
202
+ (0, import_react.createElement)("rect", { x: 2.5, y: 5, width: 19, height: 14, rx: 2.5, stroke: "currentColor", strokeWidth: 1.7 }),
172
203
  (0, import_react.createElement)("path", {
173
204
  d: "M3.5 7l7.6 5.4a1.6 1.6 0 0 0 1.8 0L20.5 7",
174
205
  stroke: "currentColor",
175
- strokeWidth: 1.6,
206
+ strokeWidth: 1.7,
176
207
  strokeLinecap: "round"
177
208
  })
178
209
  ),
@@ -181,18 +212,26 @@ function MailIcon() {
181
212
  {
182
213
  style: {
183
214
  position: "absolute",
184
- top: "-4px",
185
- right: "-6px",
186
- minWidth: "14px",
187
- height: "14px",
215
+ right: "-5px",
216
+ bottom: "-3px",
217
+ display: "flex",
218
+ alignItems: "center",
219
+ justifyContent: "center",
220
+ boxSizing: "border-box",
221
+ minWidth: "13px",
222
+ height: "13px",
188
223
  padding: "0 3px",
189
224
  borderRadius: "999px",
190
- background: "var(--dsw-alias-brand-primary, #4d6bfe)",
225
+ background: "#5686fe",
191
226
  color: "#fff",
192
227
  fontSize: "9px",
193
- lineHeight: "14px",
194
- textAlign: "center",
195
- fontWeight: 600
228
+ fontWeight: 600,
229
+ lineHeight: "13px",
230
+ zIndex: 1,
231
+ letterSpacing: "-0.01em",
232
+ fontVariantNumeric: "tabular-nums",
233
+ boxShadow: "0 0 0 2px var(--dsw-alias-bg-base, #151517)",
234
+ pointerEvents: "none"
196
235
  }
197
236
  },
198
237
  unread > 99 ? "99+" : String(unread)
@@ -621,6 +660,8 @@ function Composer({
621
660
  const [error, setError] = (0, import_react4.useState)("");
622
661
  const [confirming, setConfirming] = (0, import_react4.useState)(false);
623
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);
624
665
  const editor = (0, import_react4.useRef)(null);
625
666
  (0, import_react4.useEffect)(() => {
626
667
  if (editor.current !== null) editor.current.innerHTML = initial.html;
@@ -631,6 +672,24 @@ function Composer({
631
672
  editor.current?.focus();
632
673
  document.execCommand(command, false, value);
633
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
+ };
634
693
  const addFiles = async (files) => {
635
694
  if (files === null || files.length === 0) return;
636
695
  setError("");
@@ -801,6 +860,16 @@ function Composer({
801
860
  ),
802
861
  (0, import_react4.createElement)("button", { onClick: () => exec("removeFormat"), title: "\u6E05\u9664\u683C\u5F0F" }, "\u6E05\u9664"),
803
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
+ ),
804
873
  (0, import_react4.createElement)(
805
874
  "label",
806
875
  { className: "mailharbor-toggle small" },
@@ -816,6 +885,14 @@ function Composer({
816
885
  })
817
886
  )
818
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
+ ),
819
896
  attachments.length === 0 ? null : (0, import_react4.createElement)(
820
897
  "div",
821
898
  { className: "mailharbor-attachments", style: { padding: 0, border: 0 } },
@@ -934,25 +1011,43 @@ function srcDoc(detail, allowRemoteImages) {
934
1011
  function escapeHtml(value) {
935
1012
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
936
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
+ }
937
1019
  function hasRemoteImages(detail) {
938
1020
  return detail.html !== null && /<img[^>]+src=["']https?:/i.test(detail.html);
939
1021
  }
940
1022
  function addressLine(list) {
941
1023
  return list.map((item) => item.name !== "" ? `${item.name} <${item.address}>` : item.address).join("\u3001");
942
1024
  }
1025
+ function errorText3(error) {
1026
+ return error instanceof Error ? error.message : String(error);
1027
+ }
943
1028
  function Reader({
944
1029
  detail,
945
- account,
946
1030
  source,
947
1031
  mailbox,
948
1032
  onChanged,
949
1033
  onReply,
1034
+ onAiReply,
950
1035
  onClose
951
1036
  }) {
952
1037
  const [plain, setPlain] = (0, import_react6.useState)(false);
953
1038
  const [loadImages, setLoadImages] = (0, import_react6.useState)(false);
954
1039
  const [busy, setBusy] = (0, import_react6.useState)(false);
955
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)("");
956
1051
  const remoteImages = (0, import_react6.useMemo)(() => hasRemoteImages(detail), [detail]);
957
1052
  const html = detail.html !== null && detail.html !== "";
958
1053
  const toggleRead = async () => {
@@ -961,18 +1056,74 @@ function Reader({
961
1056
  try {
962
1057
  const result = await api.setRead({
963
1058
  id: detail.id,
964
- accountId: account.id,
1059
+ accountId: detail.accountId,
965
1060
  source,
966
1061
  mailbox,
967
1062
  read: detail.unread
968
1063
  });
969
1064
  onChanged({ unread: !result.read });
970
1065
  } catch (caught) {
971
- setError(caught instanceof Error ? caught.message : String(caught));
1066
+ setError(errorText3(caught));
972
1067
  } finally {
973
1068
  setBusy(false);
974
1069
  }
975
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
+ };
976
1127
  return (0, import_react6.createElement)(
977
1128
  "div",
978
1129
  { className: "mailharbor-reader" },
@@ -1040,6 +1191,9 @@ function Reader({
1040
1191
  (0, import_react6.createElement)(Icon, { name: "forward" }),
1041
1192
  "\u8F6C\u53D1"
1042
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"),
1043
1197
  (0, import_react6.createElement)("span", { className: "spacer" }),
1044
1198
  html ? (0, import_react6.createElement)(
1045
1199
  "button",
@@ -1075,13 +1229,13 @@ function Reader({
1075
1229
  className: "mailharbor-attachment",
1076
1230
  key: file.id,
1077
1231
  href: source === "server" ? api.attachmentUrl({
1078
- accountId: account.id,
1232
+ accountId: detail.accountId,
1079
1233
  source: "server",
1080
1234
  mailbox,
1081
1235
  uid: detail.uid ?? "",
1082
1236
  part: file.id
1083
1237
  }) : api.attachmentUrl({
1084
- accountId: account.id,
1238
+ accountId: detail.accountId,
1085
1239
  source: "local",
1086
1240
  emailId: detail.id,
1087
1241
  attachmentId: file.id
@@ -1094,13 +1248,127 @@ function Reader({
1094
1248
  (0, import_react6.createElement)("span", { className: "muted small" }, formatBytes(file.size))
1095
1249
  )
1096
1250
  )
1097
- )
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
1098
1365
  );
1099
1366
  }
1100
1367
 
1101
1368
  // src/client/panel.tsx
1102
1369
  var EMPTY_PAGE = { items: [], total: 0, offset: 0, limit: 40 };
1103
- function errorText3(error) {
1370
+ var DRAFTS = "__drafts";
1371
+ function errorText4(error) {
1104
1372
  return error instanceof ApiError || error instanceof Error ? error.message : String(error);
1105
1373
  }
1106
1374
  function matchesNarrow() {
@@ -1113,12 +1381,14 @@ function quoteForReply(detail) {
1113
1381
  function MailPanel() {
1114
1382
  const [state, setState] = (0, import_react7.useState)(null);
1115
1383
  const [error, setError] = (0, import_react7.useState)("");
1384
+ const [accountScope, setAccountScope] = (0, import_react7.useState)(null);
1116
1385
  const [source, setSource] = (0, import_react7.useState)("local");
1117
1386
  const [mailbox, setMailbox] = (0, import_react7.useState)("all");
1118
1387
  const [folders, setFolders] = (0, import_react7.useState)([]);
1119
1388
  const [query2, setQuery] = (0, import_react7.useState)("");
1120
1389
  const [search, setSearch] = (0, import_react7.useState)("");
1121
1390
  const [page, setPage] = (0, import_react7.useState)(EMPTY_PAGE);
1391
+ const [drafts, setDrafts] = (0, import_react7.useState)([]);
1122
1392
  const [limit, setLimit] = (0, import_react7.useState)(40);
1123
1393
  const [loading, setLoading] = (0, import_react7.useState)(false);
1124
1394
  const [selected, setSelected] = (0, import_react7.useState)(null);
@@ -1129,8 +1399,11 @@ function MailPanel() {
1129
1399
  const [toast, setToast] = (0, import_react7.useState)("");
1130
1400
  const [narrow, setNarrow] = (0, import_react7.useState)(() => matchesNarrow());
1131
1401
  const [tick, setTick] = (0, import_react7.useState)(0);
1132
- const account = state?.accounts.find((item) => item.id === state.activeAccountId) ?? null;
1133
- 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";
1134
1407
  const refresh = (0, import_react7.useCallback)(() => {
1135
1408
  setTick((value) => value + 1);
1136
1409
  }, []);
@@ -1143,29 +1416,32 @@ function MailPanel() {
1143
1416
  (0, import_react7.useEffect)(() => {
1144
1417
  let cancelled = false;
1145
1418
  api.state().then((next) => {
1146
- if (!cancelled) {
1147
- setState(next);
1148
- if (next.settings.render.listLimit > 0) setLimit(next.settings.render.listLimit);
1149
- }
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);
1150
1423
  }).catch((caught) => {
1151
- if (!cancelled) setError(errorText3(caught));
1424
+ if (!cancelled) setError(errorText4(caught));
1152
1425
  });
1153
1426
  return () => {
1154
1427
  cancelled = true;
1155
1428
  };
1156
1429
  }, [tick]);
1157
1430
  (0, import_react7.useEffect)(() => {
1158
- if (accountId === null) return;
1431
+ if (accountScope === null) return;
1159
1432
  let cancelled = false;
1160
- api.folders({ accountId, source }).then((result) => {
1433
+ api.folders({ accountId: accountScope, source }).then((result) => {
1161
1434
  if (cancelled) return;
1162
1435
  const items2 = source === "local" ? [
1163
1436
  {
1164
1437
  name: "all",
1165
1438
  label: "\u5168\u90E8\u90AE\u4EF6",
1166
- 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)
1167
1440
  },
1168
- ...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
+ }))
1169
1445
  ] : result.items.map((item) => ({ ...item, label: item.label ?? item.name }));
1170
1446
  setFolders(items2);
1171
1447
  }).catch(() => {
@@ -1174,26 +1450,56 @@ function MailPanel() {
1174
1450
  return () => {
1175
1451
  cancelled = true;
1176
1452
  };
1177
- }, [accountId, source, tick]);
1453
+ }, [accountScope, source, tick]);
1178
1454
  (0, import_react7.useEffect)(() => {
1179
1455
  setMailbox(source === "local" ? "all" : "INBOX");
1180
1456
  setSelected(null);
1457
+ setLimit(state?.settings.render.listLimit ?? 40);
1181
1458
  }, [source]);
1182
1459
  (0, import_react7.useEffect)(() => {
1183
- 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;
1184
1465
  let cancelled = false;
1185
1466
  setLoading(true);
1186
- api.messages({ accountId, source, mailbox, query: search, limit }).then((result) => {
1187
- 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
+ }
1188
1494
  }).catch((caught) => {
1189
- if (!cancelled) setError(errorText3(caught));
1495
+ if (!cancelled) setError(errorText4(caught));
1190
1496
  }).finally(() => {
1191
1497
  if (!cancelled) setLoading(false);
1192
1498
  });
1193
1499
  return () => {
1194
1500
  cancelled = true;
1195
1501
  };
1196
- }, [accountId, source, mailbox, search, limit, tick]);
1502
+ }, [accountScope, source, mailbox, search, limit, tick]);
1197
1503
  (0, import_react7.useEffect)(() => {
1198
1504
  if (typeof window.matchMedia !== "function") return;
1199
1505
  const media = window.matchMedia("(max-width: 900px)");
@@ -1204,11 +1510,11 @@ function MailPanel() {
1204
1510
  };
1205
1511
  }, []);
1206
1512
  (0, import_react7.useEffect)(() => {
1207
- if (!showBackup || accountId === null) return;
1513
+ if (!showBackup || accountScope === null || isAll) return;
1208
1514
  let cancelled = false;
1209
1515
  const poll = async () => {
1210
1516
  try {
1211
- const status = await api.backupStatus(accountId);
1517
+ const status = await api.backupStatus(accountScope);
1212
1518
  if (!cancelled) setBackup(status);
1213
1519
  } catch {
1214
1520
  }
@@ -1221,16 +1527,17 @@ function MailPanel() {
1221
1527
  cancelled = true;
1222
1528
  window.clearInterval(timer);
1223
1529
  };
1224
- }, [showBackup, accountId]);
1530
+ }, [showBackup, accountScope, isAll]);
1225
1531
  const openMessage = async (item) => {
1226
- if (account === null) return;
1532
+ const owner = item.accountId;
1533
+ const ownerSource = source;
1227
1534
  setError("");
1228
1535
  try {
1229
- 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 });
1230
1537
  setSelected(detail);
1231
1538
  if (detail.unread && state?.settings.read.markOnOpen === true) {
1232
1539
  try {
1233
- 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 });
1234
1541
  setSelected({ ...detail, unread: false });
1235
1542
  setPage((current) => ({
1236
1543
  ...current,
@@ -1240,14 +1547,15 @@ function MailPanel() {
1240
1547
  }
1241
1548
  }
1242
1549
  } catch (caught) {
1243
- setError(errorText3(caught));
1550
+ setError(errorText4(caught));
1244
1551
  }
1245
1552
  };
1246
1553
  const startCompose = (mode) => {
1247
- if (account === null) return;
1554
+ const owner = account ?? accounts.find((item) => item.id === state?.activeAccountId) ?? accounts[0];
1555
+ if (owner === void 0) return;
1248
1556
  const detail = selected;
1249
1557
  if (mode === "new" || detail === null) {
1250
- setCompose({ mode: "new", to: "", cc: "", bcc: "", subject: "", html: "" });
1558
+ setCompose({ mode: "new", accountId: owner.id, to: "", cc: "", bcc: "", subject: "", html: "" });
1251
1559
  return;
1252
1560
  }
1253
1561
  const subjectPrefix = mode === "forward" ? "Fwd: " : "Re: ";
@@ -1255,6 +1563,7 @@ function MailPanel() {
1255
1563
  if (mode === "forward") {
1256
1564
  setCompose({
1257
1565
  mode,
1566
+ accountId: detail.accountId,
1258
1567
  to: "",
1259
1568
  cc: "",
1260
1569
  bcc: "",
@@ -1265,9 +1574,10 @@ function MailPanel() {
1265
1574
  return;
1266
1575
  }
1267
1576
  const to = detail.from.address;
1268
- 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(", ") : "";
1269
1578
  setCompose({
1270
1579
  mode,
1580
+ accountId: detail.accountId,
1271
1581
  to,
1272
1582
  cc,
1273
1583
  bcc: "",
@@ -1276,32 +1586,58 @@ function MailPanel() {
1276
1586
  ...detail.messageId === "" ? {} : { inReplyTo: detail.messageId, references: [...detail.references, detail.messageId].join(" ") }
1277
1587
  });
1278
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
+ };
1279
1612
  const switchAccount = async (id) => {
1280
1613
  try {
1281
- await api.activateAccount(id);
1614
+ if (id !== "all") await api.activateAccount(id);
1615
+ setAccountScope(id);
1282
1616
  setSelected(null);
1617
+ if (id === "all") setSource("local");
1283
1618
  refresh();
1284
1619
  } catch (caught) {
1285
- setError(errorText3(caught));
1620
+ setError(errorText4(caught));
1286
1621
  }
1287
1622
  };
1288
1623
  const startBackup = async () => {
1289
- if (accountId === null) return;
1624
+ const owner = accountScope === null || isAll ? accounts[0] : accounts.find((item) => item.id === accountScope);
1625
+ if (owner === void 0) return;
1290
1626
  try {
1291
- const result = await api.startBackup(accountId);
1627
+ const result = await api.startBackup(owner.id);
1292
1628
  setBackup(result.status);
1293
1629
  setShowBackup(true);
1294
1630
  } catch (caught) {
1295
- setError(errorText3(caught));
1631
+ setError(errorText4(caught));
1296
1632
  }
1297
1633
  };
1298
1634
  const stopBackup = async () => {
1299
- if (accountId === null) return;
1635
+ if (accountScope === null || isAll) return;
1300
1636
  try {
1301
- const result = await api.stopBackup(accountId);
1637
+ const result = await api.stopBackup(accountScope);
1302
1638
  setBackup(result.status);
1303
1639
  } catch (caught) {
1304
- setError(errorText3(caught));
1640
+ setError(errorText4(caught));
1305
1641
  }
1306
1642
  };
1307
1643
  const folderOptions = (0, import_react7.useMemo)(
@@ -1318,7 +1654,7 @@ function MailPanel() {
1318
1654
  (0, import_react7.createElement)("div", { className: "mailharbor-empty" }, error === "" ? "\u6B63\u5728\u52A0\u8F7D\u90AE\u7BB1\u2026" : error)
1319
1655
  );
1320
1656
  }
1321
- if (account === null) {
1657
+ if (accounts.length === 0) {
1322
1658
  return (0, import_react7.createElement)(
1323
1659
  "div",
1324
1660
  { className: "mailharbor" },
@@ -1335,13 +1671,16 @@ function MailPanel() {
1335
1671
  (0, import_react7.createElement)(AddAccountForm, {
1336
1672
  onDone: (next) => {
1337
1673
  setState(next);
1674
+ setAccountScope(next.activeAccountId);
1338
1675
  setSource("local");
1339
1676
  }
1340
1677
  })
1341
1678
  )
1342
1679
  );
1343
1680
  }
1344
- 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;
1345
1684
  const items = page.items;
1346
1685
  return (0, import_react7.createElement)(
1347
1686
  "div",
@@ -1352,17 +1691,19 @@ function MailPanel() {
1352
1691
  (0, import_react7.createElement)(
1353
1692
  "div",
1354
1693
  { className: "mailharbor-account" },
1355
- (0, import_react7.createElement)(Avatar, { email: account.email, size: 26 }),
1356
- 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)(
1357
1696
  "select",
1358
1697
  {
1359
- value: account.id,
1698
+ value: accountScope ?? "",
1360
1699
  onChange: (event) => void switchAccount(event.target.value),
1361
1700
  "aria-label": "\u8D26\u53F7"
1362
1701
  },
1363
- state.accounts.map((item) => (0, import_react7.createElement)("option", { key: item.id, value: item.id }, item.label))
1364
- ) : (0, import_react7.createElement)("span", { className: "name" }, account.label),
1365
- 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
1366
1707
  ),
1367
1708
  (0, import_react7.createElement)(
1368
1709
  "div",
@@ -1374,7 +1715,18 @@ function MailPanel() {
1374
1715
  ),
1375
1716
  (0, import_react7.createElement)(
1376
1717
  "button",
1377
- { 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
+ },
1378
1730
  "\u670D\u52A1\u5668\u5B9E\u65F6"
1379
1731
  )
1380
1732
  ),
@@ -1396,7 +1748,11 @@ function MailPanel() {
1396
1748
  ),
1397
1749
  (0, import_react7.createElement)(
1398
1750
  "button",
1399
- { 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
+ },
1400
1756
  (0, import_react7.createElement)(Icon, { name: "archive" }),
1401
1757
  "\u5907\u4EFD"
1402
1758
  ),
@@ -1409,7 +1765,11 @@ function MailPanel() {
1409
1765
  ),
1410
1766
  toast === "" ? null : (0, import_react7.createElement)("div", { className: "small", style: { padding: "6px 14px", color: "var(--m-ok)" } }, toast),
1411
1767
  error === "" ? null : (0, import_react7.createElement)(ErrorBar, { message: error, onDismiss: () => setError("") }),
1412
- 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)(
1413
1773
  "div",
1414
1774
  { className: "mailharbor-body", "data-mode": narrow && selected !== null ? "reader" : "list" },
1415
1775
  (0, import_react7.createElement)(
@@ -1434,7 +1794,7 @@ function MailPanel() {
1434
1794
  ),
1435
1795
  (0, import_react7.createElement)("input", {
1436
1796
  type: "search",
1437
- placeholder: "\u641C\u7D22\u90AE\u4EF6",
1797
+ placeholder: showingDrafts ? "\u641C\u7D22\u8349\u7A3F" : "\u641C\u7D22\u90AE\u4EF6",
1438
1798
  "aria-label": "\u641C\u7D22\u90AE\u4EF6",
1439
1799
  value: query2,
1440
1800
  onChange: (event) => setQuery(event.target.value),
@@ -1462,7 +1822,73 @@ function MailPanel() {
1462
1822
  (0, import_react7.createElement)(
1463
1823
  "div",
1464
1824
  { className: "mailharbor-rows" },
1465
- 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)(
1466
1892
  "div",
1467
1893
  { className: "mailharbor-empty" },
1468
1894
  (0, import_react7.createElement)(Icon, { name: "mail", size: 28 }),
@@ -1480,8 +1906,8 @@ function MailPanel() {
1480
1906
  (item) => (0, import_react7.createElement)(
1481
1907
  "div",
1482
1908
  {
1483
- key: item.id,
1484
- 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" : ""}`,
1485
1911
  role: "button",
1486
1912
  tabIndex: 0,
1487
1913
  onClick: () => void openMessage(item),
@@ -1502,15 +1928,24 @@ function MailPanel() {
1502
1928
  item.from.name !== "" ? item.from.name : item.from.address
1503
1929
  ),
1504
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,
1505
1936
  item.backedUp === false ? (0, import_react7.createElement)("span", { className: "badge warn" }, "\u672A\u5907\u4EFD") : null,
1506
1937
  (0, import_react7.createElement)("span", { className: "date" }, shortDate(item.date))
1507
1938
  ),
1508
- (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
+ ),
1509
1944
  item.snippet === "" ? null : (0, import_react7.createElement)("span", { className: "snippet" }, item.snippet)
1510
1945
  )
1511
1946
  )
1512
1947
  ),
1513
- items.length > 0 ? (0, import_react7.createElement)(
1948
+ !showingDrafts && items.length > 0 ? (0, import_react7.createElement)(
1514
1949
  "div",
1515
1950
  { className: "mailharbor-list-foot" },
1516
1951
  (0, import_react7.createElement)("span", null, `${String(items.length)} / ${String(page.total)} \u5C01`),
@@ -1520,6 +1955,10 @@ function MailPanel() {
1520
1955
  { className: "subtle", onClick: () => setLimit((current) => current + 40) },
1521
1956
  "\u52A0\u8F7D\u66F4\u591A"
1522
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`)
1523
1962
  ) : null
1524
1963
  ),
1525
1964
  (0, import_react7.createElement)(
@@ -1533,15 +1972,25 @@ function MailPanel() {
1533
1972
  (0, import_react7.createElement)(
1534
1973
  "div",
1535
1974
  { className: "small" },
1536
- 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"
1537
1976
  )
1538
1977
  ) : (0, import_react7.createElement)(Reader, {
1539
1978
  detail: selected,
1540
- account,
1541
1979
  source,
1542
1980
  mailbox,
1543
1981
  onChanged: (patch) => setSelected({ ...selected, ...patch }),
1544
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
+ }),
1545
1994
  onClose: narrow ? () => setSelected(null) : void 0
1546
1995
  })
1547
1996
  )
@@ -1578,8 +2027,8 @@ function MailPanel() {
1578
2027
  backup.output.length === 0 ? "\uFF08\u7B49\u5F85\u8F93\u51FA\u2026\uFF09" : backup.output.join("\n")
1579
2028
  )
1580
2029
  ) : null,
1581
- compose !== null ? (0, import_react7.createElement)(Composer, {
1582
- account,
2030
+ compose !== null && composeAccount !== null ? (0, import_react7.createElement)(Composer, {
2031
+ account: composeAccount,
1583
2032
  initial: compose,
1584
2033
  onClose: () => setCompose(null),
1585
2034
  onSent: (message) => notice(message)
@@ -1588,6 +2037,7 @@ function MailPanel() {
1588
2037
  onClose: () => setShowAdd(false),
1589
2038
  onDone: (next) => {
1590
2039
  setState(next);
2040
+ setAccountScope(next.activeAccountId);
1591
2041
  refresh();
1592
2042
  }
1593
2043
  }) : null
@@ -1596,7 +2046,7 @@ function MailPanel() {
1596
2046
 
1597
2047
  // src/client/settings.tsx
1598
2048
  var import_react8 = require("react");
1599
- function errorText4(error) {
2049
+ function errorText5(error) {
1600
2050
  return error instanceof ApiError || error instanceof Error ? error.message : String(error);
1601
2051
  }
1602
2052
  function MailSettings() {
@@ -1611,6 +2061,11 @@ function MailSettings() {
1611
2061
  const [aiEnabled, setAiEnabled] = (0, import_react8.useState)(true);
1612
2062
  const [aiProvider, setAiProvider] = (0, import_react8.useState)("deepseek-account");
1613
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);
1614
2069
  const [editing, setEditing] = (0, import_react8.useState)(null);
1615
2070
  const [checkResult, setCheckResult] = (0, import_react8.useState)("");
1616
2071
  const [adding, setAdding] = (0, import_react8.useState)(false);
@@ -1629,12 +2084,37 @@ function MailSettings() {
1629
2084
  setAiProvider(view.ai.provider);
1630
2085
  setAiModel(view.ai.model);
1631
2086
  }).catch((caught) => {
1632
- if (!cancelled) setError(errorText4(caught));
2087
+ if (!cancelled) setError(errorText5(caught));
1633
2088
  });
1634
2089
  return () => {
1635
2090
  cancelled = true;
1636
2091
  };
1637
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]);
1638
2118
  const reload = () => setTick((value) => value + 1);
1639
2119
  const notify = (message) => {
1640
2120
  setToast(message);
@@ -1648,7 +2128,7 @@ function MailSettings() {
1648
2128
  setSettings(result.settings);
1649
2129
  notify("\u8BBE\u7F6E\u5DF2\u4FDD\u5B58");
1650
2130
  } catch (caught) {
1651
- setError(errorText4(caught));
2131
+ setError(errorText5(caught));
1652
2132
  }
1653
2133
  };
1654
2134
  const account = settings?.accounts.find((item) => item.id === settings.activeAccountId) ?? null;
@@ -1700,7 +2180,7 @@ function MailSettings() {
1700
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 ?? ""}`}`
1701
2181
  );
1702
2182
  } catch (caught) {
1703
- setCheckResult(`\u5931\u8D25: ${errorText4(caught)}`);
2183
+ setCheckResult(`\u5931\u8D25: ${errorText5(caught)}`);
1704
2184
  }
1705
2185
  },
1706
2186
  onSave: async (patch) => {
@@ -1787,34 +2267,135 @@ function MailSettings() {
1787
2267
  setAiEnabled(value);
1788
2268
  void saveGeneral({ ai: { enabled: value } });
1789
2269
  },
1790
- 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"
1791
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
+ ),
1792
2277
  (0, import_react8.createElement)(
1793
2278
  "div",
1794
2279
  { className: "mailharbor-grid" },
1795
2280
  (0, import_react8.createElement)(
1796
2281
  Field,
1797
2282
  { label: "Provider" },
1798
- (0, import_react8.createElement)("input", {
2283
+ aiModels.providers.length === 0 ? (0, import_react8.createElement)("input", {
1799
2284
  value: aiProvider,
1800
2285
  onChange: (event) => setAiProvider(event.target.value),
1801
2286
  onBlur: () => void saveGeneral({ ai: { provider: aiProvider } })
1802
- })
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
+ )
1803
2308
  ),
1804
2309
  (0, import_react8.createElement)(
1805
2310
  Field,
1806
2311
  { label: "\u6A21\u578B" },
1807
- (0, import_react8.createElement)("input", {
1808
- value: aiModel,
1809
- onChange: (event) => setAiModel(event.target.value),
1810
- onBlur: () => void saveGeneral({ ai: { model: aiModel } })
1811
- })
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"
1812
2393
  )
1813
2394
  ),
1814
2395
  (0, import_react8.createElement)(
1815
2396
  "p",
1816
2397
  { className: "small muted" },
1817
- "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"
1818
2399
  )
1819
2400
  ),
1820
2401
  (0, import_react8.createElement)(
@@ -1829,7 +2410,11 @@ function MailSettings() {
1829
2410
  (0, import_react8.createElement)("dt", null, "\u914D\u7F6E\u6587\u4EF6"),
1830
2411
  (0, import_react8.createElement)("dd", null, settings.configFile),
1831
2412
  (0, import_react8.createElement)("dt", null, "\u5F53\u524D\u8D26\u53F7\u6570\u636E\u5E93"),
1832
- (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
+ ),
1833
2418
  (0, import_react8.createElement)("dt", null, "\u63D2\u4EF6\u7248\u672C"),
1834
2419
  (0, import_react8.createElement)("dd", null, state.version)
1835
2420
  ),
@@ -1846,7 +2431,7 @@ function MailSettings() {
1846
2431
  notify(status.status.message === "" ? "\u5907\u4EFD\u5DF2\u5F00\u59CB" : status.status.message);
1847
2432
  reload();
1848
2433
  } catch (caught) {
1849
- setError(errorText4(caught));
2434
+ setError(errorText5(caught));
1850
2435
  }
1851
2436
  }
1852
2437
  },
@@ -2057,7 +2642,7 @@ function AccountRow({
2057
2642
  }
2058
2643
 
2059
2644
  // src/client/index.tsx
2060
- var VERSION = "0.2.0";
2645
+ var VERSION = "0.3.0";
2061
2646
  var inject = ["slots"];
2062
2647
  var PANEL_ID = "mailharbor";
2063
2648
  var STYLE_ID = "mailharbor-style";