mirror of
https://github.com/langgenius/webapp-conversation.git
synced 2025-12-08 17:32:27 +08:00
feat: support output agent info
This commit is contained in:
20
app/api/conversations/[conversationId]/name/route.ts
Normal file
20
app/api/conversations/[conversationId]/name/route.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
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 @@
|
|||||||
// 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 @@
|
|||||||
// 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 @@
|
|||||||
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'
|
|||||||
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 = () => {
|
|||||||
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 = () => {
|
|||||||
conversationList,
|
conversationList,
|
||||||
setConversationList,
|
setConversationList,
|
||||||
currConversationId,
|
currConversationId,
|
||||||
|
getCurrConversationId,
|
||||||
setCurrConversationId,
|
setCurrConversationId,
|
||||||
getConversationIdFromStorage,
|
getConversationIdFromStorage,
|
||||||
isNewConversation,
|
isNewConversation,
|
||||||
@ -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 = () => {
|
|||||||
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 = () => {
|
|||||||
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) => {
|
||||||
|
setAbortController(abortController)
|
||||||
|
},
|
||||||
|
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => {
|
||||||
|
if (!isAgentMode) {
|
||||||
responseItem.content = responseItem.content + message
|
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
|
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 = () => {
|
|||||||
})
|
})
|
||||||
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()
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
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'
|
|||||||
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() {
|
|||||||
conversationList,
|
conversationList,
|
||||||
setConversationList,
|
setConversationList,
|
||||||
currConversationId,
|
currConversationId,
|
||||||
|
getCurrConversationId,
|
||||||
setCurrConversationId,
|
setCurrConversationId,
|
||||||
getConversationIdFromStorage,
|
getConversationIdFromStorage,
|
||||||
isNewConversation,
|
isNewConversation,
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
"axios": "^1.3.5",
|
"axios": "^1.3.5",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"copy-to-clipboard": "^3.3.3",
|
"copy-to-clipboard": "^3.3.3",
|
||||||
"dify-client": "^2.1.0",
|
"dify-client": "^2.2.0",
|
||||||
"eslint": "8.36.0",
|
"eslint": "8.36.0",
|
||||||
"eslint-config-next": "13.4.0",
|
"eslint-config-next": "13.4.0",
|
||||||
"eventsource-parser": "^1.0.0",
|
"eventsource-parser": "^1.0.0",
|
||||||
|
|||||||
@ -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 () => {
|
|||||||
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 } })
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
|
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 = {
|
|||||||
* 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[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
16
types/log.ts
Normal file
16
types/log.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user