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 = { | const config: ForgeConfig = { | ||||||
|   packagerConfig: { |   packagerConfig: { | ||||||
|     asar: true, |     asar: true, | ||||||
|  |     icon: "/src/assets/images/icon.png", // no file extension required
 | ||||||
|   }, |   }, | ||||||
|   rebuildConfig: {}, |   rebuildConfig: {}, | ||||||
|   makers: [ |   makers: [ | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ | |||||||
|   <head> |   <head> | ||||||
|     <meta charset="UTF-8" /> |     <meta charset="UTF-8" /> | ||||||
|     <title>Open WebUI Assistant</title> |     <title>Open WebUI Assistant</title> | ||||||
|  |     <link rel="icon" type="image/png" href="./src/assets/images/icon.png" /> | ||||||
|   </head> |   </head> | ||||||
|   <body> |   <body> | ||||||
|     <div id="app"></div> |     <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 url = ref('http://localhost:3000') | ||||||
| const token = ref('your_jwt') | const token = ref('your_jwt') | ||||||
| 
 | 
 | ||||||
| const submitHandler = async () => { | const models = ref([]) | ||||||
|     console.log(url.value, token.value) | const selectedModel = ref('') | ||||||
| 
 | 
 | ||||||
|  | const saveHandler = async () => { | ||||||
|  |     console.log(url.value, token.value) | ||||||
| 
 | 
 | ||||||
|     if (url.value.endsWith('/')) { |     if (url.value.endsWith('/')) { | ||||||
|         url.value = url.value.substring(0, url.value.length - 1); |         url.value = url.value.substring(0, url.value.length - 1); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     window.electron.saveConfig({ |     window.electron.saveConfig({ | ||||||
|         url: url.value, |         url: url.value, | ||||||
|         token: token.value |         token: token.value | ||||||
|     }) |     }) | ||||||
| 
 |  | ||||||
|     const res = await window.electron.checkConnection() |     const res = await window.electron.checkConnection() | ||||||
| 
 | 
 | ||||||
|     new Notification("Open WebUI", { body: res ? 'Server Connection Verified' : 'Server Connection Failed' }) |     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 |         url.value = res.url | ||||||
|         token.value = res.token |         token.value = res.token | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <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=" my-auto w-full flex flex-col gap-2"> | ||||||
|             <div class="flex justify-between items-center"> |             <div class="flex justify-between items-center"> | ||||||
|                 <div class=" text-sm font-semibold">Open WebUI Assistant</div> |                 <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" |                 <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> | ||||||
|             <div class="flex flex-col gap-1.5"> |             <div class="flex flex-col gap-1.5"> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|                 <input v-model="url" |                 <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" /> |                     placeholder="Open WebUI URL" /> | ||||||
|                 <input v-model="token" |                 <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" /> |                     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> |             </div> | ||||||
| @ -76,4 +109,16 @@ onBeforeMount(async () => { | |||||||
| 
 | 
 | ||||||
| <style> | <style> | ||||||
| @import './index.css'; | @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> | </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; | keyboard.config.autoDelayMs = 0; | ||||||
| 
 | 
 | ||||||
| let WEBUI_VERSION: string | null = null; | let WEBUI_VERSION: string | null = null; | ||||||
|  | let models: object[] = []; | ||||||
| 
 | 
 | ||||||
|  | let selectedModel = ""; | ||||||
| let config = { | let config = { | ||||||
|   url: "", |   url: "", | ||||||
|   token: "", |   token: "", | ||||||
|   model: "", |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| // Handle creating/removing shortcuts on Windows when installing/uninstalling.
 | // Handle creating/removing shortcuts on Windows when installing/uninstalling.
 | ||||||
| @ -29,8 +30,9 @@ if (require("electron-squirrel-startup")) { | |||||||
| const createWindow = () => { | const createWindow = () => { | ||||||
|   // Create the browser window.
 |   // Create the browser window.
 | ||||||
|   const mainWindow = new BrowserWindow({ |   const mainWindow = new BrowserWindow({ | ||||||
|  |     icon: "/src/assets/images/icon.png", | ||||||
|     width: 300, |     width: 300, | ||||||
|     height: 150, |     height: 180, | ||||||
|     webPreferences: { |     webPreferences: { | ||||||
|       preload: path.join(__dirname, "preload.js"), |       preload: path.join(__dirname, "preload.js"), | ||||||
|     }, |     }, | ||||||
| @ -74,7 +76,7 @@ const generateResponse = async (prompt: string) => { | |||||||
|       Authorization: `Bearer ${config.token}`, |       Authorization: `Bearer ${config.token}`, | ||||||
|     }, |     }, | ||||||
|     body: JSON.stringify({ |     body: JSON.stringify({ | ||||||
|       model: "mistral:latest", |       model: selectedModel, | ||||||
|       messages: [ |       messages: [ | ||||||
|         { |         { | ||||||
|           role: "user", |           role: "user", | ||||||
| @ -148,7 +150,7 @@ const shortcutHandler = async () => { | |||||||
|   const prompt = await clipboard.readText(); |   const prompt = await clipboard.readText(); | ||||||
|   console.log(prompt); |   console.log(prompt); | ||||||
| 
 | 
 | ||||||
|   if (config.url !== "" && config.token !== "") { |   if (config.url !== "" && config.token !== "" && selectedModel !== "") { | ||||||
|     keyboard.config.autoDelayMs = 0; |     keyboard.config.autoDelayMs = 0; | ||||||
| 
 | 
 | ||||||
|     await generateResponse(prompt); |     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
 | // This method will be called when Electron has finished
 | ||||||
| // initialization and is ready to create browser windows.
 | // initialization and is ready to create browser windows.
 | ||||||
| // Some APIs can only be used after this event occurs.
 | // Some APIs can only be used after this event occurs.
 | ||||||
| @ -200,7 +242,14 @@ app | |||||||
|       return WEBUI_VERSION !== null; |       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) => { |     ipcMain.handle("load-config", (event, arg) => { | ||||||
|       return config; |       return config; | ||||||
|  | |||||||
| @ -9,6 +9,8 @@ contextBridge.exposeInMainWorld("electron", { | |||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   checkConnection: () => ipcRenderer.invoke("check-connection"), |   checkConnection: () => ipcRenderer.invoke("check-connection"), | ||||||
|  |   getModels: () => ipcRenderer.invoke("get-models"), | ||||||
|  |   selectModel: (modelId) => ipcRenderer.invoke("select-model", modelId), | ||||||
|   loadConfig: () => ipcRenderer.invoke("load-config"), |   loadConfig: () => ipcRenderer.invoke("load-config"), | ||||||
|   saveConfig: (data) => ipcRenderer.send("save-config", data), |   saveConfig: (data) => ipcRenderer.send("save-config", data), | ||||||
| }); | }); | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user