add a few quality of life changes
copy and paste and save buttons ability to set the system prompt
This commit is contained in:
122
index.html
122
index.html
@ -15,7 +15,7 @@
|
||||
width: 25%; max-width: 300px; background: #fff; padding: 0.5rem; box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||
overflow-y: auto;
|
||||
}
|
||||
@media (max-width: 767.98px) { .sidebar { display: none; } }
|
||||
.settings { margin-bottom: 1rem; }
|
||||
.chat-container {
|
||||
flex: 1; display: flex; flex-direction: column; background: #fff; padding: 1rem;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||
@ -31,6 +31,7 @@
|
||||
.assistant { background: #f8d7da; text-align: left; }
|
||||
pre { max-width: 100%; box-sizing: border-box; white-space: pre-wrap; overflow-x: auto;
|
||||
background: #2d2d2d; color: #f8f8f2; padding: 0.5rem; border-radius: 5px; }
|
||||
.code-buttons { text-align: right; margin-top: 5px; }
|
||||
.conversation-item { padding: 0.5rem; cursor: pointer; border-bottom: 1px solid #ddd; position: relative; }
|
||||
.conversation-item:hover, .conversation-item.active { background: #f0f0f0; }
|
||||
.delete-btn { position: absolute; right: 5px; top: 5px; background: transparent; border: none; color: #dc3545; font-size: 1rem; }
|
||||
@ -48,9 +49,15 @@
|
||||
<div class="offcanvas-body" id="offcanvasConversationList" style="overflow-y:auto;"></div>
|
||||
</div>
|
||||
<div class="container-main">
|
||||
<div class="sidebar d-none d-md-block" id="conversationListContainer">
|
||||
<div class="sidebar" id="conversationListContainer">
|
||||
<h5>Conversations</h5>
|
||||
<button id="newConvoBtn" class="btn btn-sm btn-primary mb-2">New Conversation</button>
|
||||
<!-- Settings area for the system prompt -->
|
||||
<div class="settings">
|
||||
<h6>System Prompt</h6>
|
||||
<textarea id="systemPrompt" style="width:100%; height:100px;"></textarea>
|
||||
<button id="savePrompt" class="btn btn-sm btn-secondary mt-1">Save Prompt</button>
|
||||
</div>
|
||||
<div id="conversationList"></div>
|
||||
</div>
|
||||
<div class="chat-container">
|
||||
@ -89,6 +96,20 @@
|
||||
// --- Conversation Management ---
|
||||
let conversations = [];
|
||||
let currentConversation = null;
|
||||
const defaultSystemPrompt = "You are ChatGPT, a helpful assistant. When providing code, wrap all code blocks with three backticks. Additionally, if you need to perform a file operation, return a JSON object (wrapped in a code block) with a key 'file_command' and the necessary arguments. For example, to list files use:\n```json\n{\"file_command\": \"list\", \"arguments\": {}}\n```\nto read a file use:\n```json\n{\"file_command\": \"read\", \"arguments\": {\"filename\": \"example.txt\"}}\n```\nand to write a file use:\n```json\n{\"file_command\": \"write\", \"arguments\": {\"filename\": \"example.txt\", \"content_b64\": \"<BASE64_ENCODED_CONTENT>\"}}\n```\nOnce approved, the result of your file command will be sent as a user's response.";
|
||||
|
||||
// Load saved system prompt from localStorage or default.
|
||||
function loadSystemPrompt() {
|
||||
const sp = localStorage.getItem('systemPrompt');
|
||||
document.getElementById('systemPrompt').value = sp || defaultSystemPrompt;
|
||||
}
|
||||
|
||||
document.getElementById('savePrompt').addEventListener("click", function() {
|
||||
const newPrompt = document.getElementById('systemPrompt').value;
|
||||
localStorage.setItem('systemPrompt', newPrompt);
|
||||
alert("System prompt saved!");
|
||||
});
|
||||
|
||||
function generateId() {
|
||||
return 'c-' + Date.now() + '-' + Math.floor(Math.random() * 1000);
|
||||
}
|
||||
@ -100,13 +121,15 @@
|
||||
function saveConversations() {
|
||||
localStorage.setItem('conversations', JSON.stringify(conversations));
|
||||
}
|
||||
// Updated system prompt instructs the assistant to use "filename" and "content_b64".
|
||||
|
||||
// Create a new conversation using the current system prompt.
|
||||
function createNewConversation() {
|
||||
const sp = localStorage.getItem('systemPrompt') || defaultSystemPrompt;
|
||||
const newConvo = {
|
||||
id: generateId(),
|
||||
title: 'New Conversation',
|
||||
messages: [
|
||||
{ role: "system", content: "You are ChatGPT, a helpful assistant. When providing code, wrap all code blocks with three backticks. You have access to a 'workspace' which is a directory on the user's computer. The workspace is accessible via the file_command described next. If you need to perform a file operation, return a JSON object (wrapped in a code block) with a key 'file_command' and the necessary arguments. For example, to list files use: ```json\n{\"file_command\": \"list\", \"arguments\": {}}\n```; to read a file use: ```json\n{\"file_command\": \"read\", \"arguments\": {\"filename\": \"example.txt\"}}\n```; and to write a file use: ```json\n{\"file_command\": \"write\", \"arguments\": {\"filename\": \"example.txt\", \"content_b64\": \"<BASE64_ENCODED_CONTENT>\"}}\n```. The user will be prompted by the chatUI to approve the action and the result of your file command will be sent as a user's response. Be proactive in using the file_command as it will be able to fill in any information you might be missing." }
|
||||
{ role: "system", content: sp }
|
||||
]
|
||||
};
|
||||
conversations.push(newConvo);
|
||||
@ -161,7 +184,10 @@
|
||||
const chatLog = document.getElementById('chatLog');
|
||||
chatLog.innerHTML = "";
|
||||
if (!currentConversation) return;
|
||||
currentConversation.messages.forEach(msg => { appendMessage(msg.role, msg.content, false); });
|
||||
currentConversation.messages.forEach(msg => {
|
||||
const messageElem = appendMessage(msg.role, msg.content, false);
|
||||
addButtonsToCodeBlocks(messageElem);
|
||||
});
|
||||
chatLog.scrollTop = chatLog.scrollHeight;
|
||||
}
|
||||
function appendMessage(role, content, update = true) {
|
||||
@ -176,14 +202,88 @@
|
||||
if (role === "assistant" && currentConversation.title === "New Conversation") autoTitleConversation();
|
||||
saveConversations();
|
||||
}
|
||||
// Check for a file command JSON block.
|
||||
if (role === "assistant") {
|
||||
checkForFileCommand(content, messageElem);
|
||||
}
|
||||
addButtonsToCodeBlocks(messageElem);
|
||||
return messageElem;
|
||||
}
|
||||
|
||||
// Searches for a JSON block (wrapped with triple backticks) with a file_command.
|
||||
// Updated: Attach "Copy" and "Save" buttons to every code block in the element.
|
||||
function addButtonsToCodeBlocks(element) {
|
||||
const codeBlocks = element.querySelectorAll("pre");
|
||||
codeBlocks.forEach(block => {
|
||||
// Check if this specific block already has a next sibling that is our button container.
|
||||
if (block.nextElementSibling && block.nextElementSibling.classList.contains("code-buttons")) {
|
||||
return; // Skip if already added.
|
||||
}
|
||||
const containerDiv = document.createElement("div");
|
||||
containerDiv.className = "code-buttons";
|
||||
const copyBtn = document.createElement("button");
|
||||
copyBtn.className = "btn btn-sm btn-outline-secondary me-2";
|
||||
copyBtn.textContent = "Copy";
|
||||
copyBtn.onclick = () => {
|
||||
navigator.clipboard.writeText(block.innerText).then(() => {
|
||||
copyBtn.textContent = "Copied!";
|
||||
setTimeout(() => { copyBtn.textContent = "Copy"; }, 2000);
|
||||
});
|
||||
};
|
||||
containerDiv.appendChild(copyBtn);
|
||||
const saveBtn = document.createElement("button");
|
||||
saveBtn.className = "btn btn-sm btn-outline-primary";
|
||||
saveBtn.textContent = "Save";
|
||||
saveBtn.onclick = () => {
|
||||
const defaultName = guessFileName(block);
|
||||
const filename = prompt("Enter filename", defaultName);
|
||||
if (!filename) return;
|
||||
const fileContent = block.innerText;
|
||||
const contentB64 = btoa(fileContent);
|
||||
fetch("/file", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ filename: filename, content_b64: contentB64 })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => { alert("File saved with status: " + data.status); })
|
||||
.catch(err => { alert("File save error: " + err.message); });
|
||||
};
|
||||
containerDiv.appendChild(saveBtn);
|
||||
block.parentNode.insertBefore(containerDiv, block.nextSibling);
|
||||
});
|
||||
}
|
||||
|
||||
function guessFileName(block) {
|
||||
const codeElem = block.querySelector("code");
|
||||
let language = "";
|
||||
if (codeElem && codeElem.className) {
|
||||
const match = codeElem.className.match(/language-([a-zA-Z]+)/);
|
||||
if (match) {
|
||||
language = match[1];
|
||||
}
|
||||
}
|
||||
let ext = "txt";
|
||||
if (language) {
|
||||
const mapping = {
|
||||
go: "go",
|
||||
python: "py",
|
||||
javascript: "js",
|
||||
js: "js",
|
||||
java: "java",
|
||||
c: "c",
|
||||
"c++": "cpp",
|
||||
cpp: "cpp",
|
||||
html: "html",
|
||||
css: "css",
|
||||
json: "json",
|
||||
bash: "sh",
|
||||
shell: "sh",
|
||||
rust: "rs"
|
||||
};
|
||||
ext = mapping[language.toLowerCase()] || "txt";
|
||||
}
|
||||
return "code." + ext;
|
||||
}
|
||||
|
||||
function checkForFileCommand(content, messageElem) {
|
||||
let regex = /```(?:json)?\s*([\s\S]*?)\s*```/gm;
|
||||
let match;
|
||||
@ -207,7 +307,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Executes the file command and then sends the result as a user message.
|
||||
async function performFileCommand(cmdObj) {
|
||||
let fileCmd = cmdObj.file_command;
|
||||
let args = cmdObj.arguments || {};
|
||||
@ -258,11 +357,9 @@
|
||||
else {
|
||||
result = "Unknown file command: " + fileCmd;
|
||||
}
|
||||
// Append the result as a user message and auto-submit it.
|
||||
sendFileResponse(result);
|
||||
}
|
||||
|
||||
// Appends the file command result as a user's message and re-submits the conversation.
|
||||
async function sendFileResponse(responseMsg) {
|
||||
appendMessage("user", responseMsg);
|
||||
const typingIndicator = appendMessage("assistant", "Typing...", false);
|
||||
@ -287,7 +384,7 @@
|
||||
const response = await fetch("/title", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ messages: [currentConversation.messages[1]] })
|
||||
body: JSON.stringify({ messages: currentConversation.messages })
|
||||
});
|
||||
if (!response.ok) throw new Error("Title API error: " + response.status);
|
||||
const data = await response.json();
|
||||
@ -336,6 +433,7 @@
|
||||
|
||||
document.getElementById('newConvoBtn').addEventListener("click", createNewConversation);
|
||||
document.getElementById('newConvoBtnMobile').addEventListener("click", createNewConversation);
|
||||
loadSystemPrompt();
|
||||
loadConversations();
|
||||
if (conversations.length === 0) createNewConversation();
|
||||
else {
|
||||
@ -345,4 +443,4 @@
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
Reference in New Issue
Block a user