From 253dc7722f79ce7ec19cfb43fdb5cb7d9de53f79 Mon Sep 17 00:00:00 2001 From: qihai Date: Fri, 5 Sep 2025 14:05:29 +0800 Subject: [PATCH 1/6] feat(biz-components): migration --- .../src/api/idl/prompt/domain/prompt.ts | 7 +- .../biz-components/src/editor-tools/index.tsx | 84 ++++++ .../cozeloop/biz-components/src/global.d.ts | 6 + .../cozeloop/biz-components/src/index.ts | 4 + .../src/message-tool-btns/index.module.less | 29 ++ .../src/message-tool-btns/index.tsx | 253 ++++++++++++++++++ 6 files changed, 380 insertions(+), 3 deletions(-) create mode 100644 frontend/packages/cozeloop/biz-components/src/editor-tools/index.tsx create mode 100644 frontend/packages/cozeloop/biz-components/src/global.d.ts create mode 100644 frontend/packages/cozeloop/biz-components/src/message-tool-btns/index.module.less create mode 100644 frontend/packages/cozeloop/biz-components/src/message-tool-btns/index.tsx diff --git a/frontend/packages/cozeloop/api-schema/src/api/idl/prompt/domain/prompt.ts b/frontend/packages/cozeloop/api-schema/src/api/idl/prompt/domain/prompt.ts index e0812fa4c..2f869dc10 100644 --- a/frontend/packages/cozeloop/api-schema/src/api/idl/prompt/domain/prompt.ts +++ b/frontend/packages/cozeloop/api-schema/src/api/idl/prompt/domain/prompt.ts @@ -106,8 +106,9 @@ export interface ContentPart { image_url?: ImageURL, } export enum ContentType { - Text = "text", - ImageURL = "image_url", + Text = 'text', + ImageURL = 'image_url', + MultiPartVariable = 'multi_part', } export interface ImageURL { uri?: string, @@ -214,4 +215,4 @@ export enum Scenario { } export interface OverridePromptParams { model_config?: ModelConfig -} \ No newline at end of file +} diff --git a/frontend/packages/cozeloop/biz-components/src/editor-tools/index.tsx b/frontend/packages/cozeloop/biz-components/src/editor-tools/index.tsx new file mode 100644 index 000000000..e7881d48d --- /dev/null +++ b/frontend/packages/cozeloop/biz-components/src/editor-tools/index.tsx @@ -0,0 +1,84 @@ +// Copyright (c) 2025 coze-dev Authors +// SPDX-License-Identifier: Apache-2.0 +import { I18n } from '@cozeloop/i18n-adapter'; +import { handleCopy } from '@cozeloop/components'; +import { + type Message, + type Role, + type Prompt, + ContentType, +} from '@cozeloop/api-schema/prompt'; +import { IconCozCopy, IconCozTrashCan } from '@coze-arch/coze-design/icons'; +import { IconButton, Popconfirm, Space } from '@coze-arch/coze-design'; + +export type PromptMessage = Omit & { + role?: R; + id?: string; + key?: string; + optimize_key?: string; +}; + +interface EditorToolsProps { + onDelete?: (id?: string | number) => void; + message?: PromptMessage; + disabled?: boolean; + promptInfo?: Prompt; + optimizeBtnHidden?: boolean; + onMessageChange?: (message: PromptMessage) => void; +} + +export function EditorTools({ + onDelete, + message, + disabled, +}: EditorToolsProps) { + const handleInfo = () => { + if (message?.parts?.length) { + return message?.parts + ?.map(it => { + if (it.type === ContentType.MultiPartVariable && it?.text) { + return `${it.text}`; + } + return it.text; + }) + .join(''); + } + return message?.content; + }; + return ( + + } + color="secondary" + size="mini" + onClick={() => { + const info = handleInfo() ?? ''; + handleCopy(info); + }} + /> + {!onDelete ? null : disabled ? ( + } + color="secondary" + size="mini" + disabled={disabled} + /> + ) : ( + onDelete?.(`${message?.key || message?.id || ''}`)} + > + } + color="secondary" + size="mini" + /> + + )} + + ); +} diff --git a/frontend/packages/cozeloop/biz-components/src/global.d.ts b/frontend/packages/cozeloop/biz-components/src/global.d.ts new file mode 100644 index 000000000..f052f32af --- /dev/null +++ b/frontend/packages/cozeloop/biz-components/src/global.d.ts @@ -0,0 +1,6 @@ +// Copyright (c) 2025 coze-dev Authors +// SPDX-License-Identifier: Apache-2.0 +declare module '*.module.less' { + const classes: { readonly [key: string]: string }; + export default classes; +} diff --git a/frontend/packages/cozeloop/biz-components/src/index.ts b/frontend/packages/cozeloop/biz-components/src/index.ts index 3d1cb4aab..f49dfd533 100644 --- a/frontend/packages/cozeloop/biz-components/src/index.ts +++ b/frontend/packages/cozeloop/biz-components/src/index.ts @@ -7,3 +7,7 @@ export { BenefitBannerScene, } from './benefit'; export { UserSelect } from './user-select'; + +export { EditorTools } from './editor-tools'; + +export { MessageToolBtns } from './message-tool-btns'; diff --git a/frontend/packages/cozeloop/biz-components/src/message-tool-btns/index.module.less b/frontend/packages/cozeloop/biz-components/src/message-tool-btns/index.module.less new file mode 100644 index 000000000..f02cf4fc7 --- /dev/null +++ b/frontend/packages/cozeloop/biz-components/src/message-tool-btns/index.module.less @@ -0,0 +1,29 @@ +// Copyright (c) 2025 coze-dev Authors +// SPDX-License-Identifier: Apache-2.0 +.tool-btns { + display: flex; + gap: 4px; + align-items: center; + justify-content: flex-end; + + margin-left: -4px; + + + .icon-button { + background: transparent; + + &:hover { + color: var(--semi-color-primary); + } + + &-active{ + background-color:rgba(var(--coze-bg-6), var(--coze-bg-6-alpha))!important; + } + } + + :global { + .semi-button-disabled { + color: var(--semi-color-disabled-text) !important; + } + } +} diff --git a/frontend/packages/cozeloop/biz-components/src/message-tool-btns/index.tsx b/frontend/packages/cozeloop/biz-components/src/message-tool-btns/index.tsx new file mode 100644 index 000000000..9528f3c2e --- /dev/null +++ b/frontend/packages/cozeloop/biz-components/src/message-tool-btns/index.tsx @@ -0,0 +1,253 @@ +// Copyright (c) 2025 coze-dev Authors +// SPDX-License-Identifier: Apache-2.0 +/* eslint-disable @coze-arch/max-line-per-function */ +/* eslint-disable complexity */ + +import { useState, type Dispatch, type SetStateAction } from 'react'; + +import classNames from 'classnames'; +import { I18n } from '@cozeloop/i18n-adapter'; +import { handleCopy } from '@cozeloop/components'; +import { + ContentType, + type Prompt, + type VariableVal, + type DebugMessage as BasicDebugMessage, + type Message, +} from '@cozeloop/api-schema/prompt'; +import { + IconCozAutoHeight, + IconCozCopy, + IconCozNode, + IconCozPencil, + IconCozRefresh, + IconCozTrashCan, +} from '@coze-arch/coze-design/icons'; +import { + Button, + Divider, + IconButton, + Popconfirm, + Space, + Tooltip, +} from '@coze-arch/coze-design'; + +import { type PromptMessage } from '../editor-tools'; + +import styles from './index.module.less'; + +interface DebugMessage extends BasicDebugMessage { + id?: string; + isEdit?: boolean; +} + +interface MessageToolBtnsProps { + item: DebugMessage; + streaming?: boolean; + canReRun?: boolean; + canFile?: boolean; + canOptimize?: boolean; + saveDisabled?: boolean; + isMarkdown?: boolean; + btnConfig?: { + hideMessageTypeSelect?: boolean; + hideDelete?: boolean; + hideEdit?: boolean; + hideRerun?: boolean; + hideCopy?: boolean; + hideTypeChange?: boolean; + hideCancel?: boolean; + hideOk?: boolean; + hideTrace?: boolean; + hideOptimize?: boolean; + }; + updateMessage?: () => void; + updateEditable?: (v: boolean) => void; + deleteChat?: () => void; + rerunLLM?: () => void; + updateMessageItem?: (v: DebugMessage) => void; + setIsMarkdown?: Dispatch>; + setDebugId?: (v: string | number) => void; + + promptInfo?: Prompt; + variables?: VariableVal[]; + optimizeSystemPrompt?: PromptMessage; + setMessageList?: SetStateAction< + Array | undefined + >; + lastItem?: DebugMessage; +} + +export function MessageToolBtns({ + item, + streaming, + updateEditable, + deleteChat, + rerunLLM, + canReRun, + updateMessageItem, + saveDisabled, + isMarkdown, + setIsMarkdown, + btnConfig, + setDebugId, +}: MessageToolBtnsProps) { + const [showPopconfirm, setShowPopconfirm] = useState(false); + + const { isEdit, parts } = item; + + if (streaming) { + return null; + } + + const content = + parts?.find(it => it?.type === ContentType.Text)?.text || item.content; + + const copyBtn = !btnConfig?.hideCopy && ( + + } + disabled={!content} + onClick={() => content && handleCopy(content)} + size="mini" + color="secondary" + /> + + ); + + const txtMdBtn = !btnConfig?.hideTypeChange && ( + + } + onClick={() => setIsMarkdown?.(v => !v)} + size="mini" + color="secondary" + /> + + ); + + const editBtn = !btnConfig?.hideEdit && ( + + } + onClick={() => updateEditable?.(true)} + size="mini" + color="secondary" + /> + + ); + + const deleteBtn = !btnConfig?.hideDelete && ( + { + deleteChat?.(); + setShowPopconfirm(false); + }} + onCancel={() => setShowPopconfirm(false)} + > + {showPopconfirm ? ( + } + size="mini" + onClick={() => setShowPopconfirm(false)} + /> + ) : ( + + + } + size="mini" + onClick={() => setShowPopconfirm(true)} + color="secondary" + /> + + + )} + + ); + + const cancelEditBtn = !btnConfig?.hideCancel && ( + + ); + + const okEditBtn = !btnConfig?.hideOk && ( + + ); + + const refreshBtn = !btnConfig?.hideRerun && ( + + } + onClick={rerunLLM} + size="mini" + color="secondary" + /> + + ); + + const traceBtn = !btnConfig?.hideTrace && ( + + } + onClick={() => { + setDebugId?.(item?.debug_id || ''); + }} + size="mini" + color="secondary" + /> + + ); + + if (isEdit) { + return ( + + {cancelEditBtn} + {okEditBtn} + + ); + } + + return ( +
+ {txtMdBtn} + + {item?.debug_id ? traceBtn : null} + {editBtn} + {copyBtn} + {canReRun ? refreshBtn : null} + {deleteBtn} +
+ ); +} From 15b24f7843667c03d0b68251da13a8c84e5359d7 Mon Sep 17 00:00:00 2001 From: qihai Date: Fri, 5 Sep 2025 15:08:56 +0800 Subject: [PATCH 2/6] feat(components): migration --- .../packages/cozeloop/components/package.json | 4 +- .../packages/cozeloop/components/src/index.ts | 1 + .../components/image-item-renderer.tsx | 129 ++++++ .../components/multipart-item-renderer.tsx | 63 +++ .../components/url-input-modal.tsx | 304 ++++++++++++++ .../src/multi-part-editor/index.module.less | 8 + .../src/multi-part-editor/index.tsx | 383 ++++++++++++++++++ .../components/src/multi-part-editor/type.tsx | 75 ++++ .../components/src/multi-part-editor/utils.ts | 38 ++ .../src/tooltip-with-disabled/index.tsx | 4 +- 10 files changed, 1005 insertions(+), 4 deletions(-) create mode 100755 frontend/packages/cozeloop/components/src/multi-part-editor/components/image-item-renderer.tsx create mode 100755 frontend/packages/cozeloop/components/src/multi-part-editor/components/multipart-item-renderer.tsx create mode 100755 frontend/packages/cozeloop/components/src/multi-part-editor/components/url-input-modal.tsx create mode 100644 frontend/packages/cozeloop/components/src/multi-part-editor/index.module.less create mode 100644 frontend/packages/cozeloop/components/src/multi-part-editor/index.tsx create mode 100644 frontend/packages/cozeloop/components/src/multi-part-editor/type.tsx create mode 100644 frontend/packages/cozeloop/components/src/multi-part-editor/utils.ts diff --git a/frontend/packages/cozeloop/components/package.json b/frontend/packages/cozeloop/components/package.json index a1e726b31..a4aaf5323 100644 --- a/frontend/packages/cozeloop/components/package.json +++ b/frontend/packages/cozeloop/components/package.json @@ -25,7 +25,8 @@ "monaco-editor": "^0.45.0", "nanoid": "^4.0.2", "react": "~18.2.0", - "react-sortable-hoc": "2.0.0" + "react-sortable-hoc": "2.0.0", + "sortablejs": "~1.15.2" }, "devDependencies": { "@coze-arch/bot-typings": "workspace:*", @@ -46,4 +47,3 @@ }, "peerDependencies": {} } - diff --git a/frontend/packages/cozeloop/components/src/index.ts b/frontend/packages/cozeloop/components/src/index.ts index e4b4ffb95..1b56b54a9 100644 --- a/frontend/packages/cozeloop/components/src/index.ts +++ b/frontend/packages/cozeloop/components/src/index.ts @@ -96,3 +96,4 @@ export { default as JumpIconButton } from './jump-button/jump-icon-button'; export { default as RouteBackAction } from './route/route-back-action'; export { BasicCard } from './basic-card'; +export { MultipartEditor } from './multi-part-editor'; diff --git a/frontend/packages/cozeloop/components/src/multi-part-editor/components/image-item-renderer.tsx b/frontend/packages/cozeloop/components/src/multi-part-editor/components/image-item-renderer.tsx new file mode 100755 index 000000000..f5d089f72 --- /dev/null +++ b/frontend/packages/cozeloop/components/src/multi-part-editor/components/image-item-renderer.tsx @@ -0,0 +1,129 @@ +/* eslint-disable @coze-arch/use-error-in-catch */ +/* eslint-disable complexity */ +import React, { useState } from 'react'; + +import { StorageProvider } from '@cozeloop/api-schema/data'; +import { + IconCozEye, + IconCozImageBroken, + IconCozRefresh, + IconCozTrashCan, +} from '@coze-arch/coze-design/icons'; +import { Image, ImagePreview, Loading } from '@coze-arch/coze-design'; + +import { ImageStatus, type MultipartItem } from '../type'; + +interface ImageItemRendererProps { + spaceID?: Int64; + item: MultipartItem; + readonly?: boolean; + onRemove: () => void; + onChange: (item: MultipartItem) => void; + uploadFile?: (params: unknown) => Promise; +} + +export const ImageItemRenderer: React.FC = ({ + spaceID, + item, + onRemove, + onChange, + uploadFile, + readonly, +}) => { + const [visible, setVisible] = useState(false); + const [fileLoadError, setFileLoadError] = useState(false); + const status = item?.sourceImage?.status; + const uri = item?.image?.uri; + const url = item?.image?.url; + const isError = status === ImageStatus.Error; + const file = item?.sourceImage?.file as File; + const retryUpload = async () => { + try { + onChange({ + ...item, + sourceImage: { + ...item.sourceImage, + status: ImageStatus.Loading, + }, + }); + const newUri = await uploadFile?.({ + file, + fileType: 'image', + spaceID, + }); + onChange({ + ...item, + sourceImage: { + ...item.sourceImage, + status: ImageStatus.Success, + }, + image: { + ...item.image, + uri: newUri, + storage_provider: StorageProvider.ImageX, + }, + }); + } catch (error) { + onChange({ + ...item, + sourceImage: { + ...item.sourceImage, + status: ImageStatus.Error, + }, + }); + } + }; + + return ( +
+
+ + } + onError={() => setFileLoadError(true)} + /> + {status !== ImageStatus.Loading && ( +
+ {isError && !readonly ? ( + + ) : null} + {(uri || url) && !fileLoadError ? ( + setVisible(true)} + /> + ) : null} + {readonly ? null : ( + + )} +
+ )} + {status === ImageStatus.Loading && ( +
+ +
+ )} +
+ {status === ImageStatus.Error && ( +
上传失败
+ )} +
+ ); +}; diff --git a/frontend/packages/cozeloop/components/src/multi-part-editor/components/multipart-item-renderer.tsx b/frontend/packages/cozeloop/components/src/multi-part-editor/components/multipart-item-renderer.tsx new file mode 100755 index 000000000..7b8a850ce --- /dev/null +++ b/frontend/packages/cozeloop/components/src/multi-part-editor/components/multipart-item-renderer.tsx @@ -0,0 +1,63 @@ +import React from 'react'; + +import { type Content, ContentType } from '@cozeloop/api-schema/evaluation'; +import { IconCozTrashCan } from '@coze-arch/coze-design/icons'; +import { Button, TextArea } from '@coze-arch/coze-design'; + +import { ImageItemRenderer } from './image-item-renderer'; + +interface MultipartItemRendererProps { + item: Content; + readonly?: boolean; + onChange: (item: Content) => void; + onRemove: () => void; +} + +export const MultipartItemRenderer: React.FC = ({ + item, + readonly, + onChange, + onRemove, +}) => { + const handleTextChange = (text: string) => { + onChange({ + ...item, + text, + }); + }; + + switch (item.content_type) { + case ContentType.Text: + return ( +
+