@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 +5 -2
- package/src/components/AgentDialog.vue +30 -3
- package/src/core/omlx-models.js +34 -0
- package/src/index.js +1 -0
- package/src/testing/index.js +3 -0
- package/src/testing/quasar.js +48 -0
- package/src/vue/use-agent.js +2 -0
- package/types/core/omlx-models.d.ts +17 -0
- package/types/index.d.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@7n/tauri-components",
|
|
3
|
-
"version": "0.
|
|
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-
|
|
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="
|
|
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,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
|
+
}
|
package/src/vue/use-agent.js
CHANGED
|
@@ -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";
|