Compare commits
1 commit
main
...
op6/chat-f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ccb3224fdb |
1 changed files with 85 additions and 5 deletions
|
|
@ -344,8 +344,17 @@ function buildPromptWindowUrl(): string {
|
|||
.feedback:empty{display:none}
|
||||
.resize-grip{position:absolute;right:6px;bottom:6px;width:16px;height:16px;border-radius:8px;cursor:nwse-resize;-webkit-app-region:no-drag;z-index:2}
|
||||
.resize-grip::before{content:"";position:absolute;right:2px;bottom:2px;width:11px;height:11px;background:linear-gradient(135deg,transparent 0,transparent 42%,var(--text-dim) 42%,var(--text-dim) 50%,transparent 50%,transparent 64%,var(--text-dim) 64%,var(--text-dim) 72%,transparent 72%);opacity:.72}
|
||||
@media (max-width:460px){.shell{padding:7px 7px 5px}.history-list{min-height:72px}.conversations-list{min-height:72px}.prompt-input{min-height:46px}.send-button{width:38px;min-width:38px}}
|
||||
</style></head><body><div class="shell" id="shell"><div class="editor-shell" id="editorShell" hidden><div id="conversations" class="conversations-list" hidden aria-label="Conversations"></div><div id="history" class="history-list" aria-label="Conversation history"></div></div><div class="toolbar"><div class="toolbar-actions"><button class="icon-button" id="editor" type="button" aria-label="Toggle editor" title="Editor"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="5" width="16" height="14" rx="2"/><path d="M8 9h8"/><path d="M8 13h5"/></svg></button><button class="icon-button" id="newChat" type="button" aria-label="Start new chat" title="New chat"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg></button><button class="icon-button" id="historyButton" type="button" aria-label="Open history" title="History"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 4v5h5"/><path d="M12 7v6l4 2"/></svg></button><button class="icon-button" id="settings" type="button" aria-label="Open settings" title="Settings"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3.2"/><path d="M19.4 15a1 1 0 0 0 .2 1.1l.1.1a1.2 1.2 0 0 1 0 1.7l-1.2 1.2a1.2 1.2 0 0 1-1.7 0l-.1-.1a1 1 0 0 0-1.1-.2 1 1 0 0 0-.6.9V20a1.2 1.2 0 0 1-1.2 1.2h-1.7A1.2 1.2 0 0 1 10.9 20v-.1a1 1 0 0 0-.6-.9 1 1 0 0 0-1.1.2l-.1.1a1.2 1.2 0 0 1-1.7 0l-1.2-1.2a1.2 1.2 0 0 1 0-1.7l.1-.1a1 1 0 0 0 .2-1.1 1 1 0 0 0-.9-.6H4A1.2 1.2 0 0 1 2.8 13v-2A1.2 1.2 0 0 1 4 9.8h.1a1 1 0 0 0 .9-.6 1 1 0 0 0-.2-1.1l-.1-.1a1.2 1.2 0 0 1 0-1.7l1.2-1.2a1.2 1.2 0 0 1 1.7 0l.1.1a1 1 0 0 0 1.1.2 1 1 0 0 0 .6-.9V4A1.2 1.2 0 0 1 10.6 2.8h1.7A1.2 1.2 0 0 1 13.5 4v.1a1 1 0 0 0 .6.9 1 1 0 0 0 1.1-.2l.1-.1a1.2 1.2 0 0 1 1.7 0l1.2 1.2a1.2 1.2 0 0 1 0 1.7l-.1.1a1 1 0 0 0-.2 1.1 1 1 0 0 0 .9.6H20a1.2 1.2 0 0 1 1.2 1.2v2A1.2 1.2 0 0 1 20 14.2h-.1a1 1 0 0 0-.9.8z"/></svg></button><button class="icon-button" id="close" type="button" aria-label="Close chat window" title="Close"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"><path d="M6 6l12 12"/><path d="M18 6L6 18"/></svg></button></div></div><div class="composer"><div class="prompt-shell" id="promptShell"><div class="input-wrap"><textarea id="prompt" class="prompt-input" placeholder="Ask anything." aria-label="Prompt input"></textarea><div class="feedback" id="feedback" aria-live="polite"></div></div><button class="send-button" id="send" type="button" aria-label="Send prompt" title="Send"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h12"/><path d="M13 6l6 6-6 6"/></svg></button></div></div><div class="resize-grip" id="resizeGrip" aria-hidden="true"></div></div></div><script>
|
||||
.attachments{display:flex;flex-wrap:wrap;gap:5px;min-height:0;padding:0 2px}
|
||||
.attachments:empty{display:none}
|
||||
.attachment-chip{display:inline-flex;align-items:center;gap:5px;max-width:100%;padding:3px 8px;border-radius:999px;background:var(--button-bg);border:1px solid var(--button-border);color:var(--text-muted);font:500 10.5px/1.3 ui-sans-serif,system-ui,sans-serif;cursor:pointer;transition:background .14s ease,border-color .14s ease,color .14s ease}
|
||||
.attachment-chip:hover{background:var(--danger-bg);border-color:var(--danger-border);color:var(--danger-text)}
|
||||
.attachment-name{display:inline-block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:160px;vertical-align:bottom}
|
||||
.attach-button{width:36px;min-width:36px;align-self:stretch;border:none;border-right:1px solid var(--button-border);border-radius:0;padding:0;background:transparent;color:var(--text-muted);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:background .14s ease,color .14s ease}
|
||||
.attach-button:hover:not(:disabled){background:var(--button-hover);color:var(--text-primary)}
|
||||
.attach-button:disabled{opacity:.55;cursor:default}
|
||||
.attach-button svg{width:15px;height:15px}
|
||||
@media (max-width:460px){.shell{padding:7px 7px 5px}.history-list{min-height:72px}.conversations-list{min-height:72px}.prompt-input{min-height:46px}.send-button{width:38px;min-width:38px}}
|
||||
</style></head><body><div class="shell" id="shell"><div class="editor-shell" id="editorShell" hidden><div id="conversations" class="conversations-list" hidden aria-label="Conversations"></div><div id="history" class="history-list" aria-label="Conversation history"></div></div><div class="toolbar"><div class="toolbar-actions"><button class="icon-button" id="editor" type="button" aria-label="Toggle editor" title="Editor"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="5" width="16" height="14" rx="2"/><path d="M8 9h8"/><path d="M8 13h5"/></svg></button><button class="icon-button" id="newChat" type="button" aria-label="Start new chat" title="New chat"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg></button><button class="icon-button" id="historyButton" type="button" aria-label="Open history" title="History"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 4v5h5"/><path d="M12 7v6l4 2"/></svg></button><button class="icon-button" id="settings" type="button" aria-label="Open settings" title="Settings"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3.2"/><path d="M19.4 15a1 1 0 0 0 .2 1.1l.1.1a1.2 1.2 0 0 1 0 1.7l-1.2 1.2a1.2 1.2 0 0 1-1.7 0l-.1-.1a1 1 0 0 0-1.1-.2 1 1 0 0 0-.6.9V20a1.2 1.2 0 0 1-1.2 1.2h-1.7A1.2 1.2 0 0 1 10.9 20v-.1a1 1 0 0 0-.6-.9 1 1 0 0 0-1.1.2l-.1.1a1.2 1.2 0 0 1-1.7 0l-1.2-1.2a1.2 1.2 0 0 1 0-1.7l.1-.1a1 1 0 0 0 .2-1.1 1 1 0 0 0-.9-.6H4A1.2 1.2 0 0 1 2.8 13v-2A1.2 1.2 0 0 1 4 9.8h.1a1 1 0 0 0 .9-.6 1 1 0 0 0-.2-1.1l-.1-.1a1.2 1.2 0 0 1 0-1.7l1.2-1.2a1.2 1.2 0 0 1 1.7 0l.1.1a1 1 0 0 0 1.1.2 1 1 0 0 0 .6-.9V4A1.2 1.2 0 0 1 10.6 2.8h1.7A1.2 1.2 0 0 1 13.5 4v.1a1 1 0 0 0 .6.9 1 1 0 0 0 1.1-.2l.1-.1a1.2 1.2 0 0 1 1.7 0l1.2 1.2a1.2 1.2 0 0 1 0 1.7l-.1.1a1 1 0 0 0-.2 1.1 1 1 0 0 0 .9.6H20a1.2 1.2 0 0 1 1.2 1.2v2A1.2 1.2 0 0 1 20 14.2h-.1a1 1 0 0 0-.9.8z"/></svg></button><button class="icon-button" id="close" type="button" aria-label="Close chat window" title="Close"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"><path d="M6 6l12 12"/><path d="M18 6L6 18"/></svg></button></div></div><div class="composer"><input type="file" id="fileInput" multiple style="display:none"><div class="attachments" id="attachments" hidden></div><div class="prompt-shell" id="promptShell"><button class="attach-button" id="attach" type="button" aria-label="Attach file" title="Attach file"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg></button><div class="input-wrap"><textarea id="prompt" class="prompt-input" placeholder="Ask anything." aria-label="Prompt input"></textarea><div class="feedback" id="feedback" aria-live="polite"></div></div><button class="send-button" id="send" type="button" aria-label="Send prompt" title="Send"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h12"/><path d="M13 6l6 6-6 6"/></svg></button></div></div><div class="resize-grip" id="resizeGrip" aria-hidden="true"></div></div></div><script>
|
||||
const shellEl = document.getElementById('shell');
|
||||
const api = window.openPetsPromptWindow;
|
||||
const editorShellEl = document.getElementById('editorShell');
|
||||
|
|
@ -360,7 +369,13 @@ function buildPromptWindowUrl(): string {
|
|||
const settingsButton = document.getElementById('settings');
|
||||
const closeButton = document.getElementById('close');
|
||||
const resizeGrip = document.getElementById('resizeGrip');
|
||||
const attachButton = document.getElementById('attach');
|
||||
const fileInput = document.getElementById('fileInput');
|
||||
const attachmentsEl = document.getElementById('attachments');
|
||||
const systemThemeMedia = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const maxAttachments = 4;
|
||||
const maxAttachmentChars = 100000;
|
||||
let attachments = [];
|
||||
const compactHeight = ${promptWindowCompactHeight};
|
||||
const expandedHeight = ${promptWindowExpandedHeight};
|
||||
const minWidth = ${promptWindowMinWidth};
|
||||
|
|
@ -491,8 +506,9 @@ function buildPromptWindowUrl(): string {
|
|||
|
||||
const setBusy = (nextBusy) => {
|
||||
busy = nextBusy;
|
||||
const canSend = Boolean(state && state.hasCredential && !busy && promptEl.value.trim());
|
||||
const canSend = Boolean(state && state.hasCredential && !busy && (promptEl.value.trim() || attachments.length > 0));
|
||||
sendButton.disabled = !canSend;
|
||||
attachButton.disabled = busy || attachments.length >= maxAttachments;
|
||||
editorButton.disabled = busy;
|
||||
newChatButton.disabled = busy;
|
||||
historyButton.disabled = busy;
|
||||
|
|
@ -500,6 +516,33 @@ function buildPromptWindowUrl(): string {
|
|||
promptEl.disabled = busy;
|
||||
};
|
||||
|
||||
const formatAttachmentSize = (bytes) => {
|
||||
if (bytes < 1024) return bytes + ' B';
|
||||
if (bytes < 1024 * 1024) return Math.round(bytes / 1024) + ' KB';
|
||||
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
||||
};
|
||||
|
||||
const renderAttachments = () => {
|
||||
if (!attachments.length) {
|
||||
attachmentsEl.innerHTML = '';
|
||||
attachmentsEl.hidden = true;
|
||||
} else {
|
||||
attachmentsEl.hidden = false;
|
||||
attachmentsEl.innerHTML = attachments.map((file, index) => {
|
||||
return '<button type="button" class="attachment-chip" data-index="' + index + '" title="Remove ' + escapeHtmlText(file.name) + '"><span class="attachment-name">' + escapeHtmlText(file.name) + '</span> · ' + formatAttachmentSize(file.size) + ' ✕</button>';
|
||||
}).join('');
|
||||
}
|
||||
fitWindowToContent();
|
||||
setBusy(busy);
|
||||
};
|
||||
|
||||
const readAttachmentFile = (file) => new Promise((resolve) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => resolve({ name: String(file.name), size: file.size, text: String(reader.result || '').slice(0, maxAttachmentChars) });
|
||||
reader.onerror = () => resolve({ name: String(file.name), size: file.size, text: '' });
|
||||
reader.readAsText(file);
|
||||
});
|
||||
|
||||
const queueWindowResize = (width, height, anchor) => {
|
||||
pendingResize = {
|
||||
width: Math.min(Math.max(Math.round(width), minWidth), maxWidth),
|
||||
|
|
@ -605,11 +648,19 @@ function buildPromptWindowUrl(): string {
|
|||
};
|
||||
|
||||
const submitPrompt = async () => {
|
||||
if (busy || !state || !state.hasCredential || !promptEl.value.trim()) return;
|
||||
if (busy || !state || !state.hasCredential || (!promptEl.value.trim() && attachments.length === 0)) return;
|
||||
let promptText = promptEl.value.trim();
|
||||
if (attachments.length > 0) {
|
||||
const blocks = attachments.map((file) => '[Attached file: ' + file.name + ' (' + file.size + ' bytes)]\n' + file.text);
|
||||
const attachmentBlock = blocks.join('\n\n---\n\n');
|
||||
promptText = promptText ? promptText + '\n\n---\n\n' + attachmentBlock : attachmentBlock;
|
||||
}
|
||||
setBusy(true);
|
||||
try {
|
||||
state = await api.submitPrompt(promptEl.value);
|
||||
state = await api.submitPrompt(promptText);
|
||||
promptEl.value = '';
|
||||
attachments = [];
|
||||
renderAttachments();
|
||||
setFeedback('', '', '');
|
||||
renderState();
|
||||
} catch (error) {
|
||||
|
|
@ -673,6 +724,35 @@ function buildPromptWindowUrl(): string {
|
|||
void submitPrompt();
|
||||
});
|
||||
|
||||
attachButton.addEventListener('click', () => {
|
||||
if (busy || attachments.length >= maxAttachments) return;
|
||||
fileInput.click();
|
||||
});
|
||||
|
||||
fileInput.addEventListener('change', () => {
|
||||
const files = Array.from(fileInput.files || []).slice(0, Math.max(0, maxAttachments - attachments.length));
|
||||
if (!files.length) return;
|
||||
setBusy(true);
|
||||
Promise.all(files.map(readAttachmentFile)).then((next) => {
|
||||
attachments.push(...next);
|
||||
fileInput.value = '';
|
||||
renderAttachments();
|
||||
}).catch(() => {
|
||||
fileInput.value = '';
|
||||
setBusy(false);
|
||||
});
|
||||
});
|
||||
|
||||
attachmentsEl.addEventListener('click', (event) => {
|
||||
const chip = event.target.closest('.attachment-chip');
|
||||
if (!chip) return;
|
||||
const index = Number(chip.dataset.index);
|
||||
if (Number.isFinite(index) && index >= 0 && index < attachments.length) {
|
||||
attachments.splice(index, 1);
|
||||
renderAttachments();
|
||||
}
|
||||
});
|
||||
|
||||
promptEl.addEventListener('input', () => {
|
||||
if (!busy && feedbackEl.dataset.reason === 'request') {
|
||||
setFeedback('', '', '');
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue