llms-py 2.0.8__py3-none-any.whl → 2.0.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. llms.py +144 -13
  2. llms_py-2.0.10.data/data/index.html +80 -0
  3. {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/llms.json +16 -10
  4. llms_py-2.0.10.data/data/ui/Avatar.mjs +28 -0
  5. llms_py-2.0.10.data/data/ui/Brand.mjs +23 -0
  6. {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/ChatPrompt.mjs +101 -69
  7. {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/Main.mjs +43 -183
  8. llms_py-2.0.10.data/data/ui/ModelSelector.mjs +29 -0
  9. llms_py-2.0.10.data/data/ui/ProviderStatus.mjs +105 -0
  10. {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/Recents.mjs +2 -1
  11. llms_py-2.0.10.data/data/ui/SettingsDialog.mjs +374 -0
  12. {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/Sidebar.mjs +11 -27
  13. llms_py-2.0.10.data/data/ui/SignIn.mjs +64 -0
  14. llms_py-2.0.10.data/data/ui/SystemPromptEditor.mjs +31 -0
  15. llms_py-2.0.10.data/data/ui/SystemPromptSelector.mjs +36 -0
  16. llms_py-2.0.10.data/data/ui/Welcome.mjs +8 -0
  17. llms_py-2.0.10.data/data/ui/ai.mjs +80 -0
  18. {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/app.css +76 -10
  19. llms_py-2.0.10.data/data/ui/lib/servicestack-vue.mjs +37 -0
  20. {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/markdown.mjs +9 -2
  21. {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/tailwind.input.css +13 -4
  22. {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/threadStore.mjs +2 -2
  23. {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/typography.css +109 -1
  24. {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/utils.mjs +8 -2
  25. {llms_py-2.0.8.dist-info → llms_py-2.0.10.dist-info}/METADATA +124 -39
  26. llms_py-2.0.10.dist-info/RECORD +40 -0
  27. llms_py-2.0.8.data/data/index.html +0 -64
  28. llms_py-2.0.8.data/data/ui/lib/servicestack-vue.min.mjs +0 -37
  29. llms_py-2.0.8.dist-info/RECORD +0 -30
  30. {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/requirements.txt +0 -0
  31. {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/App.mjs +0 -0
  32. {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/fav.svg +0 -0
  33. {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/lib/highlight.min.mjs +0 -0
  34. {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/lib/idb.min.mjs +0 -0
  35. {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/lib/marked.min.mjs +0 -0
  36. /llms_py-2.0.8.data/data/ui/lib/servicestack-client.min.mjs → /llms_py-2.0.10.data/data/ui/lib/servicestack-client.mjs +0 -0
  37. {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/lib/vue-router.min.mjs +0 -0
  38. {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/lib/vue.min.mjs +0 -0
  39. {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui.json +0 -0
  40. {llms_py-2.0.8.dist-info → llms_py-2.0.10.dist-info}/WHEEL +0 -0
  41. {llms_py-2.0.8.dist-info → llms_py-2.0.10.dist-info}/entry_points.txt +0 -0
  42. {llms_py-2.0.8.dist-info → llms_py-2.0.10.dist-info}/licenses/LICENSE +0 -0
  43. {llms_py-2.0.8.dist-info → llms_py-2.0.10.dist-info}/top_level.txt +0 -0
llms.py CHANGED
@@ -14,6 +14,7 @@ import mimetypes
14
14
  import traceback
15
15
  import sys
16
16
  import site
17
+ from urllib.parse import parse_qs
17
18
 
18
19
  import aiohttp
19
20
  from aiohttp import web
@@ -21,7 +22,7 @@ from aiohttp import web
21
22
  from pathlib import Path
22
23
  from importlib import resources # Py≥3.9 (pip install importlib_resources for 3.7/3.8)
23
24
 
24
- VERSION = "2.0.8"
25
+ VERSION = "2.0.10"
25
26
  _ROOT = None
26
27
  g_config_path = None
27
28
  g_ui_path = None
@@ -63,7 +64,8 @@ def chat_summary(chat):
63
64
  elif 'file' in item:
64
65
  if 'file_data' in item['file']:
65
66
  data = item['file']['file_data']
66
- item['file']['file_data'] = f"({len(data)})"
67
+ prefix = data.split(',', 1)[0]
68
+ item['file']['file_data'] = prefix + f",({len(data) - len(prefix)})"
67
69
  return json.dumps(clone, indent=2)
68
70
 
69
71
  def gemini_chat_summary(gemini_chat):
@@ -89,6 +91,60 @@ def is_url(url):
89
91
  def get_filename(file):
90
92
  return file.rsplit('/',1)[1] if '/' in file else 'file'
91
93
 
94
+ def parse_args_params(args_str):
95
+ """Parse URL-encoded parameters and return a dictionary."""
96
+ if not args_str:
97
+ return {}
98
+
99
+ # Parse the URL-encoded string
100
+ parsed = parse_qs(args_str, keep_blank_values=True)
101
+
102
+ # Convert to simple dict with single values (not lists)
103
+ result = {}
104
+ for key, values in parsed.items():
105
+ if len(values) == 1:
106
+ value = values[0]
107
+ # Try to convert to appropriate types
108
+ if value.lower() == 'true':
109
+ result[key] = True
110
+ elif value.lower() == 'false':
111
+ result[key] = False
112
+ elif value.isdigit():
113
+ result[key] = int(value)
114
+ else:
115
+ try:
116
+ # Try to parse as float
117
+ result[key] = float(value)
118
+ except ValueError:
119
+ # Keep as string
120
+ result[key] = value
121
+ else:
122
+ # Multiple values, keep as list
123
+ result[key] = values
124
+
125
+ return result
126
+
127
+ def apply_args_to_chat(chat, args_params):
128
+ """Apply parsed arguments to the chat request."""
129
+ if not args_params:
130
+ return chat
131
+
132
+ # Apply each parameter to the chat request
133
+ for key, value in args_params.items():
134
+ if isinstance(value, str):
135
+ if key == 'stop':
136
+ if ',' in value:
137
+ value = value.split(',')
138
+ elif key == 'max_completion_tokens' or key == 'max_tokens' or key == 'n' or key == 'seed' or key == 'top_logprobs':
139
+ value = int(value)
140
+ elif key == 'temperature' or key == 'top_p' or key == 'frequency_penalty' or key == 'presence_penalty':
141
+ value = float(value)
142
+ elif key == 'store' or key == 'logprobs' or key == 'enable_thinking' or key == 'parallel_tool_calls' or key == 'stream':
143
+ value = bool(value)
144
+ chat[key] = value
145
+
146
+ return chat
147
+
92
148
  def is_base_64(data):
93
149
  try:
94
150
  base64.b64decode(data)
@@ -190,8 +246,9 @@ async def process_chat(chat):
190
246
  content = f.read()
191
247
  file['filename'] = get_filename(url)
192
248
  file['file_data'] = f"data:{mimetype};base64,{base64.b64encode(content).decode('utf-8')}"
193
- elif is_base_64(url):
194
- file['filename'] = 'file'
249
+ elif url.startswith('data:'):
250
+ if 'filename' not in file:
251
+ file['filename'] = 'file'
195
252
  pass # use base64 data as-is
196
253
  else:
197
254
  raise Exception(f"Invalid file: {url}")
@@ -232,6 +289,25 @@ class OpenAiProvider:
232
289
  if api_key is not None:
233
290
  self.headers["Authorization"] = f"Bearer {api_key}"
234
291
 
292
+ self.frequency_penalty = float(kwargs['frequency_penalty']) if 'frequency_penalty' in kwargs else None
293
+ self.max_completion_tokens = int(kwargs['max_completion_tokens']) if 'max_completion_tokens' in kwargs else None
294
+ self.n = int(kwargs['n']) if 'n' in kwargs else None
295
+ self.parallel_tool_calls = bool(kwargs['parallel_tool_calls']) if 'parallel_tool_calls' in kwargs else None
296
+ self.presence_penalty = float(kwargs['presence_penalty']) if 'presence_penalty' in kwargs else None
297
+ self.prompt_cache_key = kwargs['prompt_cache_key'] if 'prompt_cache_key' in kwargs else None
298
+ self.reasoning_effort = kwargs['reasoning_effort'] if 'reasoning_effort' in kwargs else None
299
+ self.safety_identifier = kwargs['safety_identifier'] if 'safety_identifier' in kwargs else None
300
+ self.seed = int(kwargs['seed']) if 'seed' in kwargs else None
301
+ self.service_tier = kwargs['service_tier'] if 'service_tier' in kwargs else None
302
+ self.stop = kwargs['stop'] if 'stop' in kwargs else None
303
+ self.store = bool(kwargs['store']) if 'store' in kwargs else None
304
+ self.temperature = float(kwargs['temperature']) if 'temperature' in kwargs else None
305
+ self.top_logprobs = int(kwargs['top_logprobs']) if 'top_logprobs' in kwargs else None
306
+ self.top_p = float(kwargs['top_p']) if 'top_p' in kwargs else None
307
+ self.verbosity = kwargs['verbosity'] if 'verbosity' in kwargs else None
308
+ self.stream = bool(kwargs['stream']) if 'stream' in kwargs else None
309
+ self.enable_thinking = bool(kwargs['enable_thinking']) if 'enable_thinking' in kwargs else None
310
+
235
311
  @classmethod
236
312
  def test(cls, base_url=None, api_key=None, models={}, **kwargs):
237
313
  return base_url is not None and api_key is not None and len(models) > 0
@@ -247,6 +323,41 @@ class OpenAiProvider:
247
323
  # with open(os.path.join(os.path.dirname(__file__), 'chat.wip.json'), "w") as f:
248
324
  # f.write(json.dumps(chat, indent=2))
249
325
 
326
+ if self.frequency_penalty is not None:
327
+ chat['frequency_penalty'] = self.frequency_penalty
328
+ if self.max_completion_tokens is not None:
329
+ chat['max_completion_tokens'] = self.max_completion_tokens
330
+ if self.n is not None:
331
+ chat['n'] = self.n
332
+ if self.parallel_tool_calls is not None:
333
+ chat['parallel_tool_calls'] = self.parallel_tool_calls
334
+ if self.presence_penalty is not None:
335
+ chat['presence_penalty'] = self.presence_penalty
336
+ if self.prompt_cache_key is not None:
337
+ chat['prompt_cache_key'] = self.prompt_cache_key
338
+ if self.reasoning_effort is not None:
339
+ chat['reasoning_effort'] = self.reasoning_effort
340
+ if self.safety_identifier is not None:
341
+ chat['safety_identifier'] = self.safety_identifier
342
+ if self.seed is not None:
343
+ chat['seed'] = self.seed
344
+ if self.service_tier is not None:
345
+ chat['service_tier'] = self.service_tier
346
+ if self.stop is not None:
347
+ chat['stop'] = self.stop
348
+ if self.store is not None:
349
+ chat['store'] = self.store
350
+ if self.temperature is not None:
351
+ chat['temperature'] = self.temperature
352
+ if self.top_logprobs is not None:
353
+ chat['top_logprobs'] = self.top_logprobs
354
+ if self.top_p is not None:
355
+ chat['top_p'] = self.top_p
356
+ if self.verbosity is not None:
357
+ chat['verbosity'] = self.verbosity
358
+ if self.enable_thinking is not None:
359
+ chat['enable_thinking'] = self.enable_thinking
360
+
250
361
  chat = await process_chat(chat)
251
362
  _log(f"POST {self.chat_url}")
252
363
  _log(chat_summary(chat))
@@ -333,7 +444,14 @@ class GoogleProvider(OpenAiProvider):
333
444
  async with aiohttp.ClientSession() as session:
334
445
  for message in chat['messages']:
335
446
  if message['role'] == 'system':
336
- system_prompt = message
447
+ content = message['content']
448
+ if isinstance(content, list):
449
+ for item in content:
450
+ if 'text' in item:
451
+ system_prompt = item['text']
452
+ break
453
+ elif isinstance(content, str):
454
+ system_prompt = content
337
455
  elif 'content' in message:
338
456
  if isinstance(message['content'], list):
339
457
  parts = []
@@ -409,7 +527,7 @@ class GoogleProvider(OpenAiProvider):
409
527
  # Add system instruction if present
410
528
  if system_prompt is not None:
411
529
  gemini_chat['systemInstruction'] = {
412
- "parts": [{"text": system_prompt['content']}]
530
+ "parts": [{"text": system_prompt}]
413
531
  }
414
532
 
415
533
  if 'stop' in chat:
@@ -537,10 +655,14 @@ async def chat_completion(chat):
537
655
  # If we get here, all providers failed
538
656
  raise first_exception
539
657
 
540
- async def cli_chat(chat, image=None, audio=None, file=None, raw=False):
658
+ async def cli_chat(chat, image=None, audio=None, file=None, args=None, raw=False):
541
659
  if g_default_model:
542
660
  chat['model'] = g_default_model
543
661
 
662
+ # Apply args parameters to chat request
663
+ if args:
664
+ chat = apply_args_to_chat(chat, args)
665
+
544
666
  # process_chat downloads the image, just adding the reference here
545
667
  if image is not None:
546
668
  first_message = None
@@ -925,6 +1047,7 @@ def main():
925
1047
  parser.add_argument('--image', default=None, help='Image input to use in chat completion')
926
1048
  parser.add_argument('--audio', default=None, help='Audio input to use in chat completion')
927
1049
  parser.add_argument('--file', default=None, help='File input to use in chat completion')
1050
+ parser.add_argument('--args', default=None, help='URL-encoded parameters to add to chat request (e.g. "temperature=0.7&seed=111")', metavar='PARAMS')
928
1051
  parser.add_argument('--raw', action='store_true', help='Return raw AI JSON response')
929
1052
 
930
1053
  parser.add_argument('--list', action='store_true', help='Show list of enabled providers and their models (alias ls provider?)')
@@ -1141,7 +1264,7 @@ def main():
1141
1264
  app.router.add_route('*', '/{tail:.*}', index_handler)
1142
1265
 
1143
1266
  if os.path.exists(g_ui_path):
1144
- async def ui_json_handler(request):
1267
+ async def ui_config_handler(request):
1145
1268
  with open(g_ui_path, "r") as f:
1146
1269
  ui = json.load(f)
1147
1270
  if 'defaults' not in ui:
@@ -1153,7 +1276,7 @@ def main():
1153
1276
  "disabled": disabled
1154
1277
  }
1155
1278
  return web.json_response(ui)
1156
- app.router.add_get('/ui.json', ui_json_handler)
1279
+ app.router.add_get('/config', ui_config_handler)
1157
1280
 
1158
1281
  print(f"Starting server on port {port}...")
1159
1282
  web.run_app(app, host='0.0.0.0', port=port)
@@ -1256,13 +1379,21 @@ def main():
1256
1379
  if len(extra_args) > 0:
1257
1380
  prompt = ' '.join(extra_args)
1258
1381
  # replace content of last message if exists, else add
1259
- last_msg = chat['messages'][-1]
1260
- if last_msg['role'] == 'user':
1261
- last_msg['content'] = prompt
1382
+ last_msg = chat['messages'][-1] if 'messages' in chat else None
1383
+ if last_msg and last_msg['role'] == 'user':
1384
+ if isinstance(last_msg['content'], list):
1385
+ last_msg['content'][-1]['text'] = prompt
1386
+ else:
1387
+ last_msg['content'] = prompt
1262
1388
  else:
1263
1389
  chat['messages'].append({'role': 'user', 'content': prompt})
1264
1390
 
1265
- asyncio.run(cli_chat(chat, image=cli_args.image, audio=cli_args.audio, file=cli_args.file, raw=cli_args.raw))
1391
+ # Parse args parameters if provided
1392
+ args = None
1393
+ if cli_args.args is not None:
1394
+ args = parse_args_params(cli_args.args)
1395
+
1396
+ asyncio.run(cli_chat(chat, image=cli_args.image, audio=cli_args.audio, file=cli_args.file, args=args, raw=cli_args.raw))
1266
1397
  exit(0)
1267
1398
  except Exception as e:
1268
1399
  print(f"{cli_args.logprefix}Error: {e}")
@@ -0,0 +1,80 @@
1
+ <html>
2
+ <head>
3
+ <title>llms.py</title>
4
+ <link rel="stylesheet" href="/ui/typography.css">
5
+ <link rel="stylesheet" href="/ui/app.css">
6
+ <link rel="icon" type="image/svg" href="/ui/fav.svg">
7
+ <style>
8
+ [type='button'],button[type='submit']{cursor:pointer}
9
+ [type='checkbox'].switch:checked:hover,
10
+ [type='checkbox'].switch:checked:focus,
11
+ [type='checkbox'].switch:checked,
12
+ [type='checkbox'].switch:focus,
13
+ [type='checkbox'].switch
14
+ {
15
+ border: none;
16
+ background: none;
17
+ outline: none;
18
+ box-shadow: none;
19
+ cursor: pointer;
20
+ }
21
+ </style>
22
+ </head>
23
+ <script type="importmap">
24
+ {
25
+ "imports": {
26
+ "vue": "/ui/lib/vue.min.mjs",
27
+ "vue-router": "/ui/lib/vue-router.min.mjs",
28
+ "@servicestack/client": "/ui/lib/servicestack-client.mjs",
29
+ "@servicestack/vue": "/ui/lib/servicestack-vue.mjs",
30
+ "idb": "/ui/lib/idb.min.mjs",
31
+ "marked": "/ui/lib/marked.min.mjs",
32
+ "highlight.js": "/ui/lib/highlight.min.mjs"
33
+ }
34
+ }
35
+ </script>
36
+ <body>
37
+ <div id="app"></div>
38
+ </body>
39
+ <script type="module">
40
+ import { createApp, defineAsyncComponent } from 'vue'
41
+ import { createWebHistory, createRouter } from "vue-router"
42
+ import ServiceStackVue from "@servicestack/vue"
43
+ import App from '/ui/App.mjs'
44
+ import ai from '/ui/ai.mjs'
45
+ import SettingsDialog from '/ui/SettingsDialog.mjs'
46
+
47
+ const { config, models } = await ai.init()
48
+ const MainComponent = defineAsyncComponent(() => import(ai.base + '/ui/Main.mjs'))
49
+ const RecentsComponent = defineAsyncComponent(() => import(ai.base + '/ui/Recents.mjs'))
50
+
51
+ const Components = {
52
+ SettingsDialog,
53
+ }
54
+
55
+ const routes = [
56
+ { path: '/', component: MainComponent },
57
+ { path: '/c/:id', component: MainComponent },
58
+ { path: '/recents', component: RecentsComponent },
59
+ { path: '/:fallback(.*)*', component: MainComponent }
60
+ ]
61
+ routes.forEach(r => r.path = ai.base + r.path)
62
+ const router = createRouter({
63
+ history: createWebHistory(),
64
+ routes,
65
+ })
66
+ const app = createApp(App, { config, models })
67
+ app.use(router)
68
+ app.use(ServiceStackVue)
69
+ app.provide('ai', ai)
70
+ app.provide('config', config)
71
+ app.provide('models', models)
72
+ Object.keys(Components).forEach(name => {
73
+ app.component(name, Components[name])
74
+ })
75
+
76
+ window.ai = app.config.globalProperties.$ai = ai
77
+
78
+ app.mount('#app')
79
+ </script>
80
+ </html>
@@ -9,7 +9,12 @@
9
9
  "messages": [
10
10
  {
11
11
  "role": "user",
12
- "content": ""
12
+ "content": [
13
+ {
14
+ "type": "text",
15
+ "text": ""
16
+ }
17
+ ]
13
18
  }
14
19
  ]
15
20
  },
@@ -90,10 +95,8 @@
90
95
  "deepseek-r1:671b": "deepseek/deepseek-r1-0528:free",
91
96
  "gemini-2.0-flash": "google/gemini-2.0-flash-exp:free",
92
97
  "glm-4.5-air": "z-ai/glm-4.5-air:free",
93
- "grok-4-fast": "x-ai/grok-4-fast:free",
94
98
  "mai-ds-r1": "microsoft/mai-ds-r1:free",
95
99
  "llama3.3:70b": "meta-llama/llama-3.3-70b-instruct:free",
96
- "kimi-k2": "moonshotai/kimi-k2:free",
97
100
  "nemotron-nano:9b": "nvidia/nemotron-nano-9b-v2:free",
98
101
  "deepseek-r1-distill-llama:70b": "deepseek/deepseek-r1-distill-llama-70b:free",
99
102
  "gpt-oss:20b": "openai/gpt-oss-20b:free",
@@ -102,7 +105,6 @@
102
105
  "devstral-small": "mistralai/devstral-small-2505:free",
103
106
  "venice-uncensored:24b": "cognitivecomputations/dolphin-mistral-24b-venice-edition:free",
104
107
  "llama3.3:8b": "meta-llama/llama-3.3-8b-instruct:free",
105
- "llama3.1:405b": "meta-llama/llama-3.1-405b-instruct:free",
106
108
  "kimi-dev:72b": "moonshotai/kimi-dev-72b:free",
107
109
  "gemma3:27b": "google/gemma-3-27b-it:free",
108
110
  "qwen3-coder": "qwen/qwen3-coder:free",
@@ -171,7 +173,7 @@
171
173
  }
172
174
  },
173
175
  "ollama": {
174
- "enabled": false,
176
+ "enabled": true,
175
177
  "type": "OllamaProvider",
176
178
  "base_url": "http://localhost:11434",
177
179
  "models": {},
@@ -389,7 +391,8 @@
389
391
  "qwen2.5-vl:7b": "qwen2.5-vl-7b-instruct",
390
392
  "qwen2.5-vl:3b": "qwen2.5-vl-3b-instruct",
391
393
  "qwen2.5-omni:7b": "qwen2.5-omni-7b"
392
- }
394
+ },
395
+ "enable_thinking": false
393
396
  },
394
397
  "z.ai": {
395
398
  "enabled": false,
@@ -404,7 +407,8 @@
404
407
  "glm-4.5-airx": "glm-4.5-airx",
405
408
  "glm-4.5-flash": "glm-4.5-flash",
406
409
  "glm-4:32b": "glm-4-32b-0414-128k"
407
- }
410
+ },
411
+ "temperature": 0.7
408
412
  },
409
413
  "mistral": {
410
414
  "enabled": false,
@@ -417,20 +421,22 @@
417
421
  "devstral-medium": "devstral-medium-2507",
418
422
  "codestral:22b": "codestral-latest",
419
423
  "mistral-ocr": "mistral-ocr-latest",
420
- "voxtral-mini": "voxtral-mini-latest",
421
424
  "mistral-small3.2:24b": "mistral-small-latest",
422
425
  "magistral-small": "magistral-small-latest",
423
426
  "devstral-small": "devstral-small-2507",
424
427
  "voxtral-small": "voxtral-small-latest",
428
+ "voxtral-mini": "voxtral-mini-latest",
429
+ "codestral-embed": "codestral-embed-2505",
430
+ "mistral-embed": "mistral-embed",
425
431
  "mistral-large:123b": "mistral-large-latest",
426
432
  "pixtral-large:124b": "pixtral-large-latest",
427
433
  "pixtral:12b": "pixtral-12b",
428
- "mistral-nemo:12b": "mistral-nemo",
434
+ "mistral-nemo:12b": "open-mistral-nemo",
429
435
  "mistral-saba": "mistral-saba-latest",
430
436
  "mistral:7b": "open-mistral-7b",
431
437
  "mixtral:8x7b": "open-mixtral-8x7b",
432
438
  "mixtral:8x22b": "open-mixtral-8x22b",
433
- "ministral:8b": "ministral-3b-latest",
439
+ "ministral:8b": "ministral-8b-latest",
434
440
  "ministral:3b": "ministral-3b-latest"
435
441
  }
436
442
  }
@@ -0,0 +1,28 @@
1
+ import { computed, inject } from "vue"
2
+
3
+ export default {
4
+ template:`
5
+ <div v-if="$ai.auth?.profileUrl" :title="authTitle">
6
+ <img :src="$ai.auth.profileUrl" class="size-8 rounded-full" />
7
+ </div>
8
+ `,
9
+ setup() {
10
+ const ai = inject('ai')
11
+ const authTitle = computed(() => {
12
+ if (!ai.auth) return ''
13
+ const { userId, userName, displayName, bearerToken, roles } = ai.auth
14
+ const name = userName || displayName
15
+ const prefix = roles && roles.includes('Admin') ? 'Admin' : 'Name'
16
+ const sb = [
17
+ name ? `${prefix}: ${name}` : '',
18
+ `API Key: ${bearerToken}`,
19
+ `${userId}`,
20
+ ]
21
+ return sb.filter(x => x).join('\n')
22
+ })
23
+
24
+ return {
25
+ authTitle,
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,23 @@
1
+ export default {
2
+ template:`
3
+ <div class="flex-shrink-0 px-4 py-4 border-b border-gray-200 bg-white min-h-16 select-none">
4
+ <div class="flex items-center justify-between">
5
+ <button type="button"
6
+ @click="$emit('home')"
7
+ class="text-lg font-semibold text-gray-900 hover:text-blue-600 focus:outline-none transition-colors"
8
+ title="Go back to initial state"
9
+ >
10
+ History
11
+ </button>
12
+ <button type="button"
13
+ @click="$emit('new')"
14
+ class="text-gray-900 hover:text-blue-600 focus:outline-none transition-colors"
15
+ title="New Chat"
16
+ >
17
+ <svg class="size-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></g></svg>
18
+ </button>
19
+ </div>
20
+ </div>
21
+ `,
22
+ emits:['home','new'],
23
+ }