@7n/tauri-components 0.4.1 → 0.6.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/tauri-components",
3
- "version": "0.4.1",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "description": "Shared LLM agent engine + Vue/Quasar UI for Tauri apps (chat, journal, trust-tier approval).",
6
6
  "license": "MIT",
@@ -19,7 +19,8 @@
19
19
  "default": "./src/index.js"
20
20
  },
21
21
  "./vue": "./src/vue/index.js",
22
- "./components": "./src/components/index.js"
22
+ "./components": "./src/components/index.js",
23
+ "./testing": "./src/testing/index.js"
23
24
  },
24
25
  "files": [
25
26
  "src",
@@ -35,12 +36,14 @@
35
36
  "peerDependencies": {
36
37
  "@tauri-apps/api": "^2.0.0",
37
38
  "@tauri-apps/plugin-http": "^2.0.0",
39
+ "@vue/test-utils": "^2.0.0",
38
40
  "quasar": "^2.0.0",
39
41
  "vue": "^3.0.0"
40
42
  },
41
43
  "peerDependenciesMeta": {
42
44
  "@tauri-apps/api": { "optional": true },
43
45
  "@tauri-apps/plugin-http": { "optional": true },
46
+ "@vue/test-utils": { "optional": true },
44
47
  "quasar": { "optional": true }
45
48
  },
46
49
  "knip": {
@@ -14,7 +14,21 @@
14
14
 
15
15
  <template v-if="showConfig">
16
16
  <q-input v-model="baseUrl" dense outlined label="omlx base URL" />
17
- <q-input v-model="model" dense outlined label="model" />
17
+ <q-select
18
+ v-model="model"
19
+ :options="models"
20
+ :loading="modelsLoading"
21
+ use-input
22
+ fill-input
23
+ hide-selected
24
+ input-debounce="0"
25
+ new-value-mode="add-unique"
26
+ dense
27
+ outlined
28
+ label="model"
29
+ hint="завантажені моделі omlx; можна вписати свою"
30
+ @filter="(_, update) => update()"
31
+ />
18
32
  <q-input v-model="apiKey" dense outlined label="API key" type="password" />
19
33
  <q-separator class="q-my-sm" />
20
34
  </template>
@@ -38,7 +52,7 @@
38
52
  type="textarea"
39
53
  autogrow
40
54
  :label="inputLabel"
41
- hint="наприклад: Create a task named deploy in /Users/.../mt, agent mode"
55
+ :hint="promptHint"
42
56
  />
43
57
 
44
58
  <!-- jscpd:ignore-start — formal footer markup; jscpd html-mode aligns its
@@ -69,11 +83,12 @@ import RequestView from './RequestView.vue'
69
83
  const props = defineProps({
70
84
  modelValue: { type: Boolean, default: false },
71
85
  agent: { type: Object, required: true },
86
+ promptHint: { type: String, default: 'наприклад: Create a task named deploy in /Users/.../mt, agent mode' },
72
87
  })
73
88
  const emit = defineEmits(['update:modelValue', 'ran'])
74
89
 
75
90
  const $q = useQuasar()
76
- const { baseUrl, model, apiKey, saveOmlx, loadOmlxEnv, request, respond } = props.agent
91
+ const { baseUrl, model, apiKey, saveOmlx, loadOmlxEnv, listModels, request, respond } = props.agent
77
92
 
78
93
  const prompt = ref('')
79
94
  const running = ref(false)
@@ -81,6 +96,8 @@ const turns = ref([])
81
96
  const requestId = ref(null)
82
97
  const logEl = ref(null)
83
98
  const showConfig = ref(false)
99
+ const models = ref([])
100
+ const modelsLoading = ref(false)
84
101
 
85
102
  // Labels shift once a conversation is under way (fresh request → follow-up).
86
103
  const inputLabel = computed(() => (turns.value.length ? 'Повідомлення' : 'Prompt'))
@@ -95,6 +112,16 @@ async function onShow() {
95
112
  turns.value = []
96
113
  requestId.value = null
97
114
  await loadOmlxEnv()
115
+ // Populate the model dropdown from the omlx server (best-effort; stays editable).
116
+ if (listModels) {
117
+ modelsLoading.value = true
118
+ try {
119
+ models.value = await listModels()
120
+ }
121
+ finally {
122
+ modelsLoading.value = false
123
+ }
124
+ }
98
125
  }
99
126
 
100
127
  /**
@@ -0,0 +1,34 @@
1
+ // List the models a local omlx (OpenAI-compatible) server has loaded, via
2
+ // GET {baseUrl}/models. Generic OpenAI shape: { data: [{ id }] }. Returns [] on
3
+ // any failure so a model picker degrades gracefully to manual entry.
4
+
5
+ /**
6
+ * @param {{ baseUrl?: string, apiKey?: string, fetchFn?: typeof fetch, signal?: AbortSignal }} [params] config
7
+ * @returns {Promise<string[]>} loaded model ids (empty on error)
8
+ */
9
+ export async function listOmlxModels({ baseUrl, apiKey, fetchFn = fetch, signal } = {}) {
10
+ if (!baseUrl) return []
11
+ try {
12
+ const response = await fetchFn(`${baseUrl}/models`, {
13
+ headers: apiKey ? { authorization: `Bearer ${apiKey}` } : {},
14
+ signal,
15
+ })
16
+ if (!response.ok) return []
17
+ const data = await response.json()
18
+ return Array.isArray(data?.data) ? data.data.map(m => m?.id).filter(Boolean) : []
19
+ }
20
+ catch {
21
+ return []
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Pick a model: the preferred one if loaded, else the first available, else ''.
27
+ * @param {string[]} models loaded model ids
28
+ * @param {string} [preferred] preferred id
29
+ * @returns {string} chosen model id
30
+ */
31
+ export function resolveModel(models, preferred) {
32
+ if (preferred && models.includes(preferred)) return preferred
33
+ return models[0] ?? ''
34
+ }
package/src/index.js CHANGED
@@ -7,6 +7,7 @@ export { createAgentKit } from './core/agent-kit.js'
7
7
  export { handleApprove, handleRequest, handleRespond } from './core/agent-handler.js'
8
8
  export { createDispatch, validateInput } from './core/dispatch.js'
9
9
  export { createOpenAiChat, runAgent } from './core/llm.js'
10
+ export { listOmlxModels, resolveModel } from './core/omlx-models.js'
10
11
  export { listTools, toJsonSchema, toolManifest } from './core/manifest.js'
11
12
  export { classify, DEFAULT_ACTOR_TIERS, scopedManifest, scopedToolNames } from './core/scope.js'
12
13
  export { getTool } from './core/tools.js'
@@ -0,0 +1,3 @@
1
+ // `@7n/tauri-components/testing` — vitest helpers for Quasar component tests.
2
+ // Requires the host app's devDeps to provide @vue/test-utils + quasar (peers).
3
+ export { mountQuasar, mountWithQuasar } from './quasar.js'
@@ -0,0 +1,48 @@
1
+ import { h } from 'vue'
2
+ import { mount } from '@vue/test-utils'
3
+ import * as Quasar from 'quasar'
4
+
5
+ // Vitest helpers for mounting components that use Quasar. Registers every Quasar
6
+ // component (QBtn, QPage, …) globally so both template usage and programmatic
7
+ // h(Quasar.QLayout) resolve in mounted test trees. Identical across consumer
8
+ // apps — import from '@7n/tauri-components/testing' instead of copying it.
9
+
10
+ const QUASAR_COMPONENT_RE = /^Q[A-Z]/
11
+ const quasarComponents = Object.fromEntries(Object.entries(Quasar).filter(([name]) => QUASAR_COMPONENT_RE.test(name)))
12
+
13
+ /**
14
+ * @param {object} [userGlobal] caller's `global` mount option
15
+ * @returns {object} `global` option with Quasar plugin + components merged in
16
+ */
17
+ function quasarGlobal(userGlobal = {}) {
18
+ return {
19
+ ...userGlobal,
20
+ components: { ...quasarComponents, ...userGlobal.components },
21
+ plugins: [...(userGlobal.plugins || []), [Quasar.Quasar, { config: { dark: false } }]],
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Mount a component with Quasar registered, without any layout wrapper.
27
+ * @param {object} component Vue component (e.g. one that renders its own QLayout)
28
+ * @param {object} [options] mount options (forwarded)
29
+ * @returns {object} test wrapper
30
+ */
31
+ export function mountQuasar(component, options = {}) {
32
+ return mount(component, { ...options, global: quasarGlobal(options.global) })
33
+ }
34
+
35
+ /**
36
+ * Mount a page-level component wrapped in QLayout > QPageContainer.
37
+ * @param {object} component Vue component
38
+ * @param {object} [options] mount options (forwarded)
39
+ * @returns {object} test wrapper
40
+ */
41
+ export function mountWithQuasar(component, options = {}) {
42
+ const wrapper = {
43
+ render() {
44
+ return h(Quasar.QLayout, { view: 'hHh lpR fFf' }, () => h(Quasar.QPageContainer, () => h(component)))
45
+ },
46
+ }
47
+ return mount(wrapper, { ...options, global: quasarGlobal(options.global) })
48
+ }
@@ -1,6 +1,7 @@
1
1
  import { fetch as tauriFetch } from '@tauri-apps/plugin-http'
2
2
  import { createAgentKit } from '../core/agent-kit.js'
3
3
  import { createOpenAiChat } from '../core/llm.js'
4
+ import { listOmlxModels } from '../core/omlx-models.js'
4
5
  import { createTauriJournalStore } from './journal-store-tauri.js'
5
6
  import { tauriTransport } from './transports.js'
6
7
  import { useOmlx } from './use-omlx.js'
@@ -50,6 +51,7 @@ export function useAgent({ catalog, systemPrompt, grounding, actorTiers, actor =
50
51
  apiKey,
51
52
  saveOmlx: save,
52
53
  loadOmlxEnv: loadEnv,
54
+ listModels: () => listOmlxModels({ baseUrl: baseUrl.value, apiKey: apiKey.value || undefined, fetchFn: tauriFetch }),
53
55
  journal,
54
56
  request: intent => kit.request({ intent, actor, chat: chat() }),
55
57
  respond: (requestId, message) => kit.respond({ requestId, message, actor, chat: chat() }),
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @param {{ baseUrl?: string, apiKey?: string, fetchFn?: typeof fetch, signal?: AbortSignal }} [params] config
3
+ * @returns {Promise<string[]>} loaded model ids (empty on error)
4
+ */
5
+ export function listOmlxModels({ baseUrl, apiKey, fetchFn, signal }?: {
6
+ baseUrl?: string;
7
+ apiKey?: string;
8
+ fetchFn?: typeof fetch;
9
+ signal?: AbortSignal;
10
+ }): Promise<string[]>;
11
+ /**
12
+ * Pick a model: the preferred one if loaded, else the first available, else ''.
13
+ * @param {string[]} models loaded model ids
14
+ * @param {string} [preferred] preferred id
15
+ * @returns {string} chosen model id
16
+ */
17
+ export function resolveModel(models: string[], preferred?: string): string;
package/types/index.d.ts CHANGED
@@ -3,5 +3,6 @@ export { getTool } from "./core/tools.js";
3
3
  export { handleApprove, handleRequest, handleRespond } from "./core/agent-handler.js";
4
4
  export { createDispatch, validateInput } from "./core/dispatch.js";
5
5
  export { createOpenAiChat, runAgent } from "./core/llm.js";
6
+ export { listOmlxModels, resolveModel } from "./core/omlx-models.js";
6
7
  export { listTools, toJsonSchema, toolManifest } from "./core/manifest.js";
7
8
  export { classify, DEFAULT_ACTOR_TIERS, scopedManifest, scopedToolNames } from "./core/scope.js";