mirror of
https://github.com/Crosstalk-Solutions/project-nomad.git
synced 2026-03-28 03:29:25 +01:00
fix: service name defs and ollama ui location
This commit is contained in:
parent
4584844ca6
commit
31c671bdb5
|
|
@ -24,6 +24,7 @@ import type {
|
|||
} from '../../types/benchmark.js'
|
||||
import { randomUUID, createHmac } from 'node:crypto'
|
||||
import { DockerService } from './docker_service.js'
|
||||
import { SERVICE_NAMES } from '../../constants/service_names.js'
|
||||
|
||||
// HMAC secret for signing submissions to the benchmark repository
|
||||
// This provides basic protection against casual API abuse.
|
||||
|
|
@ -421,7 +422,7 @@ export class BenchmarkService {
|
|||
|
||||
this._updateStatus('running_ai', 'Running AI benchmark...')
|
||||
|
||||
const ollamaAPIURL = await this.dockerService.getServiceURL(DockerService.OLLAMA_SERVICE_NAME)
|
||||
const ollamaAPIURL = await this.dockerService.getServiceURL(SERVICE_NAMES.OLLAMA)
|
||||
if (!ollamaAPIURL) {
|
||||
throw new Error('AI Assistant service location could not be determined. Ensure AI Assistant is installed and running.')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,18 +6,12 @@ import transmit from '@adonisjs/transmit/services/main'
|
|||
import { doResumableDownloadWithRetry } from '../utils/downloads.js'
|
||||
import { join } from 'path'
|
||||
import { ZIM_STORAGE_PATH } from '../utils/fs.js'
|
||||
import { SERVICE_NAMES } from '../../constants/service_names.js'
|
||||
|
||||
@inject()
|
||||
export class DockerService {
|
||||
public docker: Docker
|
||||
private activeInstallations: Set<string> = new Set()
|
||||
public static KIWIX_SERVICE_NAME = 'nomad_kiwix_serve'
|
||||
public static OLLAMA_SERVICE_NAME = 'nomad_ollama'
|
||||
public static QDRANT_SERVICE_NAME = 'nomad_qdrant'
|
||||
public static CYBERCHEF_SERVICE_NAME = 'nomad_cyberchef'
|
||||
public static FLATNOTES_SERVICE_NAME = 'nomad_flatnotes'
|
||||
public static KOLIBRI_SERVICE_NAME = 'nomad_kolibri'
|
||||
public static BENCHMARK_SERVICE_NAME = 'nomad_benchmark'
|
||||
public static NOMAD_NETWORK = 'project-nomad_default'
|
||||
|
||||
constructor() {
|
||||
|
|
@ -441,7 +435,7 @@ export class DockerService {
|
|||
await new Promise((res) => this.docker.modem.followProgress(pullStream, res))
|
||||
}
|
||||
|
||||
if (service.service_name === DockerService.KIWIX_SERVICE_NAME) {
|
||||
if (service.service_name === SERVICE_NAMES.KIWIX) {
|
||||
await this._runPreinstallActions__KiwixServe()
|
||||
this._broadcast(
|
||||
service.service_name,
|
||||
|
|
@ -556,12 +550,12 @@ export class DockerService {
|
|||
logger.info(`[DockerService] Kiwix Serve pre-install: Downloading ZIM file to ${filepath}`)
|
||||
|
||||
this._broadcast(
|
||||
DockerService.KIWIX_SERVICE_NAME,
|
||||
SERVICE_NAMES.KIWIX,
|
||||
'preinstall',
|
||||
`Running pre-install actions for Kiwix Serve...`
|
||||
)
|
||||
this._broadcast(
|
||||
DockerService.KIWIX_SERVICE_NAME,
|
||||
SERVICE_NAMES.KIWIX,
|
||||
'preinstall',
|
||||
`Downloading Wikipedia ZIM file from ${WIKIPEDIA_ZIM_URL}. This may take some time...`
|
||||
)
|
||||
|
|
@ -579,13 +573,13 @@ export class DockerService {
|
|||
})
|
||||
|
||||
this._broadcast(
|
||||
DockerService.KIWIX_SERVICE_NAME,
|
||||
SERVICE_NAMES.KIWIX,
|
||||
'preinstall',
|
||||
`Downloaded Wikipedia ZIM file to ${filepath}`
|
||||
)
|
||||
} catch (error) {
|
||||
this._broadcast(
|
||||
DockerService.KIWIX_SERVICE_NAME,
|
||||
SERVICE_NAMES.KIWIX,
|
||||
'preinstall-error',
|
||||
`Failed to download Wikipedia ZIM file: ${error.message}`
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { inject } from '@adonisjs/core'
|
||||
import { ChatRequest, Ollama } from 'ollama'
|
||||
import { DockerService } from './docker_service.js'
|
||||
import { NomadOllamaModel } from '../../types/ollama.js'
|
||||
import { FALLBACK_RECOMMENDED_OLLAMA_MODELS } from '../../constants/ollama.js'
|
||||
import fs from 'node:fs/promises'
|
||||
|
|
@ -9,6 +8,7 @@ import logger from '@adonisjs/core/services/logger'
|
|||
import axios from 'axios'
|
||||
import { DownloadModelJob } from '#jobs/download_model_job'
|
||||
import { PassThrough } from 'node:stream'
|
||||
import { SERVICE_NAMES } from '../../constants/service_names.js'
|
||||
|
||||
const NOMAD_MODELS_API_BASE_URL = 'https://api.projectnomad.us/api/v1/ollama/models'
|
||||
const MODELS_CACHE_FILE = path.join(process.cwd(), 'storage', 'ollama-models-cache.json')
|
||||
|
|
@ -25,7 +25,7 @@ export class OllamaService {
|
|||
if (!this.ollamaInitPromise) {
|
||||
this.ollamaInitPromise = (async () => {
|
||||
const dockerService = new (await import('./docker_service.js')).DockerService()
|
||||
const qdrantUrl = await dockerService.getServiceURL(DockerService.OLLAMA_SERVICE_NAME)
|
||||
const qdrantUrl = await dockerService.getServiceURL(SERVICE_NAMES.OLLAMA)
|
||||
if (!qdrantUrl) {
|
||||
throw new Error('Ollama service is not installed or running.')
|
||||
}
|
||||
|
|
@ -56,7 +56,7 @@ export class OllamaService {
|
|||
return new Promise(async (resolve) => {
|
||||
try {
|
||||
const dockerService = new (await import('./docker_service.js')).DockerService()
|
||||
const container = dockerService.docker.getContainer(DockerService.OLLAMA_SERVICE_NAME)
|
||||
const container = dockerService.docker.getContainer(SERVICE_NAMES.OLLAMA)
|
||||
if (!container) {
|
||||
logger.warn('[OllamaService] Ollama container is not running. Cannot download model.')
|
||||
resolve({
|
||||
|
|
@ -242,7 +242,7 @@ export class OllamaService {
|
|||
|
||||
const dockerService = new (await import('./docker_service.js')).DockerService()
|
||||
|
||||
const ollamAPIURL = await dockerService.getServiceURL(DockerService.OLLAMA_SERVICE_NAME)
|
||||
const ollamAPIURL = await dockerService.getServiceURL(SERVICE_NAMES.OLLAMA)
|
||||
if (!ollamAPIURL) {
|
||||
logger.warn('[OllamaService] Ollama service is not running. Cannot download model.')
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { PDFParse } from 'pdf-parse'
|
|||
import { createWorker } from 'tesseract.js'
|
||||
import { fromBuffer } from 'pdf2pic'
|
||||
import { OllamaService } from './ollama_service.js'
|
||||
import { SERVICE_NAMES } from '../../constants/service_names.js'
|
||||
|
||||
@inject()
|
||||
export class RagService {
|
||||
|
|
@ -26,7 +27,7 @@ export class RagService {
|
|||
private async _initializeQdrantClient() {
|
||||
if (!this.qdrantInitPromise) {
|
||||
this.qdrantInitPromise = (async () => {
|
||||
const qdrantUrl = await this.dockerService.getServiceURL(DockerService.QDRANT_SERVICE_NAME)
|
||||
const qdrantUrl = await this.dockerService.getServiceURL(SERVICE_NAMES.QDRANT)
|
||||
if (!qdrantUrl) {
|
||||
throw new Error('Qdrant service is not installed or running.')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import InstalledTier from '#models/installed_tier'
|
|||
import WikipediaSelection from '#models/wikipedia_selection'
|
||||
import { RunDownloadJob } from '#jobs/run_download_job'
|
||||
import { DownloadCollectionOperation, DownloadRemoteSuccessCallback } from '../../types/files.js'
|
||||
import { SERVICE_NAMES } from '../../constants/service_names.js'
|
||||
|
||||
const ZIM_MIME_TYPES = ['application/x-zim', 'application/x-openzim', 'application/octet-stream']
|
||||
const CATEGORIES_URL = 'https://raw.githubusercontent.com/Crosstalk-Solutions/project-nomad/refs/heads/master/collections/kiwix-categories.json'
|
||||
|
|
@ -243,7 +244,7 @@ export class ZimService implements IZimService {
|
|||
// Restart KIWIX container to pick up new ZIM file
|
||||
if (restart) {
|
||||
await this.dockerService
|
||||
.affectContainer(DockerService.KIWIX_SERVICE_NAME, 'restart')
|
||||
.affectContainer(SERVICE_NAMES.KIWIX, 'restart')
|
||||
.catch((error) => {
|
||||
logger.error(`[ZimService] Failed to restart KIWIX container:`, error) // Don't stop the download completion, just log the error.
|
||||
})
|
||||
|
|
@ -434,7 +435,7 @@ export class ZimService implements IZimService {
|
|||
|
||||
// Restart Kiwix to reflect the change
|
||||
await this.dockerService
|
||||
.affectContainer(DockerService.KIWIX_SERVICE_NAME, 'restart')
|
||||
.affectContainer(SERVICE_NAMES.KIWIX, 'restart')
|
||||
.catch((error) => {
|
||||
logger.error(`[ZimService] Failed to restart Kiwix after Wikipedia removal:`, error)
|
||||
})
|
||||
|
|
|
|||
8
admin/constants/service_names.ts
Normal file
8
admin/constants/service_names.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export const SERVICE_NAMES = {
|
||||
KIWIX: 'nomad_kiwix_server',
|
||||
OLLAMA: 'nomad_ollama',
|
||||
QDRANT: 'nomad_qdrant',
|
||||
CYBERCHEF: 'nomad_cyberchef',
|
||||
FLATNOTES: 'nomad_flatnotes',
|
||||
KOLIBRI: 'nomad_kolibri',
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ export default class extends BaseSchema {
|
|||
UPDATE services SET
|
||||
friendly_name = 'AI Assistant',
|
||||
powered_by = 'Ollama',
|
||||
ui_location = '/chat',
|
||||
display_order = 3,
|
||||
description = 'Local AI chat that runs entirely on your hardware - no internet required'
|
||||
WHERE service_name = 'nomad_ollama'
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import Service from '#models/service'
|
||||
import { DockerService } from '#services/docker_service'
|
||||
import { BaseSeeder } from '@adonisjs/lucid/seeders'
|
||||
import { ModelAttributes } from '@adonisjs/lucid/types/model'
|
||||
import env from '#start/env'
|
||||
import { SERVICE_NAMES } from '../../constants/service_names.js'
|
||||
|
||||
export default class ServiceSeeder extends BaseSeeder {
|
||||
// Use environment variable with fallback to production default
|
||||
|
|
@ -15,7 +15,7 @@ export default class ServiceSeeder extends BaseSeeder {
|
|||
'created_at' | 'updated_at' | 'metadata' | 'id'
|
||||
>[] = [
|
||||
{
|
||||
service_name: DockerService.KIWIX_SERVICE_NAME,
|
||||
service_name: SERVICE_NAMES.KIWIX,
|
||||
friendly_name: 'Information Library',
|
||||
powered_by: 'Kiwix',
|
||||
display_order: 1,
|
||||
|
|
@ -39,7 +39,7 @@ export default class ServiceSeeder extends BaseSeeder {
|
|||
depends_on: null,
|
||||
},
|
||||
{
|
||||
service_name: DockerService.QDRANT_SERVICE_NAME,
|
||||
service_name: SERVICE_NAMES.QDRANT,
|
||||
friendly_name: 'Qdrant Vector Database',
|
||||
powered_by: null,
|
||||
display_order: 100, // Dependency service, not shown directly
|
||||
|
|
@ -62,7 +62,7 @@ export default class ServiceSeeder extends BaseSeeder {
|
|||
depends_on: null,
|
||||
},
|
||||
{
|
||||
service_name: DockerService.OLLAMA_SERVICE_NAME,
|
||||
service_name: SERVICE_NAMES.OLLAMA,
|
||||
friendly_name: 'AI Assistant',
|
||||
powered_by: 'Ollama',
|
||||
display_order: 3,
|
||||
|
|
@ -78,14 +78,14 @@ export default class ServiceSeeder extends BaseSeeder {
|
|||
},
|
||||
ExposedPorts: { '11434/tcp': {} },
|
||||
}),
|
||||
ui_location: null,
|
||||
ui_location: '/chat',
|
||||
installed: false,
|
||||
installation_status: 'idle',
|
||||
is_dependency_service: false,
|
||||
depends_on: DockerService.QDRANT_SERVICE_NAME,
|
||||
depends_on: SERVICE_NAMES.QDRANT,
|
||||
},
|
||||
{
|
||||
service_name: DockerService.CYBERCHEF_SERVICE_NAME,
|
||||
service_name: SERVICE_NAMES.CYBERCHEF,
|
||||
friendly_name: 'Data Tools',
|
||||
powered_by: 'CyberChef',
|
||||
display_order: 11,
|
||||
|
|
@ -107,7 +107,7 @@ export default class ServiceSeeder extends BaseSeeder {
|
|||
depends_on: null,
|
||||
},
|
||||
{
|
||||
service_name: DockerService.FLATNOTES_SERVICE_NAME,
|
||||
service_name: SERVICE_NAMES.FLATNOTES,
|
||||
friendly_name: 'Notes',
|
||||
powered_by: 'FlatNotes',
|
||||
display_order: 10,
|
||||
|
|
@ -131,7 +131,7 @@ export default class ServiceSeeder extends BaseSeeder {
|
|||
depends_on: null,
|
||||
},
|
||||
{
|
||||
service_name: DockerService.KOLIBRI_SERVICE_NAME,
|
||||
service_name: SERVICE_NAMES.KOLIBRI,
|
||||
friendly_name: 'Education Platform',
|
||||
powered_by: 'Kolibri',
|
||||
display_order: 2,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,12 @@ export function getServiceLink(ui_location: string): string {
|
|||
// If it's a port number, return a link to the service on that port
|
||||
return `http://${window.location.hostname}:${parsedPort}`;
|
||||
}
|
||||
// Otherwise, treat it as a path
|
||||
|
||||
const pathPattern = /^\/.+/;
|
||||
if (pathPattern.test(ui_location)) {
|
||||
// If it starts with a slash, treat it as a full path
|
||||
return ui_location;
|
||||
}
|
||||
|
||||
return `/${ui_location}`;
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ import useInternetStatus from '~/hooks/useInternetStatus'
|
|||
import { useSystemInfo } from '~/hooks/useSystemInfo'
|
||||
import classNames from 'classnames'
|
||||
import { CuratedCategory, CategoryTier, CategoryResource } from '../../../types/downloads'
|
||||
import { SERVICE_NAMES } from '../../../constants/service_names'
|
||||
|
||||
// Capability definitions - maps user-friendly categories to services
|
||||
interface Capability {
|
||||
|
|
@ -43,7 +44,7 @@ const CORE_CAPABILITIES: Capability[] = [
|
|||
'WikiHow articles and tutorials',
|
||||
'Project Gutenberg books and literature',
|
||||
],
|
||||
services: ['nomad_kiwix_serve'],
|
||||
services: [SERVICE_NAMES.KIWIX],
|
||||
icon: 'IconBooks',
|
||||
},
|
||||
{
|
||||
|
|
@ -57,7 +58,7 @@ const CORE_CAPABILITIES: Capability[] = [
|
|||
'Interactive exercises and quizzes',
|
||||
'Progress tracking for learners',
|
||||
],
|
||||
services: ['nomad_kolibri'],
|
||||
services: [SERVICE_NAMES.KOLIBRI],
|
||||
icon: 'IconSchool',
|
||||
},
|
||||
{
|
||||
|
|
@ -71,7 +72,7 @@ const CORE_CAPABILITIES: Capability[] = [
|
|||
'Ask questions, get help with writing, brainstorm ideas',
|
||||
'Runs on your own hardware with local AI models',
|
||||
],
|
||||
services: ['nomad_ollama'],
|
||||
services: [SERVICE_NAMES.OLLAMA],
|
||||
icon: 'IconRobot',
|
||||
},
|
||||
]
|
||||
|
|
@ -83,7 +84,7 @@ const ADDITIONAL_TOOLS: Capability[] = [
|
|||
technicalName: 'FlatNotes',
|
||||
description: 'Simple note-taking app with local storage',
|
||||
features: ['Markdown support', 'All notes stored locally', 'No account required'],
|
||||
services: ['nomad_flatnotes'],
|
||||
services: [SERVICE_NAMES.FLATNOTES],
|
||||
icon: 'IconNotes',
|
||||
},
|
||||
{
|
||||
|
|
@ -96,7 +97,7 @@ const ADDITIONAL_TOOLS: Capability[] = [
|
|||
'Encryption and hashing tools',
|
||||
'Data format conversion',
|
||||
],
|
||||
services: ['nomad_cyberchef'],
|
||||
services: [SERVICE_NAMES.CYBERCHEF],
|
||||
icon: 'IconChefHat',
|
||||
},
|
||||
]
|
||||
|
|
@ -804,10 +805,10 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim
|
|||
|
||||
const renderStep3 = () => {
|
||||
// Check if AI or Information capabilities are selected OR already installed
|
||||
const isAiSelected = selectedServices.includes('nomad_ollama') ||
|
||||
installedServices.some((s) => s.service_name === 'nomad_ollama')
|
||||
const isInformationSelected = selectedServices.includes('nomad_kiwix') ||
|
||||
installedServices.some((s) => s.service_name === 'nomad_kiwix')
|
||||
const isAiSelected = selectedServices.includes(SERVICE_NAMES.OLLAMA) ||
|
||||
installedServices.some((s) => s.service_name === SERVICE_NAMES.OLLAMA)
|
||||
const isInformationSelected = selectedServices.includes(SERVICE_NAMES.KIWIX) ||
|
||||
installedServices.some((s) => s.service_name === SERVICE_NAMES.KIWIX)
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import AppLayout from '~/layouts/AppLayout'
|
|||
import { getServiceLink } from '~/lib/navigation'
|
||||
import { ServiceSlim } from '../../types/services'
|
||||
import DynamicIcon, { DynamicIconName } from '~/components/DynamicIcon'
|
||||
import { SERVICE_NAMES } from '../../constants/service_names'
|
||||
|
||||
// Maps is a Core Capability (display_order: 4)
|
||||
const MAPS_ITEM = {
|
||||
|
|
@ -126,7 +127,7 @@ export default function Home(props: {
|
|||
|
||||
// Add system items
|
||||
items.push(...SYSTEM_ITEMS)
|
||||
if (props.system.services.find((s) => s.service_name === 'nomad_ollama' && s.installed)) {
|
||||
if (props.system.services.find((s) => s.service_name === SERVICE_NAMES.OLLAMA && s.installed)) {
|
||||
items.push(KNOWLEDGE_BASE_ITEM)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import { BenchmarkProgress, BenchmarkStatus } from '../../../types/benchmark'
|
|||
import BenchmarkResult from '#models/benchmark_result'
|
||||
import api from '~/lib/api'
|
||||
import useServiceInstalledStatus from '~/hooks/useServiceInstalledStatus'
|
||||
import { SERVICE_NAMES } from '../../../constants/service_names'
|
||||
|
||||
type BenchmarkProgressWithID = BenchmarkProgress & { benchmark_id: string }
|
||||
|
||||
|
|
@ -34,7 +35,7 @@ export default function BenchmarkPage(props: {
|
|||
}) {
|
||||
const { subscribe } = useTransmit()
|
||||
const queryClient = useQueryClient()
|
||||
const aiInstalled = useServiceInstalledStatus('nomad_ollama')
|
||||
const aiInstalled = useServiceInstalledStatus(SERVICE_NAMES.OLLAMA)
|
||||
const [progress, setProgress] = useState<BenchmarkProgressWithID | null>(null)
|
||||
const [isRunning, setIsRunning] = useState(props.benchmark.status !== 'idle')
|
||||
const [showDetails, setShowDetails] = useState(false)
|
||||
|
|
|
|||
|
|
@ -10,11 +10,12 @@ import api from '~/lib/api'
|
|||
import { useModals } from '~/context/ModalContext'
|
||||
import StyledModal from '~/components/StyledModal'
|
||||
import { ModelResponse } from 'ollama'
|
||||
import { SERVICE_NAMES } from '../../../constants/service_names'
|
||||
|
||||
export default function ModelsPage(props: {
|
||||
models: { availableModels: NomadOllamaModel[]; installedModels: ModelResponse[] }
|
||||
}) {
|
||||
const { isInstalled } = useServiceInstalledStatus('nomad_ollama')
|
||||
const { isInstalled } = useServiceInstalledStatus(SERVICE_NAMES.OLLAMA)
|
||||
const { addNotification } = useNotifications()
|
||||
const { openModal, closeAllModals } = useModals()
|
||||
|
||||
|
|
|
|||
|
|
@ -9,11 +9,12 @@ import StyledModal from '~/components/StyledModal'
|
|||
import useServiceInstalledStatus from '~/hooks/useServiceInstalledStatus'
|
||||
import Alert from '~/components/Alert'
|
||||
import { FileEntry } from '../../../../types/files'
|
||||
import { SERVICE_NAMES } from '../../../../constants/service_names'
|
||||
|
||||
export default function ZimPage() {
|
||||
const queryClient = useQueryClient()
|
||||
const { openModal, closeAllModals } = useModals()
|
||||
const { isInstalled } = useServiceInstalledStatus('nomad_kiwix_serve')
|
||||
const { isInstalled } = useServiceInstalledStatus(SERVICE_NAMES.KIWIX)
|
||||
const { data, isLoading } = useQuery<FileEntry[]>({
|
||||
queryKey: ['zim-files'],
|
||||
queryFn: getFiles,
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import {
|
|||
} from '../../../../types/downloads'
|
||||
import useDownloads from '~/hooks/useDownloads'
|
||||
import ActiveDownloads from '~/components/ActiveDownloads'
|
||||
import { SERVICE_NAMES } from '../../../../constants/service_names'
|
||||
|
||||
const CURATED_COLLECTIONS_KEY = 'curated-zim-collections'
|
||||
const CURATED_CATEGORIES_KEY = 'curated-categories'
|
||||
|
|
@ -60,7 +61,7 @@ export default function ZimRemoteExplorer() {
|
|||
const { openModal, closeAllModals } = useModals()
|
||||
const { addNotification } = useNotifications()
|
||||
const { isOnline } = useInternetStatus()
|
||||
const { isInstalled } = useServiceInstalledStatus('nomad_kiwix_serve')
|
||||
const { isInstalled } = useServiceInstalledStatus(SERVICE_NAMES.KIWIX)
|
||||
const { debounce } = useDebounce()
|
||||
|
||||
const [query, setQuery] = useState('')
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user