Skip to content

Commit 9368e09

Browse files
chatapi.html
Here are updates for the new Version 1.3: - New Edit Button allows you to edit your responses. - New Export Chat allows you to export to your chat in .json.
1 parent 833b750 commit 9368e09

File tree

1 file changed

+215
-11
lines changed

1 file changed

+215
-11
lines changed

chatapi.html

Lines changed: 215 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
display: flex;
7171
gap: 10px;
7272
}
73-
#settings-btn, #clear-btn {
73+
#settings-btn, #clear-btn, #export-btn {
7474
background: var(--bg-tertiary);
7575
border: 1px solid var(--border-color);
7676
color: var(--text-secondary);
@@ -84,7 +84,7 @@
8484
gap: 6px;
8585
font-family: inherit;
8686
}
87-
#settings-btn:hover, #clear-btn:hover {
87+
#settings-btn:hover, #clear-btn:hover, #export-btn:hover {
8888
background: var(--accent-color);
8989
color: white;
9090
border-color: var(--accent-color);
@@ -431,6 +431,10 @@
431431
font-size: 0.8rem;
432432
cursor: pointer;
433433
margin-top: 8px;
434+
transition: all var(--transition-fast);
435+
}
436+
.retry-button:hover {
437+
background: var(--accent-hover);
434438
}
435439
.error-message {
436440
color: var(--error-color);
@@ -458,11 +462,84 @@
458462
height: 8px;
459463
background: var(--accent-color);
460464
border-radius: 50%;
465+
animation: bounce 1.5s infinite ease-in-out;
461466
}
462467
.typing-text {
463468
color: var(--text-secondary);
464469
font-size: 0.9rem;
465470
}
471+
.edit-button {
472+
position: absolute;
473+
top: 8px;
474+
right: 8px;
475+
background: var(--bg-tertiary);
476+
color: var(--text-secondary);
477+
border: 1px solid var(--border-color);
478+
border-radius: 4px;
479+
padding: 4px 8px;
480+
font-size: 0.8rem;
481+
cursor: pointer;
482+
opacity: 0;
483+
transition: all var(--transition-fast);
484+
}
485+
.user:hover .edit-button {
486+
opacity: 1;
487+
}
488+
.edit-button:hover {
489+
background: var(--accent-color);
490+
color: white;
491+
border-color: var(--accent-color);
492+
}
493+
/* Edit mode styling */
494+
.edit-mode {
495+
padding: 12px;
496+
background: var(--bg-tertiary);
497+
border-radius: var(--radius-sm);
498+
border: 1px solid var(--accent-color);
499+
}
500+
.edit-textarea {
501+
width: 100%;
502+
min-height: 100px;
503+
background: var(--bg-secondary);
504+
border: 1px solid var(--border-color);
505+
border-radius: var(--radius-sm);
506+
color: var(--text-primary);
507+
padding: 12px;
508+
font-family: inherit;
509+
font-size: 1rem;
510+
margin-bottom: 10px;
511+
resize: vertical;
512+
}
513+
.edit-buttons {
514+
display: flex;
515+
gap: 8px;
516+
justify-content: flex-end;
517+
}
518+
.edit-save, .edit-cancel {
519+
padding: 8px 16px;
520+
border-radius: var(--radius-sm);
521+
font-weight: 500;
522+
cursor: pointer;
523+
transition: all var(--transition-fast);
524+
border: none;
525+
font-family: inherit;
526+
font-size: 0.9rem;
527+
}
528+
.edit-save {
529+
background: var(--accent-color);
530+
color: white;
531+
}
532+
.edit-save:hover {
533+
background: var(--accent-hover);
534+
}
535+
.edit-cancel {
536+
background: var(--bg-secondary);
537+
color: var(--text-primary);
538+
border: 1px solid var(--border-color);
539+
}
540+
.edit-cancel:hover {
541+
background: var(--bg-tertiary);
542+
}
466543
@media (max-width: 768px) {
467544
.message {
468545
max-width: 90%;
@@ -492,12 +569,16 @@
492569
</head>
493570
<body>
494571
<header>
495-
<div class="logo">V1.2</div>
572+
<div class="logo">V1.3</div>
496573
<div class="header-buttons">
497574
<button id="clear-btn" aria-label="Clear chat">
498575
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>
499576
Clear Chat
500577
</button>
578+
<button id="export-btn" aria-label="Export chat">
579+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
580+
Export Chat
581+
</button>
501582
<button id="settings-btn" aria-label="Open settings">
502583
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
503584
</button>
@@ -552,6 +633,7 @@ <h2>API Configuration:</h2>
552633
const send = document.getElementById('send');
553634
const stop = document.getElementById('stop');
554635
const clearBtn = document.getElementById('clear-btn');
636+
const exportBtn = document.getElementById('export-btn');
555637
const settingsBtn = document.getElementById('settings-btn');
556638
const settingsModal = document.getElementById('settings-modal');
557639
const closeBtn = document.querySelector('.close');
@@ -602,6 +684,34 @@ <h2 style="margin-bottom: 12px; color: white;">ChatAPI</h2>
602684

603685
clearBtn.addEventListener('click', clearChat);
604686

687+
function exportChat() {
688+
if (messages.length === 0) {
689+
alert('No messages to export');
690+
return;
691+
}
692+
693+
const exportData = {
694+
metadata: {
695+
exportDate: new Date().toISOString(),
696+
exportDateFormatted: new Date().toLocaleString(),
697+
totalMessages: messages.length,
698+
model: apiSettings.model
699+
},
700+
messages: messages.filter(msg => msg.role !== 'system')
701+
};
702+
703+
const dataStr = JSON.stringify(exportData, null, 2);
704+
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
705+
const timestamp = new Date().toISOString().slice(0,19).replace(/:/g, '-');
706+
const filename = `chat-export-${timestamp}.json`;
707+
const linkElement = document.createElement('a');
708+
linkElement.setAttribute('href', dataUri);
709+
linkElement.setAttribute('download', filename);
710+
linkElement.click();
711+
}
712+
713+
exportBtn.addEventListener('click', exportChat);
714+
605715
let controller = null;
606716

607717
settingsBtn.addEventListener('click', () => {
@@ -676,10 +786,10 @@ <h2 style="margin-bottom: 12px; color: white;">ChatAPI</h2>
676786
messages = [...systemMessages];
677787
}
678788

679-
messages.forEach(msg => {
789+
messages.forEach((msg, index) => {
680790
if (msg.role === 'system') return;
681791
const roleClass = msg.role === 'assistant' ? 'ai' : msg.role;
682-
const msgEl = appendMessage(roleClass, msg.content);
792+
const msgEl = appendMessage(roleClass, msg.content, index);
683793
if (msg.role === 'assistant') {
684794
msgEl.innerHTML = marked.parse(msg.content);
685795
}
@@ -703,19 +813,105 @@ <h2 style="margin-bottom: 12px; color: white;">ChatAPI</h2>
703813
}
704814
}
705815

706-
function appendMessage(role, text) {
816+
function appendMessage(role, text, index) {
707817
const msg = document.createElement('div');
708818
msg.className = `message ${role}`;
819+
if (index !== undefined) {
820+
msg.dataset.index = index;
821+
}
822+
709823
if (role === 'ai') {
710824
msg.innerHTML = marked.parse(text);
711-
} else {
712-
msg.textContent = text;
825+
} else if (role === 'user') {
826+
const contentDiv = document.createElement('div');
827+
contentDiv.textContent = text;
828+
msg.appendChild(contentDiv);
829+
830+
// Add edit button
831+
const editBtn = document.createElement('button');
832+
editBtn.className = 'edit-button';
833+
editBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>';
834+
editBtn.setAttribute('aria-label', 'Edit message');
835+
editBtn.addEventListener('click', () => enableMessageEdit(msg));
836+
msg.appendChild(editBtn);
713837
}
838+
714839
chat.appendChild(msg);
715840
chat.scrollTop = chat.scrollHeight;
716841
return msg;
717842
}
718843

844+
function enableMessageEdit(messageElement) {
845+
const index = parseInt(messageElement.dataset.index);
846+
const originalContent = messages[index].content;
847+
848+
messageElement.classList.add('edit-mode');
849+
const editHTML = `
850+
<textarea class="edit-textarea">${originalContent}</textarea>
851+
<div class="edit-buttons">
852+
<button class="edit-save" aria-label="Save changes">Save</button>
853+
<button class="edit-cancel" aria-label="Cancel editing">Cancel</button>
854+
</div>
855+
`;
856+
messageElement.innerHTML = editHTML;
857+
858+
const textarea = messageElement.querySelector('.edit-textarea');
859+
textarea.focus();
860+
861+
messageElement.querySelector('.edit-save').addEventListener('click', () => {
862+
const newContent = textarea.value.trim();
863+
if (newContent && newContent !== originalContent) {
864+
saveEditedMessage(index, newContent, messageElement);
865+
}
866+
});
867+
868+
messageElement.querySelector('.edit-cancel').addEventListener('click', () => {
869+
cancelEdit(messageElement, originalContent, index);
870+
});
871+
}
872+
873+
function saveEditedMessage(index, newContent, messageElement) {
874+
messages[index].content = newContent;
875+
876+
const messagesToRemove = messages.slice(index + 1);
877+
messagesToRemove.forEach((msg, i) => {
878+
const msgElement = chat.querySelector(`[data-index="${index + 1 + i}"]`);
879+
if (msgElement) msgElement.remove();
880+
});
881+
882+
messages = messages.slice(0, index + 1);
883+
884+
messages = messages.filter(msg => msg.role !== 'assistant');
885+
886+
saveMessagesToStorage();
887+
888+
messageElement.classList.remove('edit-mode');
889+
messageElement.textContent = newContent;
890+
messageElement.dataset.index = index;
891+
892+
const editBtn = document.createElement('button');
893+
editBtn.className = 'edit-button';
894+
editBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>';
895+
editBtn.addEventListener('click', () => enableMessageEdit(messageElement));
896+
messageElement.appendChild(editBtn);
897+
898+
const indicator = document.getElementById('typing-indicator');
899+
if (indicator) indicator.remove();
900+
sendMessage(newContent, true, index);
901+
}
902+
903+
function cancelEdit(messageElement, originalContent, originalIndex) {
904+
messageElement.classList.remove('edit-mode');
905+
messageElement.textContent = originalContent;
906+
907+
const editBtn = document.createElement('button');
908+
editBtn.className = 'edit-button';
909+
editBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>';
910+
editBtn.addEventListener('click', () => enableMessageEdit(messageElement));
911+
messageElement.appendChild(editBtn);
912+
messageElement.dataset.index = originalIndex;
913+
}
914+
719915
function addCopyButtons() {
720916
document.querySelectorAll('.ai pre').forEach(block => {
721917
if (block.querySelector('.copy-button')) return;
@@ -756,25 +952,33 @@ <h2 style="margin-bottom: 12px; color: white;">ChatAPI</h2>
756952
function resendLastMessage() {
757953
if (messages.length > 0 && messages[messages.length - 1].role === 'user') {
758954
const lastUserMessage = messages[messages.length - 1].content;
955+
const messageIndex = messages.findIndex(msg => msg.content === lastUserMessage);
956+
759957
if (messages.length > 1 && messages[messages.length - 2].role === 'assistant') {
760958
messages.pop();
959+
const aiMessage = chat.querySelector('.ai:last-child');
960+
if (aiMessage) aiMessage.remove();
761961
}
962+
762963
const indicator = document.getElementById('typing-indicator');
763964
if (indicator) indicator.remove();
764-
sendMessage(lastUserMessage, true);
965+
sendMessage(lastUserMessage, true, messageIndex);
765966
}
766967
}
767968

768-
async function sendMessage(userInputOverride = null, isRetry = false) {
969+
async function sendMessage(userInputOverride = null, isRetry = false, messageIndex = null) {
769970
const userInput = userInputOverride || input.value.trim();
770971
if (!userInput) return;
771972

772973
if (!isRetry) {
773974
hideWelcomeMessage();
774-
appendMessage('user', userInput);
975+
const msgEl = appendMessage('user', userInput, messages.length);
775976
input.value = '';
776977
messages.push({ role: 'user', content: userInput });
777978
saveMessagesToStorage();
979+
} else if (messageIndex !== null) {
980+
messages[messageIndex].content = userInput;
981+
saveMessagesToStorage();
778982
}
779983

780984
currentAiMessageElement = null;

0 commit comments

Comments
 (0)