Skip to content

Commit ab07551

Browse files
committed
feat: auto add NOMAD docs to KB on AI install
1 parent 9079820 commit ab07551

File tree

7 files changed

+206
-99
lines changed

7 files changed

+206
-99
lines changed

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,6 @@ COPY --from=build /app/build /app
3131
# Copy root package.json for version info
3232
COPY package.json /app/version.json
3333
COPY admin/docs /app/docs
34+
COPY README.md /app/README.md
3435
EXPOSE 8080
3536
CMD ["node", "./bin/server.js"]

admin/README.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

admin/app/jobs/embed_file_job.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,21 @@ export class EmbedFileJob {
3535
const ragService = new RagService(dockerService, ollamaService)
3636

3737
try {
38+
// Check if Ollama and Qdrant services are ready
39+
const existingModels = await ollamaService.getModels()
40+
if (!existingModels) {
41+
logger.warn('[EmbedFileJob] Ollama service not ready yet. Will retry...')
42+
throw new Error('Ollama service not ready yet')
43+
}
44+
45+
const qdrantUrl = await dockerService.getServiceURL('nomad_qdrant')
46+
if (!qdrantUrl) {
47+
logger.warn('[EmbedFileJob] Qdrant service not ready yet. Will retry...')
48+
throw new Error('Qdrant service not ready yet')
49+
}
50+
51+
logger.info(`[EmbedFileJob] Services ready. Processing file: ${fileName}`)
52+
3853
// Update progress starting
3954
await job.updateProgress(0)
4055
await job.updateData({
@@ -102,10 +117,10 @@ export class EmbedFileJob {
102117
try {
103118
const job = await queue.add(this.key, params, {
104119
jobId,
105-
attempts: 3,
120+
attempts: 30,
106121
backoff: {
107-
type: 'exponential',
108-
delay: 5000, // Delay 5 seconds before retrying
122+
type: 'fixed',
123+
delay: 60000, // Check every 60 seconds for service readiness
109124
},
110125
removeOnComplete: { count: 50 }, // Keep last 50 completed jobs for history
111126
removeOnFail: { count: 20 } // Keep last 20 failed jobs for debugging

admin/app/services/docker_service.ts

Lines changed: 97 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import { ZIM_STORAGE_PATH } from '../utils/fs.js'
99
import { SERVICE_NAMES } from '../../constants/service_names.js'
1010
import { exec } from 'child_process'
1111
import { promisify } from 'util'
12-
import { readdir } from 'fs/promises'
12+
// import { readdir } from 'fs/promises'
13+
import KVStore from '#models/kv_store'
1314

1415
@inject()
1516
export class DockerService {
@@ -473,34 +474,34 @@ export class DockerService {
473474
],
474475
}
475476
} else if (gpuType === 'amd') {
476-
this._broadcast(
477-
service.service_name,
478-
'gpu-config',
479-
`AMD GPU detected. Using ROCm image and configuring container with GPU support...`
480-
)
481-
482-
// Use ROCm image for AMD
483-
finalImage = 'ollama/ollama:rocm'
484-
485-
// Dynamically discover and add AMD GPU devices
486-
const amdDevices = await this._discoverAMDDevices()
487-
if (!amdDevices || amdDevices.length === 0) {
488-
this._broadcast(
489-
service.service_name,
490-
'gpu-config-error',
491-
`Failed to discover AMD GPU devices. Proceeding with CPU-only configuration...`
492-
)
493-
gpuHostConfig = { ...gpuHostConfig } // No GPU devices added
494-
logger.warn(`[DockerService] No AMD GPU devices discovered for Ollama`)
495-
} else {
496-
gpuHostConfig = {
497-
...gpuHostConfig,
498-
Devices: amdDevices,
499-
}
500-
logger.info(
501-
`[DockerService] Configured ${amdDevices.length} AMD GPU devices for Ollama`
502-
)
503-
}
477+
// this._broadcast(
478+
// service.service_name,
479+
// 'gpu-config',
480+
// `AMD GPU detected. Using ROCm image and configuring container with GPU support...`
481+
// )
482+
483+
// // Use ROCm image for AMD
484+
// finalImage = 'ollama/ollama:rocm'
485+
486+
// // Dynamically discover and add AMD GPU devices
487+
// const amdDevices = await this._discoverAMDDevices()
488+
// if (!amdDevices || amdDevices.length === 0) {
489+
// this._broadcast(
490+
// service.service_name,
491+
// 'gpu-config-error',
492+
// `Failed to discover AMD GPU devices. Proceeding with CPU-only configuration...`
493+
// )
494+
// gpuHostConfig = { ...gpuHostConfig } // No GPU devices added
495+
// logger.warn(`[DockerService] No AMD GPU devices discovered for Ollama`)
496+
// } else {
497+
// gpuHostConfig = {
498+
// ...gpuHostConfig,
499+
// Devices: amdDevices,
500+
// }
501+
// logger.info(
502+
// `[DockerService] Configured ${amdDevices.length} AMD GPU devices for Ollama`
503+
// )
504+
// }
504505
} else {
505506
this._broadcast(
506507
service.service_name,
@@ -553,6 +554,22 @@ export class DockerService {
553554
// Remove from active installs tracking
554555
this.activeInstallations.delete(service.service_name)
555556

557+
// If Ollama was just installed, trigger Nomad docs discovery and embedding
558+
if (service.service_name === SERVICE_NAMES.OLLAMA) {
559+
logger.info('[DockerService] Ollama installation complete. Enabling chat suggestions by default.')
560+
await KVStore.setValue('chat.suggestionsEnabled', "true")
561+
562+
logger.info('[DockerService] Ollama installation complete. Triggering Nomad docs discovery...')
563+
564+
// Need to use dynamic imports here to avoid circular dependency
565+
const ollamaService = new (await import('./ollama_service.js')).OllamaService()
566+
const ragService = new (await import('./rag_service.js')).RagService(this, ollamaService)
567+
568+
ragService.discoverNomadDocs().catch((error) => {
569+
logger.error('[DockerService] Failed to discover Nomad docs:', error)
570+
})
571+
}
572+
556573
this._broadcast(
557574
service.service_name,
558575
'completed',
@@ -715,57 +732,57 @@ export class DockerService {
715732
* Discover AMD GPU DRI devices dynamically.
716733
* Returns an array of device configurations for Docker.
717734
*/
718-
private async _discoverAMDDevices(): Promise<
719-
Array<{ PathOnHost: string; PathInContainer: string; CgroupPermissions: string }>
720-
> {
721-
try {
722-
const devices: Array<{
723-
PathOnHost: string
724-
PathInContainer: string
725-
CgroupPermissions: string
726-
}> = []
727-
728-
// Always add /dev/kfd (Kernel Fusion Driver)
729-
devices.push({
730-
PathOnHost: '/dev/kfd',
731-
PathInContainer: '/dev/kfd',
732-
CgroupPermissions: 'rwm',
733-
})
734-
735-
// Discover DRI devices in /dev/dri/
736-
try {
737-
const driDevices = await readdir('/dev/dri')
738-
for (const device of driDevices) {
739-
const devicePath = `/dev/dri/${device}`
740-
devices.push({
741-
PathOnHost: devicePath,
742-
PathInContainer: devicePath,
743-
CgroupPermissions: 'rwm',
744-
})
745-
}
746-
logger.info(
747-
`[DockerService] Discovered ${driDevices.length} DRI devices: ${driDevices.join(', ')}`
748-
)
749-
} catch (error) {
750-
logger.warn(`[DockerService] Could not read /dev/dri directory: ${error.message}`)
751-
// Fallback to common device names if directory read fails
752-
const fallbackDevices = ['card0', 'renderD128']
753-
for (const device of fallbackDevices) {
754-
devices.push({
755-
PathOnHost: `/dev/dri/${device}`,
756-
PathInContainer: `/dev/dri/${device}`,
757-
CgroupPermissions: 'rwm',
758-
})
759-
}
760-
logger.info(`[DockerService] Using fallback DRI devices: ${fallbackDevices.join(', ')}`)
761-
}
762-
763-
return devices
764-
} catch (error) {
765-
logger.error(`[DockerService] Error discovering AMD devices: ${error.message}`)
766-
return []
767-
}
768-
}
735+
// private async _discoverAMDDevices(): Promise<
736+
// Array<{ PathOnHost: string; PathInContainer: string; CgroupPermissions: string }>
737+
// > {
738+
// try {
739+
// const devices: Array<{
740+
// PathOnHost: string
741+
// PathInContainer: string
742+
// CgroupPermissions: string
743+
// }> = []
744+
745+
// // Always add /dev/kfd (Kernel Fusion Driver)
746+
// devices.push({
747+
// PathOnHost: '/dev/kfd',
748+
// PathInContainer: '/dev/kfd',
749+
// CgroupPermissions: 'rwm',
750+
// })
751+
752+
// // Discover DRI devices in /dev/dri/
753+
// try {
754+
// const driDevices = await readdir('/dev/dri')
755+
// for (const device of driDevices) {
756+
// const devicePath = `/dev/dri/${device}`
757+
// devices.push({
758+
// PathOnHost: devicePath,
759+
// PathInContainer: devicePath,
760+
// CgroupPermissions: 'rwm',
761+
// })
762+
// }
763+
// logger.info(
764+
// `[DockerService] Discovered ${driDevices.length} DRI devices: ${driDevices.join(', ')}`
765+
// )
766+
// } catch (error) {
767+
// logger.warn(`[DockerService] Could not read /dev/dri directory: ${error.message}`)
768+
// // Fallback to common device names if directory read fails
769+
// const fallbackDevices = ['card0', 'renderD128']
770+
// for (const device of fallbackDevices) {
771+
// devices.push({
772+
// PathOnHost: `/dev/dri/${device}`,
773+
// PathInContainer: `/dev/dri/${device}`,
774+
// CgroupPermissions: 'rwm',
775+
// })
776+
// }
777+
// logger.info(`[DockerService] Using fallback DRI devices: ${fallbackDevices.join(', ')}`)
778+
// }
779+
780+
// return devices
781+
// } catch (error) {
782+
// logger.error(`[DockerService] Error discovering AMD devices: ${error.message}`)
783+
// return []
784+
// }
785+
// }
769786

770787
private _broadcast(service: string, status: string, message: string) {
771788
transmit.broadcast('service-installation', {

admin/app/services/ollama_service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ export class OllamaService {
4242
}
4343

4444
/**
45-
* Synchronous version of model download (waits for completion). Should only be used for
46-
* small models or in contexts where a background job is incompatible.
45+
* Downloads a model from the Ollama service with progress tracking. Where possible,
46+
* one should dispatch a background job instead of calling this method directly to avoid long blocking.
4747
* @param model Model name to download
4848
* @returns Success status and message
4949
*/

0 commit comments

Comments
 (0)