feat(frontend): 对齐聊天页与消息流类型接口
This commit is contained in:
parent
662911e93b
commit
43355677f9
|
|
@ -9,6 +9,7 @@ export default tseslint.config(
|
||||||
{
|
{
|
||||||
ignores: [
|
ignores: [
|
||||||
".next",
|
".next",
|
||||||
|
"imports/**",
|
||||||
"src/components/ui/**",
|
"src/components/ui/**",
|
||||||
"src/components/ai-elements/**",
|
"src/components/ai-elements/**",
|
||||||
"*.js",
|
"*.js",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import type { UseStream } from "@langchain/langgraph-sdk/react";
|
|
||||||
import { BotIcon, PlusSquare } from "lucide-react";
|
import { BotIcon, PlusSquare } from "lucide-react";
|
||||||
import { useParams, useRouter } from "next/navigation";
|
import { useParams, useRouter } from "next/navigation";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
|
|
@ -22,7 +21,6 @@ import { useAgent } from "@/core/agents";
|
||||||
import { useI18n } from "@/core/i18n/hooks";
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
import { useNotification } from "@/core/notification/hooks";
|
import { useNotification } from "@/core/notification/hooks";
|
||||||
import { useLocalSettings } from "@/core/settings";
|
import { useLocalSettings } from "@/core/settings";
|
||||||
import type { AgentThreadState } from "@/core/threads";
|
|
||||||
import { useThreadStream } from "@/core/threads/hooks";
|
import { useThreadStream } from "@/core/threads/hooks";
|
||||||
import { textOfMessage } from "@/core/threads/utils";
|
import { textOfMessage } from "@/core/threads/utils";
|
||||||
import { env } from "@/env";
|
import { env } from "@/env";
|
||||||
|
|
@ -82,7 +80,6 @@ export default function AgentChatPage() {
|
||||||
const handleStop = useCallback(async () => {
|
const handleStop = useCallback(async () => {
|
||||||
await thread.stop();
|
await thread.stop();
|
||||||
}, [thread]);
|
}, [thread]);
|
||||||
const legacyThread = thread as unknown as UseStream<AgentThreadState>;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThreadContext.Provider value={{ thread }}>
|
<ThreadContext.Provider value={{ thread }}>
|
||||||
|
|
@ -105,7 +102,7 @@ export default function AgentChatPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex w-full items-center text-sm font-medium">
|
<div className="flex w-full items-center text-sm font-medium">
|
||||||
<ThreadTitle threadId={threadId} thread={legacyThread} />
|
<ThreadTitle threadId={threadId} thread={thread} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mr-4 flex items-center">
|
<div className="mr-4 flex items-center">
|
||||||
<Tooltip content={t.agents.newChat}>
|
<Tooltip content={t.agents.newChat}>
|
||||||
|
|
@ -130,7 +127,7 @@ export default function AgentChatPage() {
|
||||||
<MessageList
|
<MessageList
|
||||||
className={cn("size-full", !isNewThread && "pt-10")}
|
className={cn("size-full", !isNewThread && "pt-10")}
|
||||||
threadId={threadId}
|
threadId={threadId}
|
||||||
thread={legacyThread}
|
thread={thread}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -159,6 +156,7 @@ export default function AgentChatPage() {
|
||||||
<InputBox
|
<InputBox
|
||||||
className={cn("bg-background/5 w-full -translate-y-4")}
|
className={cn("bg-background/5 w-full -translate-y-4")}
|
||||||
isNewThread={isNewThread}
|
isNewThread={isNewThread}
|
||||||
|
threadId={threadId}
|
||||||
autoFocus={isNewThread}
|
autoFocus={isNewThread}
|
||||||
status={
|
status={
|
||||||
thread.error
|
thread.error
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import type { UseStream } from "@langchain/langgraph-sdk/react";
|
|
||||||
import { ArrowLeftIcon, BotIcon, CheckCircleIcon } from "lucide-react";
|
import { ArrowLeftIcon, BotIcon, CheckCircleIcon } from "lucide-react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useCallback, useMemo, useState } from "react";
|
import { useCallback, useMemo, useState } from "react";
|
||||||
|
|
@ -19,7 +18,6 @@ import { ThreadContext } from "@/components/workspace/messages/context";
|
||||||
import type { Agent } from "@/core/agents";
|
import type { Agent } from "@/core/agents";
|
||||||
import { checkAgentName, getAgent } from "@/core/agents/api";
|
import { checkAgentName, getAgent } from "@/core/agents/api";
|
||||||
import { useI18n } from "@/core/i18n/hooks";
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
import type { AgentThreadState } from "@/core/threads";
|
|
||||||
import { useThreadStream } from "@/core/threads/hooks";
|
import { useThreadStream } from "@/core/threads/hooks";
|
||||||
import { uuid } from "@/core/utils/uuid";
|
import { uuid } from "@/core/utils/uuid";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
@ -120,7 +118,6 @@ export default function NewAgentPage() {
|
||||||
);
|
);
|
||||||
|
|
||||||
// ── Shared header ──────────────────────────────────────────────────────────
|
// ── Shared header ──────────────────────────────────────────────────────────
|
||||||
const legacyThread = thread as unknown as UseStream<AgentThreadState>;
|
|
||||||
|
|
||||||
const header = (
|
const header = (
|
||||||
<header className="flex shrink-0 items-center gap-3 border-b px-4 py-3">
|
<header className="flex shrink-0 items-center gap-3 border-b px-4 py-3">
|
||||||
|
|
@ -200,7 +197,7 @@ export default function NewAgentPage() {
|
||||||
<MessageList
|
<MessageList
|
||||||
className="size-full pt-10"
|
className="size-full pt-10"
|
||||||
threadId={threadId}
|
threadId={threadId}
|
||||||
thread={legacyThread}
|
thread={thread}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import type { UseStream } from "@langchain/langgraph-sdk/react";
|
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
|
|
||||||
import { type PromptInputMessage } from "@/components/ai-elements/prompt-input";
|
import { type PromptInputMessage } from "@/components/ai-elements/prompt-input";
|
||||||
|
|
@ -21,7 +20,6 @@ import { Welcome } from "@/components/workspace/welcome";
|
||||||
import { useI18n } from "@/core/i18n/hooks";
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
import { useNotification } from "@/core/notification/hooks";
|
import { useNotification } from "@/core/notification/hooks";
|
||||||
import { useLocalSettings } from "@/core/settings";
|
import { useLocalSettings } from "@/core/settings";
|
||||||
import type { AgentThreadState } from "@/core/threads";
|
|
||||||
import { useThreadStream } from "@/core/threads/hooks";
|
import { useThreadStream } from "@/core/threads/hooks";
|
||||||
import { textOfMessage } from "@/core/threads/utils";
|
import { textOfMessage } from "@/core/threads/utils";
|
||||||
import { env } from "@/env";
|
import { env } from "@/env";
|
||||||
|
|
@ -72,7 +70,6 @@ export default function ChatPage() {
|
||||||
const handleStop = useCallback(async () => {
|
const handleStop = useCallback(async () => {
|
||||||
await thread.stop();
|
await thread.stop();
|
||||||
}, [thread]);
|
}, [thread]);
|
||||||
const legacyThread = thread as unknown as UseStream<AgentThreadState>;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThreadContext.Provider value={{ thread, isMock }}>
|
<ThreadContext.Provider value={{ thread, isMock }}>
|
||||||
|
|
@ -87,7 +84,7 @@ export default function ChatPage() {
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex w-full items-center text-sm font-medium">
|
<div className="flex w-full items-center text-sm font-medium">
|
||||||
<ThreadTitle threadId={threadId} thread={legacyThread} />
|
<ThreadTitle threadId={threadId} thread={thread} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<TokenUsageIndicator messages={thread.messages} />
|
<TokenUsageIndicator messages={thread.messages} />
|
||||||
|
|
@ -100,7 +97,7 @@ export default function ChatPage() {
|
||||||
<MessageList
|
<MessageList
|
||||||
className={cn("size-full", !isNewThread && "pt-10")}
|
className={cn("size-full", !isNewThread && "pt-10")}
|
||||||
threadId={threadId}
|
threadId={threadId}
|
||||||
thread={legacyThread}
|
thread={thread}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute right-0 bottom-0 left-0 z-30 flex justify-center px-4">
|
<div className="absolute right-0 bottom-0 left-0 z-30 flex justify-center px-4">
|
||||||
|
|
@ -127,6 +124,7 @@ export default function ChatPage() {
|
||||||
<InputBox
|
<InputBox
|
||||||
className={cn("bg-background/5 w-full -translate-y-4")}
|
className={cn("bg-background/5 w-full -translate-y-4")}
|
||||||
isNewThread={isNewThread}
|
isNewThread={isNewThread}
|
||||||
|
threadId={threadId}
|
||||||
autoFocus={isNewThread}
|
autoFocus={isNewThread}
|
||||||
status={
|
status={
|
||||||
thread.error
|
thread.error
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,7 @@ export function InputBox({
|
||||||
extraHeader,
|
extraHeader,
|
||||||
isNewThread,
|
isNewThread,
|
||||||
hasSubmitted,
|
hasSubmitted,
|
||||||
|
threadId: threadIdProp,
|
||||||
initialValue,
|
initialValue,
|
||||||
onContextChange,
|
onContextChange,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
|
|
@ -109,6 +110,7 @@ export function InputBox({
|
||||||
extraHeader?: React.ReactNode;
|
extraHeader?: React.ReactNode;
|
||||||
isNewThread?: boolean;
|
isNewThread?: boolean;
|
||||||
hasSubmitted?: boolean;
|
hasSubmitted?: boolean;
|
||||||
|
threadId?: string;
|
||||||
initialValue?: string;
|
initialValue?: string;
|
||||||
onContextChange?: (
|
onContextChange?: (
|
||||||
context: Omit<
|
context: Omit<
|
||||||
|
|
@ -126,7 +128,7 @@ export function InputBox({
|
||||||
const iframeSkill = useIframeSkill();
|
const iframeSkill = useIframeSkill();
|
||||||
|
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const threadId = params?.thread_id;
|
const threadId = threadIdProp ?? params?.thread_id;
|
||||||
const { textInput } = usePromptInputController();
|
const { textInput } = usePromptInputController();
|
||||||
const attachments = usePromptInputAttachments();
|
const attachments = usePromptInputAttachments();
|
||||||
const promptRootRef = useRef<HTMLDivElement | null>(null);
|
const promptRootRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import type { Message } from "@langchain/langgraph-sdk";
|
import type { BaseStream } from "@langchain/langgraph-sdk/react";
|
||||||
import type { UseStream } from "@langchain/langgraph-sdk/react";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Conversation,
|
Conversation,
|
||||||
|
|
@ -34,23 +33,18 @@ export function MessageList({
|
||||||
className,
|
className,
|
||||||
threadId,
|
threadId,
|
||||||
thread,
|
thread,
|
||||||
messagesOverride,
|
|
||||||
suppressThreadLoading = false,
|
|
||||||
paddingBottom = 160,
|
paddingBottom = 160,
|
||||||
}: {
|
}: {
|
||||||
className?: string;
|
className?: string;
|
||||||
threadId: string;
|
threadId: string;
|
||||||
thread: UseStream<AgentThreadState>;
|
thread: BaseStream<AgentThreadState>;
|
||||||
/** When set (e.g. from onFinish), use instead of thread.messages so SSE end shows complete state. */
|
|
||||||
messagesOverride?: Message[];
|
|
||||||
suppressThreadLoading?: boolean;
|
|
||||||
paddingBottom?: number;
|
paddingBottom?: number;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const rehypePlugins = useRehypeSplitWordsIntoSpans(thread.isLoading);
|
const rehypePlugins = useRehypeSplitWordsIntoSpans(thread.isLoading);
|
||||||
const updateSubtask = useUpdateSubtask();
|
const updateSubtask = useUpdateSubtask();
|
||||||
const messages = messagesOverride ?? thread.messages;
|
const messages = thread.messages;
|
||||||
if (thread.isThreadLoading && !suppressThreadLoading && messages.length === 0) {
|
if (thread.isThreadLoading && messages.length === 0) {
|
||||||
return <MessageListSkeleton />;
|
return <MessageListSkeleton />;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
|
@ -60,13 +54,15 @@ export function MessageList({
|
||||||
<ConversationContent className="mx-auto w-full max-w-(--container-width-md) gap-8 pt-12">
|
<ConversationContent className="mx-auto w-full max-w-(--container-width-md) gap-8 pt-12">
|
||||||
{groupMessages(messages, (group) => {
|
{groupMessages(messages, (group) => {
|
||||||
if (group.type === "human" || group.type === "assistant") {
|
if (group.type === "human" || group.type === "assistant") {
|
||||||
return group.messages.map((msg) => (
|
return group.messages.map((msg) => {
|
||||||
<MessageListItem
|
return (
|
||||||
key={`${group.id}/${msg.id}`}
|
<MessageListItem
|
||||||
message={msg}
|
key={`${group.id}/${msg.id}`}
|
||||||
isLoading={thread.isLoading}
|
message={msg}
|
||||||
/>
|
isLoading={thread.isLoading}
|
||||||
));
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
} else if (group.type === "assistant:clarification") {
|
} else if (group.type === "assistant:clarification") {
|
||||||
const message = group.messages[0];
|
const message = group.messages[0];
|
||||||
if (message && hasContent(message)) {
|
if (message && hasContent(message)) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue