From a4d2067d7089b835bb0dc4dc49b4379f7c7471e2 Mon Sep 17 00:00:00 2001 From: hailin Date: Mon, 23 Jun 2025 13:47:53 +0800 Subject: [PATCH] . --- apps/blogai/app/[locale]/morphic/action.tsx | 167 ++++++++++++-------- apps/blogai/app/[locale]/morphic/layout.tsx | 41 +++-- 2 files changed, 131 insertions(+), 77 deletions(-) diff --git a/apps/blogai/app/[locale]/morphic/action.tsx b/apps/blogai/app/[locale]/morphic/action.tsx index 23f8e9a..e7cff8a 100644 --- a/apps/blogai/app/[locale]/morphic/action.tsx +++ b/apps/blogai/app/[locale]/morphic/action.tsx @@ -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() + 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() + + 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() + 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( +
+ +
+ ) + } + + 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 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() - 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() - 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() - 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( -
- -
- ) - } - 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 }) + \ No newline at end of file diff --git a/apps/blogai/app/[locale]/morphic/layout.tsx b/apps/blogai/app/[locale]/morphic/layout.tsx index 6a0cfbc..5ec43d1 100644 --- a/apps/blogai/app/[locale]/morphic/layout.tsx +++ b/apps/blogai/app/[locale]/morphic/layout.tsx @@ -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 ( + // + // + // + //
+ // {children} + //