Merge branch 'master' of github.com:Mintplex-Labs/anything-llm into render

This commit is contained in:
timothycarambat 2024-08-12 11:59:46 -07:00
commit 8fc547e78a
61 changed files with 3064 additions and 600 deletions

View File

@ -78,8 +78,8 @@
},
"updateContentCommand": "cd server && yarn && cd ../collector && PUPPETEER_DOWNLOAD_BASE_URL=https://storage.googleapis.com/chrome-for-testing-public yarn && cd ../frontend && yarn && cd .. && yarn setup:envs && yarn prisma:setup && echo \"Please run yarn dev:server, yarn dev:collector, and yarn dev:frontend in separate terminal tabs.\"",
// Use 'postCreateCommand' to run commands after the container is created.
// This configures VITE for github codespaces
"postCreateCommand": "if [ \"${CODESPACES}\" = \"true\" ]; then echo 'VITE_API_BASE=\"https://$CODESPACE_NAME-3001.$GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN/api\"' > ./frontend/.env; fi",
// This configures VITE for github codespaces and installs gh cli
"postCreateCommand": "if [ \"${CODESPACES}\" = \"true\" ]; then echo 'VITE_API_BASE=\"https://$CODESPACE_NAME-3001.$GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN/api\"' > ./frontend/.env && (type -p wget >/dev/null || (sudo apt update && sudo apt-get install wget -y)) && sudo mkdir -p -m 755 /etc/apt/keyrings && wget -qO- https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null && sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg && echo \"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main\" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null && sudo apt update && sudo apt install gh -y; fi",
"portsAttributes": {
"3001": {
"label": "Backend",

View File

@ -0,0 +1,115 @@
name: Publish AnythingLLM Docker image on Release (amd64 & arm64)
concurrency:
group: build-${{ github.ref }}
cancel-in-progress: true
on:
release:
types: [published]
jobs:
push_multi_platform_to_registries:
name: Push Docker multi-platform image to multiple registries
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
steps:
- name: Check out the repo
uses: actions/checkout@v4
- name: Check if DockerHub build needed
shell: bash
run: |
# Check if the secret for USERNAME is set (don't even check for the password)
if [[ -z "${{ secrets.DOCKER_USERNAME }}" ]]; then
echo "DockerHub build not needed"
echo "enabled=false" >> $GITHUB_OUTPUT
else
echo "DockerHub build needed"
echo "enabled=true" >> $GITHUB_OUTPUT
fi
id: dockerhub
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a
# Only login to the Docker Hub if the repo is mintplex/anythingllm, to allow for forks to build on GHCR
if: steps.dockerhub.outputs.enabled == 'true'
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: |
${{ steps.dockerhub.outputs.enabled == 'true' && 'mintplexlabs/anythingllm' || '' }}
ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
- name: Build and push multi-platform Docker image
uses: docker/build-push-action@v6
with:
context: .
file: ./docker/Dockerfile
push: true
sbom: true
provenance: mode=max
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# For Docker scout there are some intermediary reported CVEs which exists outside
# of execution content or are unreachable by an attacker but exist in image.
# We create VEX files for these so they don't show in scout summary.
- name: Collect known and verified CVE exceptions
id: cve-list
run: |
# Collect CVEs from filenames in vex folder
CVE_NAMES=""
for file in ./docker/vex/*.vex.json; do
[ -e "$file" ] || continue
filename=$(basename "$file")
stripped_filename=${filename%.vex.json}
CVE_NAMES+=" $stripped_filename"
done
echo "CVE_EXCEPTIONS=$CVE_NAMES" >> $GITHUB_OUTPUT
shell: bash
# About VEX attestations https://docs.docker.com/scout/explore/exceptions/
# Justifications https://github.com/openvex/spec/blob/main/OPENVEX-SPEC.md#status-justifications
- name: Add VEX attestations
env:
CVE_EXCEPTIONS: ${{ steps.cve-list.outputs.CVE_EXCEPTIONS }}
run: |
echo $CVE_EXCEPTIONS
curl -sSfL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh | sh -s --
for cve in $CVE_EXCEPTIONS; do
for tag in "${{ join(fromJSON(steps.meta.outputs.json).tags, ' ') }}"; do
echo "Attaching VEX exception $cve to $tag"
docker scout attestation add \
--file "./docker/vex/$cve.vex.json" \
--predicate-type https://openvex.dev/ns/v0.2.0 \
$tag
done
done
shell: bash

View File

@ -6,7 +6,7 @@ concurrency:
on:
push:
branches: ['558-multi-modal-support'] # put your current branch to create a build. Core team only.
branches: ['pipertts-support'] # put your current branch to create a build. Core team only.
paths-ignore:
- '**.md'
- 'cloud-deployments/*'

2
.vscode/tasks.json vendored
View File

@ -46,7 +46,7 @@
}
}
},
"command": "cd ${workspaceFolder}/server/ && yarn dev",
"command": "if [ \"${CODESPACES}\" = \"true\" ]; then while ! gh codespace ports -c $CODESPACE_NAME | grep 3001; do sleep 1; done; gh codespace ports visibility 3001:public -c $CODESPACE_NAME; fi & cd ${workspaceFolder}/server/ && yarn dev",
"runOptions": {
"instanceLimit": 1,
"reevaluateOnRerun": true

View File

@ -110,6 +110,7 @@ AnythingLLM divides your documents into objects called `workspaces`. A Workspace
**TTS (text-to-speech) support:**
- Native Browser Built-in (default)
- [PiperTTSLocal - runs in browser](https://github.com/rhasspy/piper)
- [OpenAI TTS](https://platform.openai.com/docs/guides/text-to-speech/voice-options)
- [ElevenLabs](https://elevenlabs.io/)

View File

@ -9,7 +9,9 @@ sudo systemctl enable docker
sudo systemctl start docker
mkdir -p /home/anythingllm
touch /home/anythingllm/.env
cat <<EOF >/home/anythingllm/.env
${env_content}
EOF
sudo docker pull mintplexlabs/anythingllm
sudo docker run -d -p 3001:3001 --cap-add SYS_ADMIN -v /home/anythingllm:/app/server/storage -v /home/anythingllm/.env:/app/server/.env -e STORAGE_DIR="/app/server/storage" mintplexlabs/anythingllm

View File

@ -223,10 +223,6 @@ class GitLabRepoLoader {
const objects = Array.isArray(data)
? data.filter((item) => item.type === "blob")
: []; // only get files, not paths or submodules
if (objects.length === 0) {
fetching = false;
break;
}
// Apply ignore path rules to found objects. If any rules match it is an invalid file path.
console.log(

View File

@ -9,34 +9,36 @@ const { tokenizeString } = require("../../tokenizer");
const path = require("path");
const fs = require("fs");
async function discoverLinks(startUrl, depth = 1, maxLinks = 20) {
async function discoverLinks(startUrl, maxDepth = 1, maxLinks = 20) {
const baseUrl = new URL(startUrl);
const discoveredLinks = new Set();
const pendingLinks = [startUrl];
let currentLevel = 0;
depth = depth < 1 ? 1 : depth;
maxLinks = maxLinks < 1 ? 1 : maxLinks;
const discoveredLinks = new Set([startUrl]);
let queue = [[startUrl, 0]]; // [url, currentDepth]
const scrapedUrls = new Set();
// Check depth and if there are any links left to scrape
while (currentLevel < depth && pendingLinks.length > 0) {
const newLinks = await getPageLinks(pendingLinks[0], baseUrl);
pendingLinks.shift();
for (let currentDepth = 0; currentDepth < maxDepth; currentDepth++) {
const levelSize = queue.length;
const nextQueue = [];
for (let i = 0; i < levelSize && discoveredLinks.size < maxLinks; i++) {
const [currentUrl, urlDepth] = queue[i];
if (!scrapedUrls.has(currentUrl)) {
scrapedUrls.add(currentUrl);
const newLinks = await getPageLinks(currentUrl, baseUrl);
for (const link of newLinks) {
if (!discoveredLinks.has(link)) {
if (!discoveredLinks.has(link) && discoveredLinks.size < maxLinks) {
discoveredLinks.add(link);
pendingLinks.push(link);
if (urlDepth + 1 < maxDepth) {
nextQueue.push([link, urlDepth + 1]);
}
}
}
// Exit out if we reach maxLinks
if (discoveredLinks.size >= maxLinks) {
return Array.from(discoveredLinks).slice(0, maxLinks);
}
}
if (pendingLinks.length === 0) {
currentLevel++;
}
queue = nextQueue;
if (queue.length === 0 || discoveredLinks.size >= maxLinks) break;
}
return Array.from(discoveredLinks);

View File

@ -47,10 +47,12 @@ class YoutubeTranscript {
let transcript = "";
const chunks = transcriptXML.getElementsByTagName("text");
for (const chunk of chunks) {
transcript += chunk.textContent;
// Add space after each text chunk
transcript += chunk.textContent + " ";
}
return transcript;
// Trim extra whitespace
return transcript.trim().replace(/\s+/g, " ");
} catch (e) {
throw new YoutubeTranscriptError(e);
}

View File

@ -37,6 +37,7 @@ class MimeDetector {
"lua",
"pas",
"r",
"go",
],
},
true

View File

@ -13,6 +13,7 @@
"dependencies": {
"@metamask/jazzicon": "^2.0.0",
"@microsoft/fetch-event-source": "^2.0.1",
"@mintplex-labs/piper-tts-web": "^1.0.4",
"@phosphor-icons/react": "^2.1.7",
"@tremor/react": "^3.15.1",
"dompurify": "^3.0.8",
@ -24,7 +25,9 @@
"js-levenshtein": "^1.1.6",
"lodash.debounce": "^4.0.8",
"markdown-it": "^13.0.1",
"markdown-it-katex": "^2.0.3",
"moment": "^2.30.1",
"onnxruntime-web": "^1.18.0",
"pluralize": "^8.0.0",
"react": "^18.2.0",
"react-device-detect": "^2.2.2",

View File

@ -0,0 +1,220 @@
import { useState, useEffect, useRef } from "react";
import PiperTTSClient from "@/utils/piperTTS";
import { titleCase } from "text-case";
import { humanFileSize } from "@/utils/numbers";
import showToast from "@/utils/toast";
import { CircleNotch, PauseCircle, PlayCircle } from "@phosphor-icons/react";
export default function PiperTTSOptions({ settings }) {
return (
<>
<p className="text-sm font-base text-white text-opacity-60 mb-4">
All PiperTTS models will run in your browser locally. This can be
resource intensive on lower-end devices.
</p>
<div className="flex gap-x-4 items-center">
<PiperTTSModelSelection settings={settings} />
</div>
</>
);
}
function voicesByLanguage(voices = []) {
const voicesByLanguage = voices.reduce((acc, voice) => {
const langName = voice?.language?.name_english ?? "Unlisted";
acc[langName] = acc[langName] || [];
acc[langName].push(voice);
return acc;
}, {});
return Object.entries(voicesByLanguage);
}
function voiceDisplayName(voice) {
const { is_stored, name, quality, files } = voice;
const onnxFileKey = Object.keys(files).find((key) => key.endsWith(".onnx"));
const fileSize = files?.[onnxFileKey]?.size_bytes || 0;
return `${is_stored ? "✔ " : ""}${titleCase(name)}-${quality === "low" ? "Low" : "HQ"} (${humanFileSize(fileSize)})`;
}
function PiperTTSModelSelection({ settings }) {
const [loading, setLoading] = useState(true);
const [voices, setVoices] = useState([]);
const [selectedVoice, setSelectedVoice] = useState(
settings?.TTSPiperTTSVoiceModel
);
function flushVoices() {
PiperTTSClient.flush()
.then(() =>
showToast("All voices flushed from browser storage", "info", {
clear: true,
})
)
.catch((e) => console.error(e));
}
useEffect(() => {
PiperTTSClient.voices()
.then((voices) => {
if (voices?.length !== 0) return setVoices(voices);
throw new Error("Could not fetch voices from web worker.");
})
.catch((e) => {
console.error(e);
})
.finally(() => setLoading(false));
}, []);
if (loading) {
return (
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-3">
Voice Model Selection
</label>
<select
name="TTSPiperTTSVoiceModel"
value=""
disabled={true}
className="border-none bg-zinc-900 border-gray-500 text-white text-sm rounded-lg block w-full p-2.5"
>
<option value="" disabled={true}>
-- loading available models --
</option>
</select>
</div>
);
}
return (
<div className="flex flex-col w-fit">
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-3">
Voice Model Selection
</label>
<div className="flex items-center w-fit gap-x-4 mb-2">
<select
name="TTSPiperTTSVoiceModel"
required={true}
onChange={(e) => setSelectedVoice(e.target.value)}
value={selectedVoice}
className="border-none flex-shrink-0 bg-zinc-900 border-gray-500 text-white text-sm rounded-lg block w-full p-2.5"
>
{voicesByLanguage(voices).map(([lang, voices]) => {
return (
<optgroup key={lang} label={lang}>
{voices.map((voice) => (
<option key={voice.key} value={voice.key}>
{voiceDisplayName(voice)}
</option>
))}
</optgroup>
);
})}
</select>
<DemoVoiceSample voiceId={selectedVoice} />
</div>
<p className="text-xs text-white/40">
The "✔" indicates this model is already stored locally and does not
need to be downloaded when run.
</p>
</div>
{!!voices.find((voice) => voice.is_stored) && (
<button
type="button"
onClick={flushVoices}
className="w-fit border-none hover:text-white hover:underline text-white/40 text-sm my-4"
>
Flush voice cache
</button>
)}
</div>
);
}
function DemoVoiceSample({ voiceId }) {
const playerRef = useRef(null);
const [speaking, setSpeaking] = useState(false);
const [loading, setLoading] = useState(false);
const [audioSrc, setAudioSrc] = useState(null);
async function speakMessage(e) {
e.preventDefault();
if (speaking) {
playerRef?.current?.pause();
return;
}
try {
if (!audioSrc) {
setLoading(true);
const client = new PiperTTSClient({ voiceId });
const blobUrl = await client.getAudioBlobForText(
"Hello, welcome to AnythingLLM!"
);
setAudioSrc(blobUrl);
setLoading(false);
client.worker?.terminate();
PiperTTSClient._instance = null;
} else {
playerRef.current.play();
}
} catch (e) {
console.error(e);
setLoading(false);
setSpeaking(false);
}
}
useEffect(() => {
function setupPlayer() {
if (!playerRef?.current) return;
playerRef.current.addEventListener("play", () => {
setSpeaking(true);
});
playerRef.current.addEventListener("pause", () => {
playerRef.current.currentTime = 0;
setSpeaking(false);
setAudioSrc(null);
});
}
setupPlayer();
}, []);
return (
<button
type="button"
onClick={speakMessage}
disabled={loading}
className="border-none text-zinc-300 flex items-center gap-x-1"
>
{speaking ? (
<>
<PauseCircle size={20} className="flex-shrink-0" />
<p className="text-sm flex-shrink-0">Stop demo</p>
</>
) : (
<>
{loading ? (
<>
<CircleNotch size={20} className="animate-spin flex-shrink-0" />
<p className="text-sm flex-shrink-0">Loading voice</p>
</>
) : (
<>
<PlayCircle size={20} className="flex-shrink-0" />
<p className="text-sm flex-shrink-0">Play sample</p>
</>
)}
</>
)}
<audio
ref={playerRef}
hidden={true}
src={audioSrc}
autoPlay={true}
controls={false}
/>
</button>
);
}

View File

@ -7,6 +7,7 @@ import { Plus, X } from "@phosphor-icons/react";
export default function AccountModal({ user, hideModal }) {
const { pfp, setPfp } = usePfp();
const handleFileUpload = async (event) => {
const file = event.target.files[0];
if (!file) return false;
@ -133,6 +134,10 @@ export default function AccountModal({ user, hideModal }) {
required
autoComplete="off"
/>
<p className="mt-2 text-xs text-white/60">
Username must be only contain lowercase letters, numbers,
underscores, and hyphens with no spaces
</p>
</div>
<div>
<label
@ -143,10 +148,14 @@ export default function AccountModal({ user, hideModal }) {
</label>
<input
name="password"
type="password"
type="text"
className="bg-zinc-900 placeholder:text-white/20 border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
placeholder={`${user.username}'s new password`}
minLength={8}
/>
<p className="mt-2 text-xs text-white/60">
Password must be at least 8 characters long
</p>
</div>
<LanguagePreference />
</div>

View File

@ -1,9 +1,11 @@
import { useEffect, useState } from "react";
import NativeTTSMessage from "./native";
import AsyncTTSMessage from "./asyncTts";
import PiperTTSMessage from "./piperTTS";
import System from "@/models/system";
export default function TTSMessage({ slug, chatId, message }) {
const [settings, setSettings] = useState({});
const [provider, setProvider] = useState("native");
const [loading, setLoading] = useState(true);
@ -11,13 +13,26 @@ export default function TTSMessage({ slug, chatId, message }) {
async function getSettings() {
const _settings = await System.keys();
setProvider(_settings?.TextToSpeechProvider ?? "native");
setSettings(_settings);
setLoading(false);
}
getSettings();
}, []);
if (!chatId || loading) return null;
if (provider !== "native")
switch (provider) {
case "openai":
case "elevenlabs":
return <AsyncTTSMessage slug={slug} chatId={chatId} />;
case "piper_local":
return (
<PiperTTSMessage
voiceId={settings?.TTSPiperTTSVoiceModel}
message={message}
/>
);
default:
return <NativeTTSMessage message={message} />;
}
}

View File

@ -0,0 +1,91 @@
import { useEffect, useState, useRef } from "react";
import { SpeakerHigh, PauseCircle, CircleNotch } from "@phosphor-icons/react";
import { Tooltip } from "react-tooltip";
import PiperTTSClient from "@/utils/piperTTS";
export default function PiperTTS({ voiceId = null, message }) {
const playerRef = useRef(null);
const [speaking, setSpeaking] = useState(false);
const [loading, setLoading] = useState(false);
const [audioSrc, setAudioSrc] = useState(null);
async function speakMessage(e) {
e.preventDefault();
if (speaking) {
playerRef?.current?.pause();
return;
}
try {
if (!audioSrc) {
setLoading(true);
const client = new PiperTTSClient({ voiceId });
const blobUrl = await client.getAudioBlobForText(message);
setAudioSrc(blobUrl);
setLoading(false);
} else {
playerRef.current.play();
}
} catch (e) {
console.error(e);
setLoading(false);
setSpeaking(false);
}
}
useEffect(() => {
function setupPlayer() {
if (!playerRef?.current) return;
playerRef.current.addEventListener("play", () => {
setSpeaking(true);
});
playerRef.current.addEventListener("pause", () => {
playerRef.current.currentTime = 0;
setSpeaking(false);
});
}
setupPlayer();
}, []);
return (
<div className="mt-3 relative">
<button
type="button"
onClick={speakMessage}
disabled={loading}
data-tooltip-id="message-to-speech"
data-tooltip-content={
speaking ? "Pause TTS speech of message" : "TTS Speak message"
}
className="border-none text-zinc-300"
aria-label={speaking ? "Pause speech" : "Speak message"}
>
{speaking ? (
<PauseCircle size={18} className="mb-1" />
) : (
<>
{loading ? (
<CircleNotch size={18} className="mb-1 animate-spin" />
) : (
<SpeakerHigh size={18} className="mb-1" />
)}
</>
)}
<audio
ref={playerRef}
hidden={true}
src={audioSrc}
autoPlay={true}
controls={false}
/>
</button>
<Tooltip
id="message-to-speech"
place="bottom"
delayShow={300}
className="tooltip !text-xs"
/>
</div>
);
}

View File

@ -1,3 +1,4 @@
import useUser from "@/hooks/useUser";
import { PaperclipHorizontal } from "@phosphor-icons/react";
import { Tooltip } from "react-tooltip";
@ -6,6 +7,9 @@ import { Tooltip } from "react-tooltip";
* @returns
*/
export default function AttachItem() {
const { user } = useUser();
if (!!user && user.role === "default") return null;
return (
<>
<button

View File

@ -1,4 +1,4 @@
import { useState } from "react";
import { useState, useEffect } from "react";
import { X } from "@phosphor-icons/react";
import ModalWrapper from "@/components/ModalWrapper";
import { CMD_REGEX } from ".";
@ -10,9 +10,15 @@ export default function EditPresetModal({
onDelete,
preset,
}) {
const [command, setCommand] = useState(preset?.command?.slice(1) || "");
const [command, setCommand] = useState("");
const [deleting, setDeleting] = useState(false);
useEffect(() => {
if (preset && isOpen) {
setCommand(preset.command?.slice(1) || "");
}
}, [preset, isOpen]);
const handleSubmit = (e) => {
e.preventDefault();
const form = new FormData(e.target);

View File

@ -62,13 +62,18 @@ export default function SlashPresets({ setShowing, sendCommand }) {
}
fetchPresets();
closeEditModal();
closeEditModalAndResetPreset();
};
const handleDeletePreset = async (presetId) => {
await System.deleteSlashCommandPreset(presetId);
fetchPresets();
closeEditModalAndResetPreset();
};
const closeEditModalAndResetPreset = () => {
closeEditModal();
setSelectedPreset(null);
};
return (
@ -116,7 +121,7 @@ export default function SlashPresets({ setShowing, sendCommand }) {
{selectedPreset && (
<EditPresetModal
isOpen={isEditModalOpen}
onClose={closeEditModal}
onClose={closeEditModalAndResetPreset}
onSave={handleUpdatePreset}
onDelete={handleDeletePreset}
preset={selectedPreset}

View File

@ -1,10 +1,11 @@
import { useEffect } from "react";
import { useEffect, useCallback } from "react";
import { Microphone } from "@phosphor-icons/react";
import { Tooltip } from "react-tooltip";
import _regeneratorRuntime from "regenerator-runtime";
import SpeechRecognition, {
useSpeechRecognition,
} from "react-speech-recognition";
import { PROMPT_INPUT_EVENT } from "../../PromptInput";
let timeout;
const SILENCE_INTERVAL = 3_200; // wait in seconds of silence before closing.
@ -45,15 +46,49 @@ export default function SpeechToText({ sendCommand }) {
clearTimeout(timeout);
}
const handleKeyPress = useCallback(
(event) => {
if (event.ctrlKey && event.keyCode === 77) {
if (listening) {
endTTSSession();
} else {
startSTTSession();
}
}
},
[listening, endTTSSession, startSTTSession]
);
function handlePromptUpdate(e) {
if (!e?.detail && timeout) {
endTTSSession();
clearTimeout(timeout);
}
}
useEffect(() => {
if (transcript?.length > 0) {
document.addEventListener("keydown", handleKeyPress);
return () => {
document.removeEventListener("keydown", handleKeyPress);
};
}, [handleKeyPress]);
useEffect(() => {
if (!!window)
window.addEventListener(PROMPT_INPUT_EVENT, handlePromptUpdate);
return () =>
window?.removeEventListener(PROMPT_INPUT_EVENT, handlePromptUpdate);
}, []);
useEffect(() => {
if (transcript?.length > 0 && listening) {
sendCommand(transcript, false);
clearTimeout(timeout);
timeout = setTimeout(() => {
endTTSSession();
}, SILENCE_INTERVAL);
}
}, [transcript]);
}, [transcript, listening]);
if (!browserSupportsSpeechRecognition) return null;
return (
@ -69,7 +104,9 @@ export default function SpeechToText({ sendCommand }) {
>
<Microphone
weight="fill"
className="w-6 h-6 pointer-events-none text-white"
className={`w-6 h-6 pointer-events-none text-white overflow-hidden rounded-full ${
listening ? "animate-pulse-glow" : ""
}`}
/>
<Tooltip
id="tooltip-text-size-btn"

View File

@ -112,7 +112,7 @@ export default function PromptInput({
className="flex flex-col gap-y-1 rounded-t-lg md:w-3/4 w-full mx-auto max-w-xl items-center"
>
<div className="flex items-center rounded-lg md:mb-4">
<div className="w-[635px] bg-main-gradient shadow-2xl border border-white/50 rounded-2xl flex flex-col px-4 overflow-hidden">
<div className="w-[95vw] md:w-[635px] bg-main-gradient shadow-2xl border border-white/50 rounded-2xl flex flex-col px-4 overflow-hidden">
<AttachmentManager attachments={attachments} />
<div className="flex items-center w-full border-b-2 border-gray-500/50">
<textarea

View File

@ -14,6 +14,9 @@ import handleSocketResponse, {
AGENT_SESSION_START,
} from "@/utils/chat/agent";
import DnDFileUploaderWrapper from "./DnDWrapper";
import SpeechRecognition, {
useSpeechRecognition,
} from "react-speech-recognition";
export default function ChatContainer({ workspace, knownHistory = [] }) {
const { threadSlug = null } = useParams();
@ -29,6 +32,10 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
setMessage(event.target.value);
};
const { listening, resetTranscript } = useSpeechRecognition({
clearTranscriptOnListen: true,
});
// Emit an update to the state of the prompt input without directly
// passing a prop in so that it does not re-render constantly.
function setMessageEmit(messageContent = "") {
@ -57,11 +64,20 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
},
];
if (listening) {
// Stop the mic if the send button is clicked
endTTSSession();
}
setChatHistory(prevChatHistory);
setMessageEmit("");
setLoadingResponse(true);
};
function endTTSSession() {
SpeechRecognition.stopListening();
resetTranscript();
}
const regenerateAssistantMessage = (chatId) => {
const updatedHistory = chatHistory.slice(0, -1);
const lastUserMessage = updatedHistory.slice(-1)[0];

View File

@ -794,3 +794,12 @@ does not extend the close button beyond the viewport. */
top: -3.5rem;
}
}
/* Math/Katex formatting to prevent duplication of content on screen */
.katex-html[aria-hidden="true"] {
display: none;
}
.katex-mathml {
font-size: 20px;
}

View File

@ -0,0 +1,482 @@
const TRANSLATIONS = {
common: {
"workspaces-name": "Name der Arbeitsbereiche",
error: "Fehler",
success: "Erfolg",
user: "Benutzer",
selection: "Modellauswahl",
saving: "Speichern...",
save: "Änderungen speichern",
previous: "Vorherige Seite",
next: "Nächste Seite",
},
settings: {
title: "Instanzeinstellungen",
system: "Allgemeine Einstellungen",
invites: "Einladungen",
users: "Benutzer",
workspaces: "Arbeitsbereiche",
"workspace-chats": "Arbeitsbereich-Chats",
customization: "Anpassung",
"api-keys": "Entwickler-API",
llm: "LLM",
transcription: "Transkription",
embedder: "Einbettung",
"text-splitting": "Textsplitting & Chunking",
"voice-speech": "Sprache & Sprachausgabe",
"vector-database": "Vektordatenbank",
embeds: "Chat-Einbettung",
"embed-chats": "Chat-Einbettungsverlauf",
security: "Sicherheit",
"event-logs": "Ereignisprotokolle",
privacy: "Datenschutz & Datenverarbeitung",
"ai-providers": "KI-Anbieter",
"agent-skills": "Agentenfähigkeiten",
admin: "Administrator",
tools: "Werkzeuge",
"experimental-features": "Experimentelle Funktionen",
contact: "Support kontaktieren",
},
login: {
"multi-user": {
welcome: "Willkommen bei",
"placeholder-username": "Benutzername",
"placeholder-password": "Passwort",
login: "Anmelden",
validating: "Überprüfung...",
"forgot-pass": "Passwort vergessen",
reset: "Zurücksetzen",
},
"sign-in": {
start: "Melden Sie sich bei Ihrem",
end: "Konto an.",
},
"password-reset": {
title: "Passwort zurücksetzen",
description:
"Geben Sie die erforderlichen Informationen unten ein, um Ihr Passwort zurückzusetzen.",
"recovery-codes": "Wiederherstellungscodes",
"recovery-code": "Wiederherstellungscode {{index}}",
"back-to-login": "Zurück zur Anmeldung",
},
},
welcomeMessage: {
part1:
"Willkommen bei AnythingLLM, AnythingLLM ist ein Open-Source-KI-Tool von Mintplex Labs, das alles in einen trainierten Chatbot verwandelt, den Sie abfragen und mit dem Sie chatten können. AnythingLLM ist eine BYOK-Software (Bring-Your-Own-Keys), daher gibt es keine Abonnements, Gebühren oder Kosten für diese Software außerhalb der Dienste, die Sie damit nutzen möchten.",
part2:
"AnythingLLM ist der einfachste Weg, leistungsstarke KI-Produkte wie OpenAI, GPT-4, LangChain, PineconeDB, ChromaDB und andere Dienste in einem übersichtlichen Paket ohne Aufwand zusammenzufassen, um Ihre Produktivität um das 100-fache zu steigern.",
part3:
"AnythingLLM kann vollständig lokal auf Ihrem Computer mit geringem Overhead laufen - Sie werden nicht einmal bemerken, dass es da ist! Keine GPU erforderlich. Cloud- und On-Premises-Installation ist ebenfalls verfügbar.\nDas KI-Tool-Ökosystem wird jeden Tag leistungsfähiger. AnythingLLM macht es einfach, es zu nutzen.",
githubIssue: "Erstellen Sie ein Problem auf Github",
user1: "Wie fange ich an?!",
part4:
'Es ist einfach. Alle Sammlungen sind in Behältern organisiert, die wir "Arbeitsbereiche" nennen. Arbeitsbereiche sind Behälter für Dateien, Dokumente, Bilder, PDFs und andere Dateien, die in etwas umgewandelt werden, das LLMs verstehen und in Gesprächen verwenden können.\n\nSie können jederzeit Dateien hinzufügen und entfernen.',
createWorkspace: "Erstellen Sie Ihren ersten Arbeitsbereich",
user2:
"Ist das so eine Art KI-Dropbox oder so? Was ist mit dem Chatten? Es ist doch ein Chatbot, oder?",
part5:
"AnythingLLM ist mehr als eine intelligentere Dropbox.\n\nAnythingLLM bietet zwei Möglichkeiten, mit Ihren Daten zu sprechen:\n\n<i>Abfrage:</i> Ihre Chats geben Daten oder Schlussfolgerungen zurück, die in den Dokumenten Ihres Arbeitsbereichs gefunden wurden, auf die es Zugriff hat. Je mehr Dokumente Sie dem Arbeitsbereich hinzufügen, desto intelligenter wird er! \n\n<i>Konversation:</i> Ihre Dokumente + Ihr laufender Chat-Verlauf tragen gleichzeitig zum LLM-Wissen bei. Großartig für das Anhängen von Echtzeit-Textinformationen oder Korrekturen und Missverständnissen, die das LLM haben könnte. \n\nSie können zwischen beiden Modi wechseln \n<i>mitten im Chatten!</i>",
user3: "Wow, das klingt erstaunlich, lass es mich gleich ausprobieren!",
part6: "Viel Spaß!",
starOnGithub: "Stern auf GitHub",
contact: "Kontaktieren Sie Mintplex Labs",
},
"new-workspace": {
title: "Neuer Arbeitsbereich",
placeholder: "Mein Arbeitsbereich",
},
"workspaces—settings": {
general: "Allgemeine Einstellungen",
chat: "Chat-Einstellungen",
vector: "Vektordatenbank",
members: "Mitglieder",
agent: "Agentenkonfiguration",
},
general: {
vector: {
title: "Vektoranzahl",
description: "Gesamtanzahl der Vektoren in Ihrer Vektordatenbank.",
},
names: {
description: "Dies ändert nur den Anzeigenamen Ihres Arbeitsbereichs.",
},
message: {
title: "Vorgeschlagene Chat-Nachrichten",
description:
"Passen Sie die Nachrichten an, die Ihren Arbeitsbereich-Benutzern vorgeschlagen werden.",
add: "Neue Nachricht hinzufügen",
save: "Nachrichten speichern",
heading: "Erkläre mir",
body: "die Vorteile von AnythingLLM",
},
pfp: {
title: "Assistent-Profilbild",
description:
"Passen Sie das Profilbild des Assistenten für diesen Arbeitsbereich an.",
image: "Arbeitsbereich-Bild",
remove: "Arbeitsbereich-Bild entfernen",
},
delete: {
title: "Arbeitsbereich löschen",
description:
"Löschen Sie diesen Arbeitsbereich und alle seine Daten. Dies löscht den Arbeitsbereich für alle Benutzer.",
delete: "Arbeitsbereich löschen",
deleting: "Arbeitsbereich wird gelöscht...",
"confirm-start": "Sie sind dabei, Ihren gesamten",
"confirm-end":
"Arbeitsbereich zu löschen. Dies entfernt alle Vektoreinbettungen in Ihrer Vektordatenbank.\n\nDie ursprünglichen Quelldateien bleiben unberührt. Diese Aktion ist irreversibel.",
},
},
chat: {
llm: {
title: "Arbeitsbereich-LLM-Anbieter",
description:
"Der spezifische LLM-Anbieter und das Modell, das für diesen Arbeitsbereich verwendet wird. Standardmäßig wird der System-LLM-Anbieter und dessen Einstellungen verwendet.",
search: "Durchsuchen Sie alle LLM-Anbieter",
},
model: {
title: "Arbeitsbereich-Chat-Modell",
description:
"Das spezifische Chat-Modell, das für diesen Arbeitsbereich verwendet wird. Wenn leer, wird die System-LLM-Präferenz verwendet.",
wait: "-- warte auf Modelle --",
},
mode: {
title: "Chat-Modus",
chat: {
title: "Chat",
"desc-start": "wird Antworten mit dem allgemeinen Wissen des LLM",
and: "und",
"desc-end": "gefundenem Dokumentenkontext liefern.",
},
query: {
title: "Abfrage",
"desc-start": "wird Antworten",
only: "nur",
"desc-end": "liefern, wenn Dokumentenkontext gefunden wird.",
},
},
history: {
title: "Chat-Verlauf",
"desc-start":
"Die Anzahl der vorherigen Chats, die in das Kurzzeitgedächtnis der Antwort einbezogen werden.",
recommend: "Empfohlen 20. ",
"desc-end":
"Alles über 45 führt wahrscheinlich zu kontinuierlichen Chat-Ausfällen, abhängig von der Nachrichtengröße.",
},
prompt: {
title: "Prompt",
description:
"Der Prompt, der in diesem Arbeitsbereich verwendet wird. Definieren Sie den Kontext und die Anweisungen für die KI, um eine Antwort zu generieren. Sie sollten einen sorgfältig formulierten Prompt bereitstellen, damit die KI eine relevante und genaue Antwort generieren kann.",
},
refusal: {
title: "Abfragemodus-Ablehnungsantwort",
"desc-start": "Wenn im",
query: "Abfrage",
"desc-end":
"modus, möchten Sie vielleicht eine benutzerdefinierte Ablehnungsantwort zurückgeben, wenn kein Kontext gefunden wird.",
},
temperature: {
title: "LLM-Temperatur",
"desc-start":
'Diese Einstellung steuert, wie "kreativ" Ihre LLM-Antworten sein werden.',
"desc-end":
"Je höher die Zahl, desto kreativer. Bei einigen Modellen kann dies zu unverständlichen Antworten führen, wenn sie zu hoch eingestellt ist.",
hint: "Die meisten LLMs haben verschiedene akzeptable Bereiche gültiger Werte. Konsultieren Sie Ihren LLM-Anbieter für diese Informationen.",
},
},
"vector-workspace": {
identifier: "Vektordatenbank-Identifikator",
snippets: {
title: "Maximale Kontext-Snippets",
description:
"Diese Einstellung steuert die maximale Anzahl von Kontext-Snippets, die pro Chat oder Abfrage an das LLM gesendet werden.",
recommend: "Empfohlen: 4",
},
doc: {
title: "Dokumentähnlichkeitsschwelle",
description:
"Der minimale Ähnlichkeitswert, der erforderlich ist, damit eine Quelle als relevant für den Chat betrachtet wird. Je höher die Zahl, desto ähnlicher muss die Quelle dem Chat sein.",
zero: "Keine Einschränkung",
low: "Niedrig (Ähnlichkeitswert ≥ .25)",
medium: "Mittel (Ähnlichkeitswert ≥ .50)",
high: "Hoch (Ähnlichkeitswert ≥ .75)",
},
reset: {
reset: "Vektordatenbank zurücksetzen",
resetting: "Vektoren werden gelöscht...",
confirm:
"Sie sind dabei, die Vektordatenbank dieses Arbeitsbereichs zurückzusetzen. Dies entfernt alle derzeit eingebetteten Vektoreinbettungen.\n\nDie ursprünglichen Quelldateien bleiben unberührt. Diese Aktion ist irreversibel.",
error:
"Die Arbeitsbereich-Vektordatenbank konnte nicht zurückgesetzt werden!",
success: "Die Arbeitsbereich-Vektordatenbank wurde zurückgesetzt!",
},
},
agent: {
"performance-warning":
"Die Leistung von LLMs, die Werkzeugaufrufe nicht explizit unterstützen, hängt stark von den Fähigkeiten und der Genauigkeit des Modells ab. Einige Fähigkeiten können eingeschränkt oder nicht funktionsfähig sein.",
provider: {
title: "Arbeitsbereich-Agent LLM-Anbieter",
description:
"Der spezifische LLM-Anbieter und das Modell, das für den @agent-Agenten dieses Arbeitsbereichs verwendet wird.",
},
mode: {
chat: {
title: "Arbeitsbereich-Agent Chat-Modell",
description:
"Das spezifische Chat-Modell, das für den @agent-Agenten dieses Arbeitsbereichs verwendet wird.",
},
title: "Arbeitsbereich-Agent-Modell",
description:
"Das spezifische LLM-Modell, das für den @agent-Agenten dieses Arbeitsbereichs verwendet wird.",
wait: "-- warte auf Modelle --",
},
skill: {
title: "Standard-Agentenfähigkeiten",
description:
"Verbessern Sie die natürlichen Fähigkeiten des Standard-Agenten mit diesen vorgefertigten Fähigkeiten. Diese Einrichtung gilt für alle Arbeitsbereiche.",
rag: {
title: "RAG & Langzeitgedächtnis",
description:
'Erlauben Sie dem Agenten, Ihre lokalen Dokumente zu nutzen, um eine Abfrage zu beantworten oder bitten Sie den Agenten, Inhalte für den Langzeitabruf zu "merken".',
},
view: {
title: "Dokumente anzeigen & zusammenfassen",
description:
"Erlauben Sie dem Agenten, den Inhalt der aktuell eingebetteten Arbeitsbereichsdateien aufzulisten und zusammenzufassen.",
},
scrape: {
title: "Websites durchsuchen",
description:
"Erlauben Sie dem Agenten, Websites zu besuchen und deren Inhalt zu extrahieren.",
},
generate: {
title: "Diagramme generieren",
description:
"Aktivieren Sie den Standard-Agenten, um verschiedene Arten von Diagrammen aus bereitgestellten oder im Chat gegebenen Daten zu generieren.",
},
save: {
title: "Dateien generieren & im Browser speichern",
description:
"Aktivieren Sie den Standard-Agenten, um Dateien zu generieren und zu schreiben, die gespeichert und in Ihrem Browser heruntergeladen werden können.",
},
web: {
title: "Live-Websuche und -Browsing",
"desc-start":
"Ermöglichen Sie Ihrem Agenten, das Web zu durchsuchen, um Ihre Fragen zu beantworten, indem Sie eine Verbindung zu einem Websuche-Anbieter (SERP) herstellen.",
"desc-end":
"Die Websuche während Agentensitzungen funktioniert erst, wenn dies eingerichtet ist.",
},
},
},
recorded: {
title: "Arbeitsbereich-Chats",
description:
"Dies sind alle aufgezeichneten Chats und Nachrichten, die von Benutzern gesendet wurden, geordnet nach ihrem Erstellungsdatum.",
export: "Exportieren",
table: {
id: "Id",
by: "Gesendet von",
workspace: "Arbeitsbereich",
prompt: "Prompt",
response: "Antwort",
at: "Gesendet am",
},
},
appearance: {
title: "Erscheinungsbild",
description: "Passen Sie die Erscheinungseinstellungen Ihrer Plattform an.",
logo: {
title: "Logo anpassen",
description:
"Laden Sie Ihr eigenes Logo hoch, um Ihren Chatbot zu personalisieren.",
add: "Benutzerdefiniertes Logo hinzufügen",
recommended: "Empfohlene Größe: 800 x 200",
remove: "Entfernen",
replace: "Ersetzen",
},
message: {
title: "Nachrichten anpassen",
description:
"Passen Sie die automatischen Nachrichten an, die Ihren Benutzern angezeigt werden.",
new: "Neu",
system: "System",
user: "Benutzer",
message: "Nachricht",
assistant: "AnythingLLM Chat-Assistent",
"double-click": "Doppelklicken zum Bearbeiten...",
save: "Nachrichten speichern",
},
icons: {
title: "Benutzerdefinierte Fußzeilen-Icons",
description:
"Passen Sie die Fußzeilen-Icons an, die am unteren Rand der Seitenleiste angezeigt werden.",
icon: "Icon",
link: "Link",
},
},
api: {
title: "API-Schlüssel",
description:
"API-Schlüssel ermöglichen es dem Besitzer, programmatisch auf diese AnythingLLM-Instanz zuzugreifen und sie zu verwalten.",
link: "Lesen Sie die API-Dokumentation",
generate: "Neuen API-Schlüssel generieren",
table: {
key: "API-Schlüssel",
by: "Erstellt von",
created: "Erstellt",
},
},
llm: {
title: "LLM-Präferenz",
description:
"Dies sind die Anmeldeinformationen und Einstellungen für Ihren bevorzugten LLM-Chat- und Einbettungsanbieter. Es ist wichtig, dass diese Schlüssel aktuell und korrekt sind, sonst wird AnythingLLM nicht richtig funktionieren.",
provider: "LLM-Anbieter",
},
transcription: {
title: "Transkriptionsmodell-Präferenz",
description:
"Dies sind die Anmeldeinformationen und Einstellungen für Ihren bevorzugten Transkriptionsmodellanbieter. Es ist wichtig, dass diese Schlüssel aktuell und korrekt sind, sonst werden Mediendateien und Audio nicht transkribiert.",
provider: "Transkriptionsanbieter",
"warn-start":
"Die Verwendung des lokalen Whisper-Modells auf Maschinen mit begrenztem RAM oder CPU kann AnythingLLM bei der Verarbeitung von Mediendateien zum Stillstand bringen.",
"warn-recommend":
"Wir empfehlen mindestens 2 GB RAM und das Hochladen von Dateien <10 MB.",
"warn-end":
"Das eingebaute Modell wird bei der ersten Verwendung automatisch heruntergeladen.",
},
embedding: {
title: "Einbettungspräferenz",
"desc-start":
"Bei der Verwendung eines LLM, das keine native Unterstützung für eine Einbettungs-Engine bietet, müssen Sie möglicherweise zusätzlich Anmeldeinformationen für die Texteinbettung angeben.",
"desc-end":
"Einbettung ist der Prozess, Text in Vektoren umzuwandeln. Diese Anmeldeinformationen sind erforderlich, um Ihre Dateien und Prompts in ein Format umzuwandeln, das AnythingLLM zur Verarbeitung verwenden kann.",
provider: {
title: "Einbettungsanbieter",
description:
"Bei Verwendung der nativen Einbettungs-Engine von AnythingLLM ist keine Einrichtung erforderlich.",
},
},
text: {
title: "Textsplitting & Chunking-Präferenzen",
"desc-start":
"Manchmal möchten Sie vielleicht die Standardmethode ändern, wie neue Dokumente gesplittet und gechunkt werden, bevor sie in Ihre Vektordatenbank eingefügt werden.",
"desc-end":
"Sie sollten diese Einstellung nur ändern, wenn Sie verstehen, wie Textsplitting funktioniert und welche Nebenwirkungen es hat.",
"warn-start": "Änderungen hier gelten nur für",
"warn-center": "neu eingebettete Dokumente",
"warn-end": ", nicht für bestehende Dokumente.",
size: {
title: "Textchunk-Größe",
description:
"Dies ist die maximale Länge der Zeichen, die in einem einzelnen Vektor vorhanden sein können.",
recommend: "Die maximale Länge des Einbettungsmodells beträgt",
},
overlap: {
title: "Textchunk-Überlappung",
description:
"Dies ist die maximale Überlappung von Zeichen, die während des Chunkings zwischen zwei benachbarten Textchunks auftritt.",
},
},
vector: {
title: "Vektordatenbank",
description:
"Dies sind die Anmeldeinformationen und Einstellungen für die Funktionsweise Ihrer AnythingLLM-Instanz. Es ist wichtig, dass diese Schlüssel aktuell und korrekt sind.",
provider: {
title: "Vektordatenbankanbieter",
description: "Für LanceDB ist keine Konfiguration erforderlich.",
},
},
embeddable: {
title: "Einbettbare Chat-Widgets",
description:
"Einbettbare Chat-Widgets sind öffentlich zugängliche Chat-Schnittstellen, die an einen einzelnen Arbeitsbereich gebunden sind. Diese ermöglichen es Ihnen, Arbeitsbereiche zu erstellen, die Sie dann weltweit veröffentlichen können.",
create: "Einbettung erstellen",
table: {
workspace: "Arbeitsbereich",
chats: "Gesendete Chats",
Active: "Aktive Domains",
},
},
"embed-chats": {
title: "Eingebettete Chats",
description:
"Dies sind alle aufgezeichneten Chats und Nachrichten von jeder Einbettung, die Sie veröffentlicht haben.",
table: {
embed: "Einbettung",
sender: "Absender",
message: "Nachricht",
response: "Antwort",
at: "Gesendet am",
},
},
multi: {
title: "Mehrbenutzer-Modus",
description:
"Richten Sie Ihre Instanz ein, um Ihr Team zu unterstützen, indem Sie den Mehrbenutzer-Modus aktivieren.",
enable: {
"is-enable": "Mehrbenutzer-Modus ist aktiviert",
enable: "Mehrbenutzer-Modus aktivieren",
description:
"Standardmäßig sind Sie der einzige Administrator. Als Administrator müssen Sie Konten für alle neuen Benutzer oder Administratoren erstellen. Verlieren Sie Ihr Passwort nicht, da nur ein Administrator-Benutzer Passwörter zurücksetzen kann.",
username: "Administrator-Kontoname",
password: "Administrator-Kontopasswort",
},
password: {
title: "Passwortschutz",
description:
"Schützen Sie Ihre AnythingLLM-Instanz mit einem Passwort. Wenn Sie dieses vergessen, gibt es keine Wiederherstellungsmethode, also stellen Sie sicher, dass Sie dieses Passwort speichern.",
},
instance: {
title: "Instanz mit Passwort schützen",
description:
"Standardmäßig sind Sie der einzige Administrator. Als Administrator müssen Sie Konten für alle neuen Benutzer oder Administratoren erstellen. Verlieren Sie Ihr Passwort nicht, da nur ein Administrator-Benutzer Passwörter zurücksetzen kann.",
password: "Instanz-Passwort",
},
},
event: {
title: "Ereignisprotokolle",
description:
"Sehen Sie alle Aktionen und Ereignisse, die auf dieser Instanz zur Überwachung stattfinden.",
clear: "Ereignisprotokolle löschen",
table: {
type: "Ereignistyp",
user: "Benutzer",
occurred: "Aufgetreten am",
},
},
privacy: {
title: "Datenschutz & Datenverarbeitung",
description:
"Dies ist Ihre Konfiguration dafür, wie verbundene Drittanbieter und AnythingLLM Ihre Daten behandeln.",
llm: "LLM-Auswahl",
embedding: "Einbettungspräferenz",
vector: "Vektordatenbank",
anonymous: "Anonyme Telemetrie aktiviert",
},
};
export default TRANSLATIONS;

View File

@ -0,0 +1,496 @@
const TRANSLATIONS = {
common: {
"workspaces-name": "Nome dos Workspaces",
error: "erro",
success: "sucesso",
user: "Usuário",
selection: "Seleção de Modelo",
saving: "Salvando...",
save: "Salvar alterações",
previous: "Página Anterior",
next: "Próxima Página",
},
// Setting Sidebar menu items.
settings: {
title: "Configurações da Instância",
system: "Configurações Gerais",
invites: "Convites",
users: "Usuários",
workspaces: "Workspaces",
"workspace-chats": "Chats do Workspace",
customization: "Customização",
"api-keys": "API para Desenvolvedores",
llm: "LLM",
transcription: "Transcrição",
embedder: "Incorporador",
"text-splitting": "Divisor de Texto e Fragmentação",
"voice-speech": "Voz e Fala",
"vector-database": "Banco de Dados Vetorial",
embeds: "Incorporar Chat",
"embed-chats": "Histórico de Chats Incorporados",
security: "Segurança",
"event-logs": "Logs de Eventos",
privacy: "Privacidade e Dados",
"ai-providers": "Provedores de IA",
"agent-skills": "Habilidades do Agente",
admin: "Admin",
tools: "Ferramentas",
"experimental-features": "Recursos Experimentais",
contact: "Contato com Suporte",
},
// Page Definitions
login: {
"multi-user": {
welcome: "Bem-vindo ao",
"placeholder-username": "Nome de Usuário",
"placeholder-password": "Senha",
login: "Entrar",
validating: "Validando...",
"forgot-pass": "Esqueceu a senha",
reset: "Redefinir",
},
"sign-in": {
start: "Faça login na sua",
end: "conta.",
},
"password-reset": {
title: "Redefinição de Senha",
description:
"Forneça as informações necessárias abaixo para redefinir sua senha.",
"recovery-codes": "Códigos de Recuperação",
"recovery-code": "Código de Recuperação {{index}}",
"back-to-login": "Voltar ao Login",
},
},
welcomeMessage: {
part1:
"Bem-vindo ao AnythingLLM, AnythingLLM é uma ferramenta de IA de código aberto da Mintplex Labs que transforma qualquer coisa em um chatbot treinado que você pode consultar e conversar. AnythingLLM é um software BYOK (bring-your-own-keys | traga suas próprias chaves), portanto, não há assinatura, taxa ou cobranças para este software fora dos serviços que você deseja usar com ele.",
part2:
"AnythingLLM é a maneira mais fácil de reunir produtos de IA poderosos como OpenAi, GPT-4, LangChain, PineconeDB, ChromaDB e outros serviços em um pacote organizado sem complicações para aumentar sua produtividade em 100x.",
part3:
"AnythingLLM pode ser executado totalmente localmente em sua máquina com pouca sobrecarga que você nem perceberá que está lá! Não é necessário GPU. A instalação em nuvem e localmente também está disponível.\nO ecossistema de ferramentas de IA fica mais poderoso a cada dia. AnythingLLM facilita o uso.",
githubIssue: "Criar uma issue no Github",
user1: "Como eu começo?!",
part4:
'É simples. Todas as coleções são organizadas em grupos que chamamos de "Workspaces". Workspaces são grupos de arquivos, documentos, imagens, PDFs e outros arquivos que serão transformados em algo que os LLMs podem entender e usar em conversas.\n\nVocê pode adicionar e remover arquivos a qualquer momento.',
createWorkspace: "Crie seu primeiro workspace",
user2:
"Isso é como um Dropbox de IA ou algo assim? E quanto a conversar? Não é um chatbot?",
part5:
"AnythingLLM é mais do que um Dropbox mais inteligente.\n\nAnythingLLM oferece duas maneiras de conversar com seus dados:\n\n<i>Consulta:</i> Seus chats retornarão dados ou inferências encontradas com os documentos em seu workspace ao qual tem acesso. Adicionar mais documentos ao Workspace o torna mais inteligente!\n\n<i>Conversacional:</i> Seus documentos + seu histórico de chat em andamento contribuem para o conhecimento do LLM ao mesmo tempo. Ótimo para adicionar informações em tempo real baseadas em texto ou correções e mal-entendidos que o LLM possa ter.\n\nVocê pode alternar entre qualquer modo \n<i>no meio da conversa!</i>",
user3: "Uau, isso soa incrível, deixe-me experimentar já!",
part6: "Divirta-se!",
starOnGithub: "Dar estrela no GitHub",
contact: "Contato Mintplex Labs",
},
"new-workspace": {
title: "Novo Workspace",
placeholder: "Meu Workspace",
},
// Workspace Settings menu items
"workspaces—settings": {
general: "Configurações Gerais",
chat: "Configurações de Chat",
vector: "Banco de Dados Vetorial",
members: "Membros",
agent: "Configuração do Agente",
},
// General Appearance
general: {
vector: {
title: "Contagem de Vetores",
description: "Número total de vetores no seu banco de dados vetorial.",
},
names: {
description: "Isso mudará apenas o nome de exibição do seu workspace.",
},
message: {
title: "Mensagens de Chat Sugeridas",
description:
"Personalize as mensagens que serão sugeridas aos usuários do seu workspace.",
add: "Adicionar nova mensagem",
save: "Salvar Mensagens",
heading: "Explique para mim",
body: "os benefícios do AnythingLLM",
},
pfp: {
title: "Imagem de Perfil do Assistente",
description:
"Personalize a imagem de perfil do assistente para este workspace.",
image: "Imagem do Workspace",
remove: "Remover Imagem do Workspace",
},
delete: {
title: "Excluir Workspace",
description:
"Excluir este workspace e todos os seus dados. Isso excluirá o workspace para todos os usuários.",
delete: "Excluir Workspace",
deleting: "Excluindo Workspace...",
"confirm-start": "Você está prestes a excluir todo o seu",
"confirm-end":
"workspace. Isso removerá todas as incorporações vetoriais no seu banco de dados vetorial.\n\nOs arquivos de origem originais permanecerão intactos. Esta ação é irreversível.",
},
},
// Chat Settings
chat: {
llm: {
title: "Provedor de LLM do Workspace",
description:
"O provedor e modelo específico de LLM que será usado para este workspace. Por padrão, usa o provedor e as configurações do sistema LLM.",
search: "Pesquisar todos os provedores de LLM",
},
model: {
title: "Modelo de Chat do Workspace",
description:
"O modelo de chat específico que será usado para este workspace. Se vazio, usará a preferência do LLM do sistema.",
wait: "-- aguardando modelos --",
},
mode: {
title: "Modo de Chat",
chat: {
title: "Chat",
"desc-start": "fornecerá respostas com o conhecimento geral do LLM",
and: "e",
"desc-end": "contexto do documento encontrado.",
},
query: {
title: "Consulta",
"desc-start": "fornecerá respostas",
only: "somente",
"desc-end": "se o contexto do documento for encontrado.",
},
},
history: {
title: "Histórico de Chat",
"desc-start":
"O número de chats anteriores que serão incluídos na memória de curto prazo da resposta.",
recommend: "Recomendado: 20. ",
"desc-end":
"Qualquer coisa acima de 45 provavelmente levará a falhas contínuas de chat dependendo do tamanho da mensagem.",
},
prompt: {
title: "Prompt",
description:
"O prompt que será usado neste workspace. Defina o contexto e as instruções para que a IA gere uma resposta. Você deve fornecer um prompt cuidadosamente elaborado para que a IA possa gerar uma resposta relevante e precisa.",
},
refusal: {
title: "Resposta de Recusa no Modo de Consulta",
"desc-start": "Quando estiver no modo",
query: "consulta",
"desc-end":
", você pode querer retornar uma resposta de recusa personalizada quando nenhum contexto for encontrado.",
},
temperature: {
title: "Temperatura do LLM",
"desc-start":
'Esta configuração controla o quão "criativas" serão as respostas do seu LLM.',
"desc-end":
"Quanto maior o número, mais criativa será a resposta. Para alguns modelos, isso pode levar a respostas incoerentes quando configurado muito alto.",
hint: "A maioria dos LLMs tem vários intervalos aceitáveis de valores válidos. Consulte seu provedor de LLM para essa informação.",
},
},
// Vector Database
"vector-workspace": {
identifier: "Identificador do Banco de Dados Vetorial",
snippets: {
title: "Máximo de Trechos de Contexto",
description:
"Esta configuração controla a quantidade máxima de trechos de contexto que será enviada ao LLM por chat ou consulta.",
recommend: "Recomendado: 4",
},
doc: {
title: "Limite de Similaridade de Documentos",
description:
"A pontuação mínima de similaridade necessária para que uma fonte seja considerada relacionada ao chat. Quanto maior o número, mais semelhante a fonte deve ser ao chat.",
zero: "Sem restrição",
low: "Baixo (pontuação de similaridade ≥ 0,25)",
medium: "Médio (pontuação de similaridade ≥ 0,50)",
high: "Alto (pontuação de similaridade ≥ 0,75)",
},
reset: {
reset: "Redefinir Banco de Dados Vetorial",
resetting: "Limpando vetores...",
confirm:
"Você está prestes a redefinir o banco de dados vetorial deste workspace. Isso removerá todas as incorporações vetoriais atualmente embutidas.\n\nOs arquivos de origem originais permanecerão intactos. Esta ação é irreversível.",
error: "O banco de dados vetorial do workspace não pôde ser redefinido!",
success:
"O banco de dados vetorial do workspace foi redefinido com sucesso!",
},
},
// Agent Configuration
agent: {
"performance-warning":
"O desempenho dos LLMs que não suportam explicitamente a chamada de ferramentas depende muito das capacidades e da precisão do modelo. Algumas habilidades podem ser limitadas ou não funcionais.",
provider: {
title: "Provedor de LLM do Agente do Workspace",
description:
"O provedor e modelo específico de LLM que será usado para o agente @agent deste workspace.",
},
mode: {
chat: {
title: "Modelo de Chat do Agente do Workspace",
description:
"O modelo de chat específico que será usado para o agente @agent deste workspace.",
},
title: "Modelo do Agente do Workspace",
description:
"O modelo de LLM específico que será usado para o agente @agent deste workspace.",
wait: "-- aguardando modelos --",
},
skill: {
title: "Habilidades padrão do agente",
description:
"Melhore as habilidades naturais do agente padrão com essas habilidades pré-construídas. Esta configuração se aplica a todos os workspaces.",
rag: {
title: "RAG e memória de longo prazo",
description:
'Permitir que o agente utilize seus documentos locais para responder a uma consulta ou pedir ao agente para "lembrar" peças de conteúdo para recuperação de memória de longo prazo.',
},
view: {
title: "Visualizar e resumir documentos",
description:
"Permitir que o agente liste e resuma o conteúdo dos arquivos do workspace atualmente incorporados.",
},
scrape: {
title: "Raspagem de sites",
description:
"Permitir que o agente visite e raspe o conteúdo de sites.",
},
generate: {
title: "Gerar gráficos",
description:
"Habilitar o agente padrão para gerar vários tipos de gráficos a partir dos dados fornecidos ou dados no chat.",
},
save: {
title: "Gerar e salvar arquivos no navegador",
description:
"Habilitar o agente padrão para gerar e gravar arquivos que podem ser salvos e baixados no seu navegador.",
},
web: {
title: "Pesquisa e navegação na web ao vivo",
"desc-start":
"Permitir que seu agente pesquise na web para responder suas perguntas conectando-se a um provedor de pesquisa na web (SERP).",
"desc-end":
"A pesquisa na web durante as sessões do agente não funcionará até que isso seja configurado.",
},
},
},
// Workspace Chats
recorded: {
title: "Chats do Workspace",
description:
"Estes são todos os chats e mensagens gravados que foram enviados pelos usuários ordenados por data de criação.",
export: "Exportar",
table: {
id: "Id",
by: "Enviado Por",
workspace: "Workspace",
prompt: "Prompt",
response: "Resposta",
at: "Enviado Em",
},
},
// Appearance
appearance: {
title: "Aparência",
description: "Personalize as configurações de aparência da sua plataforma.",
logo: {
title: "Personalizar Logo",
description:
"Envie seu logotipo personalizado para tornar seu chatbot seu.",
add: "Adicionar um logotipo personalizado",
recommended: "Tamanho recomendado: 800 x 200",
remove: "Remover",
replace: "Substituir",
},
message: {
title: "Personalizar Mensagens",
description:
"Personalize as mensagens automáticas exibidas aos seus usuários.",
new: "Novo",
system: "sistema",
user: "usuário",
message: "mensagem",
assistant: "Assistente de Chat AnythingLLM",
"double-click": "Clique duas vezes para editar...",
save: "Salvar Mensagens",
},
icons: {
title: "Ícones de Rodapé Personalizados",
description:
"Personalize os ícones de rodapé exibidos na parte inferior da barra lateral.",
icon: "Ícone",
link: "Link",
},
},
// API Keys
api: {
title: "Chaves API",
description:
"As chaves API permitem que o titular acesse e gerencie programaticamente esta instância do AnythingLLM.",
link: "Leia a documentação da API",
generate: "Gerar Nova Chave API",
table: {
key: "Chave API",
by: "Criado Por",
created: "Criado",
},
},
llm: {
title: "Preferência de LLM",
description:
"Estas são as credenciais e configurações para seu provedor preferido de chat e incorporação de LLM. É importante que essas chaves estejam atualizadas e corretas, caso contrário, o AnythingLLM não funcionará corretamente.",
provider: "Provedor de LLM",
},
transcription: {
title: "Preferência de Modelo de Transcrição",
description:
"Estas são as credenciais e configurações para seu provedor preferido de modelo de transcrição. É importante que essas chaves estejam atualizadas e corretas, caso contrário, os arquivos de mídia e áudio não serão transcritos.",
provider: "Provedor de Transcrição",
"warn-start":
"Usar o modelo whisper local em máquinas com RAM ou CPU limitados pode travar o AnythingLLM ao processar arquivos de mídia.",
"warn-recommend":
"Recomendamos pelo menos 2GB de RAM e upload de arquivos <10Mb.",
"warn-end":
"O modelo embutido será baixado automaticamente no primeiro uso.",
},
embedding: {
title: "Preferência de Incorporação",
"desc-start":
"Ao usar um LLM que não suporta nativamente um mecanismo de incorporação - pode ser necessário especificar adicionalmente as credenciais para incorporação de texto.",
"desc-end":
"A incorporação é o processo de transformar texto em vetores. Essas credenciais são necessárias para transformar seus arquivos e prompts em um formato que o AnythingLLM possa usar para processar.",
provider: {
title: "Provedor de Incorporação",
description:
"Não é necessária configuração ao usar o mecanismo de incorporação nativo do AnythingLLM.",
},
},
text: {
title: "Preferências de Divisão e Fragmentação de Texto",
"desc-start":
"Às vezes, você pode querer alterar a maneira padrão como novos documentos são divididos e fragmentados antes de serem inseridos em seu banco de dados de vetores.",
"desc-end":
"Você só deve modificar esta configuração se entender como a divisão de texto funciona e seus efeitos colaterais.",
"warn-start": "As alterações aqui se aplicarão apenas a",
"warn-center": "documentos recém-incorporados",
"warn-end": ", não documentos existentes.",
size: {
title: "Tamanho do Fragmento de Texto",
description:
"Este é o comprimento máximo de caracteres que pode estar presente em um único vetor.",
recommend: "O comprimento máximo do modelo de incorporação é",
},
overlap: {
title: "Sobreposição de Fragmento de Texto",
description:
"Esta é a sobreposição máxima de caracteres que ocorre durante a fragmentação entre dois fragmentos de texto adjacentes.",
},
},
// Vector Database
vector: {
title: "Banco de Dados Vetorial",
description:
"Estas são as credenciais e configurações de como sua instância do AnythingLLM funcionará. É importante que essas chaves estejam atualizadas e corretas.",
provider: {
title: "Provedor de Banco de Dados Vetorial",
description: "Não há configuração necessária para o LanceDB.",
},
},
// Embeddable Chat Widgets
embeddable: {
title: "Widgets de Chat Incorporáveis",
description:
"Os widgets de chat incorporáveis são interfaces de chat públicas vinculadas a um único workspace. Eles permitem que você construa workspaces que você pode publicar para o mundo.",
create: "Criar incorporação",
table: {
workspace: "Workspace",
chats: "Chats Enviados",
Active: "Domínios Ativos",
},
},
"embed-chats": {
title: "Incorporar Chats",
description:
"Estes são todos os chats e mensagens registrados de qualquer incorporação que você publicou.",
table: {
embed: "Incorporação",
sender: "Remetente",
message: "Mensagem",
response: "Resposta",
at: "Enviado Em",
},
},
multi: {
title: "Modo Multiusuário",
description:
"Configure sua instância para suportar sua equipe ativando o Modo Multiusuário.",
enable: {
"is-enable": "Modo Multiusuário está Ativado",
enable: "Ativar Modo Multiusuário",
description:
"Por padrão, você será o único administrador. Como administrador, você precisará criar contas para todos os novos usuários ou administradores. Não perca sua senha, pois apenas um usuário Administrador pode redefinir senhas.",
username: "Nome de usuário da conta de Administrador",
password: "Senha da conta de Administrador",
},
password: {
title: "Proteção por Senha",
description:
"Proteja sua instância do AnythingLLM com uma senha. Se você esquecer esta senha, não há método de recuperação, então certifique-se de salvar esta senha.",
},
instance: {
title: "Proteger Instância com Senha",
description:
"Por padrão, você será o único administrador. Como administrador, você precisará criar contas para todos os novos usuários ou administradores. Não perca sua senha, pois apenas um usuário Administrador pode redefinir senhas.",
password: "Senha da instância",
},
},
// Event Logs
event: {
title: "Logs de Eventos",
description:
"Veja todas as ações e eventos acontecendo nesta instância para monitoramento.",
clear: "Limpar Logs de Eventos",
table: {
type: "Tipo de Evento",
user: "Usuário",
occurred: "Ocorreu Em",
},
},
// Privacy & Data-Handling
privacy: {
title: "Privacidade e Tratamento de Dados",
description:
"Esta é a sua configuração de como os provedores de terceiros conectados e o AnythingLLM tratam seus dados.",
llm: "Seleção de LLM",
embedding: "Preferência de Incorporação",
vector: "Banco de Dados Vetorial",
anonymous: "Telemetria Anônima Ativada",
},
};
export default TRANSLATIONS;

View File

@ -19,30 +19,38 @@ import Korean from "./ko/common.js";
import Spanish from "./es/common.js";
import French from "./fr/common.js";
import Mandarin from "./zh/common.js";
import German from "./de/common.js";
import Russian from "./ru/common.js";
import Italian from "./it/common.js";
import Portuguese from "./pt_BR/common.js";
export const defaultNS = "common";
export const resources = {
en: {
common: English,
},
ko: {
common: Korean,
},
zh: {
common: Mandarin,
},
es: {
common: Spanish,
},
de: {
common: German,
},
fr: {
common: French,
},
ko: {
common: Korean,
},
ru: {
common: Russian,
},
it: {
common: Italian,
},
pt: {
common: Portuguese,
},
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -7,6 +7,7 @@ import { RoleHintDisplay } from "..";
export default function NewUserModal({ closeModal }) {
const [error, setError] = useState(null);
const [role, setRole] = useState("default");
const handleCreate = async (e) => {
setError(null);
e.preventDefault();
@ -54,7 +55,18 @@ export default function NewUserModal({ closeModal }) {
minLength={2}
required={true}
autoComplete="off"
pattern="^[a-z0-9_-]+$"
onInvalid={(e) =>
e.target.setCustomValidity(
"Username must be only contain lowercase letters, numbers, underscores, and hyphens with no spaces"
)
}
onChange={(e) => e.target.setCustomValidity("")}
/>
<p className="mt-2 text-xs text-white/60">
Username must be only contain lowercase letters, numbers,
underscores, and hyphens with no spaces
</p>
</div>
<div>
<label
@ -70,7 +82,11 @@ export default function NewUserModal({ closeModal }) {
placeholder="User's initial password"
required={true}
autoComplete="off"
minLength={8}
/>
<p className="mt-2 text-xs text-white/60">
Password must be at least 8 characters long
</p>
</div>
<div>
<label
@ -84,7 +100,7 @@ export default function NewUserModal({ closeModal }) {
required={true}
defaultValue={"default"}
onChange={(e) => setRole(e.target.value)}
className="rounded-lg bg-zinc-900 px-4 py-2 text-sm text-white border-gray-500 focus:ring-blue-500 focus:border-blue-500"
className="rounded-lg bg-zinc-900 px-4 py-2 text-sm text-white border-gray-500 focus:ring-blue-500 focus:border-blue-500 w-full"
>
<option value="default">Default</option>
<option value="manager">Manager</option>

View File

@ -52,11 +52,15 @@ export default function EditUserModal({ currentUser, user, closeModal }) {
type="text"
className="bg-zinc-900 placeholder:text-white/20 border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
placeholder="User's username"
minLength={2}
defaultValue={user.username}
minLength={2}
required={true}
autoComplete="off"
/>
<p className="mt-2 text-xs text-white/60">
Username must be only contain lowercase letters, numbers,
underscores, and hyphens with no spaces
</p>
</div>
<div>
<label
@ -71,7 +75,11 @@ export default function EditUserModal({ currentUser, user, closeModal }) {
className="bg-zinc-900 placeholder:text-white/20 border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
placeholder={`${user.username}'s new password`}
autoComplete="off"
minLength={8}
/>
<p className="mt-2 text-xs text-white/60">
Password must be at least 8 characters long
</p>
</div>
<div>
<label
@ -85,7 +93,7 @@ export default function EditUserModal({ currentUser, user, closeModal }) {
required={true}
defaultValue={user.role}
onChange={(e) => setRole(e.target.value)}
className="rounded-lg bg-zinc-900 px-4 py-2 text-sm text-white border-gray-500 focus:ring-blue-500 focus:border-blue-500"
className="rounded-lg bg-zinc-900 px-4 py-2 text-sm text-white border-gray-500 focus:ring-blue-500 focus:border-blue-500 w-full"
>
<option value="default">Default</option>
<option value="manager">Manager</option>

View File

@ -1,9 +1,11 @@
import FineTuning from "@/models/experimental/fineTuning";
import { dollarFormat } from "@/utils/numbers";
import showToast from "@/utils/toast";
import { CheckCircle } from "@phosphor-icons/react";
import { useState } from "react";
import { Check } from "@phosphor-icons/react";
import { useState, useEffect } from "react";
import FineTuningSteps from "../index";
import CTAButton from "@/components/lib/CTAButton";
import Workspace from "@/models/workspace";
/**
* @param {{settings: import("../index").OrderSettings}} param0
@ -11,6 +13,18 @@ import FineTuningSteps from "../index";
*/
export default function Confirmation({ settings, setSettings, setStep }) {
const [loading, setLoading] = useState(false);
const [workspaces, setWorkspaces] = useState([]);
useEffect(() => {
Workspace.all()
.then((fetchedWorkspaces) => {
setWorkspaces(fetchedWorkspaces);
})
.catch(() => {
showToast("Failed to fetch workspaces", "error");
});
}, []);
async function handleCheckout() {
setLoading(true);
const data = await FineTuning.createOrder({
@ -40,107 +54,124 @@ export default function Confirmation({ settings, setSettings, setStep }) {
setStep(FineTuningSteps.confirmation.next());
}
const getWorkspaceName = (slug) => {
const workspace = workspaces.find((ws) => ws.slug === slug);
return workspace ? workspace.name : slug;
};
return (
<div className="flex-[2] flex flex-col gap-y-[18px] mt-10">
<div className="bg-[#303237] text-white rounded-xl flex-1 p-4 flex flex-col justify-between">
<div className="w-full flex flex-col gap-y-4">
<h2 className="text-xl text-white font-semibold">Confirm & Submit</h2>
<p>
<div className="bg-[#303237] text-white rounded-xl flex-1 p-6">
<div className="w-full flex flex-col gap-y-3 max-w-[700px]">
<h2 className="text-base text-white font-semibold">
Confirm & Submit
</h2>
<p className="text-white/80 text-sm">
Below are your fine-tuning order details. If you have any questions
before or after ordering your fine-tune you can{" "}
before or after ordering your fine-tune you can checkout the{" "}
<a
href="https://docs.useanything.com/fine-tuning/overview"
target="_blank"
className="underline"
rel="noreferrer"
className="underline text-sky-400"
>
checkout the fine-tuning FAQ
fine-tuning FAQ
</a>{" "}
or email{" "}
<a className="underline" href="mailto:team@mintplexlabs.com">
<a
className="underline text-sky-400"
href="mailto:team@mintplexlabs.com"
>
team@mintplexlabs.com
</a>
.
</p>
<div className="p-2 bg-zinc-800 text-white font-mono flex flex-col gap-y-2 h-full rounded-lg">
<div className="flex items-center gap-x-1 text-sm">
<p className="">Contact e-mail:</p>
<p className="font-thin">{settings.email}</p>
<div className="p-4 bg-zinc-900 text-white flex flex-col gap-y-2 rounded-lg mt-4">
<div className="flex flex-col gap-y-3 text-sm">
<div className="flex items-start gap-x-1">
<p className="w-1/3">Contact e-mail:</p>
<p className="text-white/80 w-2/3">{settings.email}</p>
</div>
<div className="flex items-center gap-x-1 text-sm">
<p className="">Base LLM:</p>
<p className="font-thin">{settings.baseModel}</p>
<div className="flex items-start gap-x-1">
<p className="w-1/3">Base LLM:</p>
<p className="text-white/80 w-2/3">{settings.baseModel}</p>
</div>
<div className="flex items-center gap-x-1 text-sm">
<p className="">Output model name:</p>
<p className="font-thin">"{settings.modelName}"</p>
<div className="flex items-start gap-x-1">
<p className="w-1/3">Output model name:</p>
<p className="text-white/80 w-2/3">"{settings.modelName}"</p>
</div>
<div className="flex flex-col gap-y-1 text-sm">
<div className="flex items-center gap-x-1">
<p className="">Training on workspaces:</p>
{settings.trainingData.slugs.map((slug, i) => {
return (
<p key={slug} className="font-thin">
"{slug}"
{i !== settings.trainingData.slugs.length - 1 ? "," : ""}
<div className="flex items-start gap-x-1">
<p className="w-1/3">Training on workspaces:</p>
<div className="text-white/80 w-2/3 flex flex-wrap gap-1">
{settings.trainingData.slugs.map((slug, i) => (
<span
key={slug}
className={`rounded-full bg-white/10 px-2 py-0.5 h-[20px] text-xs font-medium text-white shadow-sm`}
>
{getWorkspaceName(slug)}
</span>
))}
</div>
</div>
<div className="flex items-start gap-x-1">
<p className="w-1/3">Training data:</p>
<p className="text-white/80 w-2/3">
{settings.trainingData.feedback === true
? "Training on positive-feedback chats only"
: "Training on all chats"}
</p>
);
})}
</div>
{settings.trainingData.feedback === true ? (
<p className="underline">
training on <b>positive-feedback chats only</b>.
</p>
) : (
<p className="underline">
training on <b>all chats</b>.
</p>
)}
</div>
<br />
<div className="flex items-center gap-x-1 text-sm">
<CheckCircle className="text-green-300" />
<p className="font-thin">Agreed to Terms and Conditions</p>
</div>
<div className="flex items-center gap-x-1 text-sm">
<CheckCircle className="text-green-300" />
<p className="font-thin">Understand privacy & data handling</p>
</div>
<div className="flex items-center gap-x-1 text-sm">
<CheckCircle className="text-green-300" />
<p className="font-thin">Agreed to Fulfillment terms</p>
<div className="mt-4">
<ul className="flex flex-col gap-y-1 text-sm">
<li className="flex items-center gap-x-2">
<Check className="text-white" size={12} weight="bold" />
<p className="text-white/80">
Agreed to Terms and Conditions
</p>
</li>
<li className="flex items-center gap-x-2">
<Check className="text-white" size={12} weight="bold" />
<p className="text-white/80">
Understand privacy & data handling
</p>
</li>
<li className="flex items-center gap-x-2">
<Check className="text-white" size={12} weight="bold" />
<p className="text-white/80">Agreed to Fulfillment terms</p>
</li>
</ul>
</div>
<div>
<div className="flex items-center gap-x-1 text-lg border-t-[2px] border-white/40 pt-2 mb-0">
<div className="mt-4 border-white/40 pt-2">
<div className="flex items-center gap-x-1 text-lg mb-0">
<p className="">Total one-time cost:</p>
<p className="font-thin">
<p className="text-white/80">
{dollarFormat(settings.tuningInfo.pricing.usd)}
<sup>*</sup>
</p>
</div>
<p className="m-0 p-0 text-xs text-white/60 font-mono">
<p className="m-0 p-0 text-xs text-white/60">
<sup>*</sup> price does not include any coupons, incentives, or
discounts you can apply at checkout.
</p>
</div>
</div>
<p>
<p className="text-xs text-white/80 mt-4">
Once you proceed to checkout, if you do not complete this purchase
your data will be deleted from our servers within 1 hour of
abandonment of the creation of the checkout in accordance to our
privacy and data handling policy.
</p>
</div>
<button
<CTAButton
disabled={loading}
onClick={handleCheckout}
type="button"
className="mt-8 w-full py-2 text-center text-black bg-white hover:bg-white/80 border-none rounded-lg"
className="text-dark-text w-full mt-[18px] h-[34px] hover:bg-accent"
>
{loading ? <>Generating order...</> : <>Start Training &rarr;</>}
</button>
{loading ? "Generating order..." : "Start Training →"}
</CTAButton>
</div>
</div>
</div>
);

View File

@ -1,8 +1,14 @@
import { useEffect, useState } from "react";
import FineTuning from "@/models/experimental/fineTuning";
import Workspace from "@/models/workspace";
import { CheckCircle, Warning, X } from "@phosphor-icons/react";
import {
CheckCircle,
Warning,
X,
MagnifyingGlass,
} from "@phosphor-icons/react";
import FineTuningSteps from "..";
import CTAButton from "@/components/lib/CTAButton";
export default function DataUpload({ setSettings, setStep }) {
const [workspaces, setWorkspaces] = useState([]);
@ -41,34 +47,29 @@ export default function DataUpload({ setSettings, setStep }) {
return (
<div className="flex-[2] flex flex-col gap-y-[18px] mt-10">
<div className="bg-[#303237] text-white rounded-xl flex-1 p-4">
<form
onSubmit={handleSubmit}
className="flex flex-col justify-between h-full"
>
<div className="w-full flex flex-col gap-y-4">
<h2 className="text-xl text-white font-semibold">
Select your training dataset.
<div className="bg-[#303237] text-white rounded-xl flex-1 p-6">
<div className="w-full flex flex-col gap-y-3 max-w-[700px]">
<h2 className="text-base text-white font-semibold">
Select your training dataset
</h2>
<p>
This is the data your model will be trained and tuned on. This is
a critical step and you should always train on the exact
information you want the model to inherit. By default, AnythingLLM
will use all chats, but you can filter chats by workspace and even
limit training to chats which users have left a positive feedback
indication on (thumbs up).
<p className="text-white/80 text-sm">
This is the data your model will be trained and tuned on. This is a
critical step and you should always train on the exact information
you want the model to inherit. By default, AnythingLLM will use all
chats, but you can filter chats by workspace and even limit training
to chats which users have left a positive feedback indication on
(thumbs up).
</p>
<div className="flex flex-col pr-10">
<div className="flex flex-col gap-y-1 mb-4">
<label className="text-white text-sm font-bold">
<form onSubmit={handleSubmit} className="flex flex-col gap-y-6 mt-4">
<div className="flex flex-col">
<label className="text-white text-sm font-semibold block mb-3">
Only use positive responses
</label>
<p className="text-xs font-normal text-white/80">
<p className="text-xs font-normal text-white/80 mb-2">
Enabling this toggle will filter your dataset to only use
"positive" responses that were marked during chatting.
</p>
</div>
<label className="relative inline-flex cursor-pointer items-center w-fit">
<input
type="checkbox"
@ -84,36 +85,35 @@ export default function DataUpload({ setSettings, setStep }) {
</label>
</div>
<div className="flex flex-col pr-10">
<div className="flex flex-col gap-y-1 mb-4">
<label className="text-white text-sm font-bold">
<div className="flex flex-col">
<label className="text-white text-sm font-semibold block mb-3">
Selected Workspaces
</label>
<p className="text-xs font-normal text-white/80">
You training data will be limited to these workspaces.
<p className="text-xs font-normal text-white/80 mb-2">
Your training data will be limited to these workspaces.
</p>
</div>
<WorkspaceSelector
workspaces={workspaces}
selectedWorkspaces={dataFilters.workspaces}
setDataFilters={setDataFilters}
/>
</div>
<DatasetSummary
workspaces={dataFilters.workspaces}
feedback={dataFilters.feedback}
/>
</div>
<button
<CTAButton
type="submit"
className="mt-20 w-full py-2 text-center text-white hover:bg-primary-button border-none rounded-lg"
className="text-dark-text w-full mt-[18px] h-[34px] hover:bg-accent"
>
Proceed to Confirmation &rarr;
</button>
</CTAButton>
</form>
</div>
</div>
</div>
);
}
@ -155,33 +155,9 @@ function WorkspaceSelector({
}
return (
<div className="flex flex-col items-center">
<div className="w-full h-fit">
<div className="w-full relative z-1">
<div className="p-1 flex border border-white/40 bg-zinc-800 rounded">
<div className="flex flex-auto flex-wrap">
{selectedWorkspaces.map((workspace) => {
return (
<div
key={workspace.slug}
className="flex gap-x-1 justify-center items-center m-1 font-medium py-1 px-2 bg-zinc-800 rounded-full text-white/40 bg-white/10 border border-white/40 "
>
<div className="text-xs font-normal text-white leading-none max-w-full flex-initial">
{workspace.name}
</div>
<div className="flex flex-auto flex-row-reverse">
<button
onClick={() => handleRemoveWorkspace(workspace)}
type="button"
className="hover:text-red-500"
>
<X size={14} weight="bold" />
</button>
</div>
</div>
);
})}
<div className="flex-1">
<div className="flex flex-col gap-y-2">
<div className="min-w-[150px] max-w-[300px] h-[32px] p-[10px] rounded-lg flex items-center bg-dark-highlight mt-1">
<MagnifyingGlass size={16} className="text-white" />
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
@ -192,9 +168,31 @@ function WorkspaceSelector({
}, 500)
}
placeholder="Enter a workspace name"
className="w-[200px] bg-transparent p-1 px-2 appearance-none outline-none h-full w-full text-white"
className="bg-transparent p-1 px-2 appearance-none outline-none h-full w-full text-white text-xs placeholder:`text-white/50`"
/>
</div>
<div className="flex flex-col items-center -ml-2">
<div className="w-full h-fit">
<div className="w-full relative z-1">
<div className="p-2 flex rounded-lg">
<div className="flex flex-wrap gap-2 w-full">
{selectedWorkspaces.map((workspace) => {
return (
<div
key={workspace.slug}
className="flex items-center justify-between rounded-full h-[20px] bg-white/10 px-2 py-1 text-xs font-medium text-white shadow-sm"
>
<span className="truncate mr-1">{workspace.name}</span>
<button
onClick={() => handleRemoveWorkspace(workspace)}
type="button"
className="hover:text-red-500 flex-shrink-0"
>
<X size={10} weight="bold" />
</button>
</div>
);
})}
</div>
</div>
</div>
@ -211,6 +209,7 @@ function WorkspaceSelector({
)}
</div>
</div>
</div>
);
}
@ -221,7 +220,7 @@ function WorkspaceSuggestions({
}) {
if (availableWorkspaces.length === 0) {
return (
<div className="w-full mt-[2px] bg-zinc-800 border border-white/40 top-[45px] h-40 rounded-lg p-2 text-sm">
<div className="w-full mt-[2px] bg-zinc-900 top-[45px] h-40 rounded-lg p-2 text-sm">
<p className="text-center text-white/40">
no workspaces available to select.
</p>
@ -239,7 +238,7 @@ function WorkspaceSuggestions({
: availableWorkspaces;
return (
<div className="w-full mt-[2px] bg-zinc-800 border border-white/40 top-[45px] h-40 rounded-lg p-2 text-sm flex flex-col gap-y-1 justify-start overflow-y-scroll">
<div className="w-full mt-[2px] bg-zinc-900 top-[45px] h-40 rounded-lg p-2 text-sm flex flex-col gap-y-1 justify-start overflow-y-scroll">
{filteredWorkspace.map((workspace) => {
return (
<button
@ -271,19 +270,19 @@ function DatasetSummary({ workspaces = [], feedback = null }) {
}, [workspaces, feedback]);
return (
<div className="bg-zinc-800 text-white/80 p-2 rounded-lg font-mono">
<div className="bg-zinc-900 text-white/80 p-4 rounded-lg text-sm">
<p>Training dataset size: {stats.count ?? "Unknown"}</p>
{stats.count < stats.recommendedMin ? (
<div className="flex items-center gap-x-1 text-red-500 text-sm p-1 rounded-lg bg-red-500/20 w-fit my-2">
<Warning />
<div className="flex items-center gap-x-1 text-red-500 text-sm p-2 rounded-lg bg-red-500/20 w-fit my-2">
<Warning size={14} />
<p>
Your dataset is below the recommended minimum of{" "}
{stats.recommendedMin}! You may see no impact from a fine-tune.
</p>
</div>
) : (
<div className="flex items-center gap-x-1 text-green-500 text-sm p-1 rounded-lg bg-green-500/20 w-fit my-2">
<CheckCircle />
<div className="flex items-center gap-x-1 text-green-500 text-sm p-2 rounded-lg bg-green-500/20 w-fit my-2">
<CheckCircle size={14} />
<p>
Your dataset is large enough that you should see good results from a
fine-tune.

View File

@ -1,3 +1,4 @@
import CTAButton from "@/components/lib/CTAButton";
import FineTuningSteps from "..";
export default function Fulfillment({ setSettings, setStep }) {
@ -10,34 +11,29 @@ export default function Fulfillment({ setSettings, setStep }) {
return (
<div className="flex-[2] flex flex-col gap-y-[18px] mt-10">
<div className="bg-[#303237] text-white rounded-xl flex-1 p-4">
<div className="w-full flex flex-col gap-y-4">
<h2 className="text-xl text-white font-semibold">
<div className="bg-[#303237] text-white rounded-xl flex-1 p-6">
<div className="w-full flex flex-col gap-y-3 max-w-[700px]">
<h2 className="text-base text-white font-semibold">
Fulfillment Policy
</h2>
<p>
<p className="text-white/80 text-sm">
Fulfillment of a fine-tune model is straight-forward. We do not host
your model. We provide you a download link to run the model in a
standard format where ever you run local LLMs
</p>
<div className="flex flex-col gap-y-2 text-white/75 text-sm border p-2 border-white rounded-lg font-mono h-[60vh] overflow-y-scroll">
<h1 class="text-white/80 text-lg font-semibold">
Fulfillment Terms
</h1>
<p>
<strong>Last updated: July 15, 2024</strong>
</p>
<p>
<div className="flex flex-col gap-y-2 text-white/80 text-xs font-semibold rounded-lg p-4 h-[60vh] overflow-y-auto bg-dark-text mt-2">
<div className="text-xs text-white">
<h1>Fulfillment Terms</h1>
<p>Last updated: July 15, 2024</p>
</div>
<p className="text-white/80">
These fulfillment terms outline the agreement between Mintplex
Labs Inc. (Company, we, us, or our) and the customer
Labs Inc. ("Company," "we," "us," or "our") and the customer
regarding the creation and delivery of fine-tuned models.
</p>
<h2 class="text-white/80 text-base font-semibold">
Delivery of Model
</h2>
<p>
<h2 className="text-white mt-4">Delivery of Model</h2>
<p className="text-white/80">
Upon completion of a fine-tuning job, we will deliver a download
link to a .gguf model file suitable for LLM text inferencing. The
customer acknowledges that this exchange is strictly transactional
@ -45,17 +41,15 @@ export default function Fulfillment({ setSettings, setStep }) {
is considered concluded and will be ineligible for a refund.
</p>
<h2 class="text-white/80 text-base font-semibold">Support</h2>
<p>
<h2 className="text-white mt-4">Support</h2>
<p className="text-white/80">
Please note that the delivery of the model does not include any
dedicated support. Customers are encouraged to refer to available
documentation and resources for guidance on using the model.
</p>
<h2 class="text-white/80 text-base font-semibold">
Requesting Download Links
</h2>
<p>
<h2 className="text-white mt-4">Requesting Download Links</h2>
<p className="text-white/80">
Customers may request refreshed download links from
my.mintplexlabs.com as long as the model is retained in our cloud
storage. We will retain a model in our storage for a maximum of 3
@ -63,10 +57,8 @@ export default function Fulfillment({ setSettings, setStep }) {
links are valid for 24 hours.
</p>
<h2 class="text-white/80 text-base font-semibold">
Cancellation and Refunds
</h2>
<p>
<h2 className="text-white mt-4">Cancellation and Refunds</h2>
<p className="text-white/80">
Mintplex Labs Inc. reserves the right to cancel any fine-tuning
job at our discretion. In the event of a cancellation, a refund
may be issued. Additionally, we reserve the right to deny a
@ -74,22 +66,22 @@ export default function Fulfillment({ setSettings, setStep }) {
cause or notice to the Customer.
</p>
<h2 class="text-white/80 text-base font-semibold">No Guarantees</h2>
<p>
<h2 className="text-white mt-4">No Guarantees</h2>
<p className="text-white/80">
Mintplex Labs Inc. makes <strong>NO GUARANTEES</strong> regarding
the resulting model's output, functionality, speed, or
compatibility with your tools, infrastructure and devices. Refund
requests of this nature are not eligible for refunds.
</p>
<p>
<p className="text-white/80">
Models are delivered and accepted in "As-Is" condition. All
delivered model and output files are deemed final and
non-refundable for any reason after training is complete and a
model has been generated.
</p>
<h2 class="text-white/80 text-base font-semibold">Payment Terms</h2>
<p>
<h2 className="text-white mt-4">Payment Terms</h2>
<p className="text-white/80">
All payments are required prior to the commencement of the
fine-tuning process. Customers are responsible for ensuring that
valid payment information is provided. Checkout sessions not
@ -97,32 +89,36 @@ export default function Fulfillment({ setSettings, setStep }) {
abandoned and will be deleted from our system.
</p>
<h2 class="text-white/80 text-base font-semibold">
<h2 className="text-white mt-4">
Denial of Service for Payment Reasons
</h2>
<p>
<p className="text-white/80">
Mintplex Labs Inc. reserves the right to deny service to any
customer with an outstanding balance or invalid payment
information. If any discrepancies arise regarding payment or
usage, we may suspend services until the matter is resolved.
</p>
<h2 class="text-white/80 text-base font-semibold">Contact</h2>
<p>
<h2 className="text-white mt-4">Contact</h2>
<p className="text-white/80">
For any questions related to payment or fulfillment of services,
please contact us at{" "}
<a href="mailto:team@mintplexlabs.com">team@mintplexlabs.com</a>.
<a
href="mailto:team@mintplexlabs.com"
className="text-blue-400 hover:underline"
>
team@mintplexlabs.com
</a>
.
</p>
</div>
</div>
<button
<CTAButton
className="text-dark-text w-full mt-[18px] h-[34px] hover:bg-accent"
onClick={handleAccept}
type="button"
className="mt-8 w-full py-2 text-center text-white hover:bg-primary-button border-none rounded-lg"
>
Agree and continue &rarr;
</button>
</CTAButton>
</div>
</div>
</div>
);

View File

@ -1,5 +1,6 @@
import { CheckCircle, XCircle } from "@phosphor-icons/react";
import { Check, X } from "@phosphor-icons/react";
import FineTuningSteps from "..";
import CTAButton from "@/components/lib/CTAButton";
export default function Introduction({ setSettings, setStep }) {
const handleAccept = () => {
@ -11,12 +12,12 @@ export default function Introduction({ setSettings, setStep }) {
return (
<div className="flex-[2] flex flex-col gap-y-[18px] mt-10">
<div className="bg-[#303237] text-white rounded-xl flex-1 p-4">
<div className="w-full flex flex-col gap-y-4">
<h2 className="text-xl text-white font-semibold">
<div className="bg-[#303237] text-white rounded-xl flex-1 p-6">
<div className="w-full flex flex-col gap-y-2 max-w-[700px]">
<h2 className="text-base text-white font-semibold">
What is a "Fine-Tuned" model?
</h2>
<div className="flex flex-col gap-y-2 text-white/80">
<div className="flex flex-col gap-y-[25px] text-white/80 text-sm">
<p>
Fine-tuned models are basically "customized"
Language-Learning-Models (LLMs). These can be based on popular
@ -36,8 +37,8 @@ export default function Introduction({ setSettings, setStep }) {
</p>
</div>
<div className="flex flex-col gap-y-2 text-white/80">
<h3 className="text-lg text-white font-semibold">
<div className="flex flex-col gap-y-2 text-white/80 text-sm mt-4">
<h3 className="text-base text-white font-semibold">
When should I get a fine-tuned model?
</h3>
<p>
@ -45,65 +46,65 @@ export default function Introduction({ setSettings, setStep }) {
following
</p>
<ul className="flex flex-col gap-y-1">
<li className="flex items-center gap-x-1">
<CheckCircle className="text-green-300" /> Setting the style,
tone, format, or other qualitative aspects without prompting
<li className="flex items-center gap-x-2">
<Check className="text-white" size={12} weight="bold" /> Setting
the style, tone, format, or other qualitative aspects without
prompting
</li>
<li className="flex items-center gap-x-1">
<CheckCircle className="text-green-300" /> Improving reliability
at producing a desired output
<li className="flex items-center gap-x-2">
<Check className="text-white" size={12} weight="bold" />{" "}
Improving reliability at producing a desired output
</li>
<li className="flex items-center gap-x-1">
<CheckCircle className="text-green-300" /> Correcting failures
to follow complex prompts, citations, or lack of background
knowledge
<li className="flex items-center gap-x-2">
<Check className="text-white" size={12} weight="bold" />{" "}
Correcting failures to follow complex prompts, citations, or
lack of background knowledge
</li>
<li className="flex items-center gap-x-1">
<CheckCircle className="text-green-300" /> You want to run this
model privately or offline
<li className="flex items-center gap-x-2">
<Check className="text-white" size={12} weight="bold" /> You
want to run this model privately or offline
</li>
</ul>
</div>
<div className="flex flex-col gap-y-2 text-white/80">
<h3 className="text-lg text-white font-semibold">
<div className="flex flex-col gap-y-2 text-white/80 text-sm mt-4">
<h3 className="text-base text-white font-semibold">
What are fine-tunes bad for?
</h3>
<p>
Fine-tuned models powerful, but they are not the "silver bullet"
to any issues you have with RAG currently. Some notable
Fine-tuned models are powerful, but they are not the "silver
bullet" to any issues you have with RAG currently. Some notable
limitations are
</p>
<ul>
<li className="flex items-center gap-x-1">
<XCircle className="text-red-300" /> You need perfect recall of
some piece of literature or reference document
<X className="text-white" size={12} weight="bold" /> You need
perfect recall of some piece of literature or reference document
</li>
<li className="flex items-center gap-x-1">
<XCircle className="text-red-300" /> You want your model to have
perfect memory or recollection
<X className="text-white" size={12} weight="bold" /> You want
your model to have perfect memory or recollection
</li>
</ul>
</div>
<div className="flex flex-col gap-y-2 text-white/80">
<div className="flex flex-col gap-y-2 text-white/80 text-sm">
<p>
In summary, if you are getting good results with RAG currently,
creating a fine-tune can squeeze <b>even more performance</b> out
of a model. Fine-Tunes are are for improving response quality and
of a model. Fine-Tunes are for improving response quality and
general responses, but they are <b>not for knowledge recall</b> -
that is what RAG is for! Together, it is a powerful combination.
</p>
</div>
</div>
<button
<CTAButton
className="text-dark-text w-full mt-[18px] h-[34px] hover:bg-accent"
onClick={handleAccept}
type="button"
className="mt-8 w-full py-2 text-center text-white hover:bg-primary-button border-none rounded-lg"
text="Create fine-tune model &rarr;"
>
Start a fine-tune &rarr;
</button>
Create a fine-tune model &rarr;
</CTAButton>
</div>
</div>
</div>
);

View File

@ -2,9 +2,11 @@ import FineTuning from "@/models/experimental/fineTuning";
import { useEffect, useState } from "react";
import FineTuningSteps from "..";
import { CircleNotch } from "@phosphor-icons/react";
import CTAButton from "@/components/lib/CTAButton";
export default function OrderDetails({ setSettings, setStep }) {
const [info, setInfo] = useState({});
useEffect(() => {
FineTuning.info()
.then((res) => {
@ -32,33 +34,30 @@ export default function OrderDetails({ setSettings, setStep }) {
return (
<div className="flex-[2] flex flex-col gap-y-[18px] mt-10">
<div className="bg-[#303237] text-white rounded-xl flex-1 p-4">
<form onSubmit={handleSubmit}>
<div className="w-full flex flex-col gap-y-4">
<h2 className="text-xl text-white font-semibold">
<div className="bg-[#303237] text-white rounded-xl flex-1 p-6">
<div className="w-full flex flex-col gap-y-3 max-w-[700px]">
<h2 className="text-base text-white font-semibold">
Time to create your fine tune!
</h2>
<p>
<p className="text-white/80 text-sm">
Creating a model is quite simple. Currently we have a limited base
model selection, however in the future we plan to expand support
to many more foundational models.
model selection, however in the future we plan to expand support to
many more foundational models.
</p>
<div className="flex flex-col pr-10">
<div className="flex flex-col gap-y-1 mb-4">
<label className="text-white text-sm font-bold">
<form onSubmit={handleSubmit} className="flex flex-col gap-y-6 mt-4">
<div className="flex flex-col">
<label className="text-white text-sm font-semibold block mb-3">
Account e-mail
</label>
<p className="text-xs font-normal text-white/80">
This e-mail is where you will receive all order information
and updates. This e-mail <b>must be accurate</b> or else we
won't be able to contact you with your fine-tuned model!
<p className="text-xs font-normal text-white/80 mb-2">
This e-mail is where you will receive all order information and
updates. This e-mail <b>must be accurate</b> or else we won't be
able to contact you with your fine-tuned model!
</p>
</div>
<input
type="email"
name="email"
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
className="bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full max-w-[200px] p-2.5"
placeholder="jdoe@example.com"
required={true}
autoComplete="off"
@ -66,33 +65,29 @@ export default function OrderDetails({ setSettings, setStep }) {
/>
</div>
<div className="flex flex-col pr-10">
<div className="flex flex-col gap-y-1 mb-4">
<label className="text-white text-sm font-bold">
<div className="flex flex-col">
<label className="text-white text-sm font-semibold block mb-3">
Preferred Base Model
</label>
<p className="text-xs font-normal text-white/80">
This is the foundational model your fine-tune will be based
on. We recommend Llama 3 8B.
<p className="text-xs font-normal text-white/80 mb-2">
This is the foundational model your fine-tune will be based on.
We recommend Llama 3 8B.
</p>
</div>
{info.hasOwnProperty("availableBaseModels") ? (
<select
name="baseModel"
required={true}
className="border-none bg-zinc-900 border-gray-500 text-white text-sm rounded-lg block w-fit p-2.5"
className="bg-zinc-900 border-gray-500 text-white text-sm rounded-lg block w-full max-w-[200px] p-2.5"
>
<option disabled="true" selected="true" value="">
<option disabled value="">
-- select a base model --
</option>
<optgroup label="Available base models">
{(info?.availableBaseModels || []).map((model) => {
return (
{(info?.availableBaseModels || []).map((model) => (
<option key={model} value={model}>
{model}
</option>
);
})}
))}
</optgroup>
</select>
) : (
@ -103,36 +98,35 @@ export default function OrderDetails({ setSettings, setStep }) {
)}
</div>
<div className="flex flex-col pr-10">
<div className="flex flex-col gap-y-1 mb-4">
<label className="text-white text-sm font-bold">
<div className="flex flex-col">
<label className="text-white text-sm font-semibold block mb-3">
Model name
</label>
<p className="text-xs font-normal text-white/80">
<p className="text-xs font-normal text-white/80 mb-2">
What would you like to call your model? This has no impact on
its output or training and is only used for how we communicate
with you about the model.
</p>
</div>
<input
type="text"
name="modelName"
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
className="bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full max-w-[200px] p-2.5"
placeholder="My really cool model!"
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
</div>
<button
<CTAButton
type="submit"
className="mt-8 w-full py-2 text-center text-white hover:bg-primary-button border-none rounded-lg"
className="text-dark-text w-full mt-[18px] h-[34px] hover:bg-accent"
>
Proceed to data selection &rarr;
</button>
</CTAButton>
</form>
</div>
</div>
</div>
);
}

View File

@ -1,13 +1,15 @@
import CTAButton from "@/components/lib/CTAButton";
import paths from "@/utils/paths";
export default function OrderPlaced({ settings }) {
return (
<div className="flex-[2] flex flex-col gap-y-[18px] mt-10">
<div className="bg-[#303237] text-white rounded-xl flex-1 p-4">
<div className="w-full flex flex-col gap-y-4">
<h2 className="text-xl text-white font-semibold">
<div className="bg-[#303237] text-white rounded-xl flex-1 p-6">
<div className="w-full flex flex-col gap-y-2 max-w-[700px]">
<h2 className="text-base text-white font-semibold">
Your order is placed!
</h2>
<div className="">
<div className="flex flex-col gap-y-[25px] text-white/80 text-xs">
<p>
Your fine-tune will begin once payment is complete. If the payment
window did not automatically open - your checkout link is below.
@ -15,54 +17,66 @@ export default function OrderPlaced({ settings }) {
<a
href={settings.checkoutUrl}
target="_blank"
className="text-xs font-mono text-white/60 underline"
rel="noreferrer"
className="text-sky-400 hover:underline hover:cursor-pointer"
>
{new URL(settings.checkoutUrl).origin}
</a>
<p className="text-xs font-mono text-white/80">
<p className="text-xs text-white/80">
Your fine-tune does not begin until this payment is completed.
</p>
</div>
<div className="">
<p className="font-mono text-white/80 text-sm">
Reference: {settings.jobId}
<div className="flex flex-col gap-y-2">
<p className="text-white/80 font-medium">
Reference: <span className="font-normal">{settings.jobId}</span>
</p>
<p className="text-xs font-mono text-white/80">
<p className="text-xs text-white/80">
This reference id is how we will communicate with you about your
fine-tune training status. <b>Save this reference id.</b>
</p>
</div>
<div className="">
<p className="font-mono text-white/80 text-sm">
Contact: {settings.email}
<div className="flex flex-col gap-y-2">
<p className="text-white/80 font-medium">
Contact: <span className="font-normal">{settings.email}</span>
</p>
<p className="text-xs font-mono text-white/80">
Check the email above for order confirmation, status updates, and
more. Mintplex Labs will only contact you about your order via
email.
<p className="text-xs text-white/80">
Check the email above for order confirmation, status updates,
and more. Mintplex Labs will only contact you about your order
via email.
</p>
</div>
<div className="font-mono text-white/80 text-sm flex items-center gap-x-2">
<div className="flex flex-col items-left gap-x-4 text-xs">
<a
href="https://docs.useanything.com/fine-tuning/overview"
target="_blank"
className="underline"
rel="noreferrer"
className="text-sky-400 hover:underline hover:cursor-pointer"
>
Documentation
</a>
<a href="mailto:team@mintplexlabs.com" className="underline">
<a
href="mailto:team@mintplexlabs.com"
className="text-sky-400 hover:underline hover:cursor-pointer"
>
Contact support
</a>
</div>
<p className="text-xs font-mono text-white/80">
<p className="text-xs text-white/80">
You can close this window or navigate away once you see the
confirmation email in your inbox.
</p>
</div>
<CTAButton
className="text-dark-text w-full mt-[18px] h-[34px] hover:bg-accent"
onClick={() => (window.location.href = paths.home())}
>
Finish
</CTAButton>
</div>
</div>
</div>
);

View File

@ -1,3 +1,4 @@
import CTAButton from "@/components/lib/CTAButton";
import FineTuningSteps from "..";
export default function PrivacyHandling({ setSettings, setStep }) {
@ -10,12 +11,12 @@ export default function PrivacyHandling({ setSettings, setStep }) {
return (
<div className="flex-[2] flex flex-col gap-y-[18px] mt-10">
<div className="bg-[#303237] text-white rounded-xl flex-1 p-4">
<div className="w-full flex flex-col gap-y-4">
<h2 className="text-xl text-white font-semibold">
<div className="bg-[#303237] text-white rounded-xl flex-1 p-6">
<div className="w-full flex flex-col gap-y-3 max-w-[700px]">
<h2 className="text-base text-white font-semibold">
Data Handling Policy & Privacy
</h2>
<p>
<p className="text-white/80 text-sm">
Please accept the terms and conditions to continue with creation and
ordering of a fine-tune model. We take the handling of your data
very seriously and will only use your uploaded data for training the
@ -23,18 +24,14 @@ export default function PrivacyHandling({ setSettings, setStep }) {
completed, or canceled your information is automatically and
permanently deleted.
</p>
<div className="flex flex-col gap-y-2 text-white/75 text-sm border p-2 border-white rounded-lg font-mono h-[60vh] overflow-y-scroll">
<h1 class="text-white/80 text-lg font-semibold">Privacy Policy</h1>
<p>
<strong>Mintplex Labs Inc.</strong>
</p>
<div className="flex flex-col gap-y-2 text-white/75 text-xs font-semibold rounded-lg p-4 h-[60vh] overflow-y-auto bg-dark-text mt-2">
<div className="text-xs">
<h1 className="text-white/80">Privacy Policy</h1>
<p>Mintplex Labs Inc.</p>
<p>Effective Date: July 15, 2024</p>
<h2 class="text-white/80 text-base font-semibold">
1. Introduction
</h2>
<p>
</div>
<h2 className="text-white mt-4">1. Introduction</h2>
<p className="text-white/80">
Welcome to Mintplex Labs Inc. ("we", "our", "us"). We are
committed to protecting your privacy and ensuring the security of
your personal information. This Privacy Policy describes how we
@ -42,45 +39,41 @@ export default function PrivacyHandling({ setSettings, setStep }) {
services.
</p>
<h2 class="text-white/80 text-base font-semibold">
2. Information We Collect
</h2>
<p>
<h2 className="text-white mt-4">2. Information We Collect</h2>
<p className="text-white/80">
When you place an order with us for tuning and large language
model (LLM) fulfillment, we collect certain personal information
from you, including but not limited to:
</p>
<ul>
<ul className="list-disc pl-5 text-white/80">
<li>Email address</li>
<li>Payment information</li>
<li>Uploaded training data</li>
</ul>
<h2 class="text-white/80 text-base font-semibold">
3. Use of Information
</h2>
<p>We use the information we collect for the following purposes:</p>
<ul>
<h2 className="text-white mt-4">3. Use of Information</h2>
<p className="text-white/80">
We use the information we collect for the following purposes:
</p>
<ul className="list-disc pl-5 text-white/80">
<li>To process and fulfill your order</li>
<li>To communicate with you regarding your order</li>
<li>To improve our services</li>
</ul>
<h2 class="text-white/80 text-base font-semibold">
4. Data Retention and Deletion
</h2>
<p>
<h2 className="text-white mt-4">4. Data Retention and Deletion</h2>
<p className="text-white/80">
Uploaded training data is only retained for the duration of the
model training. Upon training completion, failure, or order
cancellation, the user data is permanently deleted from our
storage.
</p>
<p>
<p className="text-white/80">
If you partially complete the order flow and do not finalize your
order, any details and information associated with your order will
be deleted 1 hour from abandonment.
</p>
<p>
<p className="text-white/80">
After you confirm receipt of your resulting model files, you can
request us to delete your model from our storage at any time.
Additionally, we may proactively reach out to you to confirm that
@ -90,10 +83,8 @@ export default function PrivacyHandling({ setSettings, setStep }) {
storage.
</p>
<h2 class="text-white/80 text-base font-semibold">
5. Data Storage and Security
</h2>
<p>
<h2 className="text-white mt-4">5. Data Storage and Security</h2>
<p className="text-white/80">
Our cloud storage provider is AWS. We have implement standard
encryption and protection policies to ensure the security of your
data. The storage solution has no public access, and all requests
@ -104,43 +95,41 @@ export default function PrivacyHandling({ setSettings, setStep }) {
e-mail you used during checkout.
</p>
<h2 class="text-white/80 text-base font-semibold">
6. Payment Processing
</h2>
<p>
<h2 className="text-white mt-4">6. Payment Processing</h2>
<p className="text-white/80">
We use Stripe as our payment processor. Your email may be shared
with Stripe for customer service and payment management purposes.
</p>
<h2 class="text-white/80 text-base font-semibold">
7. Data Sharing
</h2>
<p>
<h2 className="text-white mt-4">7. Data Sharing</h2>
<p className="text-white/80">
We do not sell or share your personal information with third
parties except as necessary to provide our services, comply with
legal obligations, or protect our rights.
</p>
<h2 class="text-white/80 text-base font-semibold">
8. Your Rights
</h2>
<p>
<h2 className="text-white mt-4">8. Your Rights</h2>
<p className="text-white/80">
You have the right to access, correct, or delete your personal
information. If you wish to exercise these rights, please contact
us at{" "}
<a href="mailto:team@mintplexlabs.com">team@mintplexlabs.com</a>.
<a
href="mailto:team@mintplexlabs.com"
className="text-blue-400 hover:underline"
>
team@mintplexlabs.com
</a>
.
</p>
<h2 class="text-white/80 text-base font-semibold">
9. California Privacy Rights
</h2>
<p>
<h2 className="text-white mt-4">9. California Privacy Rights</h2>
<p className="text-white/80">
Under the California Consumer Privacy Act as amended by the
California Privacy Rights Act (the CCPA), California residents
California Privacy Rights Act (the "CCPA"), California residents
have additional rights beyond what is set out in this privacy
notice:
</p>
<ul>
<ul className="list-disc pl-5 text-white/80">
<li>
<strong>Right to Know:</strong> You have the right to request
information about the categories and specific pieces of personal
@ -170,63 +159,70 @@ export default function PrivacyHandling({ setSettings, setStep }) {
your CCPA rights.
</li>
</ul>
<p>
<p className="text-white/80">
<strong>Submitting a Request:</strong>
<br />
You may submit a request to know, delete, or correct your personal
information by contacting us at{" "}
<a href="mailto:team@mintplexlabs.com">team@mintplexlabs.com</a>.
We will confirm your identity before processing your request and
<a
href="mailto:team@mintplexlabs.com"
className="text-blue-400 hover:underline"
>
team@mintplexlabs.com
</a>
. We will confirm your identity before processing your request and
respond within 45 days. If more time is needed, we will inform you
of the reason and extension period in writing. You may make a
request for your information twice every 12 months. If you are
making an erasure request, please include details of the
information you would like erased.
</p>
<p>
<p className="text-white/80">
Please note that if you request that we remove your information,
we may retain some of the information for specific reasons, such
as to resolve disputes, troubleshoot problems, and as required by
law. Some information may not be completely removed from our
databases due to technical constraints and regular backups.
</p>
<p>
<p className="text-white/80">
We will not discriminate against you for exercising any of your
CCPA rights.
</p>
<h2 class="text-white/80 text-base font-semibold">
10. Contact Us
</h2>
<p>
<h2 className="text-white mt-4">10. Contact Us</h2>
<p className="text-white/80">
If you have any questions or concerns about this Privacy Policy,
please contact us at{" "}
<a href="mailto:team@mintplexlabs.com">team@mintplexlabs.com</a>.
<a
href="mailto:team@mintplexlabs.com"
className="text-blue-400 hover:underline"
>
team@mintplexlabs.com
</a>
.
</p>
<h2 class="text-white/80 text-base font-semibold">
<h2 className="text-white mt-4">
11. Changes to This Privacy Policy
</h2>
<p>
<p className="text-white/80">
We may update this Privacy Policy from time to time. We will
notify you of any changes by posting the new Privacy Policy on our
website. You are advised to review this Privacy Policy
periodically for any changes.
</p>
<p>
<p className="text-white/80">
By using our services, you agree to the terms of this Privacy
Policy.
</p>
</div>
</div>
<button
<CTAButton
className="text-dark-text w-full mt-[18px] h-[34px] hover:bg-accent"
onClick={handleAccept}
type="button"
className="mt-8 w-full py-2 text-center text-white hover:bg-primary-button border-none rounded-lg"
>
Agree and continue &rarr;
</button>
</CTAButton>
</div>
</div>
</div>
);

View File

@ -1,3 +1,4 @@
import CTAButton from "@/components/lib/CTAButton";
import FineTuningSteps from "..";
export default function TermsAndConditions({ setSettings, setStep }) {
@ -10,24 +11,21 @@ export default function TermsAndConditions({ setSettings, setStep }) {
return (
<div className="flex-[2] flex flex-col gap-y-[18px] mt-10">
<div className="bg-[#303237] text-white rounded-xl flex-1 p-4">
<div className="w-full flex flex-col gap-y-4">
<h2 className="text-xl text-white font-semibold">
<div className="bg-[#303237] text-white rounded-xl flex-1 p-6">
<div className="w-full flex flex-col gap-y-3 max-w-[700px]">
<h2 className="text-base text-white font-semibold">
Terms and Conditions
</h2>
<p>
<p className="text-white/80 text-sm">
Please accept the terms and conditions to continue with creation and
ordering of a fine-tune model.
</p>
<div className="flex flex-col gap-y-2 text-white/75 text-sm border p-2 border-white rounded-lg font-mono h-[60vh] overflow-y-scroll">
<h1 className="text-white/80 text-lg font-semibold">
Mintplex Labs Inc. Fine-Tuning Terms of Service
</h1>
<p>
<strong>Last Updated:</strong> July 15, 2024
</p>
<p>
<div className="flex flex-col gap-y-2 text-white/80 text-xs font-semibold rounded-lg p-4 h-[60vh] overflow-y-auto bg-dark-text mt-2">
<div className="text-xs text-white">
<h1>Mintplex Labs Inc. Fine-Tuning Terms of Service</h1>
<p>Last Updated: July 15, 2024</p>
</div>
<p className="text-white/80">
This Agreement is between Mintplex Labs Inc. ("Company") and the
customer ("Customer") accessing or using the services provided by
the Company. By signing up, accessing, or using the services,
@ -35,20 +33,16 @@ export default function TermsAndConditions({ setSettings, setStep }) {
be bound by the terms and conditions outlined below.
</p>
<h2 className="text-white/80 text-base font-semibold">
1. Services Provided
</h2>
<p>
<h2 className="text-white mt-4">1. Services Provided</h2>
<p className="text-white/80">
Mintplex Labs Inc. provides model fine-tuning services for
customers. The deliverable for these services is a download link
to the output ".GGUF" file that can be used by the Customer for
Large-Language text inferencing.
</p>
<h2 className="text-white/80 text-base font-semibold">
2. Payment Terms
</h2>
<ul>
<h2 className="text-white mt-4">2. Payment Terms</h2>
<ul className="list-disc pl-5 text-white/80">
<li>
<strong>One-Time Payment:</strong> A one-time payment is
required before the execution of the training.
@ -64,10 +58,8 @@ export default function TermsAndConditions({ setSettings, setStep }) {
</li>
</ul>
<h2 className="text-white/80 text-base font-semibold">
3. Order Form
</h2>
<ul>
<h2 className="text-white mt-4">3. Order Form</h2>
<ul className="list-disc pl-5 text-white/80">
<li>
<strong>Service:</strong> Model fine-tuning
</li>
@ -79,88 +71,81 @@ export default function TermsAndConditions({ setSettings, setStep }) {
</li>
</ul>
<h2 className="text-white/80 text-base font-semibold">
4. Customer Responsibilities
</h2>
<p>
<h2 className="text-white mt-4">4. Customer Responsibilities</h2>
<p className="text-white/80">
The Customer must provide all necessary data and information
required for model fine-tuning.
</p>
<p>
<p className="text-white/80">
The Customer must ensure timely payment as per the terms mentioned
above.
</p>
<p>
<p className="text-white/80">
The Customer understands the data collected for tuning will be
stored to a private cloud storage location temporarily while
training is in progress.
</p>
<p>
<p className="text-white/80">
The Customer understands the data collected for tuning will be
fully deleted once the order is completed or canceled by the
Company.
</p>
<p>
<p className="text-white/80">
The Customer understands and has reviewed the Privacy Policy for
Fine-Tuning by the Company.
</p>
<h2 className="text-white/80 text-base font-semibold">
5. Refund Policy
</h2>
<p>
<h2 className="text-white mt-4">5. Refund Policy</h2>
<p className="text-white/80">
Refunds will be processed in the event of training failure or if
the complete model file is not delivered to the Customer. Refunds
will be issued to the original payment method within 30 days of
the refund request.
</p>
<h2 className="text-white/80 text-base font-semibold">
6. Governing Law
</h2>
<p>
<h2 className="text-white mt-4">6. Governing Law</h2>
<p className="text-white/80">
This Agreement shall be governed by and construed in accordance
with the laws of the State of California.
</p>
<h2 className="text-white/80 text-base font-semibold">
7. Dispute Resolution
</h2>
<p>
<h2 className="text-white mt-4">7. Dispute Resolution</h2>
<p className="text-white/80">
Any disputes arising out of or in connection with this Agreement
shall be resolved in the state or federal courts located in
California.
</p>
<h2 className="text-white/80 text-base font-semibold">
8. Notices
</h2>
<p>
<h2 className="text-white mt-4">8. Notices</h2>
<p className="text-white/80">
All notices under this Agreement shall be in writing and shall be
deemed given when delivered personally, sent by confirmed email,
or sent by certified or registered mail, return receipt requested,
and addressed to the respective parties as follows:
</p>
<p>
<p className="text-white/80">
For Company:{" "}
<a href="mailto:team@mintplexlabs.com">team@mintplexlabs.com</a>
<a
href="mailto:team@mintplexlabs.com"
className="text-blue-400 hover:underline"
>
team@mintplexlabs.com
</a>
</p>
<p className="text-white/80">
For Customer: The main email address on Customer's account
</p>
<p>For Customer: The main email address on Customer's account</p>
<h2 className="text-white/80 text-base font-semibold">
9. Amendments
</h2>
<p>
<h2 className="text-white mt-4">9. Amendments</h2>
<p className="text-white/80">
The Company reserves the right to amend these terms at any time by
providing notice to the Customer. The Customer's continued use of
the services after such amendments will constitute acceptance of
the amended terms.
</p>
<h2 className="text-white/80 text-base font-semibold">
10. Indemnity
</h2>
<p>
<h2 className="text-white mt-4">10. Indemnity</h2>
<p className="text-white/80">
The Customer agrees to indemnify, defend, and hold harmless
Mintplex Labs Inc., its affiliates, and their respective officers,
directors, employees, agents, and representatives from and against
@ -173,15 +158,13 @@ export default function TermsAndConditions({ setSettings, setStep }) {
person or entity.
</p>
</div>
</div>
<button
<CTAButton
className="text-dark-text w-full mt-[18px] h-[34px] hover:bg-accent"
onClick={handleAccept}
type="button"
className="mt-8 w-full py-2 text-center text-white hover:bg-primary-button border-none rounded-lg"
>
Agree and continue &rarr;
</button>
</CTAButton>
</div>
</div>
</div>
);

View File

@ -26,7 +26,7 @@ import OrderPlaced from "./OrderPlaced";
const FineTuningSteps = {
intro: {
name: "Introduction to Fine-Tuning",
name: "1. Introduction to Fine-Tuning",
next: () => "privacy",
component: ({ settings, setSettings, setStep }) => (
<Introduction
@ -37,7 +37,7 @@ const FineTuningSteps = {
),
},
privacy: {
name: "How your data is handled",
name: "2. How your data is handled",
next: () => "tos",
component: ({ settings, setSettings, setStep }) => (
<PrivacyPolicy
@ -48,7 +48,7 @@ const FineTuningSteps = {
),
},
tos: {
name: "Terms of service",
name: "3. Terms of service",
next: () => "fulfillment",
component: ({ settings, setSettings, setStep }) => (
<TermsAndConditions
@ -59,7 +59,7 @@ const FineTuningSteps = {
),
},
fulfillment: {
name: "Fulfillment terms",
name: "4. Fulfillment terms",
next: () => "order-details",
component: ({ settings, setSettings, setStep }) => (
<Fulfillment
@ -70,7 +70,7 @@ const FineTuningSteps = {
),
},
"order-details": {
name: "Model details & information",
name: "5. Model details & information",
next: () => "data-selection",
component: ({ settings, setSettings, setStep }) => (
<OrderDetails
@ -81,7 +81,7 @@ const FineTuningSteps = {
),
},
"data-selection": {
name: "Data selection",
name: "6. Data selection",
next: () => "confirmation",
component: ({ settings, setSettings, setStep }) => (
<DataUpload
@ -92,7 +92,7 @@ const FineTuningSteps = {
),
},
confirmation: {
name: "Review and Submit",
name: "7. Review and Submit",
next: () => "done",
component: ({ settings, setSettings, setStep }) => (
<Confirmation
@ -103,7 +103,7 @@ const FineTuningSteps = {
),
},
done: {
name: "Order placed",
name: "8. Order placed",
next: () => "done",
component: ({ settings }) => <OrderPlaced settings={settings} />,
},
@ -133,7 +133,7 @@ export function FineTuningCreationLayout({ setStep, children }) {
return (
<div
id="fine-tune-create-order-container"
className="w-screen h-screen overflow-y-auto bg-sidebar flex"
className="w-screen h-screen overflow-hidden bg-sidebar flex md:mt-0 mt-6"
>
<Sidebar />
<div

View File

@ -1,13 +1,13 @@
import React, { useState } from "react";
import FineTuningSteps, { FineTuningCreationLayout } from "./Steps";
import { CheckCircle, Circle, Sparkle } from "@phosphor-icons/react";
import { Sparkle } from "@phosphor-icons/react";
import { isMobile } from "react-device-detect";
function SideBarSelection({ setStep, currentStep }) {
const currentIndex = Object.keys(FineTuningSteps).indexOf(currentStep);
return (
<div
className={`bg-white/5 text-white rounded-xl ${
className={`bg-white/5 text-white rounded-xl py-1 px-4 ${
isMobile ? "w-full" : "min-w-[360px] w-fit"
}`}
>
@ -21,28 +21,32 @@ function SideBarSelection({ setStep, currentStep }) {
<div
key={stepKey}
className={[
"py-3 px-4 flex items-center justify-between transition-all duration-300",
"py-3 flex items-center justify-between transition-all duration-300",
isSelected ? "rounded-t-xl" : "",
isLast ? "" : "border-b border-white/10",
].join(" ")}
>
{isDone ? (
{isDone || isSelected ? (
<button
onClick={() => setStep(stepKey)}
className="border-none hover:underline text-white/40 text-sm font-light"
className="border-none hover:underline text-sm font-medium"
>
{props.name}
</button>
) : (
<div className="text-sm font-light">{props.name}</div>
<div className="text-sm text-white/40 font-medium">
{props.name}
</div>
)}
<div className="flex items-center gap-x-2">
{isDone ? (
<CheckCircle className={`text-green-300`} />
<div className="w-[14px] h-[14px] rounded-full border border-[#32D583] flex items-center justify-center">
<div className="w-[5.6px] h-[5.6px] rounded-full bg-[#6CE9A6]"></div>
</div>
) : (
<Circle
className={`text-white-800 ${
isSelected ? "animate-pulse" : "opacity-10"
<div
className={`w-[14px] h-[14px] rounded-full border border-white ${
isSelected ? "animate-pulse" : "opacity-50"
}`}
/>
)}
@ -63,16 +67,20 @@ export default function FineTuningFlow() {
return (
<FineTuningCreationLayout setStep={setStep}>
{(settings, setSettings, setStep) => (
<div className="flex-1 flex gap-x-6 p-4 mt-10">
<div className="flex flex-col gap-y-[18px]">
<div className="flex-1 flex h-full">
<div className="flex flex-col gap-y-[18px] p-4 mt-10 w-[360px] flex-shrink-0">
<div className="text-white flex items-center gap-x-2">
<Sparkle size={24} />
<p className="text-lg font-medium">Custom Fine-Tuned Model</p>
</div>
<SideBarSelection setStep={setStep} currentStep={step} />
</div>
<div className="flex-1 overflow-y-auto p-4 mt-10 pb-[74px] h-screen">
<div className=" ml-8">
{StepPage.component({ settings, setSettings, setStep })}
</div>
</div>
</div>
)}
</FineTuningCreationLayout>
);

View File

@ -7,9 +7,11 @@ import CTAButton from "@/components/lib/CTAButton";
import OpenAiLogo from "@/media/llmprovider/openai.png";
import AnythingLLMIcon from "@/media/logo/anything-llm-icon.png";
import ElevenLabsIcon from "@/media/ttsproviders/elevenlabs.png";
import PiperTTSIcon from "@/media/ttsproviders/piper.png";
import BrowserNative from "@/components/TextToSpeech/BrowserNative";
import OpenAiTTSOptions from "@/components/TextToSpeech/OpenAiOptions";
import ElevenLabsTTSOptions from "@/components/TextToSpeech/ElevenLabsOptions";
import PiperTTSOptions from "@/components/TextToSpeech/PiperTTSOptions";
const PROVIDERS = [
{
@ -33,6 +35,13 @@ const PROVIDERS = [
options: (settings) => <ElevenLabsTTSOptions settings={settings} />,
description: "Use ElevenLabs's text to speech voices and technology.",
},
{
name: "PiperTTS",
value: "piper_local",
logo: PiperTTSIcon,
options: (settings) => <PiperTTSOptions settings={settings} />,
description: "Run TTS models locally in your browser privately.",
},
];
export default function TextToSpeechProvider({ settings }) {

View File

@ -1,5 +1,6 @@
import { encode as HTMLEncode } from "he";
import markdownIt from "markdown-it";
import markdownItKatex from "markdown-it-katex";
import hljs from "highlight.js";
import "highlight.js/styles/github-dark-dimmed.min.css";
import { v4 } from "uuid";
@ -43,7 +44,7 @@ const markdown = markdownIt({
"</pre></div>"
);
},
});
}).use(markdownItKatex);
export default function renderMarkdown(text = "") {
return markdown.render(text);

View File

@ -0,0 +1,138 @@
import showToast from "../toast";
export default class PiperTTSClient {
static _instance;
voiceId = "en_US-hfc_female-medium";
worker = null;
constructor({ voiceId } = { voiceId: null }) {
if (PiperTTSClient._instance) {
this.voiceId = voiceId !== null ? voiceId : this.voiceId;
return PiperTTSClient._instance;
}
this.voiceId = voiceId !== null ? voiceId : this.voiceId;
PiperTTSClient._instance = this;
return this;
}
#getWorker() {
if (!this.worker)
this.worker = new Worker(new URL("./worker.js", import.meta.url), {
type: "module",
});
return this.worker;
}
/**
* Get all available voices for a client
* @returns {Promise<import("@mintplex-labs/piper-tts-web/dist/types").Voice[]}>}
*/
static async voices() {
const tmpWorker = new Worker(new URL("./worker.js", import.meta.url), {
type: "module",
});
tmpWorker.postMessage({ type: "voices" });
return new Promise((resolve, reject) => {
let timeout = null;
const handleMessage = (event) => {
if (event.data.type !== "voices") {
console.log("PiperTTSWorker debug event:", event.data);
return;
}
resolve(event.data.voices);
tmpWorker.removeEventListener("message", handleMessage);
timeout && clearTimeout(timeout);
tmpWorker.terminate();
};
timeout = setTimeout(() => {
reject("TTS Worker timed out.");
}, 30_000);
tmpWorker.addEventListener("message", handleMessage);
});
}
static async flush() {
const tmpWorker = new Worker(new URL("./worker.js", import.meta.url), {
type: "module",
});
tmpWorker.postMessage({ type: "flush" });
return new Promise((resolve, reject) => {
let timeout = null;
const handleMessage = (event) => {
if (event.data.type !== "flush") {
console.log("PiperTTSWorker debug event:", event.data);
return;
}
resolve(event.data.flushed);
tmpWorker.removeEventListener("message", handleMessage);
timeout && clearTimeout(timeout);
tmpWorker.terminate();
};
timeout = setTimeout(() => {
reject("TTS Worker timed out.");
}, 30_000);
tmpWorker.addEventListener("message", handleMessage);
});
}
/**
* Runs prediction via webworker so we can get an audio blob back.
* @returns {Promise<{blobURL: string|null, error: string|null}>} objectURL blob: type.
*/
async waitForBlobResponse() {
return new Promise((resolve) => {
let timeout = null;
const handleMessage = (event) => {
if (event.data.type === "error") {
this.worker.removeEventListener("message", handleMessage);
timeout && clearTimeout(timeout);
return resolve({ blobURL: null, error: event.data.message });
}
if (event.data.type !== "result") {
console.log("PiperTTSWorker debug event:", event.data);
return;
}
resolve({
blobURL: URL.createObjectURL(event.data.audio),
error: null,
});
this.worker.removeEventListener("message", handleMessage);
timeout && clearTimeout(timeout);
};
timeout = setTimeout(() => {
resolve({ blobURL: null, error: "PiperTTSWorker Worker timed out." });
}, 30_000);
this.worker.addEventListener("message", handleMessage);
});
}
async getAudioBlobForText(textToSpeak, voiceId = null) {
const primaryWorker = this.#getWorker();
primaryWorker.postMessage({
type: "init",
text: String(textToSpeak),
voiceId: voiceId ?? this.voiceId,
// Don't reference WASM because in the docker image
// the user will be connected to internet (mostly)
// and it bloats the app size on the frontend or app significantly
// and running the docker image fully offline is not an intended use-case unlike the app.
});
const { blobURL, error } = await this.waitForBlobResponse();
if (!!error) {
showToast(
`Could not generate voice prediction. Error: ${error}`,
"error",
{ clear: true }
);
return;
}
return blobURL;
}
}

View File

@ -0,0 +1,94 @@
import * as TTS from "@mintplex-labs/piper-tts-web";
/** @type {import("@mintplexlabs/piper-web-tts").TtsSession | null} */
let PIPER_SESSION = null;
/**
* @typedef PredictionRequest
* @property {('init')} type
* @property {string} text - the text to inference on
* @property {import('@mintplexlabs/piper-web-tts').VoiceId} voiceId - the voiceID key to use.
* @property {string|null} baseUrl - the base URL to fetch WASMs from.
*/
/**
* @typedef PredictionRequestResponse
* @property {('result')} type
* @property {Blob} audio - the text to inference on
*/
/**
* @typedef VoicesRequest
* @property {('voices')} type
* @property {string|null} baseUrl - the base URL to fetch WASMs from.
*/
/**
* @typedef VoicesRequestResponse
* @property {('voices')} type
* @property {[import("@mintplex-labs/piper-tts-web/dist/types")['Voice']]} voices - available voices in array
*/
/**
* @typedef FlushRequest
* @property {('flush')} type
*/
/**
* @typedef FlushRequestResponse
* @property {('flush')} type
* @property {true} flushed
*/
/**
* Web worker for generating client-side PiperTTS predictions
* @param {MessageEvent<PredictionRequest | VoicesRequest | FlushRequest>} event - The event object containing the prediction request
* @returns {Promise<PredictionRequestResponse|VoicesRequestResponse|FlushRequestResponse>}
*/
async function main(event) {
if (event.data.type === "voices") {
const stored = await TTS.stored();
const voices = await TTS.voices();
voices.forEach((voice) => (voice.is_stored = stored.includes(voice.key)));
self.postMessage({ type: "voices", voices });
return;
}
if (event.data.type === "flush") {
await TTS.flush();
self.postMessage({ type: "flush", flushed: true });
return;
}
if (event.data?.type !== "init") return;
if (!PIPER_SESSION) {
PIPER_SESSION = new TTS.TtsSession({
voiceId: event.data.voiceId,
progress: (e) => self.postMessage(JSON.stringify(e)),
logger: (msg) => self.postMessage(msg),
...(!!event.data.baseUrl
? {
wasmPaths: {
onnxWasm: `${event.data.baseUrl}/piper/ort/`,
piperData: `${event.data.baseUrl}/piper/piper_phonemize.data`,
piperWasm: `${event.data.baseUrl}/piper/piper_phonemize.wasm`,
},
}
: {}),
});
}
if (event.data.voiceId && PIPER_SESSION.voiceId !== event.data.voiceId)
PIPER_SESSION.voiceId = event.data.voiceId;
PIPER_SESSION.predict(event.data.text)
.then((res) => {
if (res instanceof Blob) {
self.postMessage({ type: "result", audio: res });
return;
}
})
.catch((error) => {
self.postMessage({ type: "error", message: error.message, error }); // Will be an error.
});
}
self.addEventListener("message", main);

View File

@ -86,7 +86,8 @@ export default {
]
},
animation: {
sweep: "sweep 0.5s ease-in-out"
sweep: "sweep 0.5s ease-in-out",
"pulse-glow": "pulse-glow 1.5s infinite"
},
keyframes: {
sweep: {
@ -100,6 +101,26 @@ export default {
fadeOut: {
"0%": { opacity: 1 },
"100%": { opacity: 0 }
},
"pulse-glow": {
"0%": {
opacity: 1,
transform: "scale(1)",
boxShadow: "0 0 0 rgba(255, 255, 255, 0.0)",
backgroundColor: "rgba(255, 255, 255, 0.0)"
},
"50%": {
opacity: 1,
transform: "scale(1.1)",
boxShadow: "0 0 15px rgba(255, 255, 255, 0.2)",
backgroundColor: "rgba(255, 255, 255, 0.1)"
},
"100%": {
opacity: 1,
transform: "scale(1)",
boxShadow: "0 0 0 rgba(255, 255, 255, 0.0)",
backgroundColor: "rgba(255, 255, 255, 0.0)"
}
}
}
}

View File

@ -9,6 +9,14 @@ dns.setDefaultResultOrder("verbatim")
// https://vitejs.dev/config/
export default defineConfig({
assetsInclude: [
'./public/piper/ort-wasm-simd-threaded.wasm',
'./public/piper/piper_phonemize.wasm',
'./public/piper/piper_phonemize.data',
],
worker: {
format: 'es'
},
server: {
port: 3000,
host: "localhost"
@ -60,7 +68,7 @@ export default defineConfig({
},
external: [
// Reduces transformation time by 50% and we don't even use this variant, so we can ignore.
/@phosphor-icons\/react\/dist\/ssr/
/@phosphor-icons\/react\/dist\/ssr/,
]
},
commonjsOptions: {
@ -68,6 +76,7 @@ export default defineConfig({
}
},
optimizeDeps: {
include: ["@mintplex-labs/piper-tts-web"],
esbuildOptions: {
define: {
global: "globalThis"

View File

@ -496,6 +496,11 @@
resolved "https://registry.yarnpkg.com/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz#9ceecc94b49fbaa15666e38ae8587f64acce007d"
integrity sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==
"@mintplex-labs/piper-tts-web@^1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@mintplex-labs/piper-tts-web/-/piper-tts-web-1.0.4.tgz#016b196fa86dc8b616691dd381f3ca1939196444"
integrity sha512-Y24X+CJaGXoY5HFPSstHvJI6408OAtw3Pmq2OIYwpRpcwLLbgadWg8l1ODHNkgpB0Ps5fS9PAAQB60fHA3Bdag==
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
@ -532,6 +537,59 @@
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31"
integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==
"@protobufjs/base64@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735"
integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==
"@protobufjs/codegen@^2.0.4":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb"
integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==
"@protobufjs/eventemitter@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70"
integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==
"@protobufjs/fetch@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45"
integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==
dependencies:
"@protobufjs/aspromise" "^1.1.1"
"@protobufjs/inquire" "^1.1.0"
"@protobufjs/float@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1"
integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==
"@protobufjs/inquire@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089"
integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==
"@protobufjs/path@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d"
integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==
"@protobufjs/pool@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54"
integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==
"@protobufjs/utf8@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==
"@remix-run/router@1.18.0":
version "1.18.0"
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.18.0.tgz#20b033d1f542a100c1d57cfd18ecf442d1784732"
@ -652,6 +710,13 @@
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64"
integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==
"@types/node@>=13.7.0":
version "22.1.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.1.0.tgz#6d6adc648b5e03f0e83c78dc788c2b037d0ad94b"
integrity sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==
dependencies:
undici-types "~6.13.0"
"@types/prop-types@*":
version "15.7.12"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6"
@ -1729,6 +1794,11 @@ flat-cache@^3.0.4:
keyv "^4.5.3"
rimraf "^3.0.2"
flatbuffers@^1.12.0:
version "1.12.0"
resolved "https://registry.yarnpkg.com/flatbuffers/-/flatbuffers-1.12.0.tgz#72e87d1726cb1b216e839ef02658aa87dcef68aa"
integrity sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ==
flatted@^3.2.9:
version "3.3.1"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a"
@ -1898,6 +1968,11 @@ graphemer@^1.4.0:
resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
guid-typescript@^1.0.9:
version "1.0.9"
resolved "https://registry.yarnpkg.com/guid-typescript/-/guid-typescript-1.0.9.tgz#e35f77003535b0297ea08548f5ace6adb1480ddc"
integrity sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==
has-bigints@^1.0.1, has-bigints@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
@ -2347,6 +2422,13 @@ json5@^2.2.3:
object.assign "^4.1.4"
object.values "^1.1.6"
katex@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/katex/-/katex-0.6.0.tgz#12418e09121c05c92041b6b3b9fb6bab213cb6f3"
integrity sha512-rS4mY3SvHYg5LtQV6RBcK0if7ur6plyEukAOV+jGGPqFImuzu8fHL6M752iBmRGoUyF0bhZbAPoezehn7xYksA==
dependencies:
match-at "^0.1.0"
keyv@^4.5.3:
version "4.5.4"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
@ -2406,6 +2488,11 @@ lodash@^4.17.21:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
long@^5.0.0, long@^5.2.3:
version "5.2.3"
resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1"
integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==
loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@ -2425,6 +2512,13 @@ lru-cache@^5.1.1:
dependencies:
yallist "^3.0.2"
markdown-it-katex@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/markdown-it-katex/-/markdown-it-katex-2.0.3.tgz#d7b86a1aea0b9d6496fab4e7919a18fdef589c39"
integrity sha512-nUkkMtRWeg7OpdflamflE/Ho/pWl64Lk9wNBKOmaj33XkQdumhXAIYhI0WO03GeiycPCsxbmX536V5NEXpC3Ng==
dependencies:
katex "^0.6.0"
markdown-it@^13.0.1:
version "13.0.2"
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-13.0.2.tgz#1bc22e23379a6952e5d56217fbed881e0c94d536"
@ -2436,6 +2530,11 @@ markdown-it@^13.0.1:
mdurl "^1.0.1"
uc.micro "^1.0.5"
match-at@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/match-at/-/match-at-0.1.1.tgz#25d040d291777704d5e6556bbb79230ec2de0540"
integrity sha512-h4Yd392z9mST+dzc+yjuybOGFNOZjmXIPKWjxBd1Bb23r4SmDOsk2NYCU2BMUBGbSpZqwVsZYNq26QS3xfaT3Q==
mdurl@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
@ -2592,6 +2691,23 @@ once@^1.3.0:
dependencies:
wrappy "1"
onnxruntime-common@1.18.0:
version "1.18.0"
resolved "https://registry.yarnpkg.com/onnxruntime-common/-/onnxruntime-common-1.18.0.tgz#b904dc6ff134e7f21a3eab702fac17538f59e116"
integrity sha512-lufrSzX6QdKrktAELG5x5VkBpapbCeS3dQwrXbN0eD9rHvU0yAWl7Ztju9FvgAKWvwd/teEKJNj3OwM6eTZh3Q==
onnxruntime-web@^1.18.0:
version "1.18.0"
resolved "https://registry.yarnpkg.com/onnxruntime-web/-/onnxruntime-web-1.18.0.tgz#cd46268d9472f89697da0a3282f13129f0acbfa0"
integrity sha512-o1UKj4ABIj1gmG7ae0RKJ3/GT+3yoF0RRpfDfeoe0huzRW4FDRLfbkDETmdFAvnJEXuYDE0YT+hhkia0352StQ==
dependencies:
flatbuffers "^1.12.0"
guid-typescript "^1.0.9"
long "^5.2.3"
onnxruntime-common "1.18.0"
platform "^1.3.6"
protobufjs "^7.2.4"
open@^8.4.0:
version "8.4.2"
resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9"
@ -2694,6 +2810,11 @@ pirates@^4.0.1:
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9"
integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==
platform@^1.3.6:
version "1.3.6"
resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.6.tgz#48b4ce983164b209c2d45a107adb31f473a6e7a7"
integrity sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==
pluralize@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
@ -2783,6 +2904,24 @@ prop-types@^15.6.2, prop-types@^15.8.1:
object-assign "^4.1.1"
react-is "^16.13.1"
protobufjs@^7.2.4:
version "7.3.2"
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.3.2.tgz#60f3b7624968868f6f739430cfbc8c9370e26df4"
integrity sha512-RXyHaACeqXeqAKGLDl68rQKbmObRsTIn4TYVUUug1KfS47YWCo5MacGITEryugIgZqORCvJWEk4l449POg5Txg==
dependencies:
"@protobufjs/aspromise" "^1.1.2"
"@protobufjs/base64" "^1.1.2"
"@protobufjs/codegen" "^2.0.4"
"@protobufjs/eventemitter" "^1.1.0"
"@protobufjs/fetch" "^1.1.0"
"@protobufjs/float" "^1.0.2"
"@protobufjs/inquire" "^1.1.0"
"@protobufjs/path" "^1.1.2"
"@protobufjs/pool" "^1.1.0"
"@protobufjs/utf8" "^1.1.0"
"@types/node" ">=13.7.0"
long "^5.0.0"
punycode@^2.1.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
@ -3593,6 +3732,11 @@ unbox-primitive@^1.0.2:
has-symbols "^1.0.3"
which-boxed-primitive "^1.0.2"
undici-types@~6.13.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.13.0.tgz#e3e79220ab8c81ed1496b5812471afd7cf075ea5"
integrity sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==
update-browserslist-db@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e"

View File

@ -0,0 +1,230 @@
const { EmbedConfig } = require("../../../models/embedConfig");
const { EmbedChats } = require("../../../models/embedChats");
const { validApiKey } = require("../../../utils/middleware/validApiKey");
function apiEmbedEndpoints(app) {
if (!app) return;
app.get("/v1/embed", [validApiKey], async (request, response) => {
/*
#swagger.tags = ['Embed']
#swagger.description = 'List all active embeds'
#swagger.responses[200] = {
content: {
"application/json": {
schema: {
type: 'object',
example: {
embeds: [
{
"id": 1,
"uuid": "embed-uuid-1",
"enabled": true,
"chat_mode": "query",
"createdAt": "2023-04-01T12:00:00Z",
"workspace": {
"id": 1,
"name": "Workspace 1"
},
"chat_count": 10
},
{
"id": 2,
"uuid": "embed-uuid-2",
"enabled": false,
"chat_mode": "chat",
"createdAt": "2023-04-02T14:30:00Z",
"workspace": {
"id": 1,
"name": "Workspace 1"
},
"chat_count": 10
}
]
}
}
}
}
}
#swagger.responses[403] = {
schema: {
"$ref": "#/definitions/InvalidAPIKey"
}
}
*/
try {
const embeds = await EmbedConfig.whereWithWorkspace();
const filteredEmbeds = embeds.map((embed) => ({
id: embed.id,
uuid: embed.uuid,
enabled: embed.enabled,
chat_mode: embed.chat_mode,
createdAt: embed.createdAt,
workspace: {
id: embed.workspace.id,
name: embed.workspace.name,
},
chat_count: embed._count.embed_chats,
}));
response.status(200).json({ embeds: filteredEmbeds });
} catch (e) {
console.error(e.message, e);
response.sendStatus(500).end();
}
});
app.get(
"/v1/embed/:embedUuid/chats",
[validApiKey],
async (request, response) => {
/*
#swagger.tags = ['Embed']
#swagger.description = 'Get all chats for a specific embed'
#swagger.parameters['embedUuid'] = {
in: 'path',
description: 'UUID of the embed',
required: true,
type: 'string'
}
#swagger.responses[200] = {
content: {
"application/json": {
schema: {
type: 'object',
example: {
chats: [
{
"id": 1,
"session_id": "session-uuid-1",
"prompt": "Hello",
"response": "Hi there!",
"createdAt": "2023-04-01T12:00:00Z"
},
{
"id": 2,
"session_id": "session-uuid-2",
"prompt": "How are you?",
"response": "I'm doing well, thank you!",
"createdAt": "2023-04-02T14:30:00Z"
}
]
}
}
}
}
}
#swagger.responses[403] = {
schema: {
"$ref": "#/definitions/InvalidAPIKey"
}
}
#swagger.responses[404] = {
description: "Embed not found",
}
*/
try {
const { embedUuid } = request.params;
const embed = await EmbedConfig.get({ uuid: String(embedUuid) });
if (!embed) {
return response.status(404).json({ error: "Embed not found" });
}
const chats = await EmbedChats.where({ embed_id: embed.id });
const formattedChats = chats.map((chat) => ({
id: chat.id,
session_id: chat.session_id,
prompt: chat.prompt,
response: chat.response,
createdAt: chat.createdAt,
}));
response.status(200).json({ chats: formattedChats });
} catch (e) {
console.error(e.message, e);
response.sendStatus(500).end();
}
}
);
app.get(
"/v1/embed/:embedUuid/chats/:sessionUuid",
[validApiKey],
async (request, response) => {
/*
#swagger.tags = ['Embed']
#swagger.description = 'Get chats for a specific embed and session'
#swagger.parameters['embedUuid'] = {
in: 'path',
description: 'UUID of the embed',
required: true,
type: 'string'
}
#swagger.parameters['sessionUuid'] = {
in: 'path',
description: 'UUID of the session',
required: true,
type: 'string'
}
#swagger.responses[200] = {
content: {
"application/json": {
schema: {
type: 'object',
example: {
chats: [
{
"id": 1,
"prompt": "Hello",
"response": "Hi there!",
"createdAt": "2023-04-01T12:00:00Z"
}
]
}
}
}
}
}
#swagger.responses[403] = {
schema: {
"$ref": "#/definitions/InvalidAPIKey"
}
}
#swagger.responses[404] = {
description: "Embed or session not found",
}
*/
try {
const { embedUuid, sessionUuid } = request.params;
const embed = await EmbedConfig.get({ uuid: String(embedUuid) });
if (!embed) {
return response.status(404).json({ error: "Embed not found" });
}
const chats = await EmbedChats.where({
embed_id: embed.id,
session_id: String(sessionUuid),
});
if (!chats || chats.length === 0) {
return response
.status(404)
.json({ error: "No chats found for this session" });
}
const formattedChats = chats.map((chat) => ({
id: chat.id,
prompt: chat.prompt,
response: chat.response,
createdAt: chat.createdAt,
}));
response.status(200).json({ chats: formattedChats });
} catch (e) {
console.error(e.message, e);
response.sendStatus(500).end();
}
}
);
}
module.exports = { apiEmbedEndpoints };

View File

@ -7,6 +7,7 @@ const { apiWorkspaceEndpoints } = require("./workspace");
const { apiWorkspaceThreadEndpoints } = require("./workspaceThread");
const { apiUserManagementEndpoints } = require("./userManagement");
const { apiOpenAICompatibleEndpoints } = require("./openai");
const { apiEmbedEndpoints } = require("./embed");
// All endpoints must be documented and pass through the validApiKey Middleware.
// How to JSDoc an endpoint
@ -22,6 +23,7 @@ function developerEndpoints(app, router) {
apiWorkspaceThreadEndpoints(router);
apiUserManagementEndpoints(router);
apiOpenAICompatibleEndpoints(router);
apiEmbedEndpoints(router);
}
module.exports = { developerEndpoints };

View File

@ -154,6 +154,7 @@ function apiOpenAICompatibleEndpoints(app) {
workspace.chatProvider ?? process.env.LLM_PROVIDER ?? "openai",
Embedder: process.env.EMBEDDING_ENGINE || "inherit",
VectorDbSelection: process.env.VECTOR_DB || "lancedb",
TTSSelection: process.env.TTS_PROVIDER || "native",
});
await EventLogs.logEvent("api_sent_chat", {
workspaceName: workspace?.name,
@ -180,6 +181,7 @@ function apiOpenAICompatibleEndpoints(app) {
LLMSelection: process.env.LLM_PROVIDER || "openai",
Embedder: process.env.EMBEDDING_ENGINE || "inherit",
VectorDbSelection: process.env.VECTOR_DB || "lancedb",
TTSSelection: process.env.TTS_PROVIDER || "native",
});
await EventLogs.logEvent("api_sent_chat", {
workspaceName: workspace?.name,

View File

@ -73,6 +73,7 @@ function apiWorkspaceEndpoints(app) {
LLMSelection: process.env.LLM_PROVIDER || "openai",
Embedder: process.env.EMBEDDING_ENGINE || "inherit",
VectorDbSelection: process.env.VECTOR_DB || "lancedb",
TTSSelection: process.env.TTS_PROVIDER || "native",
});
await EventLogs.logEvent("api_workspace_created", {
workspaceName: workspace?.name || "Unknown Workspace",
@ -622,6 +623,7 @@ function apiWorkspaceEndpoints(app) {
LLMSelection: process.env.LLM_PROVIDER || "openai",
Embedder: process.env.EMBEDDING_ENGINE || "inherit",
VectorDbSelection: process.env.VECTOR_DB || "lancedb",
TTSSelection: process.env.TTS_PROVIDER || "native",
});
await EventLogs.logEvent("api_sent_chat", {
workspaceName: workspace?.name,
@ -745,6 +747,7 @@ function apiWorkspaceEndpoints(app) {
LLMSelection: process.env.LLM_PROVIDER || "openai",
Embedder: process.env.EMBEDDING_ENGINE || "inherit",
VectorDbSelection: process.env.VECTOR_DB || "lancedb",
TTSSelection: process.env.TTS_PROVIDER || "native",
});
await EventLogs.logEvent("api_sent_chat", {
workspaceName: workspace?.name,

View File

@ -90,6 +90,7 @@ function apiWorkspaceThreadEndpoints(app) {
LLMSelection: process.env.LLM_PROVIDER || "openai",
Embedder: process.env.EMBEDDING_ENGINE || "inherit",
VectorDbSelection: process.env.VECTOR_DB || "lancedb",
TTSSelection: process.env.TTS_PROVIDER || "native",
});
await EventLogs.logEvent("api_workspace_thread_created", {
workspaceName: workspace?.name || "Unknown Workspace",
@ -416,6 +417,7 @@ function apiWorkspaceThreadEndpoints(app) {
LLMSelection: process.env.LLM_PROVIDER || "openai",
Embedder: process.env.EMBEDDING_ENGINE || "inherit",
VectorDbSelection: process.env.VECTOR_DB || "lancedb",
TTSSelection: process.env.TTS_PROVIDER || "native",
});
await EventLogs.logEvent("api_sent_chat", {
workspaceName: workspace?.name,
@ -567,6 +569,7 @@ function apiWorkspaceThreadEndpoints(app) {
LLMSelection: process.env.LLM_PROVIDER || "openai",
Embedder: process.env.EMBEDDING_ENGINE || "inherit",
VectorDbSelection: process.env.VECTOR_DB || "lancedb",
TTSSelection: process.env.TTS_PROVIDER || "native",
});
await EventLogs.logEvent("api_sent_chat", {
workspaceName: workspace?.name,

View File

@ -98,6 +98,7 @@ function chatEndpoints(app) {
Embedder: process.env.EMBEDDING_ENGINE || "inherit",
VectorDbSelection: process.env.VECTOR_DB || "lancedb",
multiModal: Array.isArray(attachments) && attachments?.length !== 0,
TTSSelection: process.env.TTS_PROVIDER || "native",
});
await EventLogs.logEvent(
@ -226,6 +227,7 @@ function chatEndpoints(app) {
Embedder: process.env.EMBEDDING_ENGINE || "inherit",
VectorDbSelection: process.env.VECTOR_DB || "lancedb",
multiModal: Array.isArray(attachments) && attachments?.length !== 0,
TTSSelection: process.env.TTS_PROVIDER || "native",
});
await EventLogs.logEvent(

View File

@ -40,6 +40,7 @@ function workspaceThreadEndpoints(app) {
LLMSelection: process.env.LLM_PROVIDER || "openai",
Embedder: process.env.EMBEDDING_ENGINE || "inherit",
VectorDbSelection: process.env.VECTOR_DB || "lancedb",
TTSSelection: process.env.TTS_PROVIDER || "native",
},
user?.id
);

View File

@ -55,6 +55,7 @@ function workspaceEndpoints(app) {
LLMSelection: process.env.LLM_PROVIDER || "openai",
Embedder: process.env.EMBEDDING_ENGINE || "inherit",
VectorDbSelection: process.env.VECTOR_DB || "lancedb",
TTSSelection: process.env.TTS_PROVIDER || "native",
},
user?.id
);

View File

@ -142,6 +142,7 @@ const Document = {
LLMSelection: process.env.LLM_PROVIDER || "openai",
Embedder: process.env.EMBEDDING_ENGINE || "inherit",
VectorDbSelection: process.env.VECTOR_DB || "lancedb",
TTSSelection: process.env.TTS_PROVIDER || "native",
});
await EventLogs.logEvent(
"workspace_documents_added",
@ -185,6 +186,7 @@ const Document = {
LLMSelection: process.env.LLM_PROVIDER || "openai",
Embedder: process.env.EMBEDDING_ENGINE || "inherit",
VectorDbSelection: process.env.VECTOR_DB || "lancedb",
TTSSelection: process.env.TTS_PROVIDER || "native",
});
await EventLogs.logEvent(
"workspace_documents_removed",

View File

@ -214,6 +214,9 @@ const SystemSettings = {
// Eleven Labs TTS
TTSElevenLabsKey: !!process.env.TTS_ELEVEN_LABS_KEY,
TTSElevenLabsVoiceModel: process.env.TTS_ELEVEN_LABS_VOICE_MODEL,
// Piper TTS
TTSPiperTTSVoiceModel:
process.env.TTS_PIPER_VOICE_MODEL ?? "en_US-hfc_female-medium",
// --------------------------------------------------------
// Agent Settings & Configs

View File

@ -2,6 +2,7 @@ const prisma = require("../utils/prisma");
const { EventLogs } = require("./eventLogs");
const User = {
usernameRegex: new RegExp(/^[a-z0-9_-]+$/),
writable: [
// Used for generic updates so we can validate keys in request body
"username",
@ -32,7 +33,6 @@ const User = {
return String(role);
},
},
// validations for the above writable fields.
castColumnValue: function (key, value) {
switch (key) {
@ -55,6 +55,12 @@ const User = {
}
try {
// Do not allow new users to bypass validation
if (!this.usernameRegex.test(username))
throw new Error(
"Username must be only contain lowercase letters, numbers, underscores, and hyphens with no spaces"
);
const bcrypt = require("bcrypt");
const hashedPassword = bcrypt.hashSync(password, 10);
const user = await prisma.users.create({
@ -70,7 +76,6 @@ const User = {
return { user: null, error: error.message };
}
},
// Log the changes to a user object, but omit sensitive fields
// that are not meant to be logged.
loggedChanges: function (updates, prev = {}) {
@ -93,7 +98,6 @@ const User = {
where: { id: parseInt(userId) },
});
if (!currentUser) return { success: false, error: "User not found" };
// Removes non-writable fields for generic updates
// and force-casts to the proper type;
Object.entries(updates).forEach(([key, value]) => {
@ -123,6 +127,17 @@ const User = {
updates.password = bcrypt.hashSync(updates.password, 10);
}
if (
updates.hasOwnProperty("username") &&
currentUser.username !== updates.username &&
!this.usernameRegex.test(updates.username)
)
return {
success: false,
error:
"Username must be only contain lowercase letters, numbers, underscores, and hyphens with no spaces",
};
const user = await prisma.users.update({
where: { id: parseInt(userId) },
data: updates,
@ -170,7 +185,6 @@ const User = {
return null;
}
},
// Returns user object with all fields
_get: async function (clause = {}) {
try {

View File

@ -38,6 +38,7 @@ const endpointsFiles = [
"../endpoints/api/workspaceThread/index.js",
"../endpoints/api/userManagement/index.js",
"../endpoints/api/openai/index.js",
"../endpoints/api/embed/index.js",
];
swaggerAutogen(outputFile, endpointsFiles, doc).then(({ data }) => {

View File

@ -3140,6 +3140,214 @@
}
}
}
},
"/v1/embed": {
"get": {
"tags": [
"Embed"
],
"description": "List all active embeds",
"parameters": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"example": {
"embeds": [
{
"id": 1,
"uuid": "embed-uuid-1",
"enabled": true,
"chat_mode": "query",
"createdAt": "2023-04-01T12:00:00Z",
"workspace": {
"id": 1,
"name": "Workspace 1"
},
"chat_count": 10
},
{
"id": 2,
"uuid": "embed-uuid-2",
"enabled": false,
"chat_mode": "chat",
"createdAt": "2023-04-02T14:30:00Z",
"workspace": {
"id": 1,
"name": "Workspace 1"
},
"chat_count": 10
}
]
}
}
}
}
},
"403": {
"description": "Forbidden",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InvalidAPIKey"
}
},
"application/xml": {
"schema": {
"$ref": "#/components/schemas/InvalidAPIKey"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/v1/embed/{embedUuid}/chats": {
"get": {
"tags": [
"Embed"
],
"description": "Get all chats for a specific embed",
"parameters": [
{
"name": "embedUuid",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "UUID of the embed"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"example": {
"chats": [
{
"id": 1,
"session_id": "session-uuid-1",
"prompt": "Hello",
"response": "Hi there!",
"createdAt": "2023-04-01T12:00:00Z"
},
{
"id": 2,
"session_id": "session-uuid-2",
"prompt": "How are you?",
"response": "I'm doing well, thank you!",
"createdAt": "2023-04-02T14:30:00Z"
}
]
}
}
}
}
},
"403": {
"description": "Forbidden",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InvalidAPIKey"
}
},
"application/xml": {
"schema": {
"$ref": "#/components/schemas/InvalidAPIKey"
}
}
}
},
"404": {
"description": "Embed not found"
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/v1/embed/{embedUuid}/chats/{sessionUuid}": {
"get": {
"tags": [
"Embed"
],
"description": "Get chats for a specific embed and session",
"parameters": [
{
"name": "embedUuid",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "UUID of the embed"
},
{
"name": "sessionUuid",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "UUID of the session"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"example": {
"chats": [
{
"id": 1,
"prompt": "Hello",
"response": "Hi there!",
"createdAt": "2023-04-01T12:00:00Z"
}
]
}
}
}
}
},
"403": {
"description": "Forbidden",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InvalidAPIKey"
}
},
"application/xml": {
"schema": {
"$ref": "#/components/schemas/InvalidAPIKey"
}
}
}
},
"404": {
"description": "Embed or session not found"
},
"500": {
"description": "Internal Server Error"
}
}
}
}
},
"components": {

View File

@ -17,10 +17,12 @@ class GeminiLLM {
this.gemini = genAI.getGenerativeModel(
{ model: this.model },
{
// Gemini-1.5-pro and Gemini-1.5-flash are only available on the v1beta API.
apiVersion:
this.model === "gemini-1.5-pro-latest" ||
this.model === "gemini-1.5-flash-latest"
// Gemini-1.5-pro-* and Gemini-1.5-flash are only available on the v1beta API.
apiVersion: [
"gemini-1.5-pro-latest",
"gemini-1.5-flash-latest",
"gemini-1.5-pro-exp-0801",
].includes(this.model)
? "v1beta"
: "v1",
}

View File

@ -14,17 +14,6 @@ async function grepCommand(message, user = null) {
const userPresets = await SlashCommandPresets.getUserPresets(user?.id);
const availableCommands = Object.keys(VALID_COMMANDS);
// Check if the message starts with any preset command
const foundPreset = userPresets.find((p) => message.startsWith(p.command));
if (!!foundPreset) {
// Replace the preset command with the corresponding prompt
const updatedMessage = message.replace(
foundPreset.command,
foundPreset.prompt
);
return updatedMessage;
}
// Check if the message starts with any built-in command
for (let i = 0; i < availableCommands.length; i++) {
const cmd = availableCommands[i];
@ -34,7 +23,15 @@ async function grepCommand(message, user = null) {
}
}
return message;
// Replace all preset commands with their corresponding prompts
// Allows multiple commands in one message
let updatedMessage = message;
for (const preset of userPresets) {
const regex = new RegExp(preset.command, "g");
updatedMessage = updatedMessage.replace(regex, preset.prompt);
}
return updatedMessage;
}
async function chatWithWorkspace(

View File

@ -477,6 +477,12 @@ const KEY_MAPPING = {
envKey: "TTS_ELEVEN_LABS_VOICE_MODEL",
checks: [],
},
// PiperTTS Local
TTSPiperTTSVoiceModel: {
envKey: "TTS_PIPER_VOICE_MODEL",
checks: [],
},
};
function isNotEmpty(input = "") {
@ -536,7 +542,12 @@ function validOllamaLLMBasePath(input = "") {
}
function supportedTTSProvider(input = "") {
const validSelection = ["native", "openai", "elevenlabs"].includes(input);
const validSelection = [
"native",
"openai",
"elevenlabs",
"piper_local",
].includes(input);
return validSelection ? null : `${input} is not a valid TTS provider.`;
}