This commit is contained in:
hailin 2025-06-23 13:47:53 +08:00
parent 617780ef4b
commit a4d2067d70
2 changed files with 131 additions and 77 deletions

View File

@ -11,84 +11,115 @@ import { Section } from '@/components/mpv2/section'
import { FollowupPanel } from '@/components/mpv2/followup-panel'
import { inquire, researcher, taskManager, querySuggestor } from '@/lib/agents'
const initialAIState: ExperimentalMessage[] = []
async function submit(formData?: FormData, skip?: boolean) {
'use server'
const aiState = getMutableAIState<typeof AI>()
const uiStream = createStreamableUI()
const isGenerating = createStreamableValue(true)
const messages: ExperimentalMessage[] = aiState.get() as any
// Get the user input from the form data
const userInput = skip
? `{"action": "skip"}`
: (formData?.get('input') as string)
const content = skip
? userInput
: formData
? JSON.stringify(Object.fromEntries(formData))
: null
// Add the user message to the state
if (content) {
const message = { role: 'user', content }
messages.push(message as ExperimentalMessage)
aiState.update([...(aiState.get() as any), message])
}
async function processEvents() {
uiStream.update(<Spinner />)
let action: any = { object: { next: 'proceed' } }
// If the user skips the task, we proceed to the search
if (!skip) action = await taskManager(messages)
if (action.object.next === 'inquire') {
// Generate inquiry
const inquiry = await inquire(uiStream, messages)
uiStream.done()
isGenerating.done()
aiState.done([
...aiState.get(),
{ role: 'assistant', content: `inquiry: ${inquiry?.question}` }
])
return
}
// Generate the answer
let answer = ''
let errorOccurred = false
const streamText = createStreamableValue<string>()
while (answer.length === 0) {
// Search the web and generate the answer
const { fullResponse, hasError } = await researcher(
uiStream,
streamText,
messages
)
answer = fullResponse
errorOccurred = hasError
}
streamText.done()
if (!errorOccurred) {
// Generate related queries
await querySuggestor(uiStream, messages)
// Add follow-up panel
uiStream.append(
<Section title="Follow-up">
<FollowupPanel />
</Section>
)
}
isGenerating.done(false)
uiStream.done()
aiState.done([...aiState.get(), { role: 'assistant', content: answer }])
}
processEvents()
return {
id: Date.now(),
isGenerating: isGenerating.value,
component: uiStream.value
}
}
// Define the initial state of the AI. It can be any JSON object.
const initialAIState: {
role: 'user' | 'assistant' | 'system' | 'function' | 'tool'
content: string
id?: string
name?: string
}[] = []
// The initial UI state that the client will keep track of, which contains the message IDs and their UI nodes.
const initialUIState: {
id: number
isGenerating: StreamableValue<boolean>
component: React.ReactNode
}[] = []
// AI is a provider you wrap your application with so you can access AI and UI state in your components.
export const AI = createAI({
actions: {
submit: async function(formData?: FormData, skip?: boolean) {
'use server'
const aiState = getMutableAIState<typeof AI>()
const uiStream = createStreamableUI()
const isGenerating = createStreamableValue(true)
const messages: ExperimentalMessage[] = aiState.get() as any
const userInput = skip
? `{"action": "skip"}`
: (formData?.get('input') as string)
const content = skip
? userInput
: formData
? JSON.stringify(Object.fromEntries(formData))
: null
if (content) {
const message = { role: 'user', content }
messages.push(message as ExperimentalMessage)
aiState.update([...(aiState.get() as any), message])
}
async function processEvents() {
uiStream.update(<Spinner />)
let action: any = { object: { next: 'proceed' } }
if (!skip) action = await taskManager(messages)
if (action.object.next === 'inquire') {
const inquiry = await inquire(uiStream, messages)
uiStream.done()
isGenerating.done()
aiState.done([
...aiState.get(),
{ role: 'assistant', content: `inquiry: ${inquiry?.question}` }
])
return
}
let answer = ''
let errorOccurred = false
const streamText = createStreamableValue<string>()
while (answer.length === 0) {
const { fullResponse, hasError } = await researcher(
uiStream,
streamText,
messages
)
answer = fullResponse
errorOccurred = hasError
}
streamText.done()
if (!errorOccurred) {
await querySuggestor(uiStream, messages)
uiStream.append(
<Section title="Follow-up">
<FollowupPanel />
</Section>
)
}
isGenerating.done(false)
uiStream.done()
aiState.done([...aiState.get(), { role: 'assistant', content: answer }])
}
processEvents()
return {
id: Date.now(),
isGenerating: isGenerating.value,
component: uiStream.value
}
}
submit
},
// Each state can be any shape of object, but for chat applications
// it makes sense to have an array of messages. Or you may prefer something like { id: number, messages: Message[] }
initialUIState,
initialAIState
})

View File

@ -1,14 +1,12 @@
import type { Metadata, Viewport } from 'next'
import { Inter as FontSans } from 'next/font/google'
import { AI } from './action'
import '../../globals.css'
import { cn } from '@/lib/utils'
import { ThemeProvider } from '@/components/mpv2/theme-provider'
import Header from '@/components/mpv2/header'
import Footer from '@/components/mpv2/footer'
import { AIProvider } from 'ai/rsc' // ✅ 关键点
import { AI } from './action' // ✅ 注意这里的引号
const fontSans = FontSans({
subsets: ['latin'],
variable: '--font-sans'
@ -41,14 +39,39 @@ export const viewport: Viewport = {
maximumScale: 1
}
// ✅ 这里类型一定要这样!不能直接 children: React.ReactNode
export default function RootLayout({ children }: { children: React.ReactNode }) {
export default function RootLayout({
children
}: Readonly<{
children: React.ReactNode
}>) {
// return (
// <html lang="en" suppressHydrationWarning>
// <body className={cn('font-sans antialiased', fontSans.variable)}>
// <ThemeProvider
// attribute="class"
// defaultTheme="system"
// enableSystem
// disableTransitionOnChange
// >
// <Header />
// <AI>{children}</AI>
// <Footer />
// </ThemeProvider>
// </body>
// </html>
// )
return (
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{/* <Header /> */}
<AIProvider value={AI}>
{children}
</AIProvider>
<AI>{children}</AI>
{/* <Footer /> */}
</ThemeProvider>
)