|
|
|
@ -14,7 +14,6 @@
|
|
|
|
|
<script src="//unpkg.com/@highlightjs/cdn-assets@11.9.0/highlight.min.js"></script>
|
|
|
|
|
<script src="//unpkg.com/marked@12.0.2/lib/marked.umd.js" defer></script>
|
|
|
|
|
<script src="//unpkg.com/alpinejs@3.13.10/dist/cdn.min.js" defer></script>
|
|
|
|
|
<script src="//unpkg.com/clipboard@2.0.1/dist/clipboard.min.js" def></script>
|
|
|
|
|
<style>
|
|
|
|
|
:root {
|
|
|
|
|
--fg-primary: #1652f1;
|
|
|
|
@ -238,7 +237,9 @@
|
|
|
|
|
border: none;
|
|
|
|
|
outline: none;
|
|
|
|
|
resize: none;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
max-height: 500px;
|
|
|
|
|
overflow-x: hidden;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.input-toolbox {
|
|
|
|
@ -440,10 +441,7 @@
|
|
|
|
|
<!-- toolbox -->
|
|
|
|
|
<template x-if="messageIndex == chat.hoveredMessageIndex">
|
|
|
|
|
<div class="message-toolbox">
|
|
|
|
|
<div :id="'copy-message-btn-' + index" class="copy-message-btn"
|
|
|
|
|
:data-clipboard-text="message.content"
|
|
|
|
|
x-init="copyMessageClipboard = new ClipboardJS('#copy-message-btn-' + index); copyMessageClipboard.on('success', () => {toast('Copied Message')})"
|
|
|
|
|
title="Copy">
|
|
|
|
|
<div class="copy-message-btn" @click="handleCopyMessage(message.content)" title="Copy">
|
|
|
|
|
<svg fill="currentColor" viewBox="0 0 16 16">
|
|
|
|
|
<path fill-rule="evenodd"
|
|
|
|
|
d="M4 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1h1v1a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h1v1z" />
|
|
|
|
@ -479,8 +477,7 @@
|
|
|
|
|
</div>
|
|
|
|
|
<div class="input-panel">
|
|
|
|
|
<div class="input-panel-inner">
|
|
|
|
|
<textarea id="chat-input" rows="2" x-model="input" x-ref="input"
|
|
|
|
|
@keydown.enter.prevent="!$event.shiftKey && handleAsk()" @keydown.enter.shift="handleNewlineInput"
|
|
|
|
|
<textarea id="chat-input" x-model="input" x-ref="input" @keydown.enter="handleEnterKeydown"
|
|
|
|
|
placeholder="Enter to send, Shift + Enter to wrap"></textarea>
|
|
|
|
|
<div class="input-image-bar" x-show="images.length > 0">
|
|
|
|
|
<template x-for="(image, index) in images">
|
|
|
|
@ -559,7 +556,6 @@
|
|
|
|
|
model_id: "",
|
|
|
|
|
messages: [],
|
|
|
|
|
hoveredMessageIndex: null,
|
|
|
|
|
copyMessageClipboard: null,
|
|
|
|
|
askAbortController: null,
|
|
|
|
|
shouldScrollChatBodyToBottom: true,
|
|
|
|
|
isShowScrollToBottomBtn: false,
|
|
|
|
@ -587,7 +583,7 @@
|
|
|
|
|
}
|
|
|
|
|
$scrollToBottomBtns[i].style.left = offsets[i];
|
|
|
|
|
}
|
|
|
|
|
this.$watch("input", () => this.autoScrollAndHeightOnInput(this.$refs.input));
|
|
|
|
|
this.$watch("input", () => this.autosizeInput(this.$refs.input));
|
|
|
|
|
new ResizeObserver(() => {
|
|
|
|
|
this.autoHeightChatPanel();
|
|
|
|
|
}).observe($inputPanel)
|
|
|
|
@ -695,18 +691,11 @@
|
|
|
|
|
chat.shouldScrollChatBodyToBottom = true;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
handleNewlineInput(event) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
const $input = event.target;
|
|
|
|
|
const start = $input.selectionStart;
|
|
|
|
|
const end = $input.selectionEnd;
|
|
|
|
|
const text = $input.value;
|
|
|
|
|
const before = text.substring(0, start);
|
|
|
|
|
const after = text.substring(end);
|
|
|
|
|
|
|
|
|
|
$input.value = before + '\n' + after;
|
|
|
|
|
$input.selectionStart = $input.selectionEnd = start + 1;
|
|
|
|
|
this.input = $input.value;
|
|
|
|
|
handleEnterKeydown(event) {
|
|
|
|
|
if (event.shiftKey) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this.handleAsk();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
handleCopyCode(event) {
|
|
|
|
@ -723,6 +712,21 @@
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
handleCopyMessage(content) {
|
|
|
|
|
if (Array.isArray(content)) {
|
|
|
|
|
content = content.map(v => v.text || "").join("");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const $tempTextArea = document.createElement("textarea");
|
|
|
|
|
$tempTextArea.value = content;
|
|
|
|
|
document.body.appendChild($tempTextArea);
|
|
|
|
|
$tempTextArea.select();
|
|
|
|
|
$tempTextArea.setSelectionRange(0, 99999); // For mobile devices
|
|
|
|
|
document.execCommand("copy");
|
|
|
|
|
document.body.removeChild($tempTextArea);
|
|
|
|
|
toast("Copied Message")
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async handleImageUpload(event) {
|
|
|
|
|
const files = event.target.files;
|
|
|
|
|
if (!files || files.length === 0) {
|
|
|
|
@ -751,29 +755,18 @@
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
autoScrollAndHeightOnInput($input) {
|
|
|
|
|
if (!$input.value) {
|
|
|
|
|
$input.style.overflowY = "hidden";
|
|
|
|
|
$input.style.height = "51px";
|
|
|
|
|
} else if ($input.scrollHeight < 500) {
|
|
|
|
|
$input.style.overflowY = "hidden";
|
|
|
|
|
$input.style.height = "auto";
|
|
|
|
|
$input.style.height = $input.scrollHeight + "px";
|
|
|
|
|
} else {
|
|
|
|
|
$input.style.overflowY = "auto";
|
|
|
|
|
$input.style.height = "500px"
|
|
|
|
|
const lineHeight = 19;
|
|
|
|
|
const cursorTop = Math.floor(($input.selectionStart + 1) / $input.cols) * lineHeight;
|
|
|
|
|
if (cursorTop < $input.scrollTop || cursorTop > ($input.scrollTop + $input.clientHeight - lineHeight)) {
|
|
|
|
|
$input.scrollTop = cursorTop;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
autosizeInput($input) {
|
|
|
|
|
$input.style.height = 'auto';
|
|
|
|
|
$input.style.height = $input.scrollHeight + 'px';
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async ask(index) {
|
|
|
|
|
const chat = this.chats[index];
|
|
|
|
|
chat.askAbortController = new AbortController();
|
|
|
|
|
chat.shouldScrollChatBodyToBottom = true;
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
this.autoScrollChatBodyToBottom(index);
|
|
|
|
|
});
|
|
|
|
|
const lastMessage = chat.messages[chat.messages.length - 1];
|
|
|
|
|
const body = this.buildBody(index);
|
|
|
|
|
let succeed = false;
|
|
|
|
|