Skip to content

Commit f10ca70

Browse files
committed
feat(AI Assistant): custom name option for AI Assistant
1 parent f4d78c8 commit f10ca70

File tree

18 files changed

+171
-263
lines changed

18 files changed

+171
-263
lines changed

admin/app/controllers/settings_controller.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,14 @@ export default class SettingsController {
5454
const availableModels = await this.ollamaService.getAvailableModels({ sort: 'pulls', recommendedOnly: false, query: null, limit: 15 });
5555
const installedModels = await this.ollamaService.getModels();
5656
const chatSuggestionsEnabled = await KVStore.getValue('chat.suggestionsEnabled')
57+
const aiAssistantCustomName = await KVStore.getValue('ai.assistantCustomName')
5758
return inertia.render('settings/models', {
5859
models: {
5960
availableModels: availableModels?.models || [],
6061
installedModels: installedModels || [],
6162
settings: {
62-
chatSuggestionsEnabled: chatSuggestionsEnabled ?? false
63+
chatSuggestionsEnabled: chatSuggestionsEnabled ?? false,
64+
aiAssistantCustomName: aiAssistantCustomName ?? '',
6365
}
6466
}
6567
});

admin/app/models/kv_store.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,15 @@ export default class KVStore extends BaseModel {
5050
}
5151
return setting
5252
}
53+
54+
/**
55+
* Clear a setting value by key, storing null so getValue returns null.
56+
*/
57+
static async clearValue<K extends KVStoreKey>(key: K): Promise<void> {
58+
const setting = await this.findBy('key', key)
59+
if (setting && setting.value !== null) {
60+
setting.value = null
61+
await setting.save()
62+
}
63+
}
5364
}

admin/app/services/system_service.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { getAllFilesystems, getFile } from '../utils/fs.js'
1212
import axios from 'axios'
1313
import env from '#start/env'
1414
import KVStore from '#models/kv_store'
15-
import { KVStoreKey } from '../../types/kv_store.js'
15+
import { KV_STORE_SCHEMA, KVStoreKey } from '../../types/kv_store.js'
1616

1717

1818
@inject()
@@ -388,7 +388,11 @@ export class SystemService {
388388
}
389389

390390
async updateSetting(key: KVStoreKey, value: any): Promise<void> {
391-
await KVStore.setValue(key, value);
391+
if ((value === '' || value === undefined || value === null) && KV_STORE_SCHEMA[key] === 'string') {
392+
await KVStore.clearValue(key)
393+
} else {
394+
await KVStore.setValue(key, value)
395+
}
392396
}
393397

394398
/**

admin/app/validators/settings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ import { SETTINGS_KEYS } from "../../constants/kv_store.js";
44

55
export const updateSettingSchema = vine.compile(vine.object({
66
key: vine.enum(SETTINGS_KEYS),
7-
value: vine.any(),
7+
value: vine.any().optional(),
88
}))

admin/config/inertia.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import KVStore from '#models/kv_store'
12
import { SystemService } from '#services/system_service'
23
import { defineConfig } from '@adonisjs/inertia'
34
import type { InferSharedProps } from '@adonisjs/inertia/types'
@@ -14,6 +15,10 @@ const inertiaConfig = defineConfig({
1415
sharedData: {
1516
appVersion: () => SystemService.getAppVersion(),
1617
environment: process.env.NODE_ENV || 'production',
18+
aiAssistantName: async () => {
19+
const customName = await KVStore.getValue('ai.assistantCustomName')
20+
return (customName && customName.trim()) ? customName : 'AI Assistant'
21+
},
1722
},
1823

1924
/**

admin/constants/kv_store.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
import { KVStoreKey } from "../types/kv_store.js";
22

3-
export const SETTINGS_KEYS: KVStoreKey[] = ['chat.suggestionsEnabled', 'ui.hasVisitedEasySetup', 'system.earlyAccess'];
3+
export const SETTINGS_KEYS: KVStoreKey[] = ['chat.suggestionsEnabled', 'ui.hasVisitedEasySetup', 'system.earlyAccess', 'ai.assistantCustomName'];

admin/inertia/components/chat/ChatInterface.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import StyledModal from '../StyledModal'
99
import api from '~/lib/api'
1010
import { DEFAULT_QUERY_REWRITE_MODEL } from '../../../constants/ollama'
1111
import { useNotifications } from '~/context/NotificationContext'
12+
import { usePage } from '@inertiajs/react'
1213

1314
interface ChatInterfaceProps {
1415
messages: ChatMessage[]
@@ -29,6 +30,7 @@ export default function ChatInterface({
2930
chatSuggestionsLoading = false,
3031
rewriteModelAvailable = false
3132
}: ChatInterfaceProps) {
33+
const { aiAssistantName } = usePage<{ aiAssistantName: string }>().props
3234
const { addNotification } = useNotifications()
3335
const [input, setInput] = useState('')
3436
const [downloadDialogOpen, setDownloadDialogOpen] = useState(false)
@@ -160,7 +162,7 @@ export default function ChatInterface({
160162
value={input}
161163
onChange={handleInput}
162164
onKeyDown={handleKeyDown}
163-
placeholder="Type your message... (Shift+Enter for new line)"
165+
placeholder={`Type your message to ${aiAssistantName}... (Shift+Enter for new line)`}
164166
className="w-full resize-none rounded-lg border border-gray-300 px-4 py-3 pr-12 focus:outline-none focus:ring-2 focus:ring-desert-green focus:border-transparent disabled:bg-gray-50 disabled:text-gray-500"
165167
rows={1}
166168
disabled={isLoading}

admin/inertia/components/chat/ChatSidebar.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import classNames from '~/lib/classNames'
22
import StyledButton from '../StyledButton'
3-
import { router } from '@inertiajs/react'
3+
import { router, usePage } from '@inertiajs/react'
44
import { ChatSession } from '../../../types/chat'
55
import { IconMessage } from '@tabler/icons-react'
66
import { useState } from 'react'
@@ -23,6 +23,7 @@ export default function ChatSidebar({
2323
onClearHistory,
2424
isInModal = false,
2525
}: ChatSidebarProps) {
26+
const { aiAssistantName } = usePage<{ aiAssistantName: string }>().props
2627
const [isKnowledgeBaseModalOpen, setIsKnowledgeBaseModalOpen] = useState(
2728
() => new URLSearchParams(window.location.search).get('knowledge_base') === 'true'
2829
)
@@ -139,7 +140,7 @@ export default function ChatSidebar({
139140
)}
140141
</div>
141142
{isKnowledgeBaseModalOpen && (
142-
<KnowledgeBaseModal onClose={handleCloseKnowledgeBase} />
143+
<KnowledgeBaseModal aiAssistantName={aiAssistantName} onClose={handleCloseKnowledgeBase} />
143144
)}
144145
</div>
145146
)

admin/inertia/components/chat/KnowledgeBaseModal.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ import { useModals } from '~/context/ModalContext'
1111
import StyledModal from '../StyledModal'
1212

1313
interface KnowledgeBaseModalProps {
14+
aiAssistantName?: string
1415
onClose: () => void
1516
}
1617

17-
export default function KnowledgeBaseModal({ onClose }: KnowledgeBaseModalProps) {
18+
export default function KnowledgeBaseModal({ aiAssistantName = "AI Assistant", onClose }: KnowledgeBaseModalProps) {
1819
const { addNotification } = useNotifications()
1920
const [files, setFiles] = useState<File[]>([])
2021
const fileUploaderRef = useRef<React.ComponentRef<typeof FileUploader>>(null)
@@ -140,12 +141,12 @@ export default function KnowledgeBaseModal({ onClose }: KnowledgeBaseModalProps)
140141
</div>
141142
<div>
142143
<p className="font-medium text-desert-stone-dark">
143-
AI Assistant Knowledge Base Integration
144+
{aiAssistantName} Knowledge Base Integration
144145
</p>
145146
<p className="text-sm text-desert-stone">
146147
When you upload documents to your Knowledge Base, NOMAD processes and embeds
147-
the content, making it directly accessible to the AI Assistant. This allows
148-
the AI Assistant to reference your specific documents during conversations,
148+
the content, making it directly accessible to {aiAssistantName}. This allows{' '}
149+
{aiAssistantName} to reference your specific documents during conversations,
149150
providing more accurate and personalized responses based on your uploaded
150151
data.
151152
</p>
@@ -177,8 +178,7 @@ export default function KnowledgeBaseModal({ onClose }: KnowledgeBaseModalProps)
177178
</p>
178179
<p className="text-sm text-desert-stone">
179180
NOMAD will automatically discover and extract any content you save to your
180-
Information Library (if installed), making it instantly available to the AI
181-
Assistant without any extra steps.
181+
Information Library (if installed), making it instantly available to {aiAssistantName} without any extra steps.
182182
</p>
183183
</div>
184184
</div>

admin/inertia/components/inputs/Input.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { InputHTMLAttributes } from "react";
44
export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
55
name: string;
66
label: string;
7+
helpText?: string;
78
className?: string;
89
labelClassName?: string;
910
inputClassName?: string;
@@ -17,6 +18,7 @@ const Input: React.FC<InputProps> = ({
1718
className,
1819
label,
1920
name,
21+
helpText,
2022
labelClassName,
2123
inputClassName,
2224
containerClassName,
@@ -33,6 +35,7 @@ const Input: React.FC<InputProps> = ({
3335
>
3436
{label}{required ? "*" : ""}
3537
</label>
38+
{helpText && <p className="mt-1 text-sm text-gray-500">{helpText}</p>}
3639
<div className={classNames("mt-1.5", containerClassName)}>
3740
<div className="relative">
3841
{leftIcon && (

0 commit comments

Comments
 (0)