Write email replies into open composer
This commit is contained in:
parent
e131245c91
commit
7854298eae
6 changed files with 63 additions and 8 deletions
|
|
@ -1055,11 +1055,27 @@ def _turn_targets_active_document(intent: Dict[str, object], last_user: str, act
|
||||||
"""
|
"""
|
||||||
if active_document is None:
|
if active_document is None:
|
||||||
return False
|
return False
|
||||||
|
raw_doc = getattr(active_document, "current_content", "") or ""
|
||||||
|
title_l = (getattr(active_document, "title", "") or "").strip().lower()
|
||||||
|
is_email_doc = (
|
||||||
|
getattr(active_document, "language", None) == "email"
|
||||||
|
or title_l in {"new email", "new mail", "new message"}
|
||||||
|
or ("To:" in raw_doc[:400] and "Subject:" in raw_doc[:400] and "\n---\n" in raw_doc)
|
||||||
|
)
|
||||||
if "documents" in (intent.get("domains") or set()):
|
if "documents" in (intent.get("domains") or set()):
|
||||||
return True
|
return True
|
||||||
text = str(last_user or "").strip().lower()
|
text = str(last_user or "").strip().lower()
|
||||||
if not text:
|
if not text:
|
||||||
return False
|
return False
|
||||||
|
if is_email_doc and re.search(
|
||||||
|
r"\b("
|
||||||
|
r"email|mail|reply|respond|response|draft|compose|send|"
|
||||||
|
r"tell them|tell her|tell him|say|write|make it say|"
|
||||||
|
r"japanese|japan|polite|formal|tone|style"
|
||||||
|
r")\b",
|
||||||
|
text,
|
||||||
|
):
|
||||||
|
return True
|
||||||
return bool(re.search(
|
return bool(re.search(
|
||||||
r"\b("
|
r"\b("
|
||||||
r"document|doc|draft|text|poem|story|essay|outline|letter|paragraph|"
|
r"document|doc|draft|text|poem|story|essay|outline|letter|paragraph|"
|
||||||
|
|
|
||||||
|
|
@ -219,8 +219,8 @@
|
||||||
}, { once: true });
|
}, { once: true });
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<link rel="stylesheet" href="/static/style.css?v=20260630tasksactivity">
|
<link rel="stylesheet" href="/static/style.css?v=20260630emailcomposer">
|
||||||
<link rel="modulepreload" href="/static/app.js?v=20260630tasksactivity">
|
<link rel="modulepreload" href="/static/app.js?v=20260630emailcomposer">
|
||||||
<link rel="modulepreload" href="/static/js/chat.js">
|
<link rel="modulepreload" href="/static/js/chat.js">
|
||||||
<link rel="modulepreload" href="/static/js/ui.js">
|
<link rel="modulepreload" href="/static/js/ui.js">
|
||||||
<link rel="modulepreload" href="/static/js/sessions.js">
|
<link rel="modulepreload" href="/static/js/sessions.js">
|
||||||
|
|
@ -2512,7 +2512,7 @@
|
||||||
<script type="module" src="/static/js/settings.js"></script>
|
<script type="module" src="/static/js/settings.js"></script>
|
||||||
<script type="module" src="/static/js/admin.js"></script>
|
<script type="module" src="/static/js/admin.js"></script>
|
||||||
<script type="module" src="/static/js/assistant.js"></script>
|
<script type="module" src="/static/js/assistant.js"></script>
|
||||||
<script type="module" src="/static/app.js?v=20260630tasksactivity"></script> <!-- app.js must be LAST -->
|
<script type="module" src="/static/app.js?v=20260630emailcomposer"></script> <!-- app.js must be LAST -->
|
||||||
<script type="module" src="/static/js/init.js"></script>
|
<script type="module" src="/static/js/init.js"></script>
|
||||||
<script type="module" src="/static/js/a11y.js"></script>
|
<script type="module" src="/static/js/a11y.js"></script>
|
||||||
<script nonce="{{CSP_NONCE}}">if('serviceWorker' in navigator){navigator.serviceWorker.register('/static/sw.js').catch(()=>{});}</script>
|
<script nonce="{{CSP_NONCE}}">if('serviceWorker' in navigator){navigator.serviceWorker.register('/static/sw.js').catch(()=>{});}</script>
|
||||||
|
|
|
||||||
|
|
@ -780,9 +780,15 @@ import { wireArrowUpRecall, getLastUserMessageFromChatHistory } from './composer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-save document editor content before sending so the AI sees latest text
|
// Auto-save document editor content before sending so the AI sees latest text
|
||||||
const activeDocIdForSend = documentModule && typeof documentModule.getCurrentDocId === 'function'
|
const activeEmailComposerCtx = documentModule && typeof documentModule.getActiveEmailComposerContext === 'function'
|
||||||
|
? documentModule.getActiveEmailComposerContext()
|
||||||
|
: null;
|
||||||
|
let activeDocIdForSend = documentModule && typeof documentModule.getCurrentDocId === 'function'
|
||||||
? documentModule.getCurrentDocId()
|
? documentModule.getCurrentDocId()
|
||||||
: null;
|
: null;
|
||||||
|
if (!activeDocIdForSend && activeEmailComposerCtx?.docId) {
|
||||||
|
activeDocIdForSend = activeEmailComposerCtx.docId;
|
||||||
|
}
|
||||||
if (documentModule && activeDocIdForSend) {
|
if (documentModule && activeDocIdForSend) {
|
||||||
try { await documentModule.saveDocument(); } catch(e) { console.warn('doc auto-save failed', e); }
|
try { await documentModule.saveDocument(); } catch(e) { console.warn('doc auto-save failed', e); }
|
||||||
}
|
}
|
||||||
|
|
@ -826,7 +832,10 @@ import { wireArrowUpRecall, getLastUserMessageFromChatHistory } from './composer
|
||||||
try {
|
try {
|
||||||
const getEmailCtx = window.__odysseusGetActiveEmailContext;
|
const getEmailCtx = window.__odysseusGetActiveEmailContext;
|
||||||
const emCtx = typeof getEmailCtx === 'function' ? getEmailCtx() : null;
|
const emCtx = typeof getEmailCtx === 'function' ? getEmailCtx() : null;
|
||||||
if (emCtx && emCtx.uid) {
|
if (activeEmailComposerCtx && activeEmailComposerCtx.sourceUid) {
|
||||||
|
fd.append('active_email_uid', String(activeEmailComposerCtx.sourceUid));
|
||||||
|
fd.append('active_email_folder', String(activeEmailComposerCtx.sourceFolder || 'INBOX'));
|
||||||
|
} else if (emCtx && emCtx.uid) {
|
||||||
fd.append('active_email_uid', String(emCtx.uid));
|
fd.append('active_email_uid', String(emCtx.uid));
|
||||||
fd.append('active_email_folder', String(emCtx.folder || 'INBOX'));
|
fd.append('active_email_folder', String(emCtx.folder || 'INBOX'));
|
||||||
if (emCtx.account) fd.append('active_email_account', String(emCtx.account));
|
if (emCtx.account) fd.append('active_email_account', String(emCtx.account));
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import Storage from './storage.js';
|
||||||
import themeModule from './theme.js';
|
import themeModule from './theme.js';
|
||||||
import markdownModule from './markdown.js';
|
import markdownModule from './markdown.js';
|
||||||
import sessionModule from './sessions.js';
|
import sessionModule from './sessions.js';
|
||||||
|
import documentModule from './document.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle a ui_control SSE event — AI-driven UI manipulation.
|
* Handle a ui_control SSE event — AI-driven UI manipulation.
|
||||||
|
|
@ -183,6 +184,19 @@ export function handleUIControl(uiData) {
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (uiEvent === 'open_email_reply' || uiData.ui_event === 'open_email_reply') {
|
} else if (uiEvent === 'open_email_reply' || uiData.ui_event === 'open_email_reply') {
|
||||||
|
try {
|
||||||
|
var existingDocId = documentModule && documentModule.findEmailDocId
|
||||||
|
? documentModule.findEmailDocId(uiData.uid, uiData.folder || 'INBOX')
|
||||||
|
: null;
|
||||||
|
if (existingDocId && documentModule.replaceEmailReplyBody) {
|
||||||
|
if (documentModule.loadDocument) documentModule.loadDocument(existingDocId);
|
||||||
|
documentModule.replaceEmailReplyBody(existingDocId, uiData.body || '', { force: true });
|
||||||
|
if (uiModule && uiModule.showToast) uiModule.showToast('Wrote reply into the open email');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('open_email_reply existing draft update failed:', e);
|
||||||
|
}
|
||||||
import('./emailInbox.js').then(function(mod) {
|
import('./emailInbox.js').then(function(mod) {
|
||||||
var fn = mod.openReplyDraft || (mod.default && mod.default.openReplyDraft);
|
var fn = mod.openReplyDraft || (mod.default && mod.default.openReplyDraft);
|
||||||
if (fn) fn(uiData.uid, uiData.folder || 'INBOX', uiData.mode || 'reply', uiData.body || '');
|
if (fn) fn(uiData.uid, uiData.folder || 'INBOX', uiData.mode || 'reply', uiData.body || '');
|
||||||
|
|
|
||||||
|
|
@ -6539,7 +6539,7 @@ import { bindMenuDismiss, dismissOrRemove } from './escMenuStack.js';
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function replaceEmailReplyBody(docId, replyText) {
|
export async function replaceEmailReplyBody(docId, replyText, { force = false } = {}) {
|
||||||
const doc = docs.get(docId);
|
const doc = docs.get(docId);
|
||||||
if (!doc) return;
|
if (!doc) return;
|
||||||
const fields = _parseEmailHeader(doc.content || '');
|
const fields = _parseEmailHeader(doc.content || '');
|
||||||
|
|
@ -6550,7 +6550,7 @@ import { bindMenuDismiss, dismissOrRemove } from './escMenuStack.js';
|
||||||
);
|
);
|
||||||
const quote = quoteIdx >= 0 ? lines.slice(quoteIdx).join('\n') : '';
|
const quote = quoteIdx >= 0 ? lines.slice(quoteIdx).join('\n') : '';
|
||||||
const ownText = _emailReplyOwnText(fields.body || '');
|
const ownText = _emailReplyOwnText(fields.body || '');
|
||||||
if (ownText && !/^(\[AI reply draft will appear here\]|Drafting AI reply)/i.test(ownText)) {
|
if (!force && ownText && !/^(\[AI reply draft will appear here\]|Drafting AI reply)/i.test(ownText)) {
|
||||||
if (uiModule) uiModule.showToast('AI reply ready, but draft was edited');
|
if (uiModule) uiModule.showToast('AI reply ready, but draft was edited');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -10430,6 +10430,21 @@ import { bindMenuDismiss, dismissOrRemove } from './escMenuStack.js';
|
||||||
return activeDocId;
|
return activeDocId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getActiveEmailComposerContext() {
|
||||||
|
if (!activeDocId) return null;
|
||||||
|
const doc = docs.get(activeDocId);
|
||||||
|
if (!doc || doc.language !== 'email') return null;
|
||||||
|
const fields = _parseEmailHeader(doc.content || '');
|
||||||
|
return {
|
||||||
|
docId: activeDocId,
|
||||||
|
sourceUid: fields.sourceUid || '',
|
||||||
|
sourceFolder: fields.sourceFolder || 'INBOX',
|
||||||
|
inReplyTo: fields.inReplyTo || '',
|
||||||
|
to: fields.to || '',
|
||||||
|
subject: fields.subject || '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/** Find an open email tab by source UID + folder. Returns docId or null. */
|
/** Find an open email tab by source UID + folder. Returns docId or null. */
|
||||||
export function findEmailDocId(uid, folder) {
|
export function findEmailDocId(uid, folder) {
|
||||||
if (uid == null) return null;
|
if (uid == null) return null;
|
||||||
|
|
@ -10472,6 +10487,7 @@ const documentModule = {
|
||||||
exitDiffMode,
|
exitDiffMode,
|
||||||
isDiffModeActive,
|
isDiffModeActive,
|
||||||
getCurrentDocId,
|
getCurrentDocId,
|
||||||
|
getActiveEmailComposerContext,
|
||||||
findEmailDocId,
|
findEmailDocId,
|
||||||
getSelectionContext,
|
getSelectionContext,
|
||||||
clearSelection,
|
clearSelection,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
// - Other static assets (images/fonts/libs): cache-first with bg refresh.
|
// - Other static assets (images/fonts/libs): cache-first with bg refresh.
|
||||||
// - API / non-GET: never cached.
|
// - API / non-GET: never cached.
|
||||||
// Bump CACHE_NAME whenever the precache list or SW logic changes.
|
// Bump CACHE_NAME whenever the precache list or SW logic changes.
|
||||||
const CACHE_NAME = 'odysseus-v329';
|
const CACHE_NAME = 'odysseus-v330';
|
||||||
|
|
||||||
// Core shell precached on install so repeat opens are instant without any
|
// Core shell precached on install so repeat opens are instant without any
|
||||||
// network wait. Keep this list in sync with the <script type="module"> tags
|
// network wait. Keep this list in sync with the <script type="module"> tags
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue