diff --git a/static/ai-chat1.html b/static/ai-chat1.html index 07917ca..af1a0a1 100644 --- a/static/ai-chat1.html +++ b/static/ai-chat1.html @@ -234,9 +234,9 @@ // 初始化 function init() { addAIMessage("你好,请输入你想问的问题。"); - var submit = $("#submit"); - var userInput = $("#user-input"); - var focus = false; + let submit = $("#submit"); + let userInput = $("#user-input"); + let focus = false; // 监听输入框焦点 userInput.focus(function () { focus = true; diff --git a/static/index_ollama.html b/static/index_ollama.html index 529a57c..6acf1e5 100644 --- a/static/index_ollama.html +++ b/static/index_ollama.html @@ -48,30 +48,30 @@ - - - + + - - - - - - - - - - - - - - - - - - - +
+
+ + + + +
+
+
+ + + +
+
+
diff --git a/static/js/api.js b/static/js/api.js index 5d23892..fc540ed 100644 --- a/static/js/api.js +++ b/static/js/api.js @@ -58,9 +58,10 @@ function setSystemPrompt(){ async function getModels(){ - const response = await fetch(`${ollama_host}/api/tags`); - const data = await response.json(); - return data; + // const response = await fetch(`${ollama_host}/api/tags`); + // const data = await response.json(); + // return data; + return undefined; } @@ -68,8 +69,8 @@ async function getModels(){ function postRequest(data, signal) { // const URL = `${ollama_host}/api/generate`; const URL = `https://openrouter.ai/api/v1/chat/completions`; - const body = JSON.stringify({model: "google/gemma-7b-it:free", messages: [{role: "user", content: data}]}); - alert(body); + const body = JSON.stringify({model: "google/gemma-7b-it:free", messages: [{role: "user", content: data.prompt}]}); + // alert(body) return fetch(URL, { method: 'POST', headers: { diff --git a/static/js/chat.js b/static/js/chat.js index 73d8842..7e5fb5c 100644 --- a/static/js/chat.js +++ b/static/js/chat.js @@ -13,7 +13,6 @@ Also see: https://github.com/jmorganca/ollama/blob/main/docs/faq.md `; - const clipboardIcon = ` @@ -23,13 +22,13 @@ const textBoxBaseHeight = 40; // This should match the default height set in CS // change settings of marked from default to remove deprecation warnings // see conversation here: https://github.com/markedjs/marked/issues/2793 marked.use({ - mangle: false, - headerIds: false + mangle: false, + headerIds: false }); function autoFocusInput() { - const userInput = document.getElementById('user-input'); - userInput.focus(); + const userInput = document.getElementById('user-input'); + userInput.focus(); } /* @@ -37,77 +36,78 @@ takes in model as a string updates the query parameters of page url to include model name */ function updateModelInQueryString(model) { - // make sure browser supports features - if (window.history.replaceState && 'URLSearchParams' in window) { - const searchParams = new URLSearchParams(window.location.search); - searchParams.set("model", model); - // replace current url without reload - const newPathWithQuery = `${window.location.pathname}?${searchParams.toString()}` - window.history.replaceState(null, '', newPathWithQuery); - } + // make sure browser supports features + if (window.history.replaceState && 'URLSearchParams' in window) { + const searchParams = new URLSearchParams(window.location.search); + searchParams.set("model", model); + // replace current url without reload + const newPathWithQuery = `${window.location.pathname}?${searchParams.toString()}` + window.history.replaceState(null, '', newPathWithQuery); + } } // Fetch available models and populate the dropdown async function populateModels() { - document.getElementById('send-button').addEventListener('click', submitRequest); + document.getElementById('send-button').addEventListener('click', submitRequest); - try { - const data = await getModels(); + try { + const data = await getModels(); - const selectElement = document.getElementById('model-select'); + const selectElement = document.getElementById('model-select'); - // set up handler for selection - selectElement.onchange = (() => updateModelInQueryString(selectElement.value)); + // set up handler for selection + selectElement.onchange = (() => updateModelInQueryString(selectElement.value)); - data.models.forEach((model) => { - const option = document.createElement('option'); - option.value = model.name; - option.innerText = model.name; - selectElement.appendChild(option); - }); + if (data !== undefined) { + data.models.forEach((model) => { + const option = document.createElement('option'); + option.value = model.name; + option.innerText = model.name; + selectElement.appendChild(option); + }); + } - // select option present in url parameter if present - const queryParams = new URLSearchParams(window.location.search); - const requestedModel = queryParams.get('model'); - // update the selection based on if requestedModel is a value in options - if ([...selectElement.options].map(o => o.value).includes(requestedModel)) { - selectElement.value = requestedModel; + + // select option present in url parameter if present + const queryParams = new URLSearchParams(window.location.search); + const requestedModel = queryParams.get('model'); + // update the selection based on if requestedModel is a value in options + if ([...selectElement.options].map(o => o.value).includes(requestedModel)) { + selectElement.value = requestedModel; + } + // otherwise set to the first element if exists and update URL accordingly + else if (selectElement.options.length) { + selectElement.value = selectElement.options[0].value; + updateModelInQueryString(selectElement.value); + } + } catch (error) { + document.getElementById('errorText').innerHTML = + DOMPurify.sanitize(marked.parse( + `Ollama-ui was unable to communitcate with Ollama due to the following error:\n\n` + + `\`\`\`${error.message}\`\`\`\n\n---------------------\n` + + faqString)); + let modal = new bootstrap.Modal(document.getElementById('errorModal')); + modal.show(); } - // otherwise set to the first element if exists and update URL accordingly - else if (selectElement.options.length) { - selectElement.value = selectElement.options[0].value; - updateModelInQueryString(selectElement.value); - } - } - catch (error) { - document.getElementById('errorText').innerHTML = - DOMPurify.sanitize(marked.parse( - `Ollama-ui was unable to communitcate with Ollama due to the following error:\n\n` - + `\`\`\`${error.message}\`\`\`\n\n---------------------\n` - + faqString)); - let modal = new bootstrap.Modal(document.getElementById('errorModal')); - modal.show(); - } } // adjusts the padding at the bottom of scrollWrapper to be the height of the input box function adjustPadding() { - const inputBoxHeight = document.getElementById('input-area').offsetHeight; - const scrollWrapper = document.getElementById('scroll-wrapper'); - scrollWrapper.style.paddingBottom = `${inputBoxHeight + 15}px`; + const inputBoxHeight = document.getElementById('input-area').offsetHeight; + const scrollWrapper = document.getElementById('scroll-wrapper'); + scrollWrapper.style.paddingBottom = `${inputBoxHeight + 15}px`; } // sets up padding resize whenever input box has its height changed const autoResizePadding = new ResizeObserver(() => { - adjustPadding(); + adjustPadding(); }); autoResizePadding.observe(document.getElementById('input-area')); - // Function to get the selected model function getSelectedModel() { - return document.getElementById('model-select').value; + return document.getElementById('model-select').value; } // variables to handle auto-scroll @@ -117,182 +117,191 @@ const scrollWrapper = document.getElementById('scroll-wrapper'); let isAutoScrollOn = true; // autoscroll when new line is added const autoScroller = new ResizeObserver(() => { - if (isAutoScrollOn) { - scrollWrapper.scrollIntoView({behavior: "smooth", block: "end"}); - } + if (isAutoScrollOn) { + scrollWrapper.scrollIntoView({behavior: "smooth", block: "end"}); + } }); // event listener for scrolling let lastKnownScrollPosition = 0; let ticking = false; document.addEventListener("scroll", (event) => { - // if user has scrolled up and autoScroll is on we turn it off - if (!ticking && isAutoScrollOn && window.scrollY < lastKnownScrollPosition) { - window.requestAnimationFrame(() => { - isAutoScrollOn = false; - ticking = false; - }); - ticking = true; - } - // if user has scrolled nearly all the way down and autoScroll is disabled, re-enable - else if (!ticking && !isAutoScrollOn && - window.scrollY > lastKnownScrollPosition && // make sure scroll direction is down - window.scrollY >= document.documentElement.scrollHeight - window.innerHeight - 30 // add 30px of space--no need to scroll all the way down, just most of the way - ) { - window.requestAnimationFrame(() => { - isAutoScrollOn = true; - ticking = false; - }); - ticking = true; - } - lastKnownScrollPosition = window.scrollY; + // if user has scrolled up and autoScroll is on we turn it off + if (!ticking && isAutoScrollOn && window.scrollY < lastKnownScrollPosition) { + window.requestAnimationFrame(() => { + isAutoScrollOn = false; + ticking = false; + }); + ticking = true; + } + // if user has scrolled nearly all the way down and autoScroll is disabled, re-enable + else if (!ticking && !isAutoScrollOn && + window.scrollY > lastKnownScrollPosition && // make sure scroll direction is down + window.scrollY >= document.documentElement.scrollHeight - window.innerHeight - 30 // add 30px of space--no need to scroll all the way down, just most of the way + ) { + window.requestAnimationFrame(() => { + isAutoScrollOn = true; + ticking = false; + }); + ticking = true; + } + lastKnownScrollPosition = window.scrollY; }); // Function to handle the user input and call the API functions async function submitRequest() { - document.getElementById('chat-container').style.display = 'block'; + document.getElementById('chat-container').style.display = 'block'; - const input = document.getElementById('user-input').value; - const selectedModel = getSelectedModel(); - const context = document.getElementById('chat-history').context; - const systemPrompt = document.getElementById('system-prompt').value; - const data = { model: selectedModel, prompt: input, context: context, system: systemPrompt }; + const input = document.getElementById('user-input').value; + const selectedModel = getSelectedModel(); + const context = document.getElementById('chat-history').context; + const systemPrompt = document.getElementById('system-prompt').value; + const data = {model: selectedModel, prompt: input, context: context, system: systemPrompt}; - // Create user message element and append to chat history - let chatHistory = document.getElementById('chat-history'); - let userMessageDiv = document.createElement('div'); - userMessageDiv.className = 'mb-2 user-message'; - userMessageDiv.innerText = input; - chatHistory.appendChild(userMessageDiv); + // Create user message element and append to chat history + let chatHistory = document.getElementById('chat-history'); + let userMessageDiv = document.createElement('div'); + userMessageDiv.className = 'mb-2 user-message'; + userMessageDiv.innerText = input; + chatHistory.appendChild(userMessageDiv); - // Create response container - let responseDiv = document.createElement('div'); - responseDiv.className = 'response-message mb-2 text-start'; - responseDiv.style.minHeight = '3em'; // make sure div does not shrink if we cancel the request when no text has been generated yet - spinner = document.createElement('div'); - spinner.className = 'spinner-border text-light'; - spinner.setAttribute('role', 'status'); - responseDiv.appendChild(spinner); - chatHistory.appendChild(responseDiv); + // Create response container + let responseDiv = document.createElement('div'); + responseDiv.className = 'response-message mb-2 text-start'; + responseDiv.style.minHeight = '3em'; // make sure div does not shrink if we cancel the request when no text has been generated yet + spinner = document.createElement('div'); + spinner.className = 'spinner-border text-light'; + spinner.setAttribute('role', 'status'); + responseDiv.appendChild(spinner); + chatHistory.appendChild(responseDiv); - // create button to stop text generation - let interrupt = new AbortController(); - let stopButton = document.createElement('button'); - stopButton.className = 'btn btn-danger'; - stopButton.innerHTML = 'Stop'; - stopButton.onclick = (e) => { - e.preventDefault(); - interrupt.abort('Stop button pressed'); - } - // add button after sendButton - const sendButton = document.getElementById('send-button'); - sendButton.insertAdjacentElement('beforebegin', stopButton); + // create button to stop text generation + let interrupt = new AbortController(); + let stopButton = document.createElement('button'); + stopButton.className = 'btn btn-danger'; + stopButton.innerHTML = 'Stop'; + stopButton.onclick = (e) => { + e.preventDefault(); + interrupt.abort('Stop button pressed'); + } + // add button after sendButton + const sendButton = document.getElementById('send-button'); + sendButton.insertAdjacentElement('beforebegin', stopButton); - // change autoScroller to keep track of our new responseDiv - autoScroller.observe(responseDiv); + // change autoScroller to keep track of our new responseDiv + autoScroller.observe(responseDiv); - postRequest(data, interrupt.signal) - .then(async response => { - await getResponse(response, parsedResponse => { - let word = parsedResponse.response; - if (parsedResponse.done) { - chatHistory.context = parsedResponse.context; - // Copy button - let copyButton = document.createElement('button'); - copyButton.className = 'btn btn-secondary copy-button'; - copyButton.innerHTML = clipboardIcon; - copyButton.onclick = () => { - navigator.clipboard.writeText(responseDiv.hidden_text).then(() => { - console.log('Text copied to clipboard'); - }).catch(err => { - console.error('Failed to copy text:', err); + postRequest(data, interrupt.signal) + .then(async response => { + await getResponse(response, parsedResponse => { + // let word = parsedResponse.response; + console.log(response); + console.log(parsedResponse); + let word = parsedResponse?.choices[0]?.message?.content; + console.log(word); + if (parsedResponse.done) { + chatHistory.context = parsedResponse.context; + // Copy button + let copyButton = document.createElement('button'); + copyButton.className = 'btn btn-secondary copy-button'; + copyButton.innerHTML = clipboardIcon; + copyButton.onclick = () => { + navigator.clipboard.writeText(responseDiv.hidden_text).then(() => { + console.log('Text copied to clipboard'); + }).catch(err => { + console.error('Failed to copy text:', err); + }); + }; + responseDiv.appendChild(copyButton); + } + // add word to response + if (word != undefined && word != "") { + if (responseDiv.hidden_text == undefined) { + responseDiv.hidden_text = ""; + } + responseDiv.hidden_text += word; + responseDiv.innerHTML = DOMPurify.sanitize(marked.parse(responseDiv.hidden_text)); // Append word to response container + } }); - }; - responseDiv.appendChild(copyButton); - } - // add word to response - if (word != undefined && word != "") { - if (responseDiv.hidden_text == undefined){ - responseDiv.hidden_text = ""; - } - responseDiv.hidden_text += word; - responseDiv.innerHTML = DOMPurify.sanitize(marked.parse(responseDiv.hidden_text)); // Append word to response container - } - }); - }) - .then(() => { - stopButton.remove(); // Remove stop button from DOM now that all text has been generated - spinner.remove(); - }) - .catch(error => { - if (error !== 'Stop button pressed') { - console.error(error); - } - stopButton.remove(); - spinner.remove(); - }); + }) + .then(() => { + stopButton.remove(); // Remove stop button from DOM now that all text has been generated + spinner.remove(); + }) + .catch(error => { + if (error !== 'Stop button pressed') { + console.error(error); + } + stopButton.remove(); + spinner.remove(); + }); - // Clear user input - const element = document.getElementById('user-input'); - element.value = ''; - $(element).css("height", textBoxBaseHeight + "px"); + // Clear user input + const element = document.getElementById('user-input'); + element.value = ''; + $(element).css("height", textBoxBaseHeight + "px"); } // Event listener for Ctrl + Enter or CMD + Enter document.getElementById('user-input').addEventListener('keydown', function (e) { - if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { - submitRequest(); - } + if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { + submitRequest(); + } }); window.onload = () => { - updateChatList(); - populateModels(); - adjustPadding(); - autoFocusInput(); + updateChatList(); + populateModels(); + adjustPadding(); + autoFocusInput(); - document.getElementById("delete-chat").addEventListener("click", deleteChat); - document.getElementById("new-chat").addEventListener("click", startNewChat); - document.getElementById("saveName").addEventListener("click", saveChat); - document.getElementById("chat-select").addEventListener("change", loadSelectedChat); - document.getElementById("host-address").addEventListener("change", setHostAddress); - document.getElementById("system-prompt").addEventListener("change", setSystemPrompt); + document.getElementById("delete-chat").addEventListener("click", deleteChat); + document.getElementById("new-chat").addEventListener("click", startNewChat); + document.getElementById("saveName").addEventListener("click", saveChat); + document.getElementById("chat-select").addEventListener("change", loadSelectedChat); + document.getElementById("host-address").addEventListener("change", setHostAddress); + document.getElementById("system-prompt").addEventListener("change", setSystemPrompt); } function deleteChat() { - const selectedChat = document.getElementById("chat-select").value; - localStorage.removeItem(selectedChat); - updateChatList(); + const selectedChat = document.getElementById("chat-select").value; + localStorage.removeItem(selectedChat); + updateChatList(); } // Function to save chat with a unique name function saveChat() { - const chatName = document.getElementById('userName').value; + const chatName = document.getElementById('userName').value; - // Close the modal - const bootstrapModal = bootstrap.Modal.getInstance(document.getElementById('nameModal')); - bootstrapModal.hide(); + // Close the modal + const bootstrapModal = bootstrap.Modal.getInstance(document.getElementById('nameModal')); + bootstrapModal.hide(); - if (chatName === null || chatName.trim() === "") return; - const history = document.getElementById("chat-history").innerHTML; - const context = document.getElementById('chat-history').context; - const systemPrompt = document.getElementById('system-prompt').value; - const model = getSelectedModel(); - localStorage.setItem(chatName, JSON.stringify({"history":history, "context":context, system: systemPrompt, "model": model})); - updateChatList(); + if (chatName === null || chatName.trim() === "") return; + const history = document.getElementById("chat-history").innerHTML; + const context = document.getElementById('chat-history').context; + const systemPrompt = document.getElementById('system-prompt').value; + const model = getSelectedModel(); + localStorage.setItem(chatName, JSON.stringify({ + "history": history, + "context": context, + system: systemPrompt, + "model": model + })); + updateChatList(); } // Function to load selected chat from dropdown function loadSelectedChat() { - const selectedChat = document.getElementById("chat-select").value; - const obj = JSON.parse(localStorage.getItem(selectedChat)); - document.getElementById("chat-history").innerHTML = obj.history; - document.getElementById("chat-history").context = obj.context; - document.getElementById("system-prompt").value = obj.system; - updateModelInQueryString(obj.model) - document.getElementById('chat-container').style.display = 'block'; + const selectedChat = document.getElementById("chat-select").value; + const obj = JSON.parse(localStorage.getItem(selectedChat)); + document.getElementById("chat-history").innerHTML = obj.history; + document.getElementById("chat-history").context = obj.context; + document.getElementById("system-prompt").value = obj.system; + updateModelInQueryString(obj.model) + document.getElementById('chat-container').style.display = 'block'; } function startNewChat() { @@ -304,16 +313,16 @@ function startNewChat() { // Function to update chat list dropdown function updateChatList() { - const chatList = document.getElementById("chat-select"); - chatList.innerHTML = ''; - for (let i = 0; i < localStorage.length; i++) { - const key = localStorage.key(i); - if (key === "host-address" || key == "system-prompt") continue; - const option = document.createElement("option"); - option.value = key; - option.text = key; - chatList.add(option); - } + const chatList = document.getElementById("chat-select"); + chatList.innerHTML = ''; + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key === "host-address" || key == "system-prompt") continue; + const option = document.createElement("option"); + option.value = key; + option.text = key; + chatList.add(option); + } } function autoGrow(element) { diff --git a/static/static_router.go b/static/static_router.go index d663688..8c4bed7 100644 --- a/static/static_router.go +++ b/static/static_router.go @@ -39,7 +39,7 @@ func Chat2(c echo.Context) error { func Chat3(c echo.Context) error { c.Response().Header().Set("Content-Type", "text/html; charset=utf-8") - c.Response().Header().Set("Cache-Control", "max-age=3600") + c.Response().Header().Set("Cache-Control", "max-age=1") return c.Blob(http.StatusOK, "text/html; charset=utf-8", pageMap["index_ollama.html"]) } @@ -55,7 +55,7 @@ func InitStaticGroup(g *echo.Group) { g.GET("css/:name", func(c echo.Context) error { cssName := c.Param("name") - c.Response().Header().Set("Cache-Control", "max-age=3600") + c.Response().Header().Set("Cache-Control", "max-age=1") data, _ := cssMap[cssName] return c.Blob(http.StatusOK, "text/css; charset=utf-8", data) }) @@ -68,7 +68,7 @@ func InitStaticGroup(g *echo.Group) { g.GET("js/:name", func(c echo.Context) error { jsName := c.Param("name") - c.Response().Header().Set("Cache-Control", "max-age=3600") + c.Response().Header().Set("Cache-Control", "max-age=1") return c.Blob(http.StatusOK, "text/javaScript", jsMap[jsName]) })