@swiss-ai-hub/web 0.312.1 → 0.313.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.
@@ -0,0 +1,61 @@
1
+ export const SUPPORTED_SCHEMA_VERSIONS = ['1.0']
2
+
3
+ export type AgentConfig = {
4
+ schemaVersion: string
5
+ agentClass: string
6
+ agentId?: string
7
+ configuration: Record<string, unknown>
8
+ }
9
+
10
+ export type AgentConfigImportReason = 'invalidJson' | 'invalidStructure' | 'missingFields' | 'unsupportedVersion'
11
+
12
+ export class AgentConfigImportError extends Error {
13
+ readonly reason: AgentConfigImportReason
14
+
15
+ constructor(reason: AgentConfigImportReason) {
16
+ super(reason)
17
+ this.name = 'AgentConfigImportError'
18
+ this.reason = reason
19
+ }
20
+ }
21
+
22
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
23
+ typeof value === 'object' && value !== null && !Array.isArray(value)
24
+
25
+ const parseAgentConfigExport = (text: string): AgentConfig => {
26
+ let parsed: unknown
27
+ try {
28
+ parsed = JSON.parse(text)
29
+ }
30
+ catch {
31
+ throw new AgentConfigImportError('invalidJson')
32
+ }
33
+
34
+ if (!isRecord(parsed)) throw new AgentConfigImportError('invalidStructure')
35
+
36
+ const { schemaVersion, agentClass, agentId, configuration } = parsed
37
+
38
+ if (typeof schemaVersion !== 'string' || typeof agentClass !== 'string' || !agentClass || !isRecord(configuration)) {
39
+ throw new AgentConfigImportError('missingFields')
40
+ }
41
+
42
+ if (!SUPPORTED_SCHEMA_VERSIONS.includes(schemaVersion)) {
43
+ throw new AgentConfigImportError('unsupportedVersion')
44
+ }
45
+
46
+ return {
47
+ schemaVersion,
48
+ agentClass,
49
+ agentId: typeof agentId === 'string' ? agentId : undefined,
50
+ configuration,
51
+ }
52
+ }
53
+
54
+ export const useImportAgentInstance = () => {
55
+ const readAgentConfigFile = async (file: File): Promise<AgentConfig> => {
56
+ const text = await file.text()
57
+ return parseAgentConfigExport(text)
58
+ }
59
+
60
+ return { readAgentConfigFile }
61
+ }
@@ -331,6 +331,17 @@ agent:
331
331
  status: 'Status:'
332
332
  online: Online
333
333
  offline: Offline
334
+ import:
335
+ button: Importieren
336
+ error:
337
+ title: Import fehlgeschlagen
338
+ invalidJson: Die ausgewählte Datei ist kein gültiges JSON.
339
+ invalidStructure: Die ausgewählte Datei ist kein gültiger Agenten-Konfigurationsexport.
340
+ missingFields: In der Datei fehlen erforderliche Felder (schemaVersion, agentClass
341
+ oder configuration).
342
+ unsupportedVersion: Die Datei verwendet eine nicht unterstützte Export-Schemaversion.
343
+ blueprintNotFound: Die Agenten-Vorlage "{agentClass}" ist in dieser Umgebung
344
+ nicht verfügbar.
334
345
  export:
335
346
  button: Exportieren
336
347
  configuration:
@@ -328,6 +328,17 @@ agent:
328
328
  status: 'Status:'
329
329
  online: Online
330
330
  offline: Offline
331
+ import:
332
+ button: Import
333
+ error:
334
+ title: Import failed
335
+ invalidJson: The selected file is not valid JSON.
336
+ invalidStructure: The selected file is not a valid agent configuration export.
337
+ missingFields: The file is missing required fields (schemaVersion, agentClass,
338
+ or configuration).
339
+ unsupportedVersion: The file uses an unsupported export schema version.
340
+ blueprintNotFound: The agent blueprint "{agentClass}" is not available in this
341
+ environment.
331
342
  export:
332
343
  button: Export
333
344
  configuration:
@@ -329,6 +329,19 @@ agent:
329
329
  status: 'Statut:'
330
330
  online: En ligne
331
331
  offline: Hors ligne
332
+ import:
333
+ button: Importer
334
+ error:
335
+ title: Échec de l'import
336
+ invalidJson: Le fichier sélectionné n'est pas un JSON valide.
337
+ invalidStructure: Le fichier sélectionné n'est pas un export de configuration
338
+ d'agent valide.
339
+ missingFields: Il manque des champs obligatoires dans le fichier (schemaVersion,
340
+ agentClass ou configuration).
341
+ unsupportedVersion: Le fichier utilise une version de schéma d'export non prise
342
+ en charge.
343
+ blueprintNotFound: Le modèle d'agent « {agentClass} » n'est pas disponible dans
344
+ cet environnement.
332
345
  export:
333
346
  button: Exporter
334
347
  configuration:
@@ -329,6 +329,19 @@ agent:
329
329
  status: 'Stato:'
330
330
  online: Online
331
331
  offline: Offline
332
+ import:
333
+ button: Importa
334
+ error:
335
+ title: Importazione non riuscita
336
+ invalidJson: Il file selezionato non è un JSON valido.
337
+ invalidStructure: Il file selezionato non è un export di configurazione dell'agente
338
+ valido.
339
+ missingFields: Nel file mancano campi obbligatori (schemaVersion, agentClass
340
+ o configuration).
341
+ unsupportedVersion: Il file utilizza una versione dello schema di export non
342
+ supportata.
343
+ blueprintNotFound: Il modello di agente "{agentClass}" non è disponibile in
344
+ questo ambiente.
332
345
  export:
333
346
  button: Esporta
334
347
  configuration:
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "license": "AGPL-3.0-or-later",
4
4
  "author": "bbv Software Services AG (https://www.bbv.ch)",
5
5
  "type": "module",
6
- "version": "0.312.1",
6
+ "version": "0.313.0",
7
7
  "description": "Swiss AI Hub - Admin & Management UI (Nuxt 3 layer)",
8
8
  "main": "./nuxt.config.ts",
9
9
  "repository": {
@@ -16,6 +16,12 @@
16
16
  @update:model-value="toNavItem"
17
17
  />
18
18
  <div class="flex items-center gap-4">
19
+ <Button
20
+ icon="pi pi-upload"
21
+ severity="secondary"
22
+ :label="t('agent.import.button')"
23
+ @click="triggerImport"
24
+ />
19
25
  <Select
20
26
  v-model="agentClass"
21
27
  :options="agentClassOptions"
@@ -41,6 +47,14 @@
41
47
  :placeholder="t('agent.list.search_placeholder')"
42
48
  class="w-80"
43
49
  />
50
+ <input
51
+ ref="fileInput"
52
+ type="file"
53
+ accept="application/json,.json"
54
+ :aria-label="t('agent.import.button')"
55
+ class="hidden"
56
+ @change="handleFileSelected"
57
+ >
44
58
  </div>
45
59
  </div>
46
60
  <div class="flex flex-col gap-8 pt-4">
@@ -120,6 +134,8 @@
120
134
  </template>
121
135
 
122
136
  <script setup lang="ts">
137
+ import { AgentConfigImportError } from '@core/composables/agent/useImportAgentInstance'
138
+
123
139
  import type { FullAgentInstanceDto, WorkflowGraph } from '@core/sdk/client'
124
140
 
125
141
  type AgentGroup = {
@@ -135,11 +151,15 @@ type AgentGroup = {
135
151
  const router = useRouter()
136
152
  const route = useRoute()
137
153
  const tenantPath = useTenantPath()
154
+ const { tenantId } = useTenant()
138
155
  const { t, locale } = useI18n()
139
156
 
157
+ const toast = useToast()
158
+
140
159
  const { agentInstances, agentInstancesAreLoading, searchQuery, agentClass, status } = useAgentInstances()
141
160
  const { agentClasses, agentClassesAreLoading } = useAgentClasses()
142
161
  const { navItems, activeNavItem, toNavItem } = useAgentNavigation()
162
+ const { readAgentConfigFile } = useImportAgentInstance()
143
163
 
144
164
  const isLoading = computed(() => agentInstancesAreLoading.value || agentClassesAreLoading.value)
145
165
  const isTemplatesRoute = computed(() => route.path.includes('/service/agents/templates'))
@@ -148,6 +168,8 @@ const createModalOpen = ref(false)
148
168
  const selectedClassForCreate = ref('')
149
169
  const initialDataForCreate = ref<Record<string, unknown> | null>(null)
150
170
 
171
+ const fileInput = ref<HTMLInputElement | null>(null)
172
+
151
173
  const workflowModalOpen = ref(false)
152
174
  const selectedGroupForWorkflow = ref<AgentGroup | null>(null)
153
175
 
@@ -195,6 +217,50 @@ const handleClone = (agent: FullAgentInstanceDto) => {
195
217
  createModalOpen.value = true
196
218
  }
197
219
 
220
+ const triggerImport = () => fileInput.value?.click()
221
+
222
+ const handleFileSelected = async () => {
223
+ const input = fileInput.value
224
+ const file = input?.files?.[0]
225
+ if (input) input.value = ''
226
+ if (!file) return
227
+
228
+ try {
229
+ const exported = await readAgentConfigFile(file)
230
+
231
+ const blueprintExists = agentClasses.value?.find(c => c.agent_class === exported.agentClass)
232
+ if (!blueprintExists) {
233
+ toast.add({
234
+ severity: 'error',
235
+ summary: t('agent.import.error.title'),
236
+ detail: t('agent.import.error.blueprintNotFound', { agentClass: exported.agentClass }),
237
+ life: 5000,
238
+ })
239
+ return
240
+ }
241
+
242
+ const configuration: Record<string, unknown> = { ...exported.configuration, tenant_id: tenantId.value }
243
+
244
+ const orgMemory = configuration.org_memory
245
+ if (orgMemory && typeof orgMemory === 'object' && !Array.isArray(orgMemory)) {
246
+ configuration.org_memory = { ...orgMemory, tenant_id: tenantId.value }
247
+ }
248
+
249
+ selectedClassForCreate.value = exported.agentClass
250
+ initialDataForCreate.value = configuration
251
+ createModalOpen.value = true
252
+ }
253
+ catch (error) {
254
+ const reason = error instanceof AgentConfigImportError ? error.reason : 'invalidStructure'
255
+ toast.add({
256
+ severity: 'error',
257
+ summary: t('agent.import.error.title'),
258
+ detail: t(`agent.import.error.${reason}`),
259
+ life: 5000,
260
+ })
261
+ }
262
+ }
263
+
198
264
  const groupedAgents = computed<AgentGroup[]>(() => {
199
265
  const groups = new Map<string, AgentGroup>()
200
266