mirror of
https://github.com/Crosstalk-Solutions/project-nomad.git
synced 2026-04-02 06:49:26 +02:00
fix: improve type-safety for KVStore values
This commit is contained in:
parent
e12e7c1696
commit
6817e2e47e
|
|
@ -2,7 +2,6 @@ import { inject } from '@adonisjs/core'
|
||||||
import type { HttpContext } from '@adonisjs/core/http'
|
import type { HttpContext } from '@adonisjs/core/http'
|
||||||
import { ChatService } from '#services/chat_service'
|
import { ChatService } from '#services/chat_service'
|
||||||
import { createSessionSchema, updateSessionSchema, addMessageSchema } from '#validators/chat'
|
import { createSessionSchema, updateSessionSchema, addMessageSchema } from '#validators/chat'
|
||||||
import { parseBoolean } from '../utils/misc.js'
|
|
||||||
import KVStore from '#models/kv_store'
|
import KVStore from '#models/kv_store'
|
||||||
import { SystemService } from '#services/system_service'
|
import { SystemService } from '#services/system_service'
|
||||||
import { SERVICE_NAMES } from '../../constants/service_names.js'
|
import { SERVICE_NAMES } from '../../constants/service_names.js'
|
||||||
|
|
@ -20,7 +19,7 @@ export default class ChatsController {
|
||||||
const chatSuggestionsEnabled = await KVStore.getValue('chat.suggestionsEnabled')
|
const chatSuggestionsEnabled = await KVStore.getValue('chat.suggestionsEnabled')
|
||||||
return inertia.render('chat', {
|
return inertia.render('chat', {
|
||||||
settings: {
|
settings: {
|
||||||
chatSuggestionsEnabled: parseBoolean(chatSuggestionsEnabled),
|
chatSuggestionsEnabled: chatSuggestionsEnabled ?? false,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { SystemService } from '#services/system_service';
|
||||||
import { updateSettingSchema } from '#validators/settings';
|
import { updateSettingSchema } from '#validators/settings';
|
||||||
import { inject } from '@adonisjs/core';
|
import { inject } from '@adonisjs/core';
|
||||||
import type { HttpContext } from '@adonisjs/core/http'
|
import type { HttpContext } from '@adonisjs/core/http'
|
||||||
import { parseBoolean } from '../utils/misc.js';
|
import type { KVStoreKey } from '../../types/kv_store.js';
|
||||||
|
|
||||||
@inject()
|
@inject()
|
||||||
export default class SettingsController {
|
export default class SettingsController {
|
||||||
|
|
@ -59,7 +59,7 @@ export default class SettingsController {
|
||||||
availableModels: availableModels?.models || [],
|
availableModels: availableModels?.models || [],
|
||||||
installedModels: installedModels || [],
|
installedModels: installedModels || [],
|
||||||
settings: {
|
settings: {
|
||||||
chatSuggestionsEnabled: parseBoolean(chatSuggestionsEnabled)
|
chatSuggestionsEnabled: chatSuggestionsEnabled ?? false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -98,7 +98,7 @@ export default class SettingsController {
|
||||||
|
|
||||||
async getSetting({ request, response }: HttpContext) {
|
async getSetting({ request, response }: HttpContext) {
|
||||||
const key = request.qs().key;
|
const key = request.qs().key;
|
||||||
const value = await KVStore.getValue(key);
|
const value = await KVStore.getValue(key as KVStoreKey);
|
||||||
return response.status(200).send({ key, value });
|
return response.status(200).send({ key, value });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export class CheckUpdateJob {
|
||||||
`[CheckUpdateJob] Update available: ${result.currentVersion} → ${result.latestVersion}`
|
`[CheckUpdateJob] Update available: ${result.currentVersion} → ${result.latestVersion}`
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
await KVStore.setValue('system.updateAvailable', "false")
|
await KVStore.setValue('system.updateAvailable', false)
|
||||||
logger.info(
|
logger.info(
|
||||||
`[CheckUpdateJob] System is up to date (${result.currentVersion})`
|
`[CheckUpdateJob] System is up to date (${result.currentVersion})`
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { DateTime } from 'luxon'
|
import { DateTime } from 'luxon'
|
||||||
import { BaseModel, column, SnakeCaseNamingStrategy } from '@adonisjs/lucid/orm'
|
import { BaseModel, column, SnakeCaseNamingStrategy } from '@adonisjs/lucid/orm'
|
||||||
import type { KVStoreKey, KVStoreValue } from '../../types/kv_store.js'
|
import { KV_STORE_SCHEMA, type KVStoreKey, type KVStoreValue } from '../../types/kv_store.js'
|
||||||
|
import { parseBoolean } from '../utils/misc.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic key-value store model for storing various settings
|
* Generic key-value store model for storing various settings
|
||||||
|
|
@ -17,7 +18,7 @@ export default class KVStore extends BaseModel {
|
||||||
declare key: KVStoreKey
|
declare key: KVStoreKey
|
||||||
|
|
||||||
@column()
|
@column()
|
||||||
declare value: KVStoreValue
|
declare value: string | null
|
||||||
|
|
||||||
@column.dateTime({ autoCreate: true })
|
@column.dateTime({ autoCreate: true })
|
||||||
declare created_at: DateTime
|
declare created_at: DateTime
|
||||||
|
|
@ -26,26 +27,25 @@ export default class KVStore extends BaseModel {
|
||||||
declare updated_at: DateTime
|
declare updated_at: DateTime
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a setting value by key
|
* Get a setting value by key, automatically deserializing to the correct type.
|
||||||
*/
|
*/
|
||||||
static async getValue(key: KVStoreKey): Promise<KVStoreValue> {
|
static async getValue<K extends KVStoreKey>(key: K): Promise<KVStoreValue<K> | null> {
|
||||||
const setting = await this.findBy('key', key)
|
const setting = await this.findBy('key', key)
|
||||||
if (!setting || setting.value === undefined || setting.value === null) {
|
if (!setting || setting.value === undefined || setting.value === null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
if (typeof setting.value === 'string') {
|
const raw = String(setting.value)
|
||||||
return setting.value
|
return (KV_STORE_SCHEMA[key] === 'boolean' ? parseBoolean(raw) : raw) as KVStoreValue<K>
|
||||||
}
|
|
||||||
return String(setting.value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a setting value by key (creates if not exists)
|
* Set a setting value by key (creates if not exists), automatically serializing to string.
|
||||||
*/
|
*/
|
||||||
static async setValue(key: KVStoreKey, value: KVStoreValue): Promise<KVStore> {
|
static async setValue<K extends KVStoreKey>(key: K, value: KVStoreValue<K>): Promise<KVStore> {
|
||||||
const setting = await this.firstOrCreate({ key }, { key, value })
|
const serialized = String(value)
|
||||||
if (setting.value !== value) {
|
const setting = await this.firstOrCreate({ key }, { key, value: serialized })
|
||||||
setting.value = value
|
if (setting.value !== serialized) {
|
||||||
|
setting.value = serialized
|
||||||
await setting.save()
|
await setting.save()
|
||||||
}
|
}
|
||||||
return setting
|
return setting
|
||||||
|
|
|
||||||
|
|
@ -546,7 +546,7 @@ export class DockerService {
|
||||||
// If Ollama was just installed, trigger Nomad docs discovery and embedding
|
// If Ollama was just installed, trigger Nomad docs discovery and embedding
|
||||||
if (service.service_name === SERVICE_NAMES.OLLAMA) {
|
if (service.service_name === SERVICE_NAMES.OLLAMA) {
|
||||||
logger.info('[DockerService] Ollama installation complete. Default behavior is to not enable chat suggestions.')
|
logger.info('[DockerService] Ollama installation complete. Default behavior is to not enable chat suggestions.')
|
||||||
await KVStore.setValue('chat.suggestionsEnabled', "false")
|
await KVStore.setValue('chat.suggestionsEnabled', false)
|
||||||
|
|
||||||
logger.info('[DockerService] Ollama installation complete. Triggering Nomad docs discovery...')
|
logger.info('[DockerService] Ollama installation complete. Triggering Nomad docs discovery...')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ import { removeStopwords } from 'stopword'
|
||||||
import { randomUUID } from 'node:crypto'
|
import { randomUUID } from 'node:crypto'
|
||||||
import { join } from 'node:path'
|
import { join } from 'node:path'
|
||||||
import KVStore from '#models/kv_store'
|
import KVStore from '#models/kv_store'
|
||||||
import { parseBoolean } from '../utils/misc.js'
|
|
||||||
import { ZIMExtractionService } from './zim_extraction_service.js'
|
import { ZIMExtractionService } from './zim_extraction_service.js'
|
||||||
import { ZIM_BATCH_SIZE } from '../../constants/zim_extraction.js'
|
import { ZIM_BATCH_SIZE } from '../../constants/zim_extraction.js'
|
||||||
|
|
||||||
|
|
@ -885,7 +884,7 @@ export class RagService {
|
||||||
const DOCS_DIR = join(process.cwd(), 'docs')
|
const DOCS_DIR = join(process.cwd(), 'docs')
|
||||||
|
|
||||||
const alreadyEmbeddedRaw = await KVStore.getValue('rag.docsEmbedded')
|
const alreadyEmbeddedRaw = await KVStore.getValue('rag.docsEmbedded')
|
||||||
if (parseBoolean(alreadyEmbeddedRaw) && !force) {
|
if (alreadyEmbeddedRaw && !force) {
|
||||||
logger.info('[RAG] Nomad docs have already been discovered and queued. Skipping.')
|
logger.info('[RAG] Nomad docs have already been discovered and queued. Skipping.')
|
||||||
return { success: true, message: 'Nomad docs have already been discovered and queued. Skipping.' }
|
return { success: true, message: 'Nomad docs have already been discovered and queued. Skipping.' }
|
||||||
}
|
}
|
||||||
|
|
@ -927,7 +926,7 @@ export class RagService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update KV store to mark docs as discovered so we don't redo this unnecessarily
|
// Update KV store to mark docs as discovered so we don't redo this unnecessarily
|
||||||
await KVStore.setValue('rag.docsEmbedded', 'true')
|
await KVStore.setValue('rag.docsEmbedded', true)
|
||||||
|
|
||||||
return { success: true, message: `Nomad docs discovery completed. Dispatched ${filesToEmbed.length} embedding jobs.` }
|
return { success: true, message: `Nomad docs discovery completed. Dispatched ${filesToEmbed.length} embedding jobs.` }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,13 @@
|
||||||
|
|
||||||
export type KVStoreKey = 'chat.suggestionsEnabled' | 'rag.docsEmbedded' | 'system.updateAvailable' | 'system.latestVersion' | 'ui.hasVisitedEasySetup'
|
export const KV_STORE_SCHEMA = {
|
||||||
export type KVStoreValue = string | null
|
'chat.suggestionsEnabled': 'boolean',
|
||||||
|
'rag.docsEmbedded': 'boolean',
|
||||||
|
'system.updateAvailable': 'boolean',
|
||||||
|
'system.latestVersion': 'string',
|
||||||
|
'ui.hasVisitedEasySetup': 'boolean',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
type KVTagToType<T extends string> = T extends 'boolean' ? boolean : string
|
||||||
|
|
||||||
|
export type KVStoreKey = keyof typeof KV_STORE_SCHEMA
|
||||||
|
export type KVStoreValue<K extends KVStoreKey> = KVTagToType<(typeof KV_STORE_SCHEMA)[K]>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user