diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index a11777c..0000000 Binary files a/public/favicon.ico and /dev/null differ diff --git a/src/App.css b/src/App.css deleted file mode 100644 index daec866..0000000 --- a/src/App.css +++ /dev/null @@ -1,109 +0,0 @@ -/* CSS for chat container */ -.chat-page { - height: 100vh; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - background-color: #1d1d1d; /* Update background color to dark */ - color: #fff; /* Update text color to white */ -} - -.model-dropdown select { - padding: 10px; - font-size: 1.1rem; - border-radius: 5px; - border: none; - background-color: #292929; - color: #fff; -} - -/* CSS for chat messages */ -.chat-container { - height: calc(80vh - 60px); - width: 70%; - max-width: 2000px; - margin: 0 auto; - -ms-overflow-style: none; /* Internet Explorer 10+ */ - scrollbar-width: none; /* Firefox */ - overflow-y: scroll; - background-color: #292929; /* Update background color to dark */ - border-radius: 10px; - padding: 20px; - box-sizing: border-box; -} - -.chat-container::-webkit-scrollbar { - display: none; /* Safari and Chrome */ -} - -/* CSS for chat message */ -.chat-message { - display: flex; - flex-direction: column; - gap: 10px; - margin-bottom: 10px; -} - -.user-message { - align-items: flex-end; -} - -.assistant-message { - align-items: flex-start; -} - -.message-role { - font-size: 0.9rem; - font-weight: bold; -} - -.message-content { - padding: 10px; - border-radius: 5px; - font-size: 1.1rem; - background-color: #fff; /* Update background color to white */ - color: #1d1d1d; /* Update text color to dark */ -} - -/* CSS for chat input */ -.chat-input { - display: flex; - gap: 10px; - margin-top: 20px; -} - -.input-field { - flex: 1; - padding: 10px; - font-size: 1.1rem; - border-radius: 5px; - border: none; - background-color: #292929; - color: #fff; -} - -.submit-button { - padding: 10px 20px; - border-radius: 5px; - border: none; - background-color: #1db954; /* Update background color to green */ - color: #fff; - cursor: pointer; - transition: background-color 0.3s ease; -} - -.submit-button:hover { - background-color: #1ed760; /* Update background color on hover */ -} - -/* CSS for error message */ -.error-message { - margin-top: 10px; - font-size: 0.9rem; - color: #ff6961; /* Update text color to red */ -} - -.model-dropdown { - margin-bottom: 20px; -} \ No newline at end of file diff --git a/src/App.js b/src/App.js deleted file mode 100644 index 8df033f..0000000 --- a/src/App.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react"; -import ChatGptInterface from "./ChatGptInterface"; // Import the ChatGptInterface component -import "./App.css"; // Import the CSS file for styling - -function App() { - return ( -
- {/* Render the ChatGptInterface component */} -
- ); -} - -export default App; diff --git a/src/App.test.js b/src/App.test.js deleted file mode 100644 index 1f03afe..0000000 --- a/src/App.test.js +++ /dev/null @@ -1,8 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import App from './App'; - -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/src/ChatGptInterface.js b/src/ChatGptInterface.js index 926aaa5..1eba9c8 100644 --- a/src/ChatGptInterface.js +++ b/src/ChatGptInterface.js @@ -1,82 +1,111 @@ -import React, { useState, useRef, useEffect } from "react"; -import "./App.css"; // Import custom CSS for styling +import React, { useState, useRef, useEffect, Fragment } from "react"; +import "./index.css"; + +const host = "http://localhost:8080"; +const temperature = 0.7; const ChatGptInterface = () => { const [messages, setMessages] = useState([]); const [input, setInput] = useState(""); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); - const [models, setModels] = useState([]); // Added state for models + const [models, setModels] = useState([]); + const [currentAssistantMessage, setCurrentAssistantMessage] = useState(""); const chatContainerRef = useRef(null); const handleInputChange = (e) => { setInput(e.target.value); }; -const handleSubmit = async () => { - // Reset error state and set loading state - setError(null); - setIsLoading(true); - - try { - const requestOptions = { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - model: selectedModel, // Use selectedModel from state as model name - messages: [ - ...messages, - { - role: "user", - content: input, - }, - ], - temperature: 0.7, - }), - }; - - const response = await fetch( - "http://localhost:8080/v1/chat/completions", - requestOptions - ); - - const data = await response.json(); - const assistantResponse = - data?.choices?.[0]?.message?.content || "No response found"; - + const handleSubmit = async () => { + // Add user input to messages setMessages((prevMessages) => [ ...prevMessages, - { role: "user", content: input }, // Append user input message - { role: "assistant", content: assistantResponse }, + { role: "user", content: input }, ]); - // Clear input field - setInput(""); - } catch (error) { - console.error("Error:", error); - setError("Failed to fetch response. Please try again: " + error.message); // Update error message - } finally { - // Set loading state to false after response or error is received - setIsLoading(false); - } -}; + // Reset error state and set loading state + setError(null); + setIsLoading(true); + + try { + const requestOptions = { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + model: selectedModel, + messages: [ + ...messages, + { + role: "user", + content: input, + }, + ], + temperature, + stream: true, + }), + }; + + const response = await fetch(`${host}/v1/chat/completions`, requestOptions); + + let data = ""; + const reader = response.body.getReader(); + let partialData = ""; + let done = false; + let assistantResponse = ""; + + while (!done) { + const { value, done: readerDone } = await reader.read(); + + done = readerDone; + + if (value) { + const chunk = new TextDecoder().decode(value); + partialData += chunk; + const lines = partialData.split("\n"); + + for (let i = 0; i < lines.length - 1; i++) { + const line = lines[i]; + if (line.startsWith("data: ")) { + const jsonStr = line.substring("data: ".length); + const json = JSON.parse(jsonStr); + + // Check if the response contains choices and delta fields + if (json.choices && json.choices.length > 0 && json.choices[0].delta) { + const token = json.choices[0].delta.content; + if (token !== undefined) { + assistantResponse += token; + setCurrentAssistantMessage(assistantResponse); + } + } + } + } + + partialData = lines[lines.length - 1]; + } + } - // Scroll to the bottom of the chat container whenever a new message is added - useEffect(() => { - if (chatContainerRef.current) { - chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; + // Add assistant response to messages + setMessages((prevMessages) => [ + ...prevMessages, + { role: "assistant", content: assistantResponse }, + ]); + + // Clear input field and currentAssistantMessage + setInput(""); + setCurrentAssistantMessage(""); + } catch (error) { + console.error("Error:", error); + setError("Failed to fetch response. Please try again: " + error.message); + } finally { + setIsLoading(false); } - }, [messages]); - - + }; useEffect(() => { - // Fetch models on component mount const fetchModels = async () => { try { - const response = await fetch( - "http://localhost:8080/v1/models" - ); + const response = await fetch(`${host}/v1/models`); const data = await response.json(); setModels(data?.data || []); } catch (error) { @@ -84,13 +113,30 @@ const handleSubmit = async () => { } }; fetchModels(); - }, []); // Empty dependency array to fetch models only on mount + }, []); const handleModelChange = (e) => { setSelectedModel(e.target.value); }; -const [selectedModel, setSelectedModel] = useState(""); // Added state for selected model + const [selectedModel, setSelectedModel] = useState(""); + + useEffect(() => { + if (chatContainerRef.current) { + chatContainerRef.current.scrollTop = + chatContainerRef.current.scrollHeight; + } + }, [messages, currentAssistantMessage]); + + const renderMessageContent = (content) => { + const parts = content.split("\n"); + return parts.map((part, index) => ( + + {part} + {index < parts.length - 1 &&
} +
+ )); + }; return (
@@ -122,9 +168,19 @@ const [selectedModel, setSelectedModel] = useState(""); // Added state for selec {message.role === "user" ? "You" : "LocalAI"}: - {message.content} + + {renderMessageContent(message.content)} +
))} + {isLoading && ( +
+ LocalAI: + + {renderMessageContent(currentAssistantMessage)} + +
+ )}
@@ -151,4 +207,4 @@ const [selectedModel, setSelectedModel] = useState(""); // Added state for selec ); }; -export default ChatGptInterface; \ No newline at end of file +export default ChatGptInterface; diff --git a/src/index.css b/src/index.css index ec2585e..7cfec82 100644 --- a/src/index.css +++ b/src/index.css @@ -1,13 +1,123 @@ body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } + +/* CSS for chat container */ +.chat-page { + height: 100vh; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background-color: #1d1d1d; /* Update background color to dark */ + color: #fff; /* Update text color to white */ +} + +.model-dropdown select { + padding: 10px; + font-size: 1.1rem; + border-radius: 5px; + border: none; + background-color: #292929; + color: #fff; +} + +/* CSS for chat messages */ +.chat-container { + height: calc(80vh - 60px); + width: 70%; + max-width: 2000px; + margin: 0 auto; + -ms-overflow-style: none; /* Internet Explorer 10+ */ + scrollbar-width: none; /* Firefox */ + overflow-y: scroll; + background-color: #292929; /* Update background color to dark */ + border-radius: 10px; + padding: 20px; + box-sizing: border-box; +} + +.chat-container::-webkit-scrollbar { + display: none; /* Safari and Chrome */ +} + +/* CSS for chat message */ +.chat-message { + display: flex; + flex-direction: column; + gap: 10px; + margin-bottom: 10px; +} + +.user-message { + align-items: flex-end; +} + +.assistant-message { + align-items: flex-start; +} + +.message-role { + font-size: 0.9rem; + font-weight: bold; +} + +.message-content { + padding: 10px; + border-radius: 5px; + font-size: 1.1rem; + background-color: #fff; /* Update background color to white */ + color: #1d1d1d; /* Update text color to dark */ +} + +/* CSS for chat input */ +.chat-input { + display: flex; + gap: 10px; + margin-top: 20px; +} + +.input-field { + flex: 1; + padding: 10px; + font-size: 1.1rem; + border-radius: 5px; + border: none; + background-color: #292929; + color: #fff; +} + +.submit-button { + padding: 10px 20px; + border-radius: 5px; + border: none; + background-color: #1db954; /* Update background color to green */ + color: #fff; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.submit-button:hover { + background-color: #1ed760; /* Update background color on hover */ +} + +/* CSS for error message */ +.error-message { + margin-top: 10px; + font-size: 0.9rem; + color: #ff6961; /* Update text color to red */ +} + +.model-dropdown { + margin-bottom: 20px; +} diff --git a/src/index.js b/src/index.js index d563c0f..4564471 100644 --- a/src/index.js +++ b/src/index.js @@ -1,17 +1,14 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import App from './App'; -import reportWebVitals from './reportWebVitals'; +import "./index.css"; -const root = ReactDOM.createRoot(document.getElementById('root')); +import React from "react"; +import ReactDOM from "react-dom/client"; +import ChatGptInterface from "./ChatGptInterface"; + +const root = ReactDOM.createRoot(document.getElementById("root")); root.render( - +
+ +
); - -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); diff --git a/src/logo.svg b/src/logo.svg deleted file mode 100644 index 9dfc1c0..0000000 --- a/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/reportWebVitals.js b/src/reportWebVitals.js deleted file mode 100644 index 5253d3a..0000000 --- a/src/reportWebVitals.js +++ /dev/null @@ -1,13 +0,0 @@ -const reportWebVitals = onPerfEntry => { - if (onPerfEntry && onPerfEntry instanceof Function) { - import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { - getCLS(onPerfEntry); - getFID(onPerfEntry); - getFCP(onPerfEntry); - getLCP(onPerfEntry); - getTTFB(onPerfEntry); - }); - } -}; - -export default reportWebVitals; diff --git a/src/setupTests.js b/src/setupTests.js deleted file mode 100644 index 8f2609b..0000000 --- a/src/setupTests.js +++ /dev/null @@ -1,5 +0,0 @@ -// jest-dom adds custom jest matchers for asserting on DOM nodes. -// allows you to do things like: -// expect(element).toHaveTextContent(/react/i) -// learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom';