refactor(ai): extract assistant into an official module (#749)

* feat(modules): expose host data boundaries

* feat(modules): add frontend extension surfaces

* refactor(ai): remove assistant from core

* chore(ai): prepare the module extraction

* fix(modules): load extension styles after the host bundle

* chore(modules): lock SDK 3.3.0
This commit is contained in:
Darko Gjorgjijoski
2026-08-05 22:10:21 +02:00
committed by GitHub
parent 99ae7def75
commit 0b9ae9ea00
111 changed files with 1137 additions and 8952 deletions
@@ -9,7 +9,6 @@ const AdminUsersView = () => import('./views/AdminUsersView.vue')
const AdminUserEditView = () => import('./views/AdminUserEditView.vue')
const AdminSettingsView = () => import('./views/AdminSettingsView.vue')
const AdminMailConfigView = () => import('./views/settings/AdminMailConfigView.vue')
const AdminAiConfigView = () => import('./views/settings/AdminAiConfigView.vue')
const AdminPdfGenerationView = () => import('./views/settings/AdminPdfGenerationView.vue')
const AdminBackupView = () => import('./views/settings/AdminBackupView.vue')
const AdminFileDiskView = () => import('./views/settings/AdminFileDiskView.vue')
@@ -88,14 +87,6 @@ export const adminRoutes: RouteRecordRaw[] = [
},
component: AdminMailConfigView,
},
{
path: 'ai-configuration',
name: 'admin.settings.ai',
meta: {
isSuperAdmin: true,
},
component: AdminAiConfigView,
},
{
path: 'pdf-generation',
name: 'admin.settings.pdf',
@@ -54,6 +54,7 @@
import { ref, computed, watchEffect } from 'vue'
import { useRoute, useRouter, RouterView } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { extensionItems, extensionRegistry } from '@/scripts/extensions/runtime'
interface SettingsMenuItem {
title: string
@@ -73,11 +74,6 @@ const menuItems = computed<SettingsMenuItem[]>(() => [
link: '/admin/administration/settings/mail-configuration',
icon: 'EnvelopeIcon',
},
{
title: t('settings.menu_title.ai_configuration'),
link: '/admin/administration/settings/ai-configuration',
icon: 'SparklesIcon',
},
{
title: t('settings.menu_title.pdf_generation'),
link: '/admin/administration/settings/pdf-generation',
@@ -108,6 +104,11 @@ const menuItems = computed<SettingsMenuItem[]>(() => [
link: '/admin/administration/settings/appearance',
icon: 'PaintBrushIcon',
},
...extensionItems(extensionRegistry.adminSettingsNavigation.value).map((item) => ({
title: t(item.title),
link: router.resolve(item.to).fullPath,
icon: item.icon,
})),
])
watchEffect(() => {
@@ -1,107 +0,0 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useNotificationStore } from '@/scripts/stores/notification.store'
import { aiService } from '@/scripts/api/services/ai.service'
import type { AiConfig, AiDriverOption, AiTestPayload } from '@/scripts/types/ai-config'
import { getErrorTranslationKey, handleApiError } from '@/scripts/utils/error-handling'
import AiConfigurationForm from '@/scripts/features/company/settings/components/AiConfigurationForm.vue'
const { t } = useI18n()
const notificationStore = useNotificationStore()
const isSaving = ref(false)
const isTesting = ref(false)
const isFetchingInitialData = ref(false)
const configData = ref<AiConfig | null>(null)
const drivers = ref<AiDriverOption[]>([])
loadData()
async function loadData(): Promise<void> {
isFetchingInitialData.value = true
try {
const [driversResponse, configResponse] = await Promise.all([
aiService.getDrivers(),
aiService.getGlobalConfig(),
])
drivers.value = driversResponse.ai_drivers
configData.value = configResponse
} catch (error: unknown) {
const normalizedError = handleApiError(error)
notificationStore.showNotification({
type: 'error',
message: getErrorTranslationKey(normalizedError.message) ?? normalizedError.message,
})
} finally {
isFetchingInitialData.value = false
}
}
async function saveConfig(value: AiConfig): Promise<void> {
isSaving.value = true
try {
const response = await aiService.saveGlobalConfig(value)
if (response.success) {
notificationStore.showNotification({
type: 'success',
message: 'settings.ai.saved',
})
configData.value = { ...value }
}
} catch (error: unknown) {
const normalizedError = handleApiError(error)
notificationStore.showNotification({
type: 'error',
message: getErrorTranslationKey(normalizedError.message) ?? normalizedError.message,
})
} finally {
isSaving.value = false
}
}
async function testConnection(payload: AiTestPayload): Promise<void> {
isTesting.value = true
try {
const response = await aiService.testGlobalConnection(payload)
if (response.success) {
notificationStore.showNotification({
type: 'success',
message: 'settings.ai.test_success',
})
} else if (response.error) {
notificationStore.showNotification({
type: 'error',
message: t('settings.ai.errors.' + response.error, { error: response.message ?? '' }),
})
}
} catch (error: unknown) {
const normalizedError = handleApiError(error)
notificationStore.showNotification({
type: 'error',
message: getErrorTranslationKey(normalizedError.message) ?? normalizedError.message,
})
} finally {
isTesting.value = false
}
}
</script>
<template>
<BaseSettingCard
:title="$t('settings.ai.title')"
:description="$t('settings.ai.description')"
>
<div v-if="configData" class="mt-14">
<AiConfigurationForm
:config-data="configData"
:drivers="drivers"
:is-saving="isSaving"
:is-testing="isTesting"
:is-fetching-initial-data="isFetchingInitialData"
@submit-data="saveConfig"
@test-connection="testConnection"
/>
</div>
</BaseSettingCard>
</template>