Skip to content

Commit

Permalink
Refactor AIChat to support dynamic assistant configuration and improv…
Browse files Browse the repository at this point in the history
…e type safety
  • Loading branch information
trheyi committed Feb 9, 2025
1 parent 939d335 commit 05d4a8f
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
padding-right: 12px;
border-right: 1px solid var(--color_border_light);
position: relative;
width: 160px;

.avatarWrapper {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
min-width: 0;

.avatar {
width: 20px;
Expand All @@ -19,12 +22,46 @@
transform: scale(1.2);
background-color: var(--color_bg_hover);
transform-origin: center;
flex-shrink: 0;
}

.loadingAvatar {
width: 20px;
height: 20px;
border-radius: 50%;
background: linear-gradient(
90deg,
var(--color_bg_hover) 25%,
var(--color_border_light) 37%,
var(--color_bg_hover) 63%
);
background-size: 400% 100%;
flex-shrink: 0;
animation: shimmer 1.4s ease infinite;
}

.loadingName {
height: 12px;
width: 60px;
background: linear-gradient(
90deg,
var(--color_bg_hover) 25%,
var(--color_border_light) 37%,
var(--color_bg_hover) 63%
);
background-size: 400% 100%;
border-radius: 4px;
animation: shimmer 1.4s ease infinite;
}

.assistantName {
font-size: 12px;
color: var(--color_text);
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 0;
}
}

Expand All @@ -38,6 +75,7 @@
width: 16px;
height: 16px;
border-radius: 50%;
flex-shrink: 0;

&.disabled {
cursor: not-allowed;
Expand All @@ -57,3 +95,12 @@
}
}
}

@keyframes shimmer {
0% {
background-position: 100% 50%;
}
100% {
background-position: 0 50%;
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
import Icon from '@/widgets/Icon'
import styles from './index.less'
import clsx from 'clsx'
import { App } from '@/types'

interface AssistantProps {
assistant: {
assistant_id: string
assistant_name: string
assistant_avatar?: string
canDelete?: boolean
}
assistant: App.AssistantSummary | undefined
loading?: boolean
onDelete?: () => void
}

const Assistant = ({ assistant, loading, onDelete }: AssistantProps) => {
const { assistant_name, assistant_avatar } = assistant
const { assistant_id, assistant_name, assistant_avatar, assistant_deleteable } = assistant || {}
if (!assistant_id || assistant_id == '' || loading) {
return (
<div className={styles.assistantInfo}>
<div className={styles.avatarWrapper}>
<div className={styles.loadingAvatar} />
<div className={styles.loadingName} />
</div>
</div>
)
}

return (
<div className={styles.assistantInfo}>
Expand All @@ -24,17 +30,18 @@ const Assistant = ({ assistant, loading, onDelete }: AssistantProps) => {
alt='Assistant'
className={styles.avatar}
/>
<div className={styles.assistantName}>{assistant_name}</div>
<div className={styles.assistantName} title={assistant_name}>
{assistant_name}
</div>
</div>
{assistant.canDelete ||
(assistant.canDelete === undefined && (
<div
className={clsx(styles.deleteBtn, { [styles.disabled]: loading })}
onClick={!loading ? onDelete : undefined}
>
<Icon name='material-close' size={12} />
</div>
))}
{(assistant_deleteable || assistant_deleteable === undefined) && (
<div
className={clsx(styles.deleteBtn, { [styles.disabled]: loading })}
onClick={!loading ? onDelete : undefined}
>
<Icon name='material-close' size={12} />
</div>
)}
</div>
)
}
Expand Down
21 changes: 13 additions & 8 deletions packages/xgen/layouts/components/Neo/components/AIChat/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,13 @@ const AIChat = (props: AIChatProps) => {
const [inputValue, setInputValue] = useState('')
const messagesEndRef = useRef<HTMLDivElement>(null)
const [chat_id, setChatId] = useState(global.neo.chat_id || '')
const [assistant_id, setAssistantId] = useState(global.neo.assistant_id)
// const [assistant_id, setAssistantId] = useState(global.neo.assistant_id)
const [currentPage, setCurrentPage] = useState(pathname.replace(/\/_menu.*/gi, '').toLowerCase())
const [initialized, setInitialized] = useState(false)
const [placeholder, setPlaceholder] = useState<App.ChatPlaceholder | undefined>(global.neo.placeholder)

const {
assistant,
messages,
loading,
title,
Expand Down Expand Up @@ -102,7 +103,7 @@ const AIChat = (props: AIChatProps) => {
config: v.config,
signal: chat_context.signal,
chat_id: chat_id,
assistant_id: assistant_id
assistant_id: assistant?.assistant_id || ''
}
}
])
Expand All @@ -125,7 +126,7 @@ const AIChat = (props: AIChatProps) => {
// New chat
if (!initialized && !chat_id) {
// if res is not null, create a new chat
const res = await getLatestChat(assistant_id)
const res = await getLatestChat(assistant?.assistant_id || '')
res && !res.exist && handleNewChat(res) // new chat
res && res.exist && setChatId(res.chat_id) // existing chat
setInitialized(true)
Expand Down Expand Up @@ -163,7 +164,7 @@ const AIChat = (props: AIChatProps) => {
config: field.config,
signal: chat_context.signal,
chat_id: chat_id,
assistant_id: assistant_id
assistant_id: assistant?.assistant_id || ''
}
}

Expand Down Expand Up @@ -482,9 +483,9 @@ const AIChat = (props: AIChatProps) => {

// Add mock data for assistant
const mockAssistant = {
assistant_id: assistant_id || 'default-neo',
assistant_name: 'Neo Assistant',
assistant_avatar: 'https://api.dicebear.com/7.x/bottts/svg?seed=Neo'
assistant_id: assistant?.assistant_id || 'default-neo',
assistant_name: assistant?.assistant_name || 'Neo Assistant',
assistant_avatar: assistant?.assistant_avatar || 'https://api.dicebear.com/7.x/bottts/svg?seed=Neo'
// canDelete: false
}

Expand Down Expand Up @@ -569,7 +570,11 @@ const AIChat = (props: AIChatProps) => {
<div className={styles.currentPage}>
{/* Assistant Info Section */}
<div className={styles.leftSection}>
<Assistant assistant={mockAssistant} loading={loading} onDelete={() => {}} />
<Assistant
assistant={assistant || {}}
loading={loading}
onDelete={() => {}}
/>
{/* Current Page Info */}
{showCurrentPage && currentPage && (
<div className={styles.pageInfo}>
Expand Down
22 changes: 20 additions & 2 deletions packages/xgen/layouts/components/Neo/hooks/useAIChat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,15 +151,18 @@ const formatToMDX = (text: string, tokens: Record<string, { pending: boolean }>)
return formattedText
}

export default ({ assistant_id, chat_id, upload_options = {} }: Args) => {
export default ({ chat_id, upload_options = {} }: Args) => {
const event_source = useRef<EventSource>()
const global = useGlobal()
const [messages, setMessages] = useState<Array<App.ChatInfo>>([])
const [assistant, setAssistant] = useState<App.AssistantSummary | undefined>(undefined)

const [title, setTitle] = useState<string>('')
const [loading, setLoading] = useState(false)
const [attachments, setAttachments] = useState<App.ChatAttachment[]>([])
const [pendingCleanup, setPendingCleanup] = useState<App.ChatAttachment[]>([])
const uploadControllers = useRef<Map<string, AbortController>>(new Map())
const global = useGlobal()
const [assistant_id, setAssistantId] = useState<string | undefined>('')

const locale = getLocale()
const is_cn = locale === 'zh-CN'
Expand All @@ -176,6 +179,12 @@ export default ({ assistant_id, chat_id, upload_options = {} }: Args) => {
return `/api/${window.$app.api_prefix}${api}`
}, [global.app_info.optional?.neo?.api])

/** Update assistant **/
const updateAssistant = useMemoizedFn(async (assistant: App.AssistantSummary) => {
setAssistant(assistant)
setAssistantId(assistant.assistant_id)
})

/** Merge messages with same id */
const mergeMessages = useMemoizedFn((parsedContent: any[], baseMessage: any): App.ChatInfo[] => {
const res: App.ChatInfo[] = []
Expand Down Expand Up @@ -978,6 +987,9 @@ export default ({ assistant_id, chat_id, upload_options = {} }: Args) => {
setMessages(formattedMessages)
setTitle(chatInfo.chat.title || (is_cn ? '未命名' : 'Untitled'))

// Update assistant
updateAssistant(res.data.chat as App.AssistantSummary)

return {
messages: formattedMessages,
title: chatInfo.chat.title || (is_cn ? '未命名' : 'Untitled')
Expand Down Expand Up @@ -1006,6 +1018,8 @@ export default ({ assistant_id, chat_id, upload_options = {} }: Args) => {

// New chat
if (typeof res.data === 'object' && 'placeholder' in res.data) {
// Update assistant
updateAssistant(res.data as App.AssistantSummary)
return { chat_id: makeChatID(), placeholder: res.data.placeholder }
}

Expand All @@ -1022,6 +1036,9 @@ export default ({ assistant_id, chat_id, upload_options = {} }: Args) => {
setMessages(formattedMessages)
setTitle(chatInfo.chat.title || (is_cn ? '未命名' : 'Untitled'))

// Update assistant
updateAssistant(chatInfo.chat)

// Set chat_id
global.setNeoChatId(chatInfo.chat.chat_id)
return { chat_id: chatInfo.chat.chat_id, exist: true }
Expand Down Expand Up @@ -1325,6 +1342,7 @@ export default ({ assistant_id, chat_id, upload_options = {} }: Args) => {
return {
messages,
loading,
assistant,
setMessages,
cancel,
uploadFile,
Expand Down
17 changes: 16 additions & 1 deletion packages/xgen/types/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ export declare namespace App {
[key: string]: any
}

interface AssistantSummary {
assistant_id?: string
assistant_name?: string
assistant_avatar?: string
assistant_deleteable?: boolean
}

interface AssistantFilter {
keywords?: string
tags?: string[]
Expand Down Expand Up @@ -117,7 +124,15 @@ export declare namespace App {

/** Chat detail with history */
interface ChatDetail {
chat: Record<string, any>
chat: {
assistant_id?: string
assistant_name?: string
assistant_avatar?: string
assistant_deleteable?: boolean
placeholder?: ChatPlaceholder

[key: string]: any
}
history: Array<{
role: string
content: string
Expand Down

0 comments on commit 05d4a8f

Please sign in to comment.