mirror of
https://github.com/langgenius/webapp-conversation.git
synced 2025-12-08 17:32:27 +08:00
feat: move anwser related code
This commit is contained in:
@ -3,7 +3,7 @@ import classNames from 'classnames'
|
|||||||
import style from './style.module.css'
|
import style from './style.module.css'
|
||||||
|
|
||||||
export type AppIconProps = {
|
export type AppIconProps = {
|
||||||
size?: 'tiny' | 'small' | 'medium' | 'large'
|
size?: 'xs' | 'tiny' | 'small' | 'medium' | 'large'
|
||||||
rounded?: boolean
|
rounded?: boolean
|
||||||
icon?: string
|
icon?: string
|
||||||
background?: string
|
background?: string
|
||||||
|
|||||||
@ -1,15 +1,23 @@
|
|||||||
.appIcon {
|
.appIcon {
|
||||||
@apply flex items-center justify-center relative w-9 h-9 text-lg bg-teal-100 rounded-lg grow-0 shrink-0;
|
@apply flex items-center justify-center relative w-9 h-9 text-lg bg-teal-100 rounded-lg grow-0 shrink-0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.appIcon.large {
|
.appIcon.large {
|
||||||
@apply w-10 h-10;
|
@apply w-10 h-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.appIcon.small {
|
.appIcon.small {
|
||||||
@apply w-8 h-8;
|
@apply w-8 h-8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.appIcon.xs {
|
||||||
|
@apply w-3 h-3 text-base;
|
||||||
|
}
|
||||||
|
|
||||||
.appIcon.tiny {
|
.appIcon.tiny {
|
||||||
@apply w-6 h-6 text-base;
|
@apply w-6 h-6 text-base;
|
||||||
}
|
}
|
||||||
|
|
||||||
.appIcon.rounded {
|
.appIcon.rounded {
|
||||||
@apply rounded-full;
|
@apply rounded-full;
|
||||||
}
|
}
|
||||||
39
app/components/base/icons/line/arrows/chevron-down/data.json
Normal file
39
app/components/base/icons/line/arrows/chevron-down/data.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "12",
|
||||||
|
"height": "12",
|
||||||
|
"viewBox": "0 0 12 12",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"id": "chevron-down"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Icon",
|
||||||
|
"d": "M3 4.5L6 7.5L9 4.5",
|
||||||
|
"stroke": "currentColor",
|
||||||
|
"stroke-width": "1.5",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "ChevronDown"
|
||||||
|
}
|
||||||
16
app/components/base/icons/line/arrows/chevron-down/index.tsx
Normal file
16
app/components/base/icons/line/arrows/chevron-down/index.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react'
|
||||||
|
import data from './data.json'
|
||||||
|
import IconBase from '@/app/components/base/icons/IconBase'
|
||||||
|
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||||
|
props,
|
||||||
|
ref,
|
||||||
|
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||||
|
|
||||||
|
Icon.displayName = 'ChevronDown'
|
||||||
|
|
||||||
|
export default Icon
|
||||||
64
app/components/base/icons/public/data-set/data.json
Normal file
64
app/components/base/icons/public/data-set/data.json
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "12",
|
||||||
|
"height": "12",
|
||||||
|
"viewBox": "0 0 12 12",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"clip-path": "url(#clip0_7847_32895)"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"d": "M10.5 2.5C10.5 3.32843 8.48528 4 6 4C3.51472 4 1.5 3.32843 1.5 2.5M10.5 2.5C10.5 1.67157 8.48528 1 6 1C3.51472 1 1.5 1.67157 1.5 2.5M10.5 2.5V9.5C10.5 10.33 8.5 11 6 11C3.5 11 1.5 10.33 1.5 9.5V2.5M10.5 6C10.5 6.83 8.5 7.5 6 7.5C3.5 7.5 1.5 6.83 1.5 6",
|
||||||
|
"stroke": "#667085",
|
||||||
|
"stroke-width": "1.25",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "defs",
|
||||||
|
"attributes": {},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "clipPath",
|
||||||
|
"attributes": {
|
||||||
|
"id": "clip0_7847_32895"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "rect",
|
||||||
|
"attributes": {
|
||||||
|
"width": "12",
|
||||||
|
"height": "12",
|
||||||
|
"fill": "white"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "DataSet"
|
||||||
|
}
|
||||||
16
app/components/base/icons/public/data-set/index.tsx
Normal file
16
app/components/base/icons/public/data-set/index.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react'
|
||||||
|
import data from './DataSet.json'
|
||||||
|
import IconBase from '@/app/components/base/icons/IconBase'
|
||||||
|
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||||
|
props,
|
||||||
|
ref,
|
||||||
|
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||||
|
|
||||||
|
Icon.displayName = 'DataSet'
|
||||||
|
|
||||||
|
export default Icon
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "16",
|
||||||
|
"height": "16",
|
||||||
|
"viewBox": "0 0 16 16",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "g",
|
||||||
|
"attributes": {
|
||||||
|
"id": "check-circle"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"id": "Solid",
|
||||||
|
"fill-rule": "evenodd",
|
||||||
|
"clip-rule": "evenodd",
|
||||||
|
"d": "M8 0.666626C3.94992 0.666626 0.666672 3.94987 0.666672 7.99996C0.666672 12.05 3.94992 15.3333 8 15.3333C12.0501 15.3333 15.3333 12.05 15.3333 7.99996C15.3333 3.94987 12.0501 0.666626 8 0.666626ZM11.4714 6.47136C11.7318 6.21101 11.7318 5.7889 11.4714 5.52855C11.2111 5.26821 10.7889 5.26821 10.5286 5.52855L7 9.05715L5.47141 7.52855C5.21106 7.2682 4.78895 7.2682 4.5286 7.52855C4.26825 7.7889 4.26825 8.21101 4.5286 8.47136L6.5286 10.4714C6.78895 10.7317 7.21106 10.7317 7.47141 10.4714L11.4714 6.47136Z",
|
||||||
|
"fill": "currentColor"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "CheckCircle"
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react'
|
||||||
|
import data from './CheckCircle.json'
|
||||||
|
import IconBase from '@/app/components/base/icons/IconBase'
|
||||||
|
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||||
|
props,
|
||||||
|
ref,
|
||||||
|
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||||
|
|
||||||
|
Icon.displayName = 'CheckCircle'
|
||||||
|
|
||||||
|
export default Icon
|
||||||
@ -6,10 +6,13 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import LoadingAnim from '../loading-anim'
|
import LoadingAnim from '../loading-anim'
|
||||||
import type { FeedbackFunc, IChatItem } from '../type'
|
import type { FeedbackFunc, IChatItem } from '../type'
|
||||||
import s from '../style.module.css'
|
import s from '../style.module.css'
|
||||||
|
import ImageGallery from '../../base/image-gallery'
|
||||||
|
import Thought from '../thought'
|
||||||
import { randomString } from '@/utils/string'
|
import { randomString } from '@/utils/string'
|
||||||
import type { MessageRating } from '@/types/app'
|
import type { MessageRating, VisionFile } from '@/types/app'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import { Markdown } from '@/app/components/base/markdown'
|
import { Markdown } from '@/app/components/base/markdown'
|
||||||
|
import type { Emoji } from '@/types/tools'
|
||||||
|
|
||||||
const OperationBtn = ({ innerContent, onClick, className }: { innerContent: React.ReactNode; onClick?: () => void; className?: string }) => (
|
const OperationBtn = ({ innerContent, onClick, className }: { innerContent: React.ReactNode; onClick?: () => void; className?: string }) => (
|
||||||
<div
|
<div
|
||||||
@ -55,11 +58,20 @@ type IAnswerProps = {
|
|||||||
feedbackDisabled: boolean
|
feedbackDisabled: boolean
|
||||||
onFeedback?: FeedbackFunc
|
onFeedback?: FeedbackFunc
|
||||||
isResponsing?: boolean
|
isResponsing?: boolean
|
||||||
|
allToolIcons?: Record<string, string | Emoji>
|
||||||
}
|
}
|
||||||
|
|
||||||
// The component needs to maintain its own state to control whether to display input component
|
// The component needs to maintain its own state to control whether to display input component
|
||||||
const Answer: FC<IAnswerProps> = ({ item, feedbackDisabled = false, onFeedback, isResponsing }) => {
|
const Answer: FC<IAnswerProps> = ({
|
||||||
const { id, content, feedback } = item
|
item,
|
||||||
|
feedbackDisabled = false,
|
||||||
|
onFeedback,
|
||||||
|
isResponsing,
|
||||||
|
allToolIcons,
|
||||||
|
}) => {
|
||||||
|
const { id, content, feedback, agent_thoughts } = item
|
||||||
|
const isAgentMode = !!agent_thoughts && agent_thoughts.length > 0
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -121,6 +133,37 @@ const Answer: FC<IAnswerProps> = ({ item, feedbackDisabled = false, onFeedback,
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getImgs = (list?: VisionFile[]) => {
|
||||||
|
if (!list)
|
||||||
|
return []
|
||||||
|
return list.filter(file => file.type === 'image' && file.belongs_to === 'assistant')
|
||||||
|
}
|
||||||
|
|
||||||
|
const agentModeAnswer = (
|
||||||
|
<div>
|
||||||
|
{agent_thoughts?.map((item, index) => (
|
||||||
|
<div key={index}>
|
||||||
|
{item.thought && (
|
||||||
|
<Markdown content={item.thought} />
|
||||||
|
)}
|
||||||
|
{/* {item.tool} */}
|
||||||
|
{/* perhaps not use tool */}
|
||||||
|
{!!item.tool && (
|
||||||
|
<Thought
|
||||||
|
thought={item}
|
||||||
|
allToolIcons={allToolIcons || {}}
|
||||||
|
isFinished={!!item.observation || !isResponsing}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{getImgs(item.message_files).length > 0 && (
|
||||||
|
<ImageGallery srcs={getImgs(item.message_files).map(item => item.url)} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={id}>
|
<div key={id}>
|
||||||
<div className='flex items-start'>
|
<div className='flex items-start'>
|
||||||
@ -134,21 +177,17 @@ const Answer: FC<IAnswerProps> = ({ item, feedbackDisabled = false, onFeedback,
|
|||||||
<div className={`${s.answerWrap}`}>
|
<div className={`${s.answerWrap}`}>
|
||||||
<div className={`${s.answer} relative text-sm text-gray-900`}>
|
<div className={`${s.answer} relative text-sm text-gray-900`}>
|
||||||
<div className={'ml-2 py-3 px-4 bg-gray-100 rounded-tr-2xl rounded-b-2xl'}>
|
<div className={'ml-2 py-3 px-4 bg-gray-100 rounded-tr-2xl rounded-b-2xl'}>
|
||||||
{item.isOpeningStatement && (
|
{(isResponsing && (isAgentMode ? (!content && (agent_thoughts || []).filter(item => !!item.thought || !!item.tool).length === 0) : !content))
|
||||||
<div className='flex items-center mb-1 gap-1'>
|
|
||||||
<OpeningStatementIcon />
|
|
||||||
<div className='text-xs text-gray-500'>{t('app.chat.openingStatementTitle')}</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{(isResponsing && !content)
|
|
||||||
? (
|
? (
|
||||||
<div className='flex items-center justify-center w-6 h-5'>
|
<div className='flex items-center justify-center w-6 h-5'>
|
||||||
<LoadingAnim type='text' />
|
<LoadingAnim type='text' />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
: (
|
: (isAgentMode
|
||||||
<Markdown content={content} />
|
? agentModeAnswer
|
||||||
)}
|
: (
|
||||||
|
<Markdown content={content} />
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className='absolute top-[-14px] right-[-14px] flex flex-row justify-end gap-1'>
|
<div className='absolute top-[-14px] right-[-14px] flex flex-row justify-end gap-1'>
|
||||||
{!feedbackDisabled && !item.feedbackDisabled && renderItemOperation()}
|
{!feedbackDisabled && !item.feedbackDisabled && renderItemOperation()}
|
||||||
|
|||||||
61
app/components/chat/thought/index.tsx
Normal file
61
app/components/chat/thought/index.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
|
import type { ThoughtItem, ToolInfoInThought } from '../type'
|
||||||
|
import Tool from './tool'
|
||||||
|
import type { Emoji } from '@/types/tools'
|
||||||
|
|
||||||
|
export type IThoughtProps = {
|
||||||
|
thought: ThoughtItem
|
||||||
|
allToolIcons: Record<string, string | Emoji>
|
||||||
|
isFinished: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValue(value: string, isValueArray: boolean, index: number) {
|
||||||
|
if (isValueArray) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(value)[index]
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
const Thought: FC<IThoughtProps> = ({
|
||||||
|
thought,
|
||||||
|
allToolIcons,
|
||||||
|
isFinished,
|
||||||
|
}) => {
|
||||||
|
const [toolNames, isValueArray]: [string[], boolean] = (() => {
|
||||||
|
try {
|
||||||
|
if (Array.isArray(JSON.parse(thought.tool)))
|
||||||
|
return [JSON.parse(thought.tool), true]
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
}
|
||||||
|
return [[thought.tool], false]
|
||||||
|
})()
|
||||||
|
|
||||||
|
const toolThoughtList = toolNames.map((toolName, index) => {
|
||||||
|
return {
|
||||||
|
name: toolName,
|
||||||
|
input: getValue(thought.tool_input, isValueArray, index),
|
||||||
|
output: getValue(thought.observation, isValueArray, index),
|
||||||
|
isFinished,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='my-2 space-y-2'>
|
||||||
|
{toolThoughtList.map((item: ToolInfoInThought, index) => (
|
||||||
|
<Tool
|
||||||
|
key={index}
|
||||||
|
payload={item}
|
||||||
|
allToolIcons={allToolIcons}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(Thought)
|
||||||
28
app/components/chat/thought/panel.tsx
Normal file
28
app/components/chat/thought/panel.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isRequest: boolean
|
||||||
|
toolName: string
|
||||||
|
content: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Panel: FC<Props> = ({
|
||||||
|
isRequest,
|
||||||
|
toolName,
|
||||||
|
content,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='rounded-md bg-gray-100 overflow-hidden border border-black/5'>
|
||||||
|
<div className='flex items-center px-2 py-1 leading-[18px] bg-gray-50 uppercase text-xs font-medium text-gray-500'>
|
||||||
|
{t(`tools.thought.${isRequest ? 'requestTitle' : 'responseTitle'}`)} {toolName}
|
||||||
|
</div>
|
||||||
|
<div className='p-2 border-t border-black/5 leading-4 text-xs text-gray-700'>{content}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(Panel)
|
||||||
7
app/components/chat/thought/style.module.css
Normal file
7
app/components/chat/thought/style.module.css
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
.wrap {
|
||||||
|
background-color: rgba(255, 255, 255, 0.92);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapHoverEffect:hover{
|
||||||
|
box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.06), 0px 1px 3px 0px rgba(16, 24, 40, 0.1);
|
||||||
|
}
|
||||||
103
app/components/chat/thought/tool.tsx
Normal file
103
app/components/chat/thought/tool.tsx
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
import cn from 'classnames'
|
||||||
|
import type { ToolInfoInThought } from '../type'
|
||||||
|
import Panel from './panel'
|
||||||
|
import Loading02 from '@/app/components/base/icons/line/loading-02'
|
||||||
|
import ChevronDown from '@/app/components/base/icons/line/arrows/chevron-down'
|
||||||
|
import CheckCircle from '@/app/components/base/icons/solid/general/check-circle'
|
||||||
|
import DataSetIcon from '@/app/components/base/icons/public/data-set'
|
||||||
|
import type { Emoji } from '@/types/tools'
|
||||||
|
import AppIcon from '@/app/components/base/app-icon'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
payload: ToolInfoInThought
|
||||||
|
allToolIcons?: Record<string, string | Emoji>
|
||||||
|
}
|
||||||
|
|
||||||
|
const getIcon = (toolName: string, allToolIcons: Record<string, string | Emoji>) => {
|
||||||
|
if (toolName.startsWith('dataset-'))
|
||||||
|
return <DataSetIcon className='shrink-0'></DataSetIcon>
|
||||||
|
const icon = allToolIcons[toolName]
|
||||||
|
if (!icon)
|
||||||
|
return null
|
||||||
|
return (
|
||||||
|
typeof icon === 'string'
|
||||||
|
? (
|
||||||
|
<div
|
||||||
|
className='w-3 h-3 bg-cover bg-center rounded-[3px] shrink-0'
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url(${icon})`,
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<AppIcon
|
||||||
|
className='rounded-[3px] shrink-0'
|
||||||
|
size='xs'
|
||||||
|
icon={icon?.content}
|
||||||
|
background={icon?.background}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
const Tool: FC<Props> = ({
|
||||||
|
payload,
|
||||||
|
allToolIcons = {},
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { name, input, isFinished, output } = payload
|
||||||
|
const toolName = name.startsWith('dataset-') ? t('dataset.knowledge') : name
|
||||||
|
const [isShowDetail, setIsShowDetail] = useState(false)
|
||||||
|
const icon = getIcon(toolName, allToolIcons) as any
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={cn(!isShowDetail && 'shadow-sm', !isShowDetail && 'inline-block', 'max-w-full overflow-x-auto bg-white rounded-md')}>
|
||||||
|
<div
|
||||||
|
className={cn('flex items-center h-7 px-2 cursor-pointer')}
|
||||||
|
onClick={() => setIsShowDetail(!isShowDetail)}
|
||||||
|
>
|
||||||
|
{!isFinished && (
|
||||||
|
<Loading02 className='w-3 h-3 text-gray-500 animate-spin shrink-0' />
|
||||||
|
)}
|
||||||
|
{isFinished && !isShowDetail && (
|
||||||
|
<CheckCircle className='w-3 h-3 text-[#12B76A] shrink-0' />
|
||||||
|
)}
|
||||||
|
{isFinished && isShowDetail && (
|
||||||
|
icon
|
||||||
|
)}
|
||||||
|
<span className='mx-1 text-xs font-medium text-gray-500 shrink-0'>
|
||||||
|
{t(`tools.thought.${isFinished ? 'used' : 'using'}`)}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className='text-xs font-medium text-gray-700 truncate'
|
||||||
|
title={toolName}
|
||||||
|
>
|
||||||
|
{toolName}
|
||||||
|
</span>
|
||||||
|
<ChevronDown
|
||||||
|
className={cn(isShowDetail && 'rotate-180', 'ml-1 w-3 h-3 text-gray-500 select-none cursor-pointer shrink-0')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{isShowDetail && (
|
||||||
|
<div className='border-t border-black/5 p-2 space-y-2 '>
|
||||||
|
<Panel
|
||||||
|
isRequest={true}
|
||||||
|
toolName={toolName}
|
||||||
|
content={input} />
|
||||||
|
{output && (
|
||||||
|
<Panel
|
||||||
|
isRequest={false}
|
||||||
|
toolName={toolName}
|
||||||
|
content={output as string} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(Tool)
|
||||||
@ -5,6 +5,8 @@ import commonEn from './lang/common.en'
|
|||||||
import commonZh from './lang/common.zh'
|
import commonZh from './lang/common.zh'
|
||||||
import appEn from './lang/app.en'
|
import appEn from './lang/app.en'
|
||||||
import appZh from './lang/app.zh'
|
import appZh from './lang/app.zh'
|
||||||
|
import toolsEn from './lang/tools.en'
|
||||||
|
import toolsZh from './lang/tools.zh'
|
||||||
import type { Locale } from '.'
|
import type { Locale } from '.'
|
||||||
|
|
||||||
const resources = {
|
const resources = {
|
||||||
@ -12,12 +14,16 @@ const resources = {
|
|||||||
translation: {
|
translation: {
|
||||||
common: commonEn,
|
common: commonEn,
|
||||||
app: appEn,
|
app: appEn,
|
||||||
|
// tools
|
||||||
|
tools: toolsEn,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'zh-Hans': {
|
'zh-Hans': {
|
||||||
translation: {
|
translation: {
|
||||||
common: commonZh,
|
common: commonZh,
|
||||||
app: appZh,
|
app: appZh,
|
||||||
|
// tools
|
||||||
|
tools: toolsZh,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
103
i18n/lang/tools.en.ts
Normal file
103
i18n/lang/tools.en.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
const translation = {
|
||||||
|
title: 'Tools',
|
||||||
|
createCustomTool: 'Create Custom Tool',
|
||||||
|
type: {
|
||||||
|
all: 'All',
|
||||||
|
builtIn: 'Built-in',
|
||||||
|
custom: 'Custom',
|
||||||
|
},
|
||||||
|
contribute: {
|
||||||
|
line1: 'I\'m interested in ',
|
||||||
|
line2: 'contributing tools to Dify.',
|
||||||
|
viewGuide: 'View the guide',
|
||||||
|
},
|
||||||
|
author: 'By',
|
||||||
|
auth: {
|
||||||
|
unauthorized: 'To Authorize',
|
||||||
|
authorized: 'Authorized',
|
||||||
|
setup: 'Set up authorization to use',
|
||||||
|
setupModalTitle: 'Set Up Authorization',
|
||||||
|
setupModalTitleDescription: 'After configuring credentials, all members within the workspace can use this tool when orchestrating applications.',
|
||||||
|
},
|
||||||
|
includeToolNum: '{{num}} tools included',
|
||||||
|
addTool: 'Add Tool',
|
||||||
|
createTool: {
|
||||||
|
title: 'Create Custom Tool',
|
||||||
|
editAction: 'Configure',
|
||||||
|
editTitle: 'Edit Custom Tool',
|
||||||
|
name: 'Name',
|
||||||
|
toolNamePlaceHolder: 'Enter the tool name',
|
||||||
|
schema: 'Schema',
|
||||||
|
schemaPlaceHolder: 'Enter your OpenAPI schema here',
|
||||||
|
viewSchemaSpec: 'View the OpenAPI-Swagger Specification',
|
||||||
|
importFromUrl: 'Import from URL',
|
||||||
|
importFromUrlPlaceHolder: 'https://...',
|
||||||
|
urlError: 'Please enter a valid URL',
|
||||||
|
examples: 'Examples',
|
||||||
|
exampleOptions: {
|
||||||
|
json: 'Weather(JSON)',
|
||||||
|
yaml: 'Pet Store(YAML)',
|
||||||
|
blankTemplate: 'Blank Template',
|
||||||
|
},
|
||||||
|
availableTools: {
|
||||||
|
title: 'Available Tools',
|
||||||
|
name: 'Name',
|
||||||
|
description: 'Description',
|
||||||
|
method: 'Method',
|
||||||
|
path: 'Path',
|
||||||
|
action: 'Actions',
|
||||||
|
test: 'Test',
|
||||||
|
},
|
||||||
|
authMethod: {
|
||||||
|
title: 'Authorization method',
|
||||||
|
type: 'Authorization type',
|
||||||
|
types: {
|
||||||
|
none: 'None',
|
||||||
|
api_key: 'API Key',
|
||||||
|
},
|
||||||
|
key: 'Key',
|
||||||
|
value: 'Value',
|
||||||
|
},
|
||||||
|
privacyPolicy: 'Privacy policy',
|
||||||
|
privacyPolicyPlaceholder: 'Please enter privacy policy',
|
||||||
|
},
|
||||||
|
test: {
|
||||||
|
title: 'Test',
|
||||||
|
parametersValue: 'Parameters & Value',
|
||||||
|
parameters: 'Parameters',
|
||||||
|
value: 'Value',
|
||||||
|
testResult: 'Test Results',
|
||||||
|
testResultPlaceholder: 'Test result will show here',
|
||||||
|
},
|
||||||
|
thought: {
|
||||||
|
using: 'Using',
|
||||||
|
used: 'Used',
|
||||||
|
requestTitle: 'Request to',
|
||||||
|
responseTitle: 'Response from',
|
||||||
|
},
|
||||||
|
setBuiltInTools: {
|
||||||
|
info: 'Info',
|
||||||
|
setting: 'Setting',
|
||||||
|
toolDescription: 'Tool description',
|
||||||
|
parameters: 'parameters',
|
||||||
|
string: 'string',
|
||||||
|
number: 'number',
|
||||||
|
required: 'Required',
|
||||||
|
infoAndSetting: 'Info & Settings',
|
||||||
|
},
|
||||||
|
noCustomTool: {
|
||||||
|
title: 'No custom tools!',
|
||||||
|
content: 'Add and manage your custom tools here for building AI apps.',
|
||||||
|
createTool: 'Create Tool',
|
||||||
|
},
|
||||||
|
noSearchRes: {
|
||||||
|
title: 'Sorry, no results!',
|
||||||
|
content: 'We couldn\'t find any tools that match your search.',
|
||||||
|
reset: 'Reset Search',
|
||||||
|
},
|
||||||
|
builtInPromptTitle: 'Prompt',
|
||||||
|
toolRemoved: 'Tool removed',
|
||||||
|
notAuthorized: 'Tool not authorized',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translation
|
||||||
95
i18n/lang/tools.zh.ts
Normal file
95
i18n/lang/tools.zh.ts
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
const translation = {
|
||||||
|
title: '工具',
|
||||||
|
createCustomTool: '创建自定义工具',
|
||||||
|
type: {
|
||||||
|
all: '全部',
|
||||||
|
builtIn: '内置',
|
||||||
|
custom: '自定义',
|
||||||
|
},
|
||||||
|
contribute: {
|
||||||
|
line1: '我有兴趣为 ',
|
||||||
|
line2: 'Dify 贡献工具。',
|
||||||
|
viewGuide: '查看指南',
|
||||||
|
},
|
||||||
|
author: '作者',
|
||||||
|
auth: {
|
||||||
|
unauthorized: '去授权',
|
||||||
|
authorized: '已授权',
|
||||||
|
setup: '要使用请先授权',
|
||||||
|
setupModalTitle: '设置授权',
|
||||||
|
setupModalTitleDescription: '配置凭据后,工作区中的所有成员都可以在编排应用程序时使用此工具。',
|
||||||
|
},
|
||||||
|
includeToolNum: '包含 {{num}} 个工具',
|
||||||
|
addTool: '添加工具',
|
||||||
|
createTool: {
|
||||||
|
title: '创建自定义工具',
|
||||||
|
editAction: '编辑',
|
||||||
|
editTitle: '编辑自定义工具',
|
||||||
|
name: '名称',
|
||||||
|
toolNamePlaceHolder: '输入工具名称',
|
||||||
|
schema: 'Schema',
|
||||||
|
schemaPlaceHolder: '在此处输入您的 OpenAPI schema',
|
||||||
|
viewSchemaSpec: '查看 OpenAPI-Swagger 规范',
|
||||||
|
importFromUrl: '从 URL 中导入',
|
||||||
|
importFromUrlPlaceHolder: 'https://...',
|
||||||
|
urlError: '请输入有效的 URL',
|
||||||
|
examples: '例子',
|
||||||
|
exampleOptions: {
|
||||||
|
json: '天气(JSON)',
|
||||||
|
yaml: '宠物商店(YAML)',
|
||||||
|
blankTemplate: '空白模版',
|
||||||
|
},
|
||||||
|
availableTools: {
|
||||||
|
title: '可用工具',
|
||||||
|
name: '名称',
|
||||||
|
description: '描述',
|
||||||
|
method: '方法',
|
||||||
|
path: '路径',
|
||||||
|
action: '操作',
|
||||||
|
test: '测试',
|
||||||
|
},
|
||||||
|
authMethod: {
|
||||||
|
title: '鉴权方法',
|
||||||
|
type: '鉴权类型',
|
||||||
|
types: {
|
||||||
|
none: '无',
|
||||||
|
api_key: 'API Key',
|
||||||
|
},
|
||||||
|
key: '键',
|
||||||
|
value: '值',
|
||||||
|
},
|
||||||
|
privacyPolicy: '隐私协议',
|
||||||
|
privacyPolicyPlaceholder: '请输入隐私协议',
|
||||||
|
},
|
||||||
|
thought: {
|
||||||
|
using: '正在使用',
|
||||||
|
used: '已使用',
|
||||||
|
requestTitle: '请求来自',
|
||||||
|
responseTitle: '响应来自',
|
||||||
|
},
|
||||||
|
setBuiltInTools: {
|
||||||
|
info: '信息',
|
||||||
|
setting: '设置',
|
||||||
|
toolDescription: '工具描述',
|
||||||
|
parameters: '参数',
|
||||||
|
string: '字符串',
|
||||||
|
number: '数字',
|
||||||
|
required: '必填',
|
||||||
|
infoAndSetting: '信息和设置',
|
||||||
|
},
|
||||||
|
noCustomTool: {
|
||||||
|
title: '没有自定义工具!',
|
||||||
|
content: '在此统一添加和管理你的自定义工具,方便构建应用时使用。',
|
||||||
|
createTool: '创建工具',
|
||||||
|
},
|
||||||
|
noSearchRes: {
|
||||||
|
title: '抱歉,没有结果!',
|
||||||
|
content: '我们找不到任何与您的搜索相匹配的工具。',
|
||||||
|
reset: '重置搜索',
|
||||||
|
},
|
||||||
|
builtInPromptTitle: '提示词',
|
||||||
|
toolRemoved: '工具已被移除',
|
||||||
|
notAuthorized: '工具未授权',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translation
|
||||||
@ -133,4 +133,5 @@ export type VisionFile = {
|
|||||||
transfer_method: TransferMethod
|
transfer_method: TransferMethod
|
||||||
url: string
|
url: string
|
||||||
upload_file_id: string
|
upload_file_id: string
|
||||||
|
belongs_to?: string
|
||||||
}
|
}
|
||||||
|
|||||||
5
types/base.ts
Normal file
5
types/base.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export type TypeWithI18N<T = string> = {
|
||||||
|
'en_US': T
|
||||||
|
'zh_Hans': T
|
||||||
|
[key: string]: T
|
||||||
|
}
|
||||||
108
types/tools.ts
Normal file
108
types/tools.ts
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import type { TypeWithI18N } from './base'
|
||||||
|
export enum LOC {
|
||||||
|
tools = 'tools',
|
||||||
|
app = 'app',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AuthType {
|
||||||
|
none = 'none',
|
||||||
|
apiKey = 'api_key',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Credential = {
|
||||||
|
'auth_type': AuthType
|
||||||
|
'api_key_header'?: string
|
||||||
|
'api_key_value'?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CollectionType {
|
||||||
|
all = 'all',
|
||||||
|
builtIn = 'builtin',
|
||||||
|
custom = 'api',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Emoji = {
|
||||||
|
background: string
|
||||||
|
content: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Collection = {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
author: string
|
||||||
|
description: TypeWithI18N
|
||||||
|
icon: string | Emoji
|
||||||
|
label: TypeWithI18N
|
||||||
|
type: CollectionType
|
||||||
|
team_credentials: Record<string, any>
|
||||||
|
is_team_authorization: boolean
|
||||||
|
allow_delete: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ToolParameter = {
|
||||||
|
name: string
|
||||||
|
label: TypeWithI18N
|
||||||
|
human_description: TypeWithI18N
|
||||||
|
type: string
|
||||||
|
required: boolean
|
||||||
|
default: string
|
||||||
|
options?: {
|
||||||
|
label: TypeWithI18N
|
||||||
|
value: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Tool = {
|
||||||
|
name: string
|
||||||
|
label: TypeWithI18N
|
||||||
|
description: any
|
||||||
|
parameters: ToolParameter[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ToolCredential = {
|
||||||
|
name: string
|
||||||
|
label: TypeWithI18N
|
||||||
|
help: TypeWithI18N
|
||||||
|
placeholder: TypeWithI18N
|
||||||
|
type: string
|
||||||
|
required: boolean
|
||||||
|
default: string
|
||||||
|
options?: {
|
||||||
|
label: TypeWithI18N
|
||||||
|
value: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CustomCollectionBackend = {
|
||||||
|
provider: string
|
||||||
|
original_provider?: string
|
||||||
|
credentials: Credential
|
||||||
|
icon: Emoji
|
||||||
|
schema_type: string
|
||||||
|
schema: string
|
||||||
|
privacy_policy: string
|
||||||
|
tools?: ParamItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ParamItem = {
|
||||||
|
name: string
|
||||||
|
label: TypeWithI18N
|
||||||
|
human_description: TypeWithI18N
|
||||||
|
type: string
|
||||||
|
required: boolean
|
||||||
|
default: string
|
||||||
|
min?: number
|
||||||
|
max?: number
|
||||||
|
options?: {
|
||||||
|
label: TypeWithI18N
|
||||||
|
value: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CustomParamSchema = {
|
||||||
|
operation_id: string // name
|
||||||
|
summary: string
|
||||||
|
server_url: string
|
||||||
|
method: string
|
||||||
|
parameters: ParamItem[]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user