Add basic functionality
This commit is contained in:
+52
@@ -1,5 +1,57 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import Input from './components/Input.vue';
|
||||
import {load, Store} from "@tauri-apps/plugin-store";
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
const initialized = ref(false);
|
||||
const ollama_url = ref("");
|
||||
const ollama_api_key = ref("");
|
||||
|
||||
const home_page=ref(false);
|
||||
|
||||
async function submit(ev){
|
||||
ev.preventDefault();
|
||||
await store.set("url",ollama_url.value);
|
||||
await store.set("api-key",ollama_api_key.value);
|
||||
await store.save();
|
||||
try {
|
||||
await invoke("connect_ollama",{url: ollama_url.value, apiKey: ollama_api_key.value});
|
||||
} catch(e){
|
||||
window.alert("Verbindung nicht möglich: "+e);
|
||||
return;
|
||||
}
|
||||
home_page.value=true;
|
||||
}
|
||||
let store: Store;
|
||||
|
||||
async function init(){
|
||||
store = await load('store.json', { autoSave: false });
|
||||
if (await store.has("url")){
|
||||
ollama_url.value=await store.get("url");
|
||||
}
|
||||
if (await store.has("api-key")){
|
||||
ollama_api_key.value=await store.get("api-key");
|
||||
}
|
||||
initialized.value=true;
|
||||
}
|
||||
init();
|
||||
</script>
|
||||
<template>
|
||||
<main class="w-screen h-screen overflow-hidden flex items-center justify-center p-10" v-if="initialized">
|
||||
<div class="w-full h-full border rounded-2xl transition-all ease-in-out duration-200 p-6 overflow-y-scroll">
|
||||
<div v-if="!home_page">
|
||||
<h1>Moin!</h1>
|
||||
<p>Mit dieser App kannst du verschiedene Modelle über die Ollama API ausprobieren und vergleichen.</p>
|
||||
<form @submit="submit">
|
||||
<Input placeholder="Ollama URL (z.B. http://localhost:11434)" class="w-full" v-model="ollama_url"/> <br>
|
||||
<Input placeholder="API Key (optional)" class="w-full" type="password" v-model="ollama_api_key"/> <br>
|
||||
<br>
|
||||
<input value="Weiter" class="w-full border" type="submit"/>
|
||||
</form>
|
||||
</div>
|
||||
<RouterView v-else/>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</template>
|
||||
@@ -1 +1 @@
|
||||
@import "tailwindcss";
|
||||
@import "tailwindcss"
|
||||
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts"></script>
|
||||
<template>
|
||||
<RouterLink to="/">Zurück zur Startseite</RouterLink>
|
||||
<h1>Context Window einstellen</h1>
|
||||
Das Context Window muss beim Start des Ollama Servers als Environment-Variable gesetzt werden. Mehr Informationen <a href="https://docs.ollama.com/context-length" target="_blank" class="underline text-blue-700">hier.</a>
|
||||
|
||||
</template>
|
||||
@@ -0,0 +1,66 @@
|
||||
<script setup lang="ts">
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const initialized=ref(false);
|
||||
const models=ref([]);
|
||||
const model=ref("");
|
||||
const new_model=ref("");
|
||||
const information=ref(undefined);
|
||||
const system_prompt=ref("");
|
||||
|
||||
async function init(){
|
||||
models.value=await invoke("get_models");
|
||||
|
||||
initialized.value=true;
|
||||
}
|
||||
init();
|
||||
|
||||
async function load_model(localmodel){
|
||||
model.value=localmodel;
|
||||
let info;
|
||||
try{
|
||||
info=await invoke("get_model_info",{model: model.value});
|
||||
} catch(e){
|
||||
window.alert("Das funktioniert mit diesem Modell leider nicht.");
|
||||
return
|
||||
}
|
||||
|
||||
information.value={parameters: info.parameters, template: info.template}
|
||||
|
||||
console.log(information.value)
|
||||
}
|
||||
|
||||
async function save(){
|
||||
|
||||
try {
|
||||
window.alert("Antwort von Ollama: " + await invoke("create_model",{fromModel: model.value, template: information.value.template, systemPrompt: system_prompt.value, newModel: new_model.value}))
|
||||
} catch(e){
|
||||
window.alert("Das hat nicht geklappt. Fehlermeldung: "+e)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<RouterLink to="/">Zurück zur Startseite</RouterLink>
|
||||
<h1>Modell bearbeiten</h1>
|
||||
<p>Diese Funktion ist leider nicht für alle Modelle verfügbar.</p>
|
||||
<div v-if="initialized">
|
||||
<ul v-if="information==undefined">
|
||||
<li @click="load_model(model.name)" v-for="model in models">{{ model.name }}</li>
|
||||
</ul>
|
||||
<form v-else @submit.prevent="save">
|
||||
<button @click="information=undefined">Zurück zur Modellauswahl</button>
|
||||
<textarea v-model="information.template" placeholder="Prompt Template" class="w-full"></textarea>
|
||||
<textarea v-model="system_prompt" placeholder="System Prompt" class="w-full"></textarea>
|
||||
<input type="text" placeholder="Neuer Modellname" v-model="new_model">
|
||||
<input type="submit" value="Speichern"/>
|
||||
</form>
|
||||
</div>
|
||||
<div v-else>
|
||||
Rufe die Liste der Modelle bei Ollama ab...
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
@@ -0,0 +1,45 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import EmbeddingVisualizer from './EmbeddingVisualizer.vue';
|
||||
import PromptInput from './PromptInput.vue';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
const dataset=ref([]);
|
||||
|
||||
const model = ref("");
|
||||
const model_options = ref([]);
|
||||
const prompt = ref("");
|
||||
|
||||
async function execute_prompt(ev){
|
||||
ev.preventDefault();
|
||||
let embedding;
|
||||
try{
|
||||
embedding=await invoke("prompt_embedding",{model: model.value, prompt: prompt.value});
|
||||
}catch(e){
|
||||
window.alert("Fehler beim Ausführen des Prompts: "+e);
|
||||
}
|
||||
if(embedding!=undefined){
|
||||
dataset.value.push({vector: embedding, label: prompt.value});
|
||||
prompt.value="";
|
||||
}
|
||||
}
|
||||
|
||||
async function init(){
|
||||
model_options.value=await invoke("get_models")
|
||||
}
|
||||
init();
|
||||
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<RouterLink to="/">Zurück zur Startseite</RouterLink>
|
||||
<EmbeddingVisualizer :dataset="dataset"/>
|
||||
<select class="w-[500px]" v-model="model">
|
||||
<option value="" disabled></option>
|
||||
<option :value="m.name" v-for="m in model_options">{{ m.name }}</option>
|
||||
</select>
|
||||
<button class="border ml-2" @click="dataset=[]">Punkte löschen</button>
|
||||
<form @submit="execute_prompt">
|
||||
<PromptInput v-model="prompt"/>
|
||||
</form>
|
||||
</template>
|
||||
@@ -0,0 +1,153 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { UMAP } from 'umap-js';
|
||||
import { onMounted, ref, useTemplateRef, watch } from "vue";
|
||||
const canvas = useTemplateRef("canvas");
|
||||
|
||||
const {dataset} = defineProps({
|
||||
dataset: {
|
||||
type: Array<{vector: Array<number>, label: String}>,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
// Simple PCA-like projection
|
||||
function project(data) {
|
||||
if (data.length === 0) return [];
|
||||
|
||||
const vectors = data.map((d) => d.vector);
|
||||
const dims = vectors[0].length;
|
||||
|
||||
// Center the data
|
||||
const means = Array(dims).fill(0);
|
||||
vectors.forEach((v) => v.forEach((val, i) => (means[i] += val)));
|
||||
means.forEach((m, i) => (means[i] = m / vectors.length));
|
||||
|
||||
const centered = vectors.map((v) => v.map((val, i) => val - means[i]));
|
||||
|
||||
// Simple projection using first two "principal" directions
|
||||
// (This is a simplified version - real PCA would compute eigenvectors)
|
||||
const proj = centered.map((v) => {
|
||||
const x = v.reduce(
|
||||
(sum, val, i) => sum + val * (i % 2 === 0 ? 1 : -0.5),
|
||||
0,
|
||||
);
|
||||
const y = v.reduce(
|
||||
(sum, val, i) => sum + val * (i % 2 === 1 ? 1 : -0.5),
|
||||
0,
|
||||
);
|
||||
return [x, y];
|
||||
});
|
||||
|
||||
// Normalize to [0, 1]
|
||||
const minX = Math.min(...proj.map((p) => p[0]));
|
||||
const maxX = Math.max(...proj.map((p) => p[0]));
|
||||
const minY = Math.min(...proj.map((p) => p[1]));
|
||||
const maxY = Math.max(...proj.map((p) => p[1]));
|
||||
|
||||
const rangeX = maxX - minX || 1;
|
||||
const rangeY = maxY - minY || 1;
|
||||
|
||||
return proj.map((p) => [(p[0] - minX) / rangeX, (p[1] - minY) / rangeY]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function draw(ctx, width, height, pointSize, showConnections, showLabels, points) {
|
||||
let projected=project(points);
|
||||
|
||||
const padding = 50;
|
||||
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
// Draw grid
|
||||
ctx.strokeStyle = document.documentElement.classList.contains("dark")
|
||||
? "#374151"
|
||||
: "#e5e7eb";
|
||||
ctx.lineWidth = 1;
|
||||
for (let i = 0; i <= 10; i++) {
|
||||
const x = padding + ((width - 2 * padding) * i) / 10;
|
||||
const y = padding + ((height - 2 * padding) * i) / 10;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, padding);
|
||||
ctx.lineTo(x, height - padding);
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(padding, y);
|
||||
ctx.lineTo(width - padding, y);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Draw connections to nearest neighbors
|
||||
if (showConnections && projected.length > 1) {
|
||||
ctx.strokeStyle = "#94a3b8";
|
||||
ctx.lineWidth = 1;
|
||||
projected.forEach((p, i) => {
|
||||
// Find nearest neighbor
|
||||
let minDist = Infinity;
|
||||
let nearest = -1;
|
||||
projected.forEach((q, j) => {
|
||||
if (i !== j) {
|
||||
const dist = Math.hypot(p[0] - q[0], p[1] - q[1]);
|
||||
if (dist < minDist) {
|
||||
minDist = dist;
|
||||
nearest = j;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (nearest >= 0) {
|
||||
const x1 = padding + p[0] * (width - 2 * padding);
|
||||
const y1 = padding + p[1] * (height - 2 * padding);
|
||||
const x2 = padding + projected[nearest][0] * (width - 2 * padding);
|
||||
const y2 = padding + projected[nearest][1] * (height - 2 * padding);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x1, y1);
|
||||
ctx.lineTo(x2, y2);
|
||||
ctx.stroke();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Draw points
|
||||
projected.forEach((p, i) => {
|
||||
const x = padding + p[0] * (width - 2 * padding);
|
||||
const y = padding + p[1] * (height - 2 * padding);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, pointSize, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
// Draw labels
|
||||
if (showLabels) {
|
||||
ctx.fillStyle = document.documentElement.classList.contains("dark")
|
||||
? "#e5e7eb"
|
||||
: "#374151";
|
||||
ctx.font = "12px Inter, sans-serif";
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(points[i].label, x, y - pointSize - 5);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
let ctx;
|
||||
|
||||
watch(()=>dataset, async (old_value, new_value)=>{
|
||||
draw(ctx,500,500,3,false,true, new_value);
|
||||
}, {deep: true});
|
||||
|
||||
|
||||
onMounted(()=>{
|
||||
ctx=canvas.value.getContext("2d");
|
||||
draw(ctx,500,500,3,false,true, dataset);
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<canvas ref="canvas" class="w-[500px] h-[500px] border border-gray-200 rounded-lg bg-gray-50" width="500" height="500"></canvas>
|
||||
</template>
|
||||
@@ -0,0 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
<template>
|
||||
<main>
|
||||
<h1>Home</h1>
|
||||
<RouterLink to="/pull">Modelle runterladen</RouterLink> <br>
|
||||
<RouterLink to="/embedding">Embedding-Modelle</RouterLink> <br>
|
||||
<RouterLink to="/normal">"Normale" Modelle</RouterLink> <br>
|
||||
<RouterLink to="/context">Context Window einstellen</RouterLink> <br>
|
||||
<RouterLink to="/edit">Modell bearbeiten</RouterLink> <br>
|
||||
</main>
|
||||
</template>
|
||||
@@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
const model=defineModel();
|
||||
const {placeholder, type} = defineProps({
|
||||
"placeholder": {
|
||||
type: String
|
||||
},
|
||||
"type": {
|
||||
type: String
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<input :placeholder="placeholder" v-model="model" :type="type" class="border rounded-md my-1"/>
|
||||
</template>
|
||||
@@ -0,0 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
import Input from './Input.vue';
|
||||
let {placeholder} = defineProps({
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "Prompt"
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<Input class="rounded-full w-full border-2 p-4 text-xl" :placeholder="placeholder"/>
|
||||
</template>
|
||||
@@ -0,0 +1,60 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import PromptInput from './PromptInput.vue';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
|
||||
const prompt=ref("");
|
||||
const response=ref("");
|
||||
const thinking_response=ref("");
|
||||
const model=ref("");
|
||||
const model_options=ref([]);
|
||||
const thinking=ref(true);
|
||||
const temperature=ref(0.2);
|
||||
|
||||
|
||||
async function submit(ev){
|
||||
ev.preventDefault();
|
||||
response.value="Generiere eine Antwort..."
|
||||
thinking_response.value="";
|
||||
|
||||
try {
|
||||
let output:any=await invoke("prompt_normal",{model: model.value, prompt: prompt.value, thinking: thinking.value, temperature: temperature.value});
|
||||
response.value=output.response;
|
||||
thinking_response.value=output.thinking;
|
||||
}catch(e){
|
||||
window.alert("Fehler beim Generieren der Antwort: "+e);
|
||||
}
|
||||
|
||||
prompt.value="";
|
||||
|
||||
}
|
||||
|
||||
|
||||
async function init(){
|
||||
model_options.value=await invoke("get_models");
|
||||
}
|
||||
init();
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<RouterLink to="/">Zurück zur Startseite</RouterLink>
|
||||
<br>
|
||||
<br>
|
||||
<i class="whitespace-pre-line">{{ thinking_response }}</i> <br>
|
||||
<br>
|
||||
<div class="whitespace-pre-line">{{ response }}</div>
|
||||
<br>
|
||||
|
||||
<form @submit="submit">
|
||||
|
||||
<select class="w-[500px]" v-model="model">
|
||||
<option value="" disabled></option>
|
||||
<option :value="m.name" v-for="m in model_options">{{ m.name }}</option>
|
||||
</select>
|
||||
Thinking: <input type="checkbox" v-model="thinking">
|
||||
Temperature: <input type="number" v-model="temperature" max="1" min="0" step="0.01" class="w-16 border">
|
||||
<PromptInput v-model="prompt"/>
|
||||
<input type="submit" value="Antwort generieren" class="border rounded-md p-2 cursor-pointer">
|
||||
</form>
|
||||
</template>
|
||||
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { openUrl } from '@tauri-apps/plugin-opener';
|
||||
</script>
|
||||
<template>
|
||||
<RouterLink to="/">Zurück zur Startseite</RouterLink>
|
||||
<br><br>
|
||||
Gehe auf <span class="text-blue-600 underline cursor-pointer" @click="openUrl('https://ollama.com/search')">https://ollama.com/search</span> und suche dir das Modell, was du haben möchtest, raus. <br>
|
||||
Dann öffne ein Terminal und führe "ollama pull {modellname}" aus.
|
||||
</template>
|
||||
+4
-1
@@ -1,5 +1,8 @@
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
import "./assets/style.css"
|
||||
import router from "./router"
|
||||
|
||||
createApp(App).mount("#app");
|
||||
createApp(App)
|
||||
.use(router)
|
||||
.mount("#app");
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
import HomeView from '../components/HomeView.vue'
|
||||
import EmbeddingPrompt from '../components/EmbeddingPrompt.vue'
|
||||
import PullModelsView from '../components/PullModelsView.vue'
|
||||
import PromptView from '../components/PromptView.vue'
|
||||
import ContextView from '../components/ContextView.vue'
|
||||
import EditView from '../components/EditView.vue'
|
||||
|
||||
const routes = [
|
||||
{ path: '/', component: HomeView },
|
||||
{ path: '/normal', component: PromptView },
|
||||
{ path: '/pull', component: PullModelsView },
|
||||
{ path: '/embedding', component: EmbeddingPrompt },
|
||||
{ path: '/context', component: ContextView },
|
||||
{ path: '/edit', component: EditView },
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes,
|
||||
})
|
||||
|
||||
export default router
|
||||
Reference in New Issue
Block a user