Alpha Commit

master
Harald Wolff 2024-04-26 14:59:57 +02:00
parent a03ed88240
commit c8f87a62b8
18 changed files with 1961 additions and 79 deletions

6
.gitmodules vendored 100644
View File

@ -0,0 +1,6 @@
[submodule "lnstyles"]
path = contrib/lnstyles
url = https://git.l--n.de/ln-www/lnstyles.git
[submodule "contrib/lnview"]
path = contrib/lnview
url = https://git.l--n.de/ln-www/lnview.git

6
.idea/vcs.xml 100644
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -45,7 +45,7 @@ export class AssistantConnection extends EventTarget {
message: text,
});
console.log("assistant: send", msg);
this._socket.send(msg);
this._socket.send(text);
}
}

153
authenticate.js 100644
View File

@ -0,0 +1,153 @@
export class LoginIdentity {
constructor(baseUrl) {
this._baseUrl = baseUrl;
this._username = null;
this._token = null;
}
get authenticated() { return (!!this._token); }
get username(){ return this._username; }
get token(){ return this._token; }
fetch(url, options={}){
if (!this._token)
throw "Unauthorized";
if (!options.headers)
options.headers = {}
options.headers["Authorization"] = "Token " + this._token;
return window.fetch(url, options)
.then(response => {
if (response.status === 401){
this._token = null;
this._username = null;
this.saveToken();
}
return response;
});
}
saveToken(){
let backup = { token: this._token, username: this._username };
localStorage.setItem("identity", JSON.stringify(backup));
}
restoreToken(){
let restore = JSON.parse(localStorage.getItem("identity") || '{ "token": null, "username": null }');
this._token = restore.token;
this._username = restore.username;
if (this._token){
this.fetch(this._baseUrl + "/v1/checkToken")
.then(response => {
if (!response.ok)
{
this._token = null;
this._username = null;
} else {
this._token = restore.token;
this._username = restore.username;
}
}, (reason)=>{
console.log(reason);
this._token = null;
this._username = null;
});
}
this._token = null;
this._username = null;
}
authenticate(username, password) {
return fetch(this._baseUrl + '/v1/login?username=' + encodeURIComponent(username))
.then((resp)=>resp.json())
.then((r)=>this.hashChallenge(password, r.salt, r.challenge))
.then((r)=>fetch(
this._baseUrl + '/v1/authenticate?username=' + username + '&response=' + encodeURIComponent(this.encodeBase64(r.response))))
.then((r)=>{
if (!r.ok)
throw "Unauthorized";
return r.json();
})
.then((r)=>{
this._username = r.username;
this._token = r.token;
this.saveToken();
})
;
}
decodeBase64(b64){
let bs = atob(b64);
let ui8 = new Uint8Array(bs.length);
for (let i = 0; i < ui8.length; i++)
ui8[i] = bs.charCodeAt(i);
return ui8;
}
encodeBase64(bytes){
const bs = Array.from(bytes, (byte) => String.fromCodePoint(byte), ).join("");
let b64 = btoa(bs);
return b64;
}
async hashPassword(password, salt) {
let bpassword = new TextEncoder().encode(password);
let bsalt = this.decodeBase64(salt);
return this.hash(bsalt, bpassword);
}
async hashChallenge(password, salt, challenge) {
let hashedPassword = await this.hashPassword(password, salt);
let bsalt = this.decodeBase64(salt);
let bchallenge = this.decodeBase64(challenge);
console.log("MMM", hashedPassword, bsalt, bchallenge);
const data = this.concatTypedArrays(bchallenge, hashedPassword, bchallenge);
let signatureBuffer = await this.hash(bsalt, data);
console.log("response", signatureBuffer);
return { challenge: challenge, response: signatureBuffer };
}
async hash(key, data){
const cryptoKey = await window.crypto.subtle.importKey(
"raw",
key,
{name: "HMAC", hash: "SHA-512"},
false,
["sign"]
);
// Konvertiere das Byte-Array in ein ArrayBuffer
const dataBuffer = data.buffer;
// Berechne den HMAC-SHA512-Hash
const signatureBuffer = await window.crypto.subtle.sign(
"HMAC",
cryptoKey,
dataBuffer
);
return new Uint8Array(signatureBuffer);
}
concatTypedArrays(...typedArrays) {
let concatLength = 0;
let offset = 0;
typedArrays.forEach((v)=>{ concatLength += v.length; });
let r = new Uint8Array(concatLength);
typedArrays.forEach((v)=>{
r.set(v, offset);
offset += v.length;
});
return r;
}
}

5
chat.css 100644
View File

@ -0,0 +1,5 @@
.bg-assistant {
background-color: #E3F6FF;
}
/*# sourceMappingURL=chat.css.map */

1
chat.css.map 100644
View File

@ -0,0 +1 @@
{"version":3,"sourceRoot":"","sources":["chat.scss"],"names":[],"mappings":"AAEA;EACI","file":"chat.css"}

188
chat.html 100644
View File

@ -0,0 +1,188 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="contrib/lnstyles/css/lnstyles.css">
<link rel="stylesheet" href="contrib/highlight/default.min.css">
<link rel="stylesheet" href="chat.css">
<link rel="stylesheet" href="code.css">
<link rel="icon" type="image/svg+xml" href="/contrib/lnstyles/svg/ln.svg">
<script type="application/javascript" src="./contrib/highlight/highlight.min.js"></script>
<title>ai.chat - local ai test chat</title>
</head>
<body class="p-4">
<div id="app" class="border-radius-4 h-100 display-flex flex-column" v-cloak>
<div class="flex-grow-0 p-2 bg-white border-radius-4 row w-100">
<div class="sz-4">
<h1>AI.chat</h1>
</div>
<div class="sz-8">
<div v-for="sysMessage in chatApp.sysMessages.slice(-3)"><small><pre>{{ sysMessage }}</pre></small></div>
</div>
</div>
<div v-if="!chatApp.identity.authenticated" class="flex-grow-1 fill">
<div class="row p-4" style="align-items: stretch;">
<div class="sz-12 text-center">Benutzername</div>
<div class="sz-12 text-center"><input type="text" v-model="loginUsername"></div>
<div class="sz-12 text-center">Passwort</div>
<div class="sz-12 text-center"><input type="password" v-model="loginPassword"></div>
<div class="sz-12 text-center"><button @click="authenticate">Anmelden...</button></div>
</div>
</div>
<div v-if="chatApp.identity.authenticated" class="flex-grow-1 fill">
<div class="row" style="align-items: stretch;">
<div class="sz-3 bg-white border-radius-2 h-100 overflow-auto">
<div class="">
<button @click="newChat();">neu...</button>
</div>
<div v-for="chat in chatApp.chats.slice().reverse()"
class="border-radius-1 border-1 row m-1 p-1"
:class="{ 'bg-selection': (chatApp.currentChat == chat), 'bg-lightblue': (chat.waiting) }"
@click="chatApp.currentChat = chat;"
>
<div class="sz-11 row">
<small>{{ new Date(chat.created).toLocaleString() }}</small>
<small v-if="chat.assistant"
class="flex-grow-1 text-right">{{ chat.assistant.name }}</small>
</div>
<div class="sz-1">
<img src="contrib/lnstyles/svg/delete.svg"
@click="chatApp.removeChat(chat);">
</div>
<div class="sz-12 text-overflow-ellipsis smaller">{{ chat.title }}</div>
</div>
</div>
<div class="sz-9 bg-white border-radius-2 display-flex flex-column h-100">
<div class="bg-gray p-2 m-4 border-radius-2 flex-grow-1 overflow-scroll" style="max-height: 100%;">
<div id="chatMessages">
<template v-for="chatMessage in chatApp.currentChat.messages">
<div
v-if="chatMessage.role !== 'system'"
class="border-radius-2 p-2 mb-2 position-relative"
:class="{ 'bg-lightgreen': (chatMessage.role === 'user'), 'ml-4': (chatMessage.role === 'user'), 'bg-assistant': (chatMessage.role === 'assistant'), 'mr-4': (chatMessage.role === 'assistant') }"
>
<img
class="position-absolute" style="width: 1em; right: 1em; top: 1em;"
src="contrib/lnstyles/svg/circle.svg"
@click="copyToClipboard($event);"
>
<div v-html="convertMarkdown(chatMessage.content)"></div>
</div>
</template>
</div>
</div>
<div class="p-1 flex-grow-0">
<div class="row">
<div class="sz-12 small">
<select v-model="chatApp.currentChat.assistant">
<option :value="null">-</option>
<option v-for="assistant in assistants"
:value="assistant"
>{{ assistant.name }}</option>
</select>
<span v-if="chatApp.currentChat.waiting">warte auf Antwort...</span>
<button v-if="chatApp.currentChat.chatting && !chatApp.currentChat.waiting" @click="chatApp.currentChat.rebuildChat();">nochmal versuchen...</button>
<button v-if="!chatApp.currentChat.chatting && !chatApp.currentChat.waiting && !chatApp.currentChat.empty" @click="chatApp.currentChat.rebuildChat();">wiederholen...</button>
</div>
<textarea :disabled="chatApp.currentChat.chatting || undefined"
class="sz-12 border-radius-1 p-2"
id="prompt"
v-model="currentMessageText"
style="min-height: 10em;"
@keypress="promptKeypress"
></textarea>
</div>
</div>
</div>
</div>
</div>
</div>
<script type="application/javascript" src="markdown-it.min.js"></script>
<script type="module">
import { createApp, reactive, nextTick } from "./contrib/lnstyles/js/vue.esm-browser.js";
import { ChatApp } from "./chat.js";
import { LNS } from "./contrib/lnstyles/js/lnstyles.js";
var $ = LNS;
let config = await fetch("config.json").then((r) => r.json());
let markdown = markdownit();
markdown.use((md) => {
const temp = md.renderer.rules.fence.bind(md.renderer.rules)
md.renderer.rules.fence = (tokens, idx, options, env, slf) => {
const token = tokens[idx]
const code = token.content.trim()
if (token.info.length > 0) {
return `<pre><code class="hljs">${hljs.highlightAuto(code, [token.info]).value}</code></pre>`
}
return temp(tokens, idx, options, env, slf)
}
});
var defaultRender = markdown.renderer.rules.link_open || function (tokens, idx, options, env, self) {
return self.renderToken(tokens, idx, options);
};
markdown.renderer.rules.link_open = function (tokens, idx, options, env, self) {
// Add a new `target` attribute, or replace the value of the existing one.
tokens[idx].attrSet('target', '_blank');
// Pass the token to the default renderer.
return defaultRender(tokens, idx, options, env, self);
};
var app = createApp({
data() {
let chatApp =new ChatApp();
reactive(chatApp).loginIdentity.restoreToken();
return {
chatApp: chatApp,
currentMessageText: "",
assistants: config.assistants,
loginUsername: "",
loginPassword: "",
};
},
methods: {
promptKeypress(evt) {
if ((evt.which === 13) && !evt.shiftKey) {
evt.preventDefault();
this.chatApp.chat(this.currentMessageText);
this.currentMessageText = "";
}
},
convertMarkdown(md) {
let mdHtml = markdown.render(md);
return mdHtml;
},
newChat() {
this.chatApp.newChat();
},
copyToClipboard(evt) {
let e = evt.target.parentElement;
navigator.clipboard.writeText(e.innerText)
.then(
() => {
console.log("copy succeded");
},
() => {
console.log("copy failed");
}
)
},
authenticate(){
console.log("authenticate", this.loginUsername);
this.chatApp.identity.authenticate(this.loginUsername, this.loginPassword);
}
}
})
.mount("#app");
</script>
</body>
</html>

266
chat.js 100644
View File

@ -0,0 +1,266 @@
import { LoginIdentity } from "./authenticate.js";
export class Chat {
constructor(chatApp, backup) {
this._chatApp = chatApp;
this._created = new Date().getTime();
this._messages = [];
this._waiting = false;
this._assistant = null;
if (backup){
if (backup.created)
this._created = backup.created;
if (backup.messages)
this._messages = backup.messages;
if (backup.assistant)
this._assistant = backup.assistant;
for (let n=0; n<this._messages.length; n++){
if (!this._messages[n] || !this._messages[n].role || !this._messages[n].content) {
this._messages.splice(n,1);
}
}
}
}
backup(){
return { created: this._created, messages: this._messages, assistant: this._assistant };
}
get title(){
let t = null;
if (this._messages[0] && this._messages[0].role !== "system")
t = this._messages[0].content;
if (!t && this._messages[1] && this._messages[1].role !== "system")
t = this._messages[1].content;
if (!t)
t = "";
// if (t.length > 32)
// return t.slice(0, 30) + "...";
return t;
}
get created(){ return this._created; }
get messages(){ return Array.from(this._messages); }
chat(content){
if (!content)
return;
this.pushMessage({
role: "user", content,
});
this._chatApp.saveChats();
this.chatRequest();
}
chatRequest(){
this._waiting = true;
this._chatApp
.chatRequest(this)
.then((assistantMessage)=>{
this.pushMessage(assistantMessage);
this._chatApp.saveChats();
})
.finally(()=>{
this._waiting = false;
this._chatApp.scrollLastMessage();
});
this._chatApp.scrollLastMessage();
}
pushMessage(message){
if (!message || !message.role || !message.content){
this._chatApp.log("pushMessage() failed. " + message);
} else {
this._messages.push(message);
}
this._chatApp.saveChats();
}
get empty(){
return this._messages.length === (this.assistant ? 1 : 0);
}
get chatting(){
let lastMessage = this._messages.at(-1);
return (lastMessage && (lastMessage.role === "user"));
}
get waiting(){ return this._waiting; }
rebuildChat(){
if (!this._waiting){
let lastMessage = this._messages.at(-1);
while (lastMessage && lastMessage.role === "assistant"){
this._messages.pop();
lastMessage = this._messages.at(-1);
}
this.chatRequest();
}
}
get tools(){
return undefined;
}
get assistant(){ return this._assistant; }
set assistant(assistant){
this._assistant = assistant;
if (this._assistant) {
let systemPrompt = {role: "system", content: assistant.prompt};
if (this._messages.length) {
if (this._messages[0].role !== "system") {
this._messages.splice(0, 0, systemPrompt);
} else {
this._messages[0].content = systemPrompt.content;
}
} else {
this._messages.push(systemPrompt);
}
}
this._chatApp.saveChats();
}
}
export class ChatApp {
constructor(opts) {
window.$chatApp = this;
this.opts = opts;
this.chats = [];
this.baseUri = location.origin;
this.sysMessages = [];
this.loginIdentity = new LoginIdentity(this.baseUri);
this.restoreChats();
if (!this.chats.length) {
this.chats.push(new Chat(this));
}
this.activeChat = this.chats.at(-1);
}
log(message) {
this.sysMessages.push(message);
}
get identity() {
return this.loginIdentity;
}
restoreChats() {
let backup = JSON.parse(localStorage.getItem("ai.chats"));
if (backup && backup.chats) {
backup.chats.forEach((v) => {
this.chats.push(new Chat(this, v));
});
}
}
saveChats() {
let backup = {chats: [],};
this.chats.forEach((v) => {
backup.chats.push(v.backup());
});
localStorage.setItem("ai.chats", JSON.stringify(backup));
}
chat(content) {
if (this.activeChat) {
this.activeChat.chat(content);
}
}
get currentChat() {
return this.activeChat;
}
set currentChat(currentChat) {
if (this.chats.includes(currentChat)) {
this.activeChat = currentChat;
}
}
chatRequest(chat) {
return this.identity.fetch(this.baseUri + "/v1/chat/completions", {
method: "POST",
headers: {
"Accept": "application/json",
"Content-Type": "application/json; charset=UTF-8",
},
body: JSON.stringify({
messages: chat.messages,
})
}).then((response) => {
return this.fetchWithRetry(response.url + "?wait=true", 15);
})
.then(
(response) => {
if (response.ok) {
response.json()
.then((json) => {
let assistantMessage = json.choices[0].message;
if (!assistantMessage) {
this.log(`chatRequest failed with: ${JSON.stringify(json)}`);
} else {
chat.pushMessage(assistantMessage);
}
});
}
this.log(`chatRequest (I) failed with: ${response}`);
},
(reason) => {
this.log(`chatRequest (II) failed with: ${reason}`);
}).catch(error => {
this.log(`chatRequest (III) failed with: ${error}`);
console.error(error);
});
}
newChat() {
this.chats.push(new Chat(this));
this.activeChat = this.chats.at(-1);
}
scrollLastMessage() {
setTimeout(() => {
document.querySelector("#chatMessages > div:last-of-type").scrollIntoView({behavior: "smooth"});
}, 200);
}
removeChat(chat) {
let idxChat = this.chats.indexOf(chat);
if (idxChat >= 0)
this.chats.splice(idxChat, 1);
this.saveChats();
}
async fetchWithRetry(url, maxRetries = 3) {
let retries = 0;
while (retries < maxRetries) {
const response = await this.identity.fetch(url);
if (response.status !== 204) {
return response;
}
retries++;
console.log(`No content received (Status 204). Retrying... (${retries}/${maxRetries})`);
// Warte für 1 Sekunde, bevor du es erneut versuchst.
await new Promise(resolve => setTimeout(resolve, 1000));
}
throw new Error(`Failed to get content after ${maxRetries} retries.`);
}
}

5
chat.scss 100644
View File

@ -0,0 +1,5 @@
.bg-assistant {
background-color: #E3F6FF;
}

42
code.css 100644
View File

@ -0,0 +1,42 @@
code {
display: inline-block;
background-color: black;
color: #CCC;
white-space: pre-wrap;
border-radius: 0.25em;
padding: 0.15em;
margin: 0.1em;
}
code.hljs {
background-color: black;
color: #CCC;
padding: 0.5em;
border-radius: 0.5em;
}
code.hljs .hljs-punctuation {
color: #A0A0A0;
}
code.hljs .hljs-keyword {
font-weight: bold;
color: rgb(86, 156, 214);
}
code.hljs .hljs-function {
color: #D0D0D0;
}
code.hljs .hljs-built_in {
color: #49b749;
}
code.hljs .hljs-title {
color: #DBDBAA;
}
code.hljs .hljs-string {
color: #bc6060;
}
code.hljs .hljs-number {
color: #bc6060;
}
code.hljs .hljs-type {
color: #4d99bf;
}
/*# sourceMappingURL=code.css.map */

1
code.css.map 100644
View File

@ -0,0 +1 @@
{"version":3,"sourceRoot":"","sources":["code.scss"],"names":[],"mappings":"AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EAEA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE","file":"code.css"}

52
code.scss 100644
View File

@ -0,0 +1,52 @@
code {
display: inline-block;
background-color: black;
color: #CCC;
white-space: pre-wrap;
border-radius: 0.25em;
padding: 0.15em;
margin: 0.1em;
&.hljs {
background-color: black;
color: #CCC;
padding: 0.5em;
border-radius: 0.5em;
.hljs-punctuation {
color: #A0A0A0;
}
.hljs-keyword {
font-weight: bold;
color: rgb(86, 156, 214);
}
.hljs-function {
color: #D0D0D0;
}
.hljs-built_in {
color: #49b749;
}
.hljs-title {
color: #DBDBAA;
}
.hljs-string {
color: #bc6060;
}
.hljs-number {
color: #bc6060;
}
.hljs-type {
color: #4d99bf;
}
}
}

View File

@ -1,4 +1,13 @@
{
"websocket": "ws://localhost:6565",
"api_key": null
"api_key": null,
"assistants": [
{ "name": "KI Assistent", "prompt": "Du bist ein neutraler und objektiver KI Assistent. Deine Antworten formatierst Du in Markdown." },
{ "name": "Therapeut", "prompt": "Du bist ein einfühlsamer und kompetenter Psychotherapeut, der durch einfühlsame und empathische Fragen dem Benutzer hilft, seine Gefühle zu verstehen und seelische Probleme zu überwinden." },
{ "name": "Ingenieur", "prompt": "Du bist ein hochqualifizierter Ingenieur, welcher technische Aufgaben, Probleme, Fragestellungen und Zusammenhänge erläutert bzw. erklärt." },
{ "name": "Anwalt", "prompt": "\n\nDu bist ein erfahrener Anwalt mit umfangreichem Fachwissen in verschiedenen Rechtsgebieten. Du hast eine langjährige Erfahrung in der Beratung von Mandanten und der Vertretung ihrer Interessen vor Gericht. Du bist ein guter Zuhörer und hast die Fähigkeit, komplexe juristische Sachverhalte einfach und verständlich zu erklären. Du bist ein professioneller und ethischer Anwalt, der stets die Interessen deiner Mandanten im Blick hat und ihnen eine ehrliche und kompetente Beratung bietet.\n\nDeine Aufgabe ist es, Mandanten in verschiedenen Rechtsgebieten zu beraten und zu vertreten. Dazu gehört die Analyse von Sachverhalten, die Erstellung von Verträgen und anderen Dokumenten, die Vorbereitung von Gerichtsverfahren und die Vertretung von Mandanten vor Gericht. Du bist auch für die Kommunikation mit Mandanten, Gerichten und anderen Beteiligten verantwortlich und sorgst dafür, dass alle relevanten Informationen ausgetauscht werden.\n\nAls Anwalt bist du stets auf dem neuesten Stand der Rechtsprechung und der Gesetzgebung und hast ein tiefes Verständnis für die verschiedenen Rechtsgebiete. Du bist in der Lage, deine Mandanten über die möglichen Risiken und Chancen in ihren Fällen aufzuklären und ihnen eine fundierte Entscheidungsgrundlage zu bieten.\n\nDu bist ein Teamplayer und arbeitest eng mit anderen Anwälten, Juristen und Mitarbeitern zusammen, um die bestmöglichen Ergebnisse für deine Mandanten zu erzielen. Du bist auch bereit, deine Mandanten über die Kosten und Gebühren deiner Dienstleistungen aufzuklären und ihnen eine transparente und faire Preisgestaltung zu bieten.\n\nDein Ziel ist es, deine Mandanten bestmöglich zu vertreten und ihnen eine professionelle, ethische und erfolgreiche Rechtsberatung zu bieten."},
{ "name": "Pflegeberater", "prompt": "Du bist ein hochqualifizierter und erfahrener Pflegeberater, der Menschen dabei hilft, die bestmögliche Pflege für ihre individuellen Bedürfnisse zu finden. Deine Aufgabe besteht darin, Menschen mit Empathie, Verständnis und Fachwissen zu beraten und zu unterstützen. Du bist ein guter Zuhörer und stellst sicher, dass du die Bedürfnisse und Bedenken deiner Klienten verstehst, bevor du Empfehlungen gibst. Du bist stets auf dem neuesten Stand der Pflegebranche und kennst die neuesten Technologien, Methoden und Ressourcen, die zur Verbesserung der Pflege beitragen können. Du bist ein professioneller und vertrauenswürdiger Berater, der sicherstellen möchte, dass deine Klienten die bestmögliche Pflege erhalten, um ihr Wohlbefinden und ihre Lebensqualität zu verbessern." },
{ "name": "Jugendlicher Gamer", "prompt": "Du bist eine 14 jährige Gamerin, die neben der Schule ihre komplette Freizeit am PC verbringt um zu spielen. Deine Rechtschreibung ist schlecht bis mangelhaft und deine Sprachkenntnisse sind nur marginal."},
{ "name": "Erweiterter Assistent", "prompt": "Du bist ein KI Assistent mit der Fähigkeit bei Bedarf entweder externe Funktionen aufzurufen, dem Benutzer zu antworten oder weitere notwendige Informationen vom Benutzer abzufragen. \nErsetze \"Funktionsname\" durch den Namen der Funktion, die du aufrufen möchtest, und \"Argumente\" durch die entsprechenden Argumente, sofern die Funktion diese benötigt.\nDie folgenden Funktionen stehen Dir zur Verfügung (Prototypen in C#):\nfloat get_temp() // Liefert die aktuelle Aussentemperatur.\nstring[] web_search(string query) // Sucht in einer Suchmaschine nach relevanten URLs.\nstring customer_authenticate(string customerno, string supportpassword) // Gibt null oder einen Token zurück, wenn das korrekte Kundenpasswort füpr eine Kundennummer angegeben wurde.\n\nErgebnisse bisheriger Funktionsaufrufe erhälst Du in diesem Format:\n<|result|>{ \"function\": Funktionsname, \"result\": Ergebnis }\nWenn du eine Funktion aufrufen möchtest, verwende die folgende Syntax:\n<|call|>{\"function\":Funktionsname, \"arguments\": { argumente }\n\n1. Entscheide ob eine Funktion aufgerufen werden muss.\n2. Wenn ja, dann antworte ausschliesslich mit dem Funktionsaufruf gemäß der Vorlage ansonsten beantworte die Fragestellung ohne eine weitere Funktion aufzurufen.\n"}
]
}

View File

@ -0,0 +1,9 @@
/*!
Theme: Default
Description: Original highlight.js style
Author: (c) Ivan Sagalaev <maniac@softwaremaniacs.org>
Maintainer: @highlightjs/core-team
Website: https://highlightjs.org/
License: see project LICENSE
Touched: 2021
*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#f0f0f0;color:#444}.hljs-comment{color:#888}.hljs-punctuation,.hljs-tag{color:#444a}.hljs-tag .hljs-attr,.hljs-tag .hljs-name{color:#444}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#bc6060}.hljs-literal{color:#78a960}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

1213
contrib/highlight/highlight.min.js vendored 100644

File diff suppressed because one or more lines are too long

View File

@ -8,7 +8,7 @@
<title>ai.chat - local ai test chat</title>
</head>
<body class="p-4">
<div id="app" class="container-md bg-white border-radius-4 h-100 display-flex flex-column">
<div id="app" class="container-md bg-white border-radius-4 h-100 display-flex flex-column" v->
<div class="flex-grow-0 p-2">
<h1>AI.chat</h1>
</div>
@ -50,7 +50,6 @@
return defaultRender(tokens, idx, options, env, self);
};
var app = createApp({
data(){
return {
@ -74,7 +73,8 @@
},
appendMessage(message){
this.chatMessages.push(message)
}
},
}
})
.mount("#app");

View File

@ -1,74 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="contrib/lnstyles/css/lnstyles.css">
<link rel="icon" type="image/svg+xml" href="/contrib/lnstyles/svg/ln.svg">
<script type="application/javascript" src="showdown.min.js"></script>
<title>ai.chat - local ai test chat</title>
</head>
<body class="p-4">
<div class="container-md bg-white border-radius-4 h-100 display-flex flex-column">
<div class="flex-grow-0">
<h1>AI.chat</h1>
</div>
<div class="bg-gray p-2 m-4 border-radius-2 overflow-auto flex-grow-1">
<div id="chat"></div>
</div>
<div class="p-2 flex-grow-0">
<div class="row">
<textarea class="sz-12" id="prompt" style="min-height: 10em;"></textarea>
<button class="sz-2" id="submit">SENDEN</button>
</div>
</div>
</div>
<script type="module">
import { LNS } from "./contrib/lnstyles/js/lnstyles.js";
var $ = LNS;
const markdown = new showdown.Converter();
var model = "mixtral";
var messages = [];
function chat(){
let prompt = $("#prompt").value();
if (prompt === "")
return;
$("#chat").append($("<div class='bg-lightgreen border-radius-1 m-1 p-2 ml-4 bg-white pre'></div>").text(prompt));
let rm = Array.from(messages);
rm.push({
role: "user",
content: prompt,
});
fetch("http://localhost:8080/v1/chat/completions",{
method: "POST",
headers: {
"content-type": "application/json"
},
body: JSON.stringify({ model, messages: rm }),
})
.then((r)=>r.json())
.then((j)=>{
messages.push({
role: "user",
content: prompt,
});
messages.push(j.choices[0].message);
let hmsg = $("<div class='bg-lightblue border-radius-1 m-1 p-2 mr-4 bg-lightgray pre'></div>").html(markdown.makeHtml(j.choices[0].message.content));
$("#chat").append(hmsg);
hmsg.scrollIntoView();
console.log(j, messages);
})
}
$("#submit").on("click", chat);
</script>
</body>
</html>

BIN
lnstyles.zip 100644

Binary file not shown.