Commit b62e34c6 authored by Joel's avatar Joel

feat: support output agent info

parent 2019d8f3
import { type NextRequest } from 'next/server'
import { NextResponse } from 'next/server'
import { client, getInfo } from '@/app/api/utils/common'
export async function POST(request: NextRequest, { params }: {
params: { conversationId: string }
}) {
const body = await request.json()
const {
auto_generate,
name,
} = body
const { conversationId } = params
const { user } = getInfo(request)
// auto generate name
const { data } = await client.renameConversation(conversationId, name, user, auto_generate)
console.log(conversationId, name, user, auto_generate)
return NextResponse.json(data)
}
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// DON NOT EDIT IT MANUALLY // DON NOT EDIT IT MANUALLY
import * as React from 'react' import * as React from 'react'
import data from './DataSet.json' import data from './data.json'
import IconBase from '@/app/components/base/icons/IconBase' import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// DON NOT EDIT IT MANUALLY // DON NOT EDIT IT MANUALLY
import * as React from 'react' import * as React from 'react'
import data from './CheckCircle.json' import data from './data.json'
import IconBase from '@/app/components/base/icons/IconBase' import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
......
...@@ -3,16 +3,16 @@ ...@@ -3,16 +3,16 @@
import type { FC } from 'react' import type { FC } from 'react'
import React, { useEffect, useRef, useState } from 'react' import React, { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import produce from 'immer' import produce, { setAutoFreeze } from 'immer'
import { useBoolean, useGetState } from 'ahooks' import { useBoolean, useGetState } from 'ahooks'
import useConversation from '@/hooks/use-conversation' import useConversation from '@/hooks/use-conversation'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
import Sidebar from '@/app/components/sidebar' import Sidebar from '@/app/components/sidebar'
import ConfigSence from '@/app/components/config-scence' import ConfigSence from '@/app/components/config-scence'
import Header from '@/app/components/header' import Header from '@/app/components/header'
import { fetchAppParams, fetchChatList, fetchConversations, sendChatMessage, updateFeedback } from '@/service' import { fetchAppParams, fetchChatList, fetchConversations, generationConversationName, sendChatMessage, updateFeedback } from '@/service'
import type { ConversationItem, Feedbacktype, IChatItem, PromptConfig, VisionFile, VisionSettings } from '@/types/app' import type { ConversationItem, Feedbacktype, IChatItem, PromptConfig, VisionFile, VisionSettings } from '@/types/app'
import { TransferMethod } from '@/types/app' import { Resolution, TransferMethod } from '@/types/app'
import Chat from '@/app/components/chat' import Chat from '@/app/components/chat'
import { setLocaleOnClient } from '@/i18n/client' import { setLocaleOnClient } from '@/i18n/client'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
...@@ -20,6 +20,7 @@ import Loading from '@/app/components/base/loading' ...@@ -20,6 +20,7 @@ import Loading from '@/app/components/base/loading'
import { replaceVarWithValues, userInputsFormToPromptVariables } from '@/utils/prompt' import { replaceVarWithValues, userInputsFormToPromptVariables } from '@/utils/prompt'
import AppUnavailable from '@/app/components/app-unavailable' import AppUnavailable from '@/app/components/app-unavailable'
import { API_KEY, APP_ID, APP_INFO, isShowPrompt, promptTemplate } from '@/config' import { API_KEY, APP_ID, APP_INFO, isShowPrompt, promptTemplate } from '@/config'
import type { Annotation as AnnotationType } from '@/types/log'
const Main: FC = () => { const Main: FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
...@@ -36,13 +37,26 @@ const Main: FC = () => { ...@@ -36,13 +37,26 @@ const Main: FC = () => {
const [inited, setInited] = useState<boolean>(false) const [inited, setInited] = useState<boolean>(false)
// in mobile, show sidebar by click button // in mobile, show sidebar by click button
const [isShowSidebar, { setTrue: showSidebar, setFalse: hideSidebar }] = useBoolean(false) const [isShowSidebar, { setTrue: showSidebar, setFalse: hideSidebar }] = useBoolean(false)
const [visionConfig, setVisionConfig] = useState<VisionSettings | undefined>(undefined) const [visionConfig, setVisionConfig] = useState<VisionSettings | undefined>({
enabled: false,
number_limits: 2,
detail: Resolution.low,
transfer_methods: [TransferMethod.local_file],
})
useEffect(() => { useEffect(() => {
if (APP_INFO?.title) if (APP_INFO?.title)
document.title = `${APP_INFO.title} - Powered by Dify` document.title = `${APP_INFO.title} - Powered by Dify`
}, [APP_INFO?.title]) }, [APP_INFO?.title])
// onData change thought (the produce obj). https://github.com/immerjs/immer/issues/576
useEffect(() => {
setAutoFreeze(false)
return () => {
setAutoFreeze(true)
}
}, [])
/* /*
* conversation info * conversation info
*/ */
...@@ -50,6 +64,7 @@ const Main: FC = () => { ...@@ -50,6 +64,7 @@ const Main: FC = () => {
conversationList, conversationList,
setConversationList, setConversationList,
currConversationId, currConversationId,
getCurrConversationId,
setCurrConversationId, setCurrConversationId,
getConversationIdFromStorage, getConversationIdFromStorage,
isNewConversation, isNewConversation,
...@@ -244,6 +259,7 @@ const Main: FC = () => { ...@@ -244,6 +259,7 @@ const Main: FC = () => {
}, []) }, [])
const [isResponsing, { setTrue: setResponsingTrue, setFalse: setResponsingFalse }] = useBoolean(false) const [isResponsing, { setTrue: setResponsingTrue, setFalse: setResponsingFalse }] = useBoolean(false)
const [abortController, setAbortController] = useState<AbortController | null>(null)
const { notify } = Toast const { notify } = Toast
const logError = (message: string) => { const logError = (message: string) => {
notify({ type: 'error', message }) notify({ type: 'error', message })
...@@ -267,6 +283,36 @@ const Main: FC = () => { ...@@ -267,6 +283,36 @@ const Main: FC = () => {
return true return true
} }
const [controlFocus, setControlFocus] = useState(0)
const [openingSuggestedQuestions, setOpeningSuggestedQuestions] = useState<string[]>([])
const [messageTaskId, setMessageTaskId] = useState('')
const [hasStopResponded, setHasStopResponded, getHasStopResponded] = useGetState(false)
const [isResponsingConIsCurrCon, setIsResponsingConCurrCon, getIsResponsingConIsCurrCon] = useGetState(true)
const [userQuery, setUserQuery] = useState('')
const updateCurrentQA = ({
responseItem,
questionId,
placeholderAnswerId,
questionItem,
}: {
responseItem: IChatItem
questionId: string
placeholderAnswerId: string
questionItem: IChatItem
}) => {
// closesure new list is outdated.
const newListWithAnswer = produce(
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
(draft) => {
if (!draft.find(item => item.id === questionId))
draft.push({ ...questionItem })
draft.push({ ...responseItem })
})
setChatList(newListWithAnswer)
}
const handleSend = async (message: string, files?: VisionFile[]) => { const handleSend = async (message: string, files?: VisionFile[]) => {
if (isResponsing) { if (isResponsing) {
notify({ type: 'info', message: t('app.errorMessage.waitForResponse') }) notify({ type: 'info', message: t('app.errorMessage.waitForResponse') })
...@@ -309,23 +355,145 @@ const Main: FC = () => { ...@@ -309,23 +355,145 @@ const Main: FC = () => {
const newList = [...getChatList(), questionItem, placeholderAnswerItem] const newList = [...getChatList(), questionItem, placeholderAnswerItem]
setChatList(newList) setChatList(newList)
let isAgentMode = false
// answer // answer
const responseItem = { const responseItem: IChatItem = {
id: `${Date.now()}`, id: `${Date.now()}`,
content: '', content: '',
agent_thoughts: [],
message_files: [],
isAnswer: true, isAnswer: true,
} }
let hasSetResponseId = false
const prevTempNewConversationId = getCurrConversationId() || '-1'
let tempNewConversationId = '' let tempNewConversationId = ''
setResponsingTrue() setResponsingTrue()
sendChatMessage(data, { sendChatMessage(data, {
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId }: any) => { getAbortController: (abortController) => {
responseItem.content = responseItem.content + message setAbortController(abortController)
responseItem.id = messageId },
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => {
if (!isAgentMode) {
responseItem.content = responseItem.content + message
}
else {
const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1]
if (lastThought)
lastThought.thought = lastThought.thought + message // need immer setAutoFreeze
}
if (messageId && !hasSetResponseId) {
responseItem.id = messageId
hasSetResponseId = true
}
if (isFirstMessage && newConversationId) if (isFirstMessage && newConversationId)
tempNewConversationId = newConversationId tempNewConversationId = newConversationId
// closesure new list is outdated. setMessageTaskId(taskId)
// has switched to other conversation
if (prevTempNewConversationId !== getCurrConversationId()) {
setIsResponsingConCurrCon(false)
return
}
updateCurrentQA({
responseItem,
questionId,
placeholderAnswerId,
questionItem,
})
},
async onCompleted(hasError?: boolean) {
if (hasError)
return
if (getConversationIdChangeBecauseOfNew()) {
const { data: allConversations }: any = await fetchConversations()
const newItem: any = await generationConversationName(allConversations[0].id)
const newAllConversations = produce(allConversations, (draft: any) => {
draft[0].name = newItem.name
})
setConversationList(newAllConversations as any)
}
setConversationIdChangeBecauseOfNew(false)
resetNewConversationInputs()
setChatNotStarted()
setCurrConversationId(tempNewConversationId, APP_ID, true)
setResponsingFalse()
},
onFile(file) {
const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1]
if (lastThought)
lastThought.message_files = [...(lastThought as any).message_files, { ...file }]
updateCurrentQA({
responseItem,
questionId,
placeholderAnswerId,
questionItem,
})
},
onThought(thought) {
isAgentMode = true
const response = responseItem as any
if (thought.message_id && !hasSetResponseId) {
response.id = thought.message_id
hasSetResponseId = true
}
// responseItem.id = thought.message_id;
if (response.agent_thoughts.length === 0) {
response.agent_thoughts.push(thought)
}
else {
const lastThought = response.agent_thoughts[response.agent_thoughts.length - 1]
// thought changed but still the same thought, so update.
if (lastThought.id === thought.id) {
thought.thought = lastThought.thought
thought.message_files = lastThought.message_files
responseItem.agent_thoughts![response.agent_thoughts.length - 1] = thought
}
else {
responseItem.agent_thoughts!.push(thought)
}
}
// has switched to other conversation
if (prevTempNewConversationId !== getCurrConversationId()) {
setIsResponsingConCurrCon(false)
return false
}
updateCurrentQA({
responseItem,
questionId,
placeholderAnswerId,
questionItem,
})
},
onMessageEnd: (messageEnd) => {
if (messageEnd.metadata?.annotation_reply) {
responseItem.id = messageEnd.id
responseItem.annotation = ({
id: messageEnd.metadata.annotation_reply.id,
authorName: messageEnd.metadata.annotation_reply.account.name,
} as AnnotationType)
const newListWithAnswer = produce(
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
(draft) => {
if (!draft.find(item => item.id === questionId))
draft.push({ ...questionItem })
draft.push({
...responseItem,
})
})
setChatList(newListWithAnswer)
return
}
// not support show citation
// responseItem.citation = messageEnd.retriever_resources
const newListWithAnswer = produce( const newListWithAnswer = produce(
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId), getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
(draft) => { (draft) => {
...@@ -336,19 +504,16 @@ const Main: FC = () => { ...@@ -336,19 +504,16 @@ const Main: FC = () => {
}) })
setChatList(newListWithAnswer) setChatList(newListWithAnswer)
}, },
async onCompleted() { onMessageReplace: (messageReplace) => {
setResponsingFalse() setChatList(produce(
if (!tempNewConversationId) getChatList(),
return (draft) => {
const current = draft.find(item => item.id === messageReplace.id)
if (getConversationIdChangeBecauseOfNew()) { if (current)
const { data: conversations }: any = await fetchConversations() current.content = messageReplace.answer
setConversationList(conversations as ConversationItem[]) },
} ))
setConversationIdChangeBecauseOfNew(false)
resetNewConversationInputs()
setChatNotStarted()
setCurrConversationId(tempNewConversationId, APP_ID, true)
}, },
onError() { onError() {
setResponsingFalse() setResponsingFalse()
......
import { useState } from 'react' import { useState } from 'react'
import produce from 'immer' import produce from 'immer'
import { useGetState } from 'ahooks'
import type { ConversationItem } from '@/types/app' import type { ConversationItem } from '@/types/app'
const storageConversationIdKey = 'conversationIdInfo' const storageConversationIdKey = 'conversationIdInfo'
...@@ -7,7 +8,7 @@ const storageConversationIdKey = 'conversationIdInfo' ...@@ -7,7 +8,7 @@ const storageConversationIdKey = 'conversationIdInfo'
type ConversationInfoType = Omit<ConversationItem, 'inputs' | 'id'> type ConversationInfoType = Omit<ConversationItem, 'inputs' | 'id'>
function useConversation() { function useConversation() {
const [conversationList, setConversationList] = useState<ConversationItem[]>([]) const [conversationList, setConversationList] = useState<ConversationItem[]>([])
const [currConversationId, doSetCurrConversationId] = useState<string>('-1') const [currConversationId, doSetCurrConversationId, getCurrConversationId] = useGetState<string>('-1')
// when set conversation id, we do not have set appId // when set conversation id, we do not have set appId
const setCurrConversationId = (id: string, appId: string, isSetToLocalStroge = true, newConversationName = '') => { const setCurrConversationId = (id: string, appId: string, isSetToLocalStroge = true, newConversationName = '') => {
doSetCurrConversationId(id) doSetCurrConversationId(id)
...@@ -50,6 +51,7 @@ function useConversation() { ...@@ -50,6 +51,7 @@ function useConversation() {
conversationList, conversationList,
setConversationList, setConversationList,
currConversationId, currConversationId,
getCurrConversationId,
setCurrConversationId, setCurrConversationId,
getConversationIdFromStorage, getConversationIdFromStorage,
isNewConversation, isNewConversation,
......
...@@ -21,7 +21,7 @@ export const sendChatMessage = async (body: Record<string, any>, { onData, onCom ...@@ -21,7 +21,7 @@ export const sendChatMessage = async (body: Record<string, any>, { onData, onCom
} }
export const fetchConversations = async () => { export const fetchConversations = async () => {
return get('conversations', { params: { limit: 20, first_id: '' } }) return get('conversations', { params: { limit: 100, first_id: '' } })
} }
export const fetchChatList = async (conversationId: string) => { export const fetchChatList = async (conversationId: string) => {
...@@ -36,3 +36,7 @@ export const fetchAppParams = async () => { ...@@ -36,3 +36,7 @@ export const fetchAppParams = async () => {
export const updateFeedback = async ({ url, body }: { url: string; body: Feedbacktype }) => { export const updateFeedback = async ({ url, body }: { url: string; body: Feedbacktype }) => {
return post(url, { body }) return post(url, { body })
} }
export const generationConversationName = async (id: string) => {
return post(`conversations/${id}/name`, { body: { auto_generate: true } })
}
import type { Annotation } from './log'
import type { Locale } from '@/i18n' import type { Locale } from '@/i18n'
import type { ThoughtItem } from '@/app/components/chat/type'
export type PromptVariable = { export type PromptVariable = {
key: string key: string
...@@ -74,9 +76,12 @@ export type IChatItem = { ...@@ -74,9 +76,12 @@ export type IChatItem = {
* More information about this message * More information about this message
*/ */
more?: MessageMore more?: MessageMore
isIntroduction?: boolean annotation?: Annotation
useCurrentUserAvatar?: boolean useCurrentUserAvatar?: boolean
isOpeningStatement?: boolean isOpeningStatement?: boolean
suggestedQuestions?: string[]
log?: { role: string; text: string }[]
agent_thoughts?: ThoughtItem[]
message_files?: VisionFile[] message_files?: VisionFile[]
} }
......
export type LogAnnotation = {
content: string
account: {
id: string
name: string
email: string
}
created_at: number
}
export type Annotation = {
id: string
authorName: string
logAnnotation?: LogAnnotation
created_at?: number
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment