mirror of
https://github.com/mudler/LocalAI.git
synced 2025-06-06 19:04:59 +00:00
feat: support multiple files
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
parent
956c4ff660
commit
0532adfada
2 changed files with 98 additions and 78 deletions
|
@ -48,10 +48,10 @@ function submitSystemPrompt(event) {
|
||||||
document.getElementById("systemPrompt").blur();
|
document.getElementById("systemPrompt").blur();
|
||||||
}
|
}
|
||||||
|
|
||||||
var image = "";
|
var images = [];
|
||||||
var audio = "";
|
var audios = [];
|
||||||
var fileContent = "";
|
var fileContents = [];
|
||||||
var currentFileName = "";
|
var currentFileNames = [];
|
||||||
|
|
||||||
async function extractTextFromPDF(pdfData) {
|
async function extractTextFromPDF(pdfData) {
|
||||||
try {
|
try {
|
||||||
|
@ -73,24 +73,25 @@ async function extractTextFromPDF(pdfData) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function readInputFile() {
|
function readInputFile() {
|
||||||
if (!this.files || !this.files[0]) return;
|
if (!this.files || !this.files.length) return;
|
||||||
|
|
||||||
const file = this.files[0];
|
Array.from(this.files).forEach(file => {
|
||||||
const FR = new FileReader();
|
const FR = new FileReader();
|
||||||
currentFileName = file.name;
|
currentFileNames.push(file.name);
|
||||||
const fileExtension = file.name.split('.').pop().toLowerCase();
|
const fileExtension = file.name.split('.').pop().toLowerCase();
|
||||||
|
|
||||||
FR.addEventListener("load", async function(evt) {
|
FR.addEventListener("load", async function(evt) {
|
||||||
if (fileExtension === 'pdf') {
|
if (fileExtension === 'pdf') {
|
||||||
try {
|
try {
|
||||||
fileContent = await extractTextFromPDF(evt.target.result);
|
const content = await extractTextFromPDF(evt.target.result);
|
||||||
|
fileContents.push({ name: file.name, content: content });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error processing PDF:', error);
|
console.error('Error processing PDF:', error);
|
||||||
fileContent = "Error processing PDF file";
|
fileContents.push({ name: file.name, content: "Error processing PDF file" });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For text and markdown files
|
// For text and markdown files
|
||||||
fileContent = evt.target.result;
|
fileContents.push({ name: file.name, content: evt.target.result });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -99,6 +100,7 @@ function readInputFile() {
|
||||||
} else {
|
} else {
|
||||||
FR.readAsText(file);
|
FR.readAsText(file);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function submitPrompt(event) {
|
function submitPrompt(event) {
|
||||||
|
@ -107,19 +109,25 @@ function submitPrompt(event) {
|
||||||
const input = document.getElementById("input").value;
|
const input = document.getElementById("input").value;
|
||||||
let fullInput = input;
|
let fullInput = input;
|
||||||
|
|
||||||
// If there's file content, append it to the input for the LLM
|
// If there are file contents, append them to the input for the LLM
|
||||||
if (fileContent) {
|
if (fileContents.length > 0) {
|
||||||
fullInput += "\n\nFile content:\n" + fileContent;
|
fullInput += "\n\nFile contents:\n";
|
||||||
|
fileContents.forEach(file => {
|
||||||
|
fullInput += `\n--- ${file.name} ---\n${file.content}\n`;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show file icon in chat if there's a file
|
// Show file icons in chat if there are files
|
||||||
let displayContent = input;
|
let displayContent = input;
|
||||||
if (currentFileName) {
|
if (currentFileNames.length > 0) {
|
||||||
displayContent += `\n\n<i class="fa-solid fa-file"></i> Attached file: ${currentFileName}`;
|
displayContent += "\n\n";
|
||||||
|
currentFileNames.forEach(fileName => {
|
||||||
|
displayContent += `<i class="fa-solid fa-file"></i> Attached file: ${fileName}\n`;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the message to the chat UI with just the icon
|
// Add the message to the chat UI with just the icons
|
||||||
Alpine.store("chat").add("user", displayContent, image, audio);
|
Alpine.store("chat").add("user", displayContent, images, audios);
|
||||||
|
|
||||||
// Update the last message in the store with the full content
|
// Update the last message in the store with the full content
|
||||||
const history = Alpine.store("chat").history;
|
const history = Alpine.store("chat").history;
|
||||||
|
@ -132,33 +140,37 @@ function submitPrompt(event) {
|
||||||
Alpine.nextTick(() => { document.getElementById('messages').scrollIntoView(false); });
|
Alpine.nextTick(() => { document.getElementById('messages').scrollIntoView(false); });
|
||||||
promptGPT(systemPrompt, fullInput);
|
promptGPT(systemPrompt, fullInput);
|
||||||
|
|
||||||
// Reset file content and name after sending
|
// Reset file contents and names after sending
|
||||||
fileContent = "";
|
fileContents = [];
|
||||||
currentFileName = "";
|
currentFileNames = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function readInputImage() {
|
function readInputImage() {
|
||||||
if (!this.files || !this.files[0]) return;
|
if (!this.files || !this.files.length) return;
|
||||||
|
|
||||||
|
Array.from(this.files).forEach(file => {
|
||||||
const FR = new FileReader();
|
const FR = new FileReader();
|
||||||
|
|
||||||
FR.addEventListener("load", function(evt) {
|
FR.addEventListener("load", function(evt) {
|
||||||
image = evt.target.result;
|
images.push(evt.target.result);
|
||||||
});
|
});
|
||||||
|
|
||||||
FR.readAsDataURL(this.files[0]);
|
FR.readAsDataURL(file);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function readInputAudio() {
|
function readInputAudio() {
|
||||||
if (!this.files || !this.files[0]) return;
|
if (!this.files || !this.files.length) return;
|
||||||
|
|
||||||
|
Array.from(this.files).forEach(file => {
|
||||||
const FR = new FileReader();
|
const FR = new FileReader();
|
||||||
|
|
||||||
FR.addEventListener("load", function(evt) {
|
FR.addEventListener("load", function(evt) {
|
||||||
audio = evt.target.result;
|
audios.push(evt.target.result);
|
||||||
});
|
});
|
||||||
|
|
||||||
FR.readAsDataURL(this.files[0]);
|
FR.readAsDataURL(file);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function promptGPT(systemPrompt, input) {
|
async function promptGPT(systemPrompt, input) {
|
||||||
|
@ -177,7 +189,7 @@ async function promptGPT(systemPrompt, input) {
|
||||||
|
|
||||||
// loop all messages, and check if there are images or audios. If there are, we need to change the content field
|
// loop all messages, and check if there are images or audios. If there are, we need to change the content field
|
||||||
messages.forEach((message) => {
|
messages.forEach((message) => {
|
||||||
if (message.image || message.audio) {
|
if ((message.image && message.image.length > 0) || (message.audio && message.audio.length > 0)) {
|
||||||
// The content field now becomes an array
|
// The content field now becomes an array
|
||||||
message.content = [
|
message.content = [
|
||||||
{
|
{
|
||||||
|
@ -186,37 +198,42 @@ async function promptGPT(systemPrompt, input) {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
if (message.image) {
|
if (message.image && message.image.length > 0) {
|
||||||
|
message.image.forEach(img => {
|
||||||
message.content.push(
|
message.content.push(
|
||||||
{
|
{
|
||||||
"type": "image_url",
|
"type": "image_url",
|
||||||
"image_url": {
|
"image_url": {
|
||||||
"url": message.image,
|
"url": img,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
});
|
||||||
delete message.image;
|
delete message.image;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.audio) {
|
if (message.audio && message.audio.length > 0) {
|
||||||
|
message.audio.forEach(aud => {
|
||||||
message.content.push(
|
message.content.push(
|
||||||
{
|
{
|
||||||
"type": "audio_url",
|
"type": "audio_url",
|
||||||
"audio_url": {
|
"audio_url": {
|
||||||
"url": message.audio,
|
"url": aud,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
});
|
||||||
delete message.audio;
|
delete message.audio;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// reset the form and the files
|
// reset the form and the files
|
||||||
image = "";
|
images = [];
|
||||||
audio = "";
|
audios = [];
|
||||||
document.getElementById("input_image").value = null;
|
document.getElementById("input_image").value = null;
|
||||||
document.getElementById("input_audio").value = null;
|
document.getElementById("input_audio").value = null;
|
||||||
|
document.getElementById("input_file").value = null;
|
||||||
document.getElementById("fileName").innerHTML = "";
|
document.getElementById("fileName").innerHTML = "";
|
||||||
|
|
||||||
// Source: https://stackoverflow.com/a/75751803/11386095
|
// Source: https://stackoverflow.com/a/75751803/11386095
|
||||||
|
|
|
@ -221,12 +221,11 @@ SOFTWARE.
|
||||||
<div class="flex-1 p-4 overflow-auto" id="chat" x-data="{history: $store.chat.history}">
|
<div class="flex-1 p-4 overflow-auto" id="chat" x-data="{history: $store.chat.history}">
|
||||||
<p id="usage" x-show="history.length === 0" class="text-gray-300">
|
<p id="usage" x-show="history.length === 0" class="text-gray-300">
|
||||||
Start chatting with the AI by typing a prompt in the input field below and pressing Enter.<br>
|
Start chatting with the AI by typing a prompt in the input field below and pressing Enter.<br>
|
||||||
For models that support images, you can upload an image by clicking the paperclip
|
<ul class="list-disc list-inside">
|
||||||
<i class="fa-solid fa-paperclip"></i> icon. <br>
|
<li>For models that support images, you can upload an image by clicking the <i class="fa-solid fa-image"></i> icon.</li>
|
||||||
For models that support audio, you can upload an audio file by clicking the microphone
|
<li>For models that support audio, you can upload an audio file by clicking the <i class="fa-solid fa-microphone"></i> icon.</li>
|
||||||
<i class="fa-solid fa-microphone"></i> icon. <br>
|
<li>To send a text, markdown or PDF file, click the <i class="fa-solid fa-file"></i> icon.</li>
|
||||||
To send a text, markdown or PDF file, click the file
|
</ul>
|
||||||
<i class="fa-solid fa-file"></i> icon.
|
|
||||||
</p>
|
</p>
|
||||||
<div id="messages" class="max-w-3xl mx-auto">
|
<div id="messages" class="max-w-3xl mx-auto">
|
||||||
<template x-for="message in history">
|
<template x-for="message in history">
|
||||||
|
@ -296,8 +295,8 @@ SOFTWARE.
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick="document.getElementById('input_image').click()"
|
onclick="document.getElementById('input_image').click()"
|
||||||
class="fa-solid fa-paperclip text-gray-400 absolute right-12 top-4 text-lg p-2 hover:text-blue-400 transition-colors duration-200"
|
class="fa-solid fa-image text-gray-400 absolute right-12 top-4 text-lg p-2 hover:text-blue-400 transition-colors duration-200"
|
||||||
title="Attach an image"
|
title="Attach images"
|
||||||
></button>
|
></button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -338,22 +337,26 @@ SOFTWARE.
|
||||||
<input
|
<input
|
||||||
id="input_image"
|
id="input_image"
|
||||||
type="file"
|
type="file"
|
||||||
|
multiple
|
||||||
|
accept="image/*"
|
||||||
style="display: none;"
|
style="display: none;"
|
||||||
@change="fileName = $event.target.files[0].name"
|
@change="fileName = $event.target.files.length + ' image(s) selected'"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
id="input_audio"
|
id="input_audio"
|
||||||
type="file"
|
type="file"
|
||||||
|
multiple
|
||||||
accept="audio/*"
|
accept="audio/*"
|
||||||
style="display: none;"
|
style="display: none;"
|
||||||
@change="fileName = $event.target.files[0].name"
|
@change="fileName = $event.target.files.length + ' audio file(s) selected'"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
id="input_file"
|
id="input_file"
|
||||||
type="file"
|
type="file"
|
||||||
|
multiple
|
||||||
accept=".txt,.md,.pdf"
|
accept=".txt,.md,.pdf"
|
||||||
style="display: none;"
|
style="display: none;"
|
||||||
@change="fileName = $event.target.files[0].name"
|
@change="fileName = $event.target.files.length + ' file(s) selected'"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue