mirror of
				https://github.com/open-webui/assistant
				synced 2025-06-26 18:15:57 +00:00 
			
		
		
		
	feat: model select
This commit is contained in:
		
							parent
							
								
									51d339b8a7
								
							
						
					
					
						commit
						847d47e17c
					
				| @ -10,6 +10,7 @@ import { FuseV1Options, FuseVersion } from "@electron/fuses"; | ||||
| const config: ForgeConfig = { | ||||
|   packagerConfig: { | ||||
|     asar: true, | ||||
|     icon: "/src/assets/images/icon.png", // no file extension required
 | ||||
|   }, | ||||
|   rebuildConfig: {}, | ||||
|   makers: [ | ||||
|  | ||||
| @ -3,6 +3,7 @@ | ||||
|   <head> | ||||
|     <meta charset="UTF-8" /> | ||||
|     <title>Open WebUI Assistant</title> | ||||
|     <link rel="icon" type="image/png" href="./src/assets/images/icon.png" /> | ||||
|   </head> | ||||
|   <body> | ||||
|     <div id="app"></div> | ||||
|  | ||||
							
								
								
									
										69
									
								
								src/App.vue
									
									
									
									
									
								
							
							
						
						
									
										69
									
								
								src/App.vue
									
									
									
									
									
								
							| @ -6,25 +6,38 @@ import { ref, onBeforeMount } from 'vue'; | ||||
| const url = ref('http://localhost:3000') | ||||
| const token = ref('your_jwt') | ||||
| 
 | ||||
| const submitHandler = async () => { | ||||
|     console.log(url.value, token.value) | ||||
| const models = ref([]) | ||||
| const selectedModel = ref('') | ||||
| 
 | ||||
| const saveHandler = async () => { | ||||
|     console.log(url.value, token.value) | ||||
| 
 | ||||
|     if (url.value.endsWith('/')) { | ||||
|         url.value = url.value.substring(0, url.value.length - 1); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     window.electron.saveConfig({ | ||||
|         url: url.value, | ||||
|         token: token.value | ||||
|     }) | ||||
| 
 | ||||
|     const res = await window.electron.checkConnection() | ||||
| 
 | ||||
|     new Notification("Open WebUI", { body: res ? 'Server Connection Verified' : 'Server Connection Failed' }) | ||||
| 
 | ||||
|     console.log(res) | ||||
|     if (res) { | ||||
|         models.value = await window.electron.getModels() | ||||
| 
 | ||||
|         console.log(models.value) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const selectModelHandler = async () => { | ||||
|     console.log(selectedModel.value) | ||||
| 
 | ||||
|     if (selectedModel.value) { | ||||
|         selectedModel.value = await window.electron.selectModel(selectedModel.value) | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| @ -36,33 +49,53 @@ onBeforeMount(async () => { | ||||
|         url.value = res.url | ||||
|         token.value = res.token | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|     <div class=" h-screen w-screen px-3 flex justify-center"> | ||||
|     <div class=" h-screen w-screen p-3 flex justify-center"> | ||||
|         <div class=" my-auto w-full flex flex-col gap-2"> | ||||
|             <div class="flex justify-between items-center"> | ||||
|                 <div class=" text-sm font-semibold">Open WebUI Assistant</div> | ||||
| 
 | ||||
|                 <button class="bg-neutral-700 hover:bg-neutral-800 transition text-white text-xs px-3 py-1 rounded-lg" | ||||
|                     @click="submitHandler">Save</button> | ||||
|                     @click="saveHandler">Save</button> | ||||
|             </div> | ||||
|             <div class="flex flex-col gap-1.5"> | ||||
| 
 | ||||
| 
 | ||||
|                 <input v-model="url" | ||||
|                     class=" w-full bg-gray-100 hover:bg-gray-200 transition rounded-lg py-1 px-2 text-sm outline-none" | ||||
|                     class=" w-full bg-gray-100 hover:bg-gray-200 transition rounded-lg py-1 px-2 text-xs outline-none" | ||||
|                     placeholder="Open WebUI URL" /> | ||||
|                 <input v-model="token" | ||||
|                     class=" w-full bg-gray-100 hover:bg-gray-200 transition rounded-lg py-1 px-2 text-sm outline-none" | ||||
|                     class=" w-full bg-gray-100 hover:bg-gray-200 transition rounded-lg py-1 px-2 text-xs outline-none" | ||||
|                     placeholder="Open WebUI Token" /> | ||||
| 
 | ||||
|                 <hr /> | ||||
| 
 | ||||
|                 <div class="flex gap-1"> | ||||
| 
 | ||||
| 
 | ||||
|                     <select v-model="selectedModel" | ||||
|                         class=" w-full bg-gray-100 hover:bg-gray-200 text-xs text-gray-700 transition rounded-lg py-1 px-2 outline-none"> | ||||
|                         <option value="" disabled class="text-xs text-gray-200">Select a model</option> | ||||
|                         <option v-for="model in models" v-bind:value="model.name">{{ model.name }}</option> | ||||
|                     </select> | ||||
| 
 | ||||
|                     <button class="p-1 bg-gray-100 hover:bg-gray-200 transition rounded-lg" @click="selectModelHandler"> | ||||
| 
 | ||||
|                         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="w-4 h-4"> | ||||
|                             <path fill-rule="evenodd" | ||||
|                                 d="M12.416 3.376a.75.75 0 0 1 .208 1.04l-5 7.5a.75.75 0 0 1-1.154.114l-3-3a.75.75 0 0 1 1.06-1.06l2.353 2.353 4.493-6.74a.75.75 0 0 1 1.04-.207Z" | ||||
|                                 clip-rule="evenodd" /> | ||||
|                         </svg> | ||||
| 
 | ||||
|                     </button> | ||||
| 
 | ||||
|                 </div> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|             </div> | ||||
| @ -76,4 +109,16 @@ onBeforeMount(async () => { | ||||
| 
 | ||||
| <style> | ||||
| @import './index.css'; | ||||
| 
 | ||||
| select { | ||||
|     background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236B7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E"); | ||||
|     background-position: right 0.5rem center; | ||||
|     background-repeat: no-repeat; | ||||
|     background-size: 1.5em 1.5em; | ||||
|     padding-right: 2.5rem; | ||||
|     -webkit-print-color-adjust: exact; | ||||
|     print-color-adjust: exact; | ||||
|     /* for Chrome */ | ||||
|     -webkit-appearance: none; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										
											BIN
										
									
								
								src/assets/images/icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/assets/images/icon.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 12 KiB | 
							
								
								
									
										59
									
								
								src/main.ts
									
									
									
									
									
								
							
							
						
						
									
										59
									
								
								src/main.ts
									
									
									
									
									
								
							| @ -14,11 +14,12 @@ import { splitStream, sleep } from "./utils"; | ||||
| keyboard.config.autoDelayMs = 0; | ||||
| 
 | ||||
| let WEBUI_VERSION: string | null = null; | ||||
| let models: object[] = []; | ||||
| 
 | ||||
| let selectedModel = ""; | ||||
| let config = { | ||||
|   url: "", | ||||
|   token: "", | ||||
|   model: "", | ||||
| }; | ||||
| 
 | ||||
| // Handle creating/removing shortcuts on Windows when installing/uninstalling.
 | ||||
| @ -29,8 +30,9 @@ if (require("electron-squirrel-startup")) { | ||||
| const createWindow = () => { | ||||
|   // Create the browser window.
 | ||||
|   const mainWindow = new BrowserWindow({ | ||||
|     icon: "/src/assets/images/icon.png", | ||||
|     width: 300, | ||||
|     height: 150, | ||||
|     height: 180, | ||||
|     webPreferences: { | ||||
|       preload: path.join(__dirname, "preload.js"), | ||||
|     }, | ||||
| @ -74,7 +76,7 @@ const generateResponse = async (prompt: string) => { | ||||
|       Authorization: `Bearer ${config.token}`, | ||||
|     }, | ||||
|     body: JSON.stringify({ | ||||
|       model: "mistral:latest", | ||||
|       model: selectedModel, | ||||
|       messages: [ | ||||
|         { | ||||
|           role: "user", | ||||
| @ -148,7 +150,7 @@ const shortcutHandler = async () => { | ||||
|   const prompt = await clipboard.readText(); | ||||
|   console.log(prompt); | ||||
| 
 | ||||
|   if (config.url !== "" && config.token !== "") { | ||||
|   if (config.url !== "" && config.token !== "" && selectedModel !== "") { | ||||
|     keyboard.config.autoDelayMs = 0; | ||||
| 
 | ||||
|     await generateResponse(prompt); | ||||
| @ -189,6 +191,46 @@ const getVersion = async () => { | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| const getModels = async () => { | ||||
|   if (config.url) { | ||||
|     const res = await fetch(`${config.url}/ollama/api/tags`, { | ||||
|       method: "GET", | ||||
|       headers: { | ||||
|         "Content-Type": "application/json", | ||||
|         Authorization: `Bearer ${config.token}`, | ||||
|       }, | ||||
|     }) | ||||
|       .then(async (res) => { | ||||
|         if (!res.ok) throw await res.json(); | ||||
|         return res.json(); | ||||
|       }) | ||||
|       .catch((err) => { | ||||
|         console.log(err); | ||||
|         return null; | ||||
|       }); | ||||
| 
 | ||||
|     console.log(res); | ||||
| 
 | ||||
|     if (res) { | ||||
|       return res.models; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   return null; | ||||
| }; | ||||
| 
 | ||||
| const selectModel = async (modelId) => { | ||||
|   console.log(modelId); | ||||
|   selectedModel = modelId; | ||||
| 
 | ||||
|   new Notification({ | ||||
|     title: "Open WebUI", | ||||
|     body: `'${modelId}' selected.`, | ||||
|   }).show(); | ||||
| 
 | ||||
|   return selectedModel; | ||||
| }; | ||||
| 
 | ||||
| // This method will be called when Electron has finished
 | ||||
| // initialization and is ready to create browser windows.
 | ||||
| // Some APIs can only be used after this event occurs.
 | ||||
| @ -200,7 +242,14 @@ app | ||||
|       return WEBUI_VERSION !== null; | ||||
|     }); | ||||
| 
 | ||||
|     ipcMain.handle("get-models", (event, arg) => {}); | ||||
|     ipcMain.handle("get-models", async (event, arg) => { | ||||
|       models = await getModels(); | ||||
|       return models; | ||||
|     }); | ||||
| 
 | ||||
|     ipcMain.handle("select-model", async (event, modelId) => { | ||||
|       return await selectModel(modelId); | ||||
|     }); | ||||
| 
 | ||||
|     ipcMain.handle("load-config", (event, arg) => { | ||||
|       return config; | ||||
|  | ||||
| @ -9,6 +9,8 @@ contextBridge.exposeInMainWorld("electron", { | ||||
|   }, | ||||
| 
 | ||||
|   checkConnection: () => ipcRenderer.invoke("check-connection"), | ||||
|   getModels: () => ipcRenderer.invoke("get-models"), | ||||
|   selectModel: (modelId) => ipcRenderer.invoke("select-model", modelId), | ||||
|   loadConfig: () => ipcRenderer.invoke("load-config"), | ||||
|   saveConfig: (data) => ipcRenderer.send("save-config", data), | ||||
| }); | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user