From 9d0724de55c55c251effc5343038cc9f05dc73f9 Mon Sep 17 00:00:00 2001 From: madinah <497350746@qq.com> Date: Fri, 5 Sep 2025 15:13:48 +0800 Subject: [PATCH 1/2] feat(trace): add observation components --- .../config/subspaces/default/pnpm-lock.yaml | 150 ++++-- frontend/apps/cozeloop/tailwind.config.ts | 1 + .../trace-detail-open/.stylelintrc.js | 7 + .../observation/trace-detail-open/README.md | 16 + .../config/rush-project.json | 12 + .../trace-detail-open/eslint.config.js | 9 + .../trace-detail-open/package.json | 60 +++ .../trace-detail-open/src/index.tsx | 81 ++++ .../trace-detail-open/src/typings.d.ts | 3 + .../trace-detail-open/tsconfig.build.json | 48 ++ .../trace-detail-open/tsconfig.json | 15 + .../trace-detail-open/tsconfig.misc.json | 20 + .../trace-detail-open/vitest.config.ts | 6 + .../observation/trace-detail/package.json | 14 +- .../observation/trace-detail/src/index.tsx | 10 +- .../biz/trace-detail-pane/index.tsx | 5 +- .../trace-detail/biz/trace-detail/index.tsx | 30 +- .../biz/trace-detail/interface.ts | 6 +- .../feedback/hooks/use-list-annotations.ts | 34 ++ .../trace-detail/components/feedback/index.ts | 5 + .../components/feedback/score.tsx | 37 ++ .../trace-detail/components/feedback/tab.tsx | 22 + .../feedback/trace-detail-table.tsx | 296 ++++++++++++ .../graphs/trace-tree/node/index.tsx | 19 +- .../components/span-detail/index.tsx | 54 ++- .../components/span-detail/span-header.tsx | 6 + .../src/trace-detail/consts/span.tsx | 60 ++- .../trace-detail/hooks/use-fetch-spans.tsx | 7 +- .../hooks/use-trace-detail-context.ts | 23 + .../trace-detail/src/trace-detail/index.tsx | 8 +- .../trace-detail/src/utils/dayjs.ts | 2 +- .../trace-detail/tsconfig.misc.json | 8 +- .../observation/trace-list/package.json | 11 +- .../filter-bar/categorical-select/index.tsx | 58 +++ .../src/components/filter-bar/custom-view.tsx | 100 ++-- .../src/components/filter-bar/data-source.tsx | 2 - .../components/filter-bar/filter-select.tsx | 20 +- .../src/components/filter-bar/index.tsx | 23 +- .../filter-bar/prompt-select/index.tsx | 4 +- .../filter-select-ui/index.module.less | 6 + .../src/components/filter-select-ui/index.tsx | 431 +++++++++++------- .../trace-list/src/components/index.tsx | 2 + .../src/components/left-manual-expr/index.tsx | 105 +++++ .../src/components/logic-expr/const.ts | 23 + .../src/components/logic-expr/consts.ts | 14 + .../logic-expr/error-msg-render.tsx | 15 +- .../src/components/logic-expr/left-render.tsx | 44 +- .../src/components/logic-expr/logic-expr.tsx | 35 +- .../components/logic-expr/operator-render.tsx | 28 +- .../components/logic-expr/right-render.tsx | 61 ++- .../src/components/logic-expr/utils.ts | 73 ++- .../queries/table/columns/index.tsx | 68 ++- .../queries/table/hooks/use-fetch-traces.tsx | 55 ++- .../src/components/queries/table/index.tsx | 7 +- .../components/table-header-text/index.tsx | 46 +- .../trace-list/src/consts/filter.ts | 2 + .../trace-list/src/consts/index.ts | 7 +- .../trace-list/src/consts/trace-attrs.ts | 1 + .../trace-list/src/contexts/trace-context.tsx | 2 +- .../trace-list/src/hooks/index.tsx | 2 + .../src/hooks/use-fetch-meta-info.ts | 45 +- .../trace-list/src/hooks/use-get-meta-info.ts | 78 ++++ .../src/hooks/use-sync-url-params.ts | 27 +- .../observation/trace-list/src/index.tsx | 15 + .../observation/trace-list/src/utils/url.ts | 45 ++ .../trace-list/tsconfig.build.json | 5 +- .../observation/trace-list/tsconfig.misc.json | 2 +- .../src/components/message-parts.tsx | 31 +- .../components/multi-part-render/index.tsx | 78 ++++ .../src/components/multi-part-render/type.ts | 14 + .../src/components/plain-text.tsx | 15 +- .../src/components/span-field-render.tsx | 28 +- .../src/span-definition/model/index.tsx | 1 + .../src/span-definition/model/render.tsx | 22 +- .../src/span-definition/model/schema.ts | 217 ++++----- .../prompt/argument-value-render.tsx | 51 +++ .../span-definition/prompt/index.module.less | 37 ++ .../src/span-definition/prompt/index.tsx | 4 +- .../src/span-definition/prompt/render.tsx | 30 +- .../src/span-definition/prompt/schema.ts | 150 +++--- .../trace-struct-data/src/types/index.ts | 2 +- .../trace-struct-data/tsconfig.build.json | 15 - .../trace-struct-data/tsconfig.misc.json | 2 +- .../src/locales/observation/en-US.json | 5 +- .../src/locales/observation/zh-CN.json | 5 +- rush.json | 7 +- 86 files changed, 2505 insertions(+), 745 deletions(-) create mode 100644 frontend/packages/cozeloop/observation/trace-detail-open/.stylelintrc.js create mode 100644 frontend/packages/cozeloop/observation/trace-detail-open/README.md create mode 100644 frontend/packages/cozeloop/observation/trace-detail-open/config/rush-project.json create mode 100644 frontend/packages/cozeloop/observation/trace-detail-open/eslint.config.js create mode 100644 frontend/packages/cozeloop/observation/trace-detail-open/package.json create mode 100644 frontend/packages/cozeloop/observation/trace-detail-open/src/index.tsx create mode 100644 frontend/packages/cozeloop/observation/trace-detail-open/src/typings.d.ts create mode 100644 frontend/packages/cozeloop/observation/trace-detail-open/tsconfig.build.json create mode 100644 frontend/packages/cozeloop/observation/trace-detail-open/tsconfig.json create mode 100644 frontend/packages/cozeloop/observation/trace-detail-open/tsconfig.misc.json create mode 100644 frontend/packages/cozeloop/observation/trace-detail-open/vitest.config.ts create mode 100644 frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/feedback/hooks/use-list-annotations.ts create mode 100644 frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/feedback/index.ts create mode 100644 frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/feedback/score.tsx create mode 100644 frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/feedback/tab.tsx create mode 100644 frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/feedback/trace-detail-table.tsx create mode 100644 frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/hooks/use-trace-detail-context.ts create mode 100644 frontend/packages/cozeloop/observation/trace-list/src/components/filter-bar/categorical-select/index.tsx create mode 100644 frontend/packages/cozeloop/observation/trace-list/src/components/left-manual-expr/index.tsx create mode 100644 frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/const.ts create mode 100644 frontend/packages/cozeloop/observation/trace-list/src/hooks/use-get-meta-info.ts create mode 100644 frontend/packages/cozeloop/observation/trace-struct-data/src/components/multi-part-render/index.tsx create mode 100644 frontend/packages/cozeloop/observation/trace-struct-data/src/components/multi-part-render/type.ts create mode 100644 frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/prompt/argument-value-render.tsx diff --git a/common/config/subspaces/default/pnpm-lock.yaml b/common/config/subspaces/default/pnpm-lock.yaml index 06c4c4a55..d41a30bda 100644 --- a/common/config/subspaces/default/pnpm-lock.yaml +++ b/common/config/subspaces/default/pnpm-lock.yaml @@ -1806,7 +1806,7 @@ importers: version: link:../i18n '@cozeloop/observation-component-adapter': specifier: workspace:* - version: link:../observation/trace-detail + version: link:../observation/trace-detail-open '@cozeloop/prompt-components': specifier: workspace:* version: link:../prompt-components @@ -1936,7 +1936,7 @@ importers: version: link:../i18n '@cozeloop/observation-component-adapter': specifier: workspace:* - version: link:../observation/trace-detail + version: link:../observation/trace-detail-open '@cozeloop/prompt-components': specifier: workspace:* version: link:../prompt-components @@ -2484,6 +2484,88 @@ importers: '@rsbuild/core': specifier: ~1.1.0 version: 1.1.13 + '@types/json-bigint': + specifier: ~1.0.4 + version: 1.0.4 + '@types/lodash-es': + specifier: ^4.17.10 + version: 4.17.12 + '@types/react': + specifier: 18.2.37 + version: 18.2.37 + '@types/react-dom': + specifier: 18.2.15 + version: 18.2.15 + '@vitest/coverage-v8': + specifier: ~3.0.5 + version: 3.0.9(vitest@3.0.9) + happy-dom: + specifier: ^15.10.2 + version: 15.11.7 + react-dom: + specifier: 18.2.0 + version: 18.2.0(react@18.2.0) + tsconfig-paths: + specifier: 4.1.0 + version: 4.1.0 + vite: + specifier: ^4.3.9 + version: 4.5.14(@types/node@18.18.9) + vite-plugin-svgr: + specifier: ~3.3.0 + version: 3.3.0(typescript@5.8.2)(vite@4.5.14) + vitest: + specifier: ~3.0.5 + version: 3.0.9(happy-dom@15.11.7) + + ../../../frontend/packages/cozeloop/observation/trace-detail-open: + dependencies: + '@coze-arch/coze-design': + specifier: 0.0.6-alpha.101d0c + version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + '@cozeloop/api-schema': + specifier: workspace:* + version: link:../../api-schema + '@cozeloop/biz-hooks-adapter': + specifier: workspace:* + version: link:../../biz-hooks + '@cozeloop/components': + specifier: workspace:* + version: link:../../components + '@cozeloop/observation-component': + specifier: workspace:* + version: link:../trace-detail + '@cozeloop/tag-components': + specifier: workspace:* + version: link:../../tag-components + ahooks: + specifier: 3.7.8 + version: 3.7.8(patch_hash=sa4ddrxdk2yhjzudeck6u5ww3i)(react@18.2.0) + classnames: + specifier: ^2.3.2 + version: 2.5.1 + dayjs: + specifier: ^1.11.7 + version: 1.11.13 + lodash-es: + specifier: ^4.17.21 + version: 4.17.21 + devDependencies: + '@coze-arch/bot-typings': + specifier: workspace:* + version: link:../../../arch/bot-typings + '@coze-arch/eslint-config': + specifier: workspace:* + version: link:../../../../config/eslint-config + '@coze-arch/stylelint-config': + specifier: workspace:* + version: link:../../../../config/stylelint-config + '@coze-arch/ts-config': + specifier: workspace:* + version: link:../../../../config/ts-config + '@coze-arch/vitest-config': + specifier: workspace:* + version: link:../../../../config/vitest-config '@storybook/addon-essentials': specifier: ^7.6.7 version: 7.6.20(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) @@ -2508,12 +2590,15 @@ importers: '@storybook/test': specifier: ^7.6.7 version: 7.6.20 - '@types/json-bigint': - specifier: ~1.0.4 - version: 1.0.4 - '@types/lodash-es': - specifier: ^4.17.10 - version: 4.17.12 + '@testing-library/jest-dom': + specifier: ^6.1.5 + version: 6.6.3 + '@testing-library/react': + specifier: ^14.1.2 + version: 14.3.1(react-dom@18.2.0)(react@18.2.0) + '@testing-library/react-hooks': + specifier: ^8.0.1 + version: 8.0.1(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) '@types/react': specifier: 18.2.37 version: 18.2.37 @@ -2523,21 +2608,18 @@ importers: '@vitest/coverage-v8': specifier: ~3.0.5 version: 3.0.9(vitest@3.0.9) - happy-dom: - specifier: ^15.10.2 - version: 15.11.7 + react: + specifier: 18.2.0 + version: 18.2.0 react-dom: specifier: 18.2.0 version: 18.2.0(react@18.2.0) storybook: specifier: ^7.6.7 version: 7.6.20 - tsconfig-paths: - specifier: 4.1.0 - version: 4.1.0 - vite: - specifier: ^4.3.9 - version: 4.5.14(@types/node@18.18.9) + stylelint: + specifier: ^15.11.0 + version: 15.11.0(typescript@5.8.2) vite-plugin-svgr: specifier: ~3.3.0 version: 3.3.0(typescript@5.8.2)(vite@4.5.14) @@ -2573,7 +2655,10 @@ importers: version: link:../../i18n '@cozeloop/observation-component-adapter': specifier: workspace:* - version: link:../trace-detail + version: link:../trace-detail-open + '@cozeloop/tag-components': + specifier: workspace:* + version: link:../../tag-components '@cozeloop/tea-adapter': specifier: workspace:* version: link:../../tea @@ -2626,30 +2711,6 @@ importers: '@coze-arch/vitest-config': specifier: workspace:* version: link:../../../../config/vitest-config - '@storybook/addon-essentials': - specifier: ^7.6.7 - version: 7.6.20(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) - '@storybook/addon-interactions': - specifier: ^7.6.7 - version: 7.6.20 - '@storybook/addon-links': - specifier: ^7.6.7 - version: 7.6.20(react@18.2.0) - '@storybook/addon-onboarding': - specifier: ^1.0.10 - version: 1.0.11(react-dom@18.2.0)(react@18.2.0) - '@storybook/blocks': - specifier: ^7.6.7 - version: 7.6.20(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) - '@storybook/react': - specifier: ^7.6.7 - version: 7.6.20(react-dom@18.2.0)(react@18.2.0)(typescript@5.8.2) - '@storybook/react-vite': - specifier: ^7.6.7 - version: 7.6.20(react-dom@18.2.0)(react@18.2.0)(typescript@5.8.2)(vite@4.5.14) - '@storybook/test': - specifier: ^7.6.7 - version: 7.6.20 '@testing-library/jest-dom': specifier: ^6.1.5 version: 6.6.3 @@ -2677,9 +2738,6 @@ importers: react-dom: specifier: 18.2.0 version: 18.2.0(react@18.2.0) - storybook: - specifier: ^7.6.7 - version: 7.6.20 stylelint: specifier: ^15.11.0 version: 15.11.0(typescript@5.8.2) @@ -2987,7 +3045,7 @@ importers: version: link:../i18n '@cozeloop/observation-component-adapter': specifier: workspace:* - version: link:../observation/trace-detail + version: link:../observation/trace-detail-open '@cozeloop/prompt-components': specifier: workspace:* version: link:../prompt-components diff --git a/frontend/apps/cozeloop/tailwind.config.ts b/frontend/apps/cozeloop/tailwind.config.ts index ebbe19c0a..78f324e31 100644 --- a/frontend/apps/cozeloop/tailwind.config.ts +++ b/frontend/apps/cozeloop/tailwind.config.ts @@ -18,5 +18,6 @@ export default createTailwindcssConfig({ '@cozeloop/trace-list', '@cozeloop/observation-component-adapter', '@cozeloop/tag-pages', + '@cozeloop/observation-component', ], }) as TailwindConfig; diff --git a/frontend/packages/cozeloop/observation/trace-detail-open/.stylelintrc.js b/frontend/packages/cozeloop/observation/trace-detail-open/.stylelintrc.js new file mode 100644 index 000000000..9f56c6416 --- /dev/null +++ b/frontend/packages/cozeloop/observation/trace-detail-open/.stylelintrc.js @@ -0,0 +1,7 @@ +// Copyright (c) 2025 coze-dev Authors +// SPDX-License-Identifier: Apache-2.0 +const { defineConfig } = require('@coze-arch/stylelint-config'); + +module.exports = defineConfig({ + extends: [], +}); diff --git a/frontend/packages/cozeloop/observation/trace-detail-open/README.md b/frontend/packages/cozeloop/observation/trace-detail-open/README.md new file mode 100644 index 000000000..c89a0f777 --- /dev/null +++ b/frontend/packages/cozeloop/observation/trace-detail-open/README.md @@ -0,0 +1,16 @@ +# @cozeloop/observation-component-adapter + +> Project template for react component with storybook. + +## Features + +- [x] eslint & ts +- [x] esm bundle +- [x] umd bundle +- [x] storybook + +## Commands + +- init: `rush update` +- dev: `npm run dev` +- build: `npm run build` diff --git a/frontend/packages/cozeloop/observation/trace-detail-open/config/rush-project.json b/frontend/packages/cozeloop/observation/trace-detail-open/config/rush-project.json new file mode 100644 index 000000000..1c23b14b8 --- /dev/null +++ b/frontend/packages/cozeloop/observation/trace-detail-open/config/rush-project.json @@ -0,0 +1,12 @@ +{ + "operationSettings": [ + { + "operationName": "test:cov", + "outputFolderNames": ["coverage"] + }, + { + "operationName": "ts-check", + "outputFolderNames": ["dist"] + } + ] +} diff --git a/frontend/packages/cozeloop/observation/trace-detail-open/eslint.config.js b/frontend/packages/cozeloop/observation/trace-detail-open/eslint.config.js new file mode 100644 index 000000000..5ab1c0f6a --- /dev/null +++ b/frontend/packages/cozeloop/observation/trace-detail-open/eslint.config.js @@ -0,0 +1,9 @@ +// Copyright (c) 2025 coze-dev Authors +// SPDX-License-Identifier: Apache-2.0 +const { defineConfig } = require('@coze-arch/eslint-config'); + +module.exports = defineConfig({ + packageRoot: __dirname, + preset: 'web', + rules: {}, +}); diff --git a/frontend/packages/cozeloop/observation/trace-detail-open/package.json b/frontend/packages/cozeloop/observation/trace-detail-open/package.json new file mode 100644 index 000000000..3d94662ab --- /dev/null +++ b/frontend/packages/cozeloop/observation/trace-detail-open/package.json @@ -0,0 +1,60 @@ +{ + "name": "@cozeloop/observation-component-adapter", + "version": "0.0.1", + "description": "trace detail packages, add extra base on the base observation component", + "license": "Apache-2.0", + "author": "liuwei.felix@bytedance.com", + "maintainers": [], + "main": "src/index.tsx", + "scripts": { + "build": "exit 0", + "dev": "storybook dev -p 6006", + "lint": "eslint ./ --cache", + "test": "vitest --run --passWithNoTests", + "test:cov": "npm run test -- --coverage" + }, + "dependencies": { + "@coze-arch/coze-design": "0.0.6-alpha.346d77", + "@cozeloop/api-schema": "workspace:*", + "@cozeloop/biz-hooks-adapter": "workspace:*", + "@cozeloop/components": "workspace:*", + "@cozeloop/observation-component": "workspace:*", + "@cozeloop/tag-components": "workspace:*", + "ahooks": "^3.7.8", + "classnames": "^2.3.2", + "dayjs": "^1.11.7", + "lodash-es": "^4.17.21" + }, + "devDependencies": { + "@coze-arch/bot-typings": "workspace:*", + "@coze-arch/eslint-config": "workspace:*", + "@coze-arch/stylelint-config": "workspace:*", + "@coze-arch/ts-config": "workspace:*", + "@coze-arch/vitest-config": "workspace:*", + "@storybook/addon-essentials": "^7.6.7", + "@storybook/addon-interactions": "^7.6.7", + "@storybook/addon-links": "^7.6.7", + "@storybook/addon-onboarding": "^1.0.10", + "@storybook/blocks": "^7.6.7", + "@storybook/react": "^7.6.7", + "@storybook/react-vite": "^7.6.7", + "@storybook/test": "^7.6.7", + "@testing-library/jest-dom": "^6.1.5", + "@testing-library/react": "^14.1.2", + "@testing-library/react-hooks": "^8.0.1", + "@types/react": "18.2.37", + "@types/react-dom": "18.2.15", + "@vitest/coverage-v8": "~3.0.5", + "react": "~18.2.0", + "react-dom": "~18.2.0", + "storybook": "^7.6.7", + "stylelint": "^15.11.0", + "vite-plugin-svgr": "~3.3.0", + "vitest": "~3.0.5" + }, + "peerDependencies": { + "react": ">=18.2.0", + "react-dom": ">=18.2.0" + } +} + diff --git a/frontend/packages/cozeloop/observation/trace-detail-open/src/index.tsx b/frontend/packages/cozeloop/observation/trace-detail-open/src/index.tsx new file mode 100644 index 000000000..2f226e387 --- /dev/null +++ b/frontend/packages/cozeloop/observation/trace-detail-open/src/index.tsx @@ -0,0 +1,81 @@ +// Copyright (c) 2025 coze-dev Authors +// SPDX-License-Identifier: Apache-2.0 + +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/naming-convention */ +import { AnnotationPanel } from '@cozeloop/tag-components'; +import { + TraceDetailPanel as TraceDetailPanelInner, + type TraceDetailPanelProps, + type TraceDetailContext, + tabs, + TraceDetail as TraceDetailInner, + type TraceDetailProps, +} from '@cozeloop/observation-component'; +import { type span } from '@cozeloop/api-schema/observation'; + +interface TraceDetailOpenPanelProps { + forceOverwrite?: boolean; +} + +interface TraceDetailOpenPanelProps { + forceOverwrite?: boolean; +} + +type TracePanelWrapperProps = TraceDetailPanelProps & + TraceDetailContext & + TraceDetailOpenPanelProps; + +type TraceWrapperDetailProps = TraceDetailProps & + TraceDetailContext & + TraceDetailOpenPanelProps; + +export const TraceDetailWrapper = < + T extends + | ((props: TraceWrapperDetailProps) => JSX.Element) + | ((props: TracePanelWrapperProps) => JSX.Element), +>({ + Component, +}: { + Component: T; +}) => { + const Wrapper = (props: Parameters[number]) => { + const { forceOverwrite } = props; + + const traceDetailOpenPanelProps = forceOverwrite + ? { + extraSpanDetailTabs: tabs, + spanDetailHeaderSlot: ( + span: span.OutputSpan, + platform: string | number, + ) => , + ...props, + } + : { + ...props, + extraSpanDetailTabs: tabs, + spanDetailHeaderSlot: ( + span: span.OutputSpan, + platform: string | number, + ) => , + }; + + return ; + }; + + return Wrapper; +}; + +export const TraceDetailPanel = TraceDetailWrapper({ + Component: TraceDetailPanelInner, +}); +export const TraceDetail = TraceDetailWrapper({ + Component: TraceDetailInner, +}); + +export { + NODE_CONFIG_MAP, + SpanType, + getEndTime, + getStartTime, +} from '@cozeloop/observation-component'; diff --git a/frontend/packages/cozeloop/observation/trace-detail-open/src/typings.d.ts b/frontend/packages/cozeloop/observation/trace-detail-open/src/typings.d.ts new file mode 100644 index 000000000..aaca1e197 --- /dev/null +++ b/frontend/packages/cozeloop/observation/trace-detail-open/src/typings.d.ts @@ -0,0 +1,3 @@ +// Copyright (c) 2025 coze-dev Authors +// SPDX-License-Identifier: Apache-2.0 +/// diff --git a/frontend/packages/cozeloop/observation/trace-detail-open/tsconfig.build.json b/frontend/packages/cozeloop/observation/trace-detail-open/tsconfig.build.json new file mode 100644 index 000000000..13e70b44e --- /dev/null +++ b/frontend/packages/cozeloop/observation/trace-detail-open/tsconfig.build.json @@ -0,0 +1,48 @@ +{ + "extends": "@coze-arch/ts-config/tsconfig.web.json", + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "jsx": "react-jsx", + "lib": ["DOM", "ESNext"], + "module": "ESNext", + "target": "ES2020", + "moduleResolution": "bundler", + "tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo" + }, + "include": ["src"], + "exclude": ["node_modules", "dist"], + "references": [ + { + "path": "../../api-schema/tsconfig.build.json" + }, + { + "path": "../../../arch/bot-typings/tsconfig.build.json" + }, + { + "path": "../../biz-hooks/tsconfig.build.json" + }, + { + "path": "../../components/tsconfig.build.json" + }, + { + "path": "../../../../config/eslint-config/tsconfig.build.json" + }, + { + "path": "../../../../config/stylelint-config/tsconfig.build.json" + }, + { + "path": "../../../../config/ts-config/tsconfig.build.json" + }, + { + "path": "../../../../config/vitest-config/tsconfig.build.json" + }, + { + "path": "../../tag-components/tsconfig.build.json" + }, + { + "path": "../trace-detail/tsconfig.build.json" + } + ] +} diff --git a/frontend/packages/cozeloop/observation/trace-detail-open/tsconfig.json b/frontend/packages/cozeloop/observation/trace-detail-open/tsconfig.json new file mode 100644 index 000000000..b3951a309 --- /dev/null +++ b/frontend/packages/cozeloop/observation/trace-detail-open/tsconfig.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "exclude": ["**/*"], + "compilerOptions": { + "composite": true + }, + "references": [ + { + "path": "./tsconfig.build.json" + }, + { + "path": "./tsconfig.misc.json" + } + ] +} diff --git a/frontend/packages/cozeloop/observation/trace-detail-open/tsconfig.misc.json b/frontend/packages/cozeloop/observation/trace-detail-open/tsconfig.misc.json new file mode 100644 index 000000000..7bc9c63e7 --- /dev/null +++ b/frontend/packages/cozeloop/observation/trace-detail-open/tsconfig.misc.json @@ -0,0 +1,20 @@ +{ + "extends": "@coze-arch/ts-config/tsconfig.web.json", + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "rootDir": "./", + "outDir": "./dist", + "jsx": "react-jsx", + "lib": ["DOM", "ESNext"], + "module": "ESNext", + "target": "ES2020", + "moduleResolution": "bundler" + }, + "include": ["__tests__", "vitest.config.ts", "stories"], + "exclude": ["./dist"], + "references": [ + { + "path": "./tsconfig.build.json" + } + ] +} diff --git a/frontend/packages/cozeloop/observation/trace-detail-open/vitest.config.ts b/frontend/packages/cozeloop/observation/trace-detail-open/vitest.config.ts new file mode 100644 index 000000000..c8378dcdb --- /dev/null +++ b/frontend/packages/cozeloop/observation/trace-detail-open/vitest.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from '@coze-arch/vitest-config'; + +export default defineConfig({ + dirname: __dirname, + preset: 'web', +}); diff --git a/frontend/packages/cozeloop/observation/trace-detail/package.json b/frontend/packages/cozeloop/observation/trace-detail/package.json index 0356421ae..fb33a8d7a 100644 --- a/frontend/packages/cozeloop/observation/trace-detail/package.json +++ b/frontend/packages/cozeloop/observation/trace-detail/package.json @@ -1,13 +1,11 @@ { - "name": "@cozeloop/observation-component-adapter", + "name": "@cozeloop/observation-component", "version": "0.0.1", "description": "Analytics Components", "author": "liuwei.felix@bytedance.com", "main": "src/index.tsx", "scripts": { "build": "exit 0", - "build:storybook": "storybook build", - "dev": "storybook dev -p 6006", "lint": "eslint ./ --cache", "test": "NODE_OPTIONS='--max_old_space_size=2048' NODE_ENV=test vitest --run --passWithNoTests", "test:cov": "npm run test -- --coverage" @@ -44,14 +42,6 @@ "@emotion/styled": "11.11.0", "@mui/material": "^5", "@rsbuild/core": "~1.1.0", - "@storybook/addon-essentials": "^7.6.7", - "@storybook/addon-interactions": "^7.6.7", - "@storybook/addon-links": "^7.6.7", - "@storybook/addon-onboarding": "^1.0.10", - "@storybook/blocks": "^7.6.7", - "@storybook/react": "^7.6.7", - "@storybook/react-vite": "^7.6.7", - "@storybook/test": "^7.6.7", "@types/json-bigint": "~1.0.4", "@types/lodash-es": "^4.17.10", "@types/react": "18.2.37", @@ -59,7 +49,6 @@ "@vitest/coverage-v8": "~3.0.5", "happy-dom": "^15.10.2", "react-dom": "~18.2.0", - "storybook": "^7.6.7", "tsconfig-paths": "4.1.0", "vite": "^4.3.9", "vite-plugin-svgr": "~3.3.0", @@ -67,3 +56,4 @@ }, "// deps": "immer@^10.0.3 为脚本自动补齐,请勿改动" } + diff --git a/frontend/packages/cozeloop/observation/trace-detail/src/index.tsx b/frontend/packages/cozeloop/observation/trace-detail/src/index.tsx index 7e68e700f..cb5edfdcc 100644 --- a/frontend/packages/cozeloop/observation/trace-detail/src/index.tsx +++ b/frontend/packages/cozeloop/observation/trace-detail/src/index.tsx @@ -7,6 +7,14 @@ export { getStartTime, getRootSpan, NODE_CONFIG_MAP, + tabs, + TraceFeedBack, + ManualAnnotation, } from './trace-detail'; export { SpanType } from './trace-detail/typings/params'; -export type { TraceDetailOptions, TraceDetailProps } from './trace-detail'; +export type { + TraceDetailOptions, + TraceDetailProps, + TraceDetailPanelProps, + TraceDetailContext, +} from './trace-detail'; diff --git a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/biz/trace-detail-pane/index.tsx b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/biz/trace-detail-pane/index.tsx index a949da452..215d3b280 100644 --- a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/biz/trace-detail-pane/index.tsx +++ b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/biz/trace-detail-pane/index.tsx @@ -7,13 +7,14 @@ import classNames from 'classnames'; import { useMouseDownOffset } from '@cozeloop/base-hooks'; import { Layout, SideSheet } from '@coze-arch/coze-design'; +import { type TraceDetailContext } from '@/trace-detail'; import { PERCENT } from '@/consts'; import { type TraceDetailProps } from '../trace-detail/interface'; import { TraceDetail } from '../trace-detail'; import { DEFAULT_WIDTH, MAX_WIDTH, MIN_WIDTH } from './config'; -interface TraceDetailPanelProps +export interface TraceDetailPanelProps extends Omit { visible: boolean; onClose: () => void; @@ -24,7 +25,7 @@ export const TraceDetailPanel = ({ onClose, headerConfig, ...props -}: TraceDetailPanelProps) => { +}: TraceDetailPanelProps & TraceDetailContext) => { const [sidePaneWidth, setSidePaneWidth] = useState(DEFAULT_WIDTH); const prevWidthRef = useRef(sidePaneWidth); diff --git a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/biz/trace-detail/index.tsx b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/biz/trace-detail/index.tsx index c62bd318f..44cefabff 100644 --- a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/biz/trace-detail/index.tsx +++ b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/biz/trace-detail/index.tsx @@ -1,5 +1,6 @@ // Copyright (c) 2025 coze-dev Authors // SPDX-License-Identifier: Apache-2.0 +/* eslint-disable @coze-arch/max-line-per-function */ import { useEffect, useImperativeHandle, useState } from 'react'; import { useUpdateEffect } from 'ahooks'; @@ -10,6 +11,7 @@ import { changeSpanNodeCollapseStatus, getNodeConfig, } from '@/trace-detail/utils/span'; +import { traceDetailContext } from '@/trace-detail/hooks/use-trace-detail-context'; import { useTeaDuration } from '@/trace-detail/hooks/use-tea-duration'; import { useFetchSpans } from '@/trace-detail/hooks/use-fetch-spans'; import { INVALIDATE_CODE } from '@/trace-detail/consts/code'; @@ -37,6 +39,9 @@ export const TraceDetail = (props: TraceDetailProps) => { onReady, switchConfig, hideTraceDetailHeader = false, + defaultActiveTabKey, + spanDetailHeaderSlot, + extraSpanDetailTabs, } = props; const [selectedSpanId, setSelectedSpanId] = useState(''); const [rootNodes, setRootNodes] = useState(undefined); @@ -48,7 +53,7 @@ export const TraceDetail = (props: TraceDetailProps) => { spaceName, endTime, moduleName, - platformType, + platformType: platformType.toString(), startTime, options: { dataSource, @@ -61,7 +66,7 @@ export const TraceDetail = (props: TraceDetailProps) => { space_id: spaceID, space_name: spaceName, search_type: searchType, - platform_type: platformType, + platform_type: platformType.toString(), module_name: moduleName, }, ); @@ -157,16 +162,27 @@ export const TraceDetail = (props: TraceDetailProps) => { searchType, defaultSpanID, endTime, - platformType, + platformType: platformType.toString(), startTime, }, hideTraceDetailHeader, ...props, }; - return layout === 'vertical' ? ( - - ) : ( - + return ( + + {layout === 'vertical' ? ( + + ) : ( + + )} + ); }; diff --git a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/biz/trace-detail/interface.ts b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/biz/trace-detail/interface.ts index 780a9eefa..2ee067507 100644 --- a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/biz/trace-detail/interface.ts +++ b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/biz/trace-detail/interface.ts @@ -8,20 +8,21 @@ import { type Span, type TraceAdvanceInfo, } from '@/trace-detail/typings/params'; +import { type TraceDetailContext } from '@/trace-detail/hooks/use-trace-detail-context'; import { type SpanNode } from '@/trace-detail/components/graphs/trace-tree/type'; export interface TraceDetailOptions { refresh: () => void; } -export interface TraceDetailProps { +export interface TraceDetailProps extends TraceDetailContext { spaceID: string; spaceName: string; searchType: 'trace_id'; id: string; dataSource?: DataSource; moduleName: string; - platformType?: string; + platformType?: string | number; startTime?: number | string; endTime?: number | string; defaultSpanID?: string; @@ -46,7 +47,6 @@ export interface TraceDetailProps { style?: CSSProperties; onReady?: () => void; hideTraceDetailHeader?: boolean; - defaultActiveTabKey?: string; } export interface SwitchConfig { diff --git a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/feedback/hooks/use-list-annotations.ts b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/feedback/hooks/use-list-annotations.ts new file mode 100644 index 000000000..0ea9edb40 --- /dev/null +++ b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/feedback/hooks/use-list-annotations.ts @@ -0,0 +1,34 @@ +// Copyright (c) 2025 coze-dev Authors +// SPDX-License-Identifier: Apache-2.0 +import { useRequest } from 'ahooks'; +import { useSpace } from '@cozeloop/biz-hooks-adapter'; +import { type ListAnnotationsRequest } from '@cozeloop/api-schema/observation'; +import { observabilityTrace } from '@cozeloop/api-schema'; + +interface ListAnnotationsParams { + span_id: string; + trace_id: string; + start_time: string; + platform_type?: string | number; + desc_by_updated_at?: boolean; +} + +export const useListAnnotations = (params: ListAnnotationsParams) => { + const { spaceID } = useSpace(); + + const service = useRequest( + async (descByUpdatedAt: boolean) => { + const { annotations } = await observabilityTrace.ListAnnotations({ + workspace_id: spaceID, + ...params, + desc_by_updated_at: descByUpdatedAt, + } as unknown as ListAnnotationsRequest); + return annotations; + }, + { + manual: true, + }, + ); + + return service; +}; diff --git a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/feedback/index.ts b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/feedback/index.ts new file mode 100644 index 000000000..f0f5a6d75 --- /dev/null +++ b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/feedback/index.ts @@ -0,0 +1,5 @@ +// Copyright (c) 2025 coze-dev Authors +// SPDX-License-Identifier: Apache-2.0 +export { tabs } from './tab'; +export { ManualAnnotation } from './score'; +export { TraceFeedBack } from './trace-detail-table'; diff --git a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/feedback/score.tsx b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/feedback/score.tsx new file mode 100644 index 000000000..fcc03a2ec --- /dev/null +++ b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/feedback/score.tsx @@ -0,0 +1,37 @@ +// Copyright (c) 2025 coze-dev Authors +// SPDX-License-Identifier: Apache-2.0 +import { annotation as AnnotationType } from '@cozeloop/api-schema/observation'; +import { Divider, Typography } from '@coze-arch/coze-design'; + +interface ManualAnnotationProps { + annotation: AnnotationType.Annotation; +} + +export const ManualAnnotation = (props: ManualAnnotationProps) => { + const { annotation } = props; + const { type } = annotation; + + return ( +
+
+ + {type === AnnotationType.AnnotationType.ManualFeedback + ? (annotation.manual_feedback?.tag_key_name ?? '-') + : (annotation.key ?? '-')} + + + + {type === AnnotationType.AnnotationType.ManualFeedback + ? (annotation.manual_feedback?.tag_value ?? '-') + : (annotation.value ?? '-')}{' '} + +
+
+ ); +}; diff --git a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/feedback/tab.tsx b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/feedback/tab.tsx new file mode 100644 index 000000000..69ffe71ec --- /dev/null +++ b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/feedback/tab.tsx @@ -0,0 +1,22 @@ +// Copyright (c) 2025 coze-dev Authors +// SPDX-License-Identifier: Apache-2.0 +import { type span } from '@cozeloop/api-schema/observation'; + +import { type TraceDetailContext } from '@/trace-detail'; + +import { TraceFeedBack } from './trace-detail-table'; + +export const tabs: TraceDetailContext['extraSpanDetailTabs'] = [ + { + label: 'Feedback', + tabKey: 'feedback', + render: (span: span.OutputSpan, platformType: string | number) => ( + + ), + visible: true, + }, +]; diff --git a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/feedback/trace-detail-table.tsx b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/feedback/trace-detail-table.tsx new file mode 100644 index 000000000..1749c5e6a --- /dev/null +++ b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/feedback/trace-detail-table.tsx @@ -0,0 +1,296 @@ +// Copyright (c) 2025 coze-dev Authors +// SPDX-License-Identifier: Apache-2.0 +/* eslint-disable @coze-arch/max-line-per-function */ +import React, { useEffect, useState } from 'react'; + +import dayjs from 'dayjs'; +import classNames from 'classnames'; +import { useUpdate } from 'ahooks'; +import LoopTableSortIcon from '@cozeloop/components/src/table/sort-icon'; +import { LoopTable, UserProfile } from '@cozeloop/components'; +import { useBaseURL } from '@cozeloop/biz-hooks-adapter'; +import { + annotation as AnnotationType, + type span, +} from '@cozeloop/api-schema/observation'; +import { IconCozIllusNone } from '@coze-arch/coze-design/illustrations'; +import { + IconCozLongArrowTopRight, + IconCozRefresh, +} from '@coze-arch/coze-design/icons'; +import { + Button, + EmptyState, + Tooltip, + Typography, +} from '@coze-arch/coze-design'; + +import { ManualAnnotation } from './score'; +import { useListAnnotations } from './hooks/use-list-annotations'; +const { Text } = Typography; + +const SOURCE_TEXT = { + [AnnotationType.AnnotationType.AutoEvaluate]: '自动评测', + [AnnotationType.AnnotationType.ManualFeedback]: '人工标注', + [AnnotationType.AnnotationType.CozeFeedback]: 'Coze 对话', +}; + +export const Source = ({ + annotation, +}: { + annotation: AnnotationType.Annotation; +}) => { + const { baseURL } = useBaseURL(); + + return ( +
{ + if (!annotation.auto_evaluate?.task_id) { + return; + } + window.open( + `${baseURL}/observation/tasks/${annotation.auto_evaluate?.task_id}`, + ); + }} + > + + {SOURCE_TEXT[annotation.type ?? ''] ?? '-'} + + {annotation.type === AnnotationType.AnnotationType.AutoEvaluate && ( +
+ +
+ )} +
+ ); +}; + +interface TraceFeedBackProps { + span: span.OutputSpan; + platformType: string | number; + annotationRefreshKey: number; + customRenderCols?: { + [key: string]: (annotation: AnnotationType.Annotation) => React.ReactNode; + }; + description?: React.ReactNode; +} + +interface FeedbackResultProps { + onRefresh: () => void; + onRefreshLoading?: boolean; +} + +const FeedbackResult = (props: FeedbackResultProps) => { + const { onRefresh, onRefreshLoading } = props; + + return ( +
+ + 反馈结果 + + + + +
+ ); +}; +interface CreateTimeTitleProps { + onChange?: () => void; + descByUpdatedAt?: boolean; +} + +const UpdateTimeTitle = ({ + onChange, + descByUpdatedAt, +}: CreateTimeTitleProps) => ( +
onChange?.()} + > + 更新时间 + +
+); + +export const TraceFeedBack = ({ + span, + customRenderCols, + annotationRefreshKey, + platformType, + description, +}: TraceFeedBackProps) => { + const update = useUpdate(); + const [descByUpdatedAt, setDescByUpdatedAt] = useState(false); + const { runAsync, loading } = useListAnnotations({ + span_id: span.span_id, + trace_id: span.trace_id, + start_time: span.started_at, + platform_type: platformType, + }); + useEffect(() => { + runAsync(descByUpdatedAt).then(data => { + span.annotations = data ?? []; + update(); + }); + }, [annotationRefreshKey]); + const columns = [ + { + title: '来源', + dataIndex: 'source', + width: 120, + render: (_, annotation: AnnotationType.Annotation) => ( + + ), + }, + { + title: () => ( + { + runAsync(descByUpdatedAt).then(data => { + span.annotations = data ?? []; + update(); + }); + }} + onRefreshLoading={loading} + /> + ), + dataIndex: 'feedback', + width: 200, + render: (_, annotation: AnnotationType.Annotation) => ( +
+ {customRenderCols?.feedback?.(annotation) ?? ( + + )} +
+ ), + }, + { + title: '更新人', + dataIndex: 'updater', + width: 170, + render: (_, annotation: AnnotationType.Annotation) => { + const name = annotation.base_info?.updated_by?.name; + const avatarUrl = annotation.base_info?.updated_by?.avatar_url ?? '-'; + if (!name) { + return '-'; + } + + return { + const createdAt = annotation.base_info?.created_at ?? '-'; + return ( + + {createdAt + ? dayjs(Number(createdAt)).format('MM-DD HH:mm:ss') + : '-'} + + ); + }, + }, + { + title: () => ( + { + runAsync(!descByUpdatedAt).then(data => { + span.annotations = data ?? []; + update(); + }); + setDescByUpdatedAt(pre => !pre); + }} + /> + ), + dataIndex: 'updateTime', + width: 170, + render: (_, annotation: AnnotationType.Annotation) => { + const updatedAt = annotation.base_info?.updated_at ?? '-'; + return ( + + {updatedAt + ? dayjs(Number(updatedAt)).format('MM-DD HH:mm:ss') + : '-'} + + ); + }, + }, + + { + title: () =>
原因
, + dataIndex: 'reasoning', + width: 372, + render: (_, annotation: AnnotationType.Annotation) => ( +
+ + {annotation.type === AnnotationType.AnnotationType.CozeFeedback + ? annotation.reasoning + : (annotation.auto_evaluate?.evaluator_result?.correction + ?.explain ?? + annotation.auto_evaluate?.evaluator_result?.reasoning ?? + '-')} + {} + +
+ ), + }, + ]; + + return ( + } + title="暂无 Feedback" + description={ + <> + {description ?? ( +
+ 点击右上方标注数据按钮进行创建 +
+ )} + + } + /> + } + /> + ); +}; diff --git a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/graphs/trace-tree/node/index.tsx b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/graphs/trace-tree/node/index.tsx index e14b0fcf2..c6a461c2e 100644 --- a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/graphs/trace-tree/node/index.tsx +++ b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/graphs/trace-tree/node/index.tsx @@ -2,10 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 /* eslint-disable complexity */ /* eslint-disable @coze-arch/max-line-per-function */ +import { isEmpty } from 'lodash-es'; import classNames from 'classnames'; import { I18n } from '@cozeloop/i18n-adapter'; import { SpanStatus, SpanType } from '@cozeloop/api-schema/observation'; -import { IconCozArrowDown, IconCozClock } from '@coze-arch/coze-design/icons'; +import { + IconCozArrowDown, + IconCozClock, + IconCozSuccessRate, +} from '@coze-arch/coze-design/icons'; import { Tag, Tooltip } from '@coze-arch/coze-design'; import { formatTime } from '@/utils/time'; @@ -46,6 +51,7 @@ export const CustomTreeNode = ({ children, span_type, } = spanNode; + const hasChildren = children?.length && children?.length > 0; const isBroken = [BROKEN_ROOT_SPAN_ID, NORMAL_BROKEN_SPAN_ID].includes( span_id, @@ -190,6 +196,17 @@ export const CustomTreeNode = ({ ) : null} + {!isEmpty(spanNode.annotations) ? ( + + } + > + feedback + + ) : null} diff --git a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/span-detail/index.tsx b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/span-detail/index.tsx index 1b92e08de..91bd305b9 100644 --- a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/span-detail/index.tsx +++ b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/span-detail/index.tsx @@ -2,19 +2,21 @@ // SPDX-License-Identifier: Apache-2.0 import { useMemo } from 'react'; -import { isEmpty } from 'lodash-es'; +import { isEmpty, isFunction } from 'lodash-es'; import cs from 'classnames'; -import { IconCozIllusEmpty } from '@coze-arch/coze-design/illustrations'; -import { Col, Empty, Row, Tabs } from '@coze-arch/coze-design'; -import { I18n } from '@cozeloop/i18n-adapter'; import { TraceStructData, SpanContentContainer, RawContent, getSpanContentField, } from '@cozeloop/trace-struct-data'; +import { I18n } from '@cozeloop/i18n-adapter'; +import { PlatformType } from '@cozeloop/api-schema/observation'; +import { IconCozIllusEmpty } from '@coze-arch/coze-design/illustrations'; +import { Col, Empty, Row, Tabs } from '@coze-arch/coze-design'; import type { Span } from '@/trace-detail/typings/params'; +import { useTraceDetailContext } from '@/trace-detail/hooks/use-trace-detail-context'; import { SpanFieldList } from '../span-detail-list'; import { SpanDetailHeader } from './span-header'; @@ -32,6 +34,11 @@ interface SpanDetailProps { moduleName?: string; } +enum TabKey { + Run = 'run', + Metadata = 'metadata', +} + export const SpanDetail = ({ span, baseInfoPosition = 'right', @@ -45,11 +52,28 @@ export const SpanDetail = ({ const { runtime, ...otherTags } = custom_tags || {}; const overviewFields = useMemo(() => geSpanOverviewField(span), [span]); const spanContentList = useMemo(() => getSpanContentField(span), [span]); + const { extraSpanDetailTabs, defaultActiveTabKey, platformType } = + useTraceDetailContext(); + + const actualDefaultActiveTabKey = useMemo(() => { + const targetTabs = extraSpanDetailTabs?.find( + tab => tab.tabKey === defaultActiveTabKey, + ); + if ( + !targetTabs || + (!isFunction(targetTabs.visible) && !targetTabs.visible) || + (isFunction(targetTabs.visible) && !targetTabs.visible(span)) + ) { + return TabKey.Run; + } + return defaultActiveTabKey; + }, [defaultActiveTabKey, extraSpanDetailTabs]); + return (
- - + + {spanContentList?.length > 0 ? ( @@ -91,7 +115,7 @@ export const SpanDetail = ({ {showTags ? ( - + {!isEmpty(otherTags) && ( <> ) : null} + + {extraSpanDetailTabs + ?.filter(tab => + isFunction(tab.visible) ? tab.visible(span) : (tab.visible ?? true), + ) + ?.map(extraTab => ( + +
+ {extraTab.render(span, platformType ?? PlatformType.Cozeloop)} +
+
+ ))}
); diff --git a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/span-detail/span-header.tsx b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/span-detail/span-header.tsx index 1c3ea3f93..9e1add35a 100644 --- a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/span-detail/span-header.tsx +++ b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/span-detail/span-header.tsx @@ -1,8 +1,10 @@ // Copyright (c) 2025 coze-dev Authors // SPDX-License-Identifier: Apache-2.0 +import { PlatformType } from '@cozeloop/api-schema/observation'; import { Typography } from '@coze-arch/coze-design'; import { type Span, SpanType } from '@/trace-detail/typings/params'; +import { useTraceDetailContext } from '@/trace-detail/hooks/use-trace-detail-context'; import { getNodeConfig } from '../../utils/span'; import { CustomIconWrapper } from '../../consts/span'; @@ -17,6 +19,7 @@ export const SpanDetailHeader: React.FC<{ spanTypeEnum: type ?? SpanType.Unknown, spanType: span_type, }); + const { spanDetailHeaderSlot, platformType } = useTraceDetailContext(); return (
@@ -37,6 +40,9 @@ export const SpanDetailHeader: React.FC<{ {span.span_name}
+
+ {spanDetailHeaderSlot?.(span, platformType ?? PlatformType.Cozeloop)} +
); }; diff --git a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/consts/span.tsx b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/consts/span.tsx index abbd84f00..f6a337db1 100644 --- a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/consts/span.tsx +++ b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/consts/span.tsx @@ -106,11 +106,13 @@ export const NODE_CONFIG_MAP: Record = { color: '#b9ecac', typeName: 'parser', character: 'Pa', - icon: ({ className }) => ( - + icon: ({ className, size }) => ( + + + ), }, [SpanType.Embedding]: { @@ -122,22 +124,26 @@ export const NODE_CONFIG_MAP: Record = { color: '#cfecac', typeName: 'memory', character: 'Me', - icon: ({ className }) => ( - + icon: ({ className, size }) => ( + + + ), }, [SpanType.Plugin]: { color: '#abcbf4', typeName: 'plugin', character: 'Pl', - icon: ({ className }) => ( - + icon: ({ className, size }) => ( + + + ), }, @@ -191,11 +197,13 @@ export const NODE_CONFIG_MAP: Record = { color: '#ffd2d7', typeName: 'vector_store', character: 'VS', - icon: ({ className }) => ( - + icon: ({ className, size }) => ( + + + ), }, @@ -209,11 +217,13 @@ export const NODE_CONFIG_MAP: Record = { color: '#d1aef4', typeName: 'agent', character: 'Ag', - icon: ({ className }) => ( - + icon: ({ className, size }) => ( + + + ), }, [SpanType.CozeBot]: { diff --git a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/hooks/use-fetch-spans.tsx b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/hooks/use-fetch-spans.tsx index 2489a1dfc..5aab57243 100644 --- a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/hooks/use-fetch-spans.tsx +++ b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/hooks/use-fetch-spans.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { useState } from 'react'; -import { uniqWith } from 'lodash-es'; +import { cloneDeep, uniqWith } from 'lodash-es'; import { useRequest } from 'ahooks'; import { EVENT_NAMES, sendEvent } from '@cozeloop/tea-adapter'; import { type PlatformType } from '@cozeloop/api-schema/observation'; @@ -62,9 +62,10 @@ export const useFetchSpans = ({ platform_type: platformType as PlatformType, }, { - __disableErrorToast: true, + disableErrorToast: true, }, ); + data = { spans: res.spans, advanceInfo: res.traces_advance_info, @@ -91,7 +92,7 @@ export const useFetchSpans = ({ resSpans = resSpans.map(span => { if (span.span_id === newRootSpan.span_id) { - return newRootSpan; + return cloneDeep(newRootSpan); } return span; }); diff --git a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/hooks/use-trace-detail-context.ts b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/hooks/use-trace-detail-context.ts new file mode 100644 index 000000000..5ba52c2db --- /dev/null +++ b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/hooks/use-trace-detail-context.ts @@ -0,0 +1,23 @@ +// Copyright (c) 2025 coze-dev Authors +// SPDX-License-Identifier: Apache-2.0 +import { createContext, useContext } from 'react'; + +import { type span } from '@cozeloop/api-schema/observation'; + +interface ExtraTab { + label: string; + tabKey: string; + render: (span: span.OutputSpan, platformType: string | number) => JSX.Element; + visible?: ((span: span.OutputSpan) => boolean) | boolean; +} +export interface TraceDetailContext { + extraSpanDetailTabs?: ExtraTab[]; + defaultActiveTabKey?: string; + spanDetailHeaderSlot?: ( + span: span.OutputSpan, + platform: string | number, + ) => JSX.Element; + platformType?: string | number; +} +export const traceDetailContext = createContext({}); +export const useTraceDetailContext = () => useContext(traceDetailContext); diff --git a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/index.tsx b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/index.tsx index b965313c5..99d95d093 100644 --- a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/index.tsx +++ b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/index.tsx @@ -1,6 +1,9 @@ // Copyright (c) 2025 coze-dev Authors // SPDX-License-Identifier: Apache-2.0 -export { TraceDetailPanel } from './biz/trace-detail-pane'; +export { + TraceDetailPanel, + type TraceDetailPanelProps, +} from './biz/trace-detail-pane'; export { TraceDetail } from './biz/trace-detail'; export { type TraceDetailOptions, @@ -9,3 +12,6 @@ export { export { getEndTime, getStartTime } from './utils/time'; export { getRootSpan } from './utils/span'; export { NODE_CONFIG_MAP } from './consts/span'; + +export { type TraceDetailContext } from './hooks/use-trace-detail-context'; +export { tabs, TraceFeedBack, ManualAnnotation } from './components/feedback'; diff --git a/frontend/packages/cozeloop/observation/trace-detail/src/utils/dayjs.ts b/frontend/packages/cozeloop/observation/trace-detail/src/utils/dayjs.ts index 5773eddb3..e4e25404a 100644 --- a/frontend/packages/cozeloop/observation/trace-detail/src/utils/dayjs.ts +++ b/frontend/packages/cozeloop/observation/trace-detail/src/utils/dayjs.ts @@ -29,4 +29,4 @@ const dayJsTimeZone = (param?: ConfigType): Dayjs => { export default dayJsTimeZone; -export { Dayjs, QUnitType } from 'dayjs'; +export { Dayjs, type QUnitType } from 'dayjs'; diff --git a/frontend/packages/cozeloop/observation/trace-detail/tsconfig.misc.json b/frontend/packages/cozeloop/observation/trace-detail/tsconfig.misc.json index 40408ae05..53e222ea1 100644 --- a/frontend/packages/cozeloop/observation/trace-detail/tsconfig.misc.json +++ b/frontend/packages/cozeloop/observation/trace-detail/tsconfig.misc.json @@ -1,7 +1,13 @@ { "extends": "@coze-arch/ts-config/tsconfig.web.json", "$schema": "https://json.schemastore.org/tsconfig", - "include": ["__tests__", "vitest.config.mts", "tailwind.config.ts"], + "include": [ + "__tests__", + "vitest.config.mts", + "tailwind.config.ts", + "stories", + ".storybook/*" + ], "exclude": ["./dist"], "references": [ { diff --git a/frontend/packages/cozeloop/observation/trace-list/package.json b/frontend/packages/cozeloop/observation/trace-list/package.json index c0d4c99a4..3b85c0f22 100644 --- a/frontend/packages/cozeloop/observation/trace-list/package.json +++ b/frontend/packages/cozeloop/observation/trace-list/package.json @@ -38,7 +38,6 @@ }, "scripts": { "build": "exit 0", - "dev": "storybook dev -p 6006", "lint": "eslint ./ --cache", "test": "vitest --run --passWithNoTests", "test:cov": "npm run test -- --coverage" @@ -53,6 +52,7 @@ "@cozeloop/guard": "workspace:*", "@cozeloop/i18n-adapter": "workspace:*", "@cozeloop/observation-component-adapter": "workspace:*", + "@cozeloop/tag-components": "workspace:*", "@cozeloop/tea-adapter": "workspace:*", "@cozeloop/toolkit": "workspace:*", "ahooks": "^3.7.8", @@ -72,14 +72,6 @@ "@coze-arch/stylelint-config": "workspace:*", "@coze-arch/ts-config": "workspace:*", "@coze-arch/vitest-config": "workspace:*", - "@storybook/addon-essentials": "^7.6.7", - "@storybook/addon-interactions": "^7.6.7", - "@storybook/addon-links": "^7.6.7", - "@storybook/addon-onboarding": "^1.0.10", - "@storybook/blocks": "^7.6.7", - "@storybook/react": "^7.6.7", - "@storybook/react-vite": "^7.6.7", - "@storybook/test": "^7.6.7", "@testing-library/jest-dom": "^6.1.5", "@testing-library/react": "^14.1.2", "@testing-library/react-hooks": "^8.0.1", @@ -89,7 +81,6 @@ "@vitest/coverage-v8": "~3.0.5", "react": "~18.2.0", "react-dom": "~18.2.0", - "storybook": "^7.6.7", "stylelint": "^15.11.0", "vite": "^4.3.9", "vite-plugin-svgr": "~3.3.0", diff --git a/frontend/packages/cozeloop/observation/trace-list/src/components/filter-bar/categorical-select/index.tsx b/frontend/packages/cozeloop/observation/trace-list/src/components/filter-bar/categorical-select/index.tsx new file mode 100644 index 000000000..b1b63d745 --- /dev/null +++ b/frontend/packages/cozeloop/observation/trace-list/src/components/filter-bar/categorical-select/index.tsx @@ -0,0 +1,58 @@ +// Copyright (c) 2025 coze-dev Authors +// SPDX-License-Identifier: Apache-2.0 +import { useEffect } from 'react'; + +import classNames from 'classnames'; +import { useBatchGetTags } from '@cozeloop/tag-components'; +import { Select, Skeleton, type SelectProps } from '@coze-arch/coze-design'; + +import { type Left } from '@/components/logic-expr/logic-expr'; + +import styles from '../prompt-select/index.module.less'; + +export interface CategoricalSelectProps extends SelectProps { + filterPlayground?: boolean; + className?: string; + left: Left; +} + +export const CategoricalSelect = (props: CategoricalSelectProps) => { + const { left, className } = props; + const service = useBatchGetTags(); + + useEffect(() => { + if (!left?.extraInfo?.tag_key_id) { + return; + } + + service.runAsync([left?.extraInfo?.tag_key_id ?? '']); + }, [left?.extraInfo?.tag_key_id]); + + if (props.allowCreate && service.loading) { + return ( + } + loading + className="w-full h-[32px]" + /> + ); + } + + return ( + { setLocalViewMethod(value as string); }} + disabled={readonly || disabled} />
{ + const typedValue = v as string; + onLeftExprTypeChange?.(typedValue, left); + onExprChange?.({ + left: { + type: typedValue, + value: + typedValue === MANUAL_FEEDBACK + ? (left?.value ?? '') + : typedValue, + }, + operator: undefined, + right: undefined, + }); + }} + optionList={tagLeftOption} + /> + { + const { value, label, ...rest } = v as any; + const { content_type, tag_key_id } = rest; + + const typedValue = value as string; + onLeftExprValueChange?.(typedValue, left); + onExprChange?.({ + left: { + type: left?.type, + value: `${MANUAL_FEEDBACK_PREFIX}${typedValue}`, + extraInfo: { + content_type, + tag_key_id, + }, + }, + operator: undefined, + right: undefined, + }); + }} + onChangeWithObject + showDisableTag + /> +
+ ); +}; diff --git a/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/const.ts b/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/const.ts new file mode 100644 index 000000000..7b1d4322e --- /dev/null +++ b/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/const.ts @@ -0,0 +1,23 @@ +// Copyright (c) 2025 coze-dev Authors +// SPDX-License-Identifier: Apache-2.0 +import { QueryType } from '@cozeloop/api-schema/observation'; +import { tag } from '@cozeloop/api-schema/data'; + +export const AUTO_EVAL_FEEDBACK_PREFIX = 'evaluator_version_'; +export const AUTO_EVAL_FEEDBACK = 'feedback_auto_evaluator'; +export const MANUAL_FEEDBACK_PREFIX = 'manual_feedback_'; +export const MANUAL_FEEDBACK = 'feedback_manual'; +const { TagContentType } = tag; + +export const MANUAL_FEEDBACK_OPERATORS = { + [TagContentType.Boolean]: [QueryType.In, QueryType.not_In], + [TagContentType.Categorical]: [QueryType.In, QueryType.not_In], + [TagContentType.FreeText]: [QueryType.Exist, QueryType.Match], + [TagContentType.ContinuousNumber]: [ + QueryType.Lte, + QueryType.Gte, + QueryType.Lt, + QueryType.Gt, + QueryType.Exist, + ], +}; diff --git a/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/consts.ts b/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/consts.ts index 2f1a685b9..d900d524e 100644 --- a/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/consts.ts +++ b/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/consts.ts @@ -79,6 +79,10 @@ export enum FilterFields { TOKENS = 'token', BOT_ID = 'bot_id', APP_ID = 'app_id', + FEEDBACK = 'feedback_auto_evaluator', + FEEDBACK_MANUAL = 'feedback_manual', + FEEDBACK_COZE = 'feedback_coze', + WORKFLOW_ID = 'workflow_id', } export const SELECT_RENDER_CMP_OP_LIST = ['in_list']; @@ -101,6 +105,7 @@ export const NUMBER_RENDER_CMP_OP_LIST = [ FilterFields.OUTPUT_TOKENS, FilterFields.TOTAL_TOKENS, FilterFields.TOKENS, + FilterFields.FEEDBACK, ]; export const THREADS_STATUS_RECORDS: Partial< @@ -119,6 +124,15 @@ export const THREADS_STATUS_RECORDS: Partial< }, }; +export const THREADS_FEEDBACK_COZE_RECORDS = { + like: { + label: I18n.t('task_filter_thumbs_up'), + }, + dislike: { + label: I18n.t('task_filter_thumbs_down'), + }, +}; + export enum BotEnv { DEV = 0, ONLINE = 1, diff --git a/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/error-msg-render.tsx b/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/error-msg-render.tsx index 027652a88..795301fa4 100644 --- a/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/error-msg-render.tsx +++ b/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/error-msg-render.tsx @@ -1,17 +1,18 @@ // Copyright (c) 2025 coze-dev Authors // SPDX-License-Identifier: Apache-2.0 +import { I18n } from '@cozeloop/i18n-adapter'; import { type OptionProps } from '@coze-arch/coze-design'; import { checkValueIsEmpty } from './right-render'; -import { I18n } from '@cozeloop/i18n-adapter'; +import { type Left } from './logic-expr'; interface ErrorMsgRenderProps { expr: { - left?: string; + left?: Left; right?: string | number | string[] | number[]; }; tagLeftOption: OptionProps[]; - checkIsInvalidateExpr: (expr: string) => boolean; + checkIsInvalidateExpr: (expr: Left | undefined) => boolean; valueChangeMap: Record; } @@ -21,20 +22,20 @@ export const ErrorMsgRender = ({ checkIsInvalidateExpr, valueChangeMap, }: ErrorMsgRenderProps) => { - const { left = '', right } = expr; + const { left, right } = expr; - const isInvalidateExpr = checkIsInvalidateExpr(left ?? ''); + const isInvalidateExpr = checkIsInvalidateExpr(left as Left | undefined); const leftname = tagLeftOption.find(item => item.value === left)?.label; if (isInvalidateExpr) { return (
- {leftname ?? left} {I18n.t('filter_item_conflict')} + {leftname ?? left?.type ?? ''} {I18n.t('filter_item_conflict')}
); } - if (checkValueIsEmpty(right) && left && valueChangeMap[left]) { + if (checkValueIsEmpty(right) && left && valueChangeMap[left.value ?? '']) { return (
{I18n.t('not_allowed_to_be_empty')} diff --git a/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/left-render.tsx b/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/left-render.tsx index 140d1f584..365aa5744 100644 --- a/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/left-render.tsx +++ b/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/left-render.tsx @@ -1,35 +1,45 @@ // Copyright (c) 2025 coze-dev Authors // SPDX-License-Identifier: Apache-2.0 import classNames from 'classnames'; -import { type OptionProps, Select } from '@coze-arch/coze-design'; +import { Select, type OptionProps } from '@coze-arch/coze-design'; + +import { type Left, type CustomRightRenderMap } from './logic-expr'; import styles from './index.module.less'; interface LeftRendererProps { expr: { - left?: string; + left?: Left; }; onExprChange?: (value: { - left?: string; + left?: Left; operator?: number; right?: string | number | string[] | number[]; }) => void; tagLeftOption: OptionProps[]; disabled?: boolean; defaultImmutableKeys?: string[]; - checkIsInvalidateExpr: (expr: string) => boolean; + checkIsInvalidateExpr: (expr: Left | undefined) => boolean; + customLeftRenderMap: CustomRightRenderMap; } -export const LeftRenderer = ({ - expr, - onExprChange, - tagLeftOption, - disabled, - defaultImmutableKeys, - checkIsInvalidateExpr, -}: LeftRendererProps) => { +export const LeftRenderer = (props: LeftRendererProps) => { + const { + expr, + onExprChange, + tagLeftOption, + disabled, + defaultImmutableKeys, + checkIsInvalidateExpr, + customLeftRenderMap, + } = props; const { left } = expr; - const isInvalidateExpr = checkIsInvalidateExpr(left ?? ''); + const isInvalidateExpr = checkIsInvalidateExpr(left); + const CustomLeftRender = customLeftRenderMap[left?.type ?? '']; + + if (CustomLeftRender) { + return ; + } return (
{ const typedValue = v as string; onExprChange?.({ - left: typedValue, + left: { type: typedValue, value: undefined }, operator: undefined, right: undefined, }); diff --git a/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/logic-expr.tsx b/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/logic-expr.tsx index 8f5dec53e..0033bc689 100644 --- a/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/logic-expr.tsx +++ b/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/logic-expr.tsx @@ -20,7 +20,7 @@ import { ErrorMsgRender } from './error-msg-render'; import styles from './index.module.less'; type LogicExprTypes = [ - string | undefined, + Left | undefined, number | undefined | string, string | number | string[] | number[] | undefined, ]; @@ -32,7 +32,7 @@ export type CustomRightRenderMap = Record< >; export interface LogicValue { - filter_fields?: LogicItem[]; + filter_fields: LogicItem[]; query_and_or?: string; sub_filter?: Array; } @@ -41,6 +41,8 @@ export interface LogicItem { field_name: string; query_type: string; values: string[]; + logic_field_name_type?: string; + extraInfo?: Record; } export interface AnalyticsLogicExprProps { @@ -54,6 +56,8 @@ export interface AnalyticsLogicExprProps { invalidateExpr?: Set; // 新增的自定义渲染器 customRightRenderMap?: CustomRightRenderMap; + customLeftRenderMap?: CustomRightRenderMap; + ignoreKeys?: string[]; } // Helper function to sort strings by first character (letters first) @@ -68,6 +72,12 @@ const sortByFirstChar = (a: string, b: string): number => { return aIsLetter ? -1 : bIsLetter ? 1 : 0; }; +export interface Left { + type: string | undefined; + value: string | undefined; + extraInfo?: Record; +} + export const AnalyticsLogicExpr = (props: AnalyticsLogicExprProps) => { const { value, @@ -79,6 +89,8 @@ export const AnalyticsLogicExpr = (props: AnalyticsLogicExprProps) => { allowLogicOperators = ['and'], invalidateExpr = new Set(), customRightRenderMap = {}, + customLeftRenderMap = {}, + ignoreKeys = [], } = props; const exprValue = useMemo( @@ -87,11 +99,13 @@ export const AnalyticsLogicExpr = (props: AnalyticsLogicExprProps) => { value, tagFilterRecord, defaultImmutableKeys, + ignoreKeys, ), - [value, defaultImmutableKeys, tagFilterRecord], + [value, defaultImmutableKeys, tagFilterRecord, ignoreKeys], ); - const checkIsInvalidateExpr = (expr: string) => invalidateExpr.has(expr); + const checkIsInvalidateExpr = (expr: Left | undefined) => + expr ? invalidateExpr.has(expr.value ?? '') : false; const [valueChangeMap, setValueChangeMap] = useState>( {}, ); @@ -99,9 +113,11 @@ export const AnalyticsLogicExpr = (props: AnalyticsLogicExprProps) => { const { tagLeftOption } = useMemo<{ tagLeftOption: OptionProps[]; }>(() => { - const selectedItemKeyList = exprValue?.exprs?.map((item: any) => item.left); + const selectedItemKeyList = + exprValue?.exprs?.map((item: any) => item.left?.type) || []; return { tagLeftOption: Object.keys(tagFilterRecord) + .filter(key => !ignoreKeys.includes(key)) .sort((a, b) => sortByFirstChar(a, b)) .map(key => ({ label: getKeyCopywriting(key), @@ -110,7 +126,7 @@ export const AnalyticsLogicExpr = (props: AnalyticsLogicExprProps) => { disableDuplicateSelect && selectedItemKeyList?.includes(key), })), }; - }, [exprValue, disableDuplicateSelect]); + }, [exprValue, disableDuplicateSelect, ignoreKeys, tagFilterRecord]); const handleValueChangeStatus = (fieldName: string, changed: boolean) => { setValueChangeMap(prev => ({ @@ -127,7 +143,7 @@ export const AnalyticsLogicExpr = (props: AnalyticsLogicExprProps) => { onDeleteExpr={key => { setValueChangeMap(prev => ({ ...prev, - [key as string]: false, + [key?.value as string]: false, })); }} exprGroupRenderContentItemsClassName={ @@ -141,6 +157,7 @@ export const AnalyticsLogicExpr = (props: AnalyticsLogicExprProps) => { disabled={disabled} defaultImmutableKeys={defaultImmutableKeys} checkIsInvalidateExpr={checkIsInvalidateExpr} + customLeftRenderMap={customLeftRenderMap} /> )} operatorRender={operatorRenderProps => ( @@ -155,7 +172,7 @@ export const AnalyticsLogicExpr = (props: AnalyticsLogicExprProps) => { )} rightRender={rightRenderProps => { const { - expr: { left = '', operator, right }, + expr: { left, operator, right }, onChange: onRightValueChange, } = rightRenderProps; @@ -169,7 +186,7 @@ export const AnalyticsLogicExpr = (props: AnalyticsLogicExprProps) => { disabled={disabled} defaultImmutableKeys={defaultImmutableKeys} isInvalidateExpr={isInvalidateExpr} - valueChanged={valueChangeMap[left]} + valueChanged={valueChangeMap[left?.value ?? '']} tagFilterRecord={tagFilterRecord} onRightValueChange={onRightValueChange} onValueChangeStatus={handleValueChangeStatus} diff --git a/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/operator-render.tsx b/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/operator-render.tsx index 208e77e63..ca8cd96c2 100644 --- a/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/operator-render.tsx +++ b/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/operator-render.tsx @@ -8,17 +8,19 @@ import { I18n } from '@cozeloop/i18n-adapter'; import { type FieldMeta } from '@cozeloop/api-schema/observation'; import { type OptionProps, Select } from '@coze-arch/coze-design'; +import { type Left } from './logic-expr'; import { LOGIC_OPERATOR_RECORDS, SELECT_MULTIPLE_RENDER_CMP_OP_LIST, SELECT_RENDER_CMP_OP_LIST, } from './consts'; +import { MANUAL_FEEDBACK, MANUAL_FEEDBACK_OPERATORS } from './const'; import styles from './index.module.less'; interface OperatorRendererProps { expr: { - left?: string; + left?: Left; operator?: number | string; right?: string | number | string[] | number[]; }; @@ -29,7 +31,7 @@ interface OperatorRendererProps { tagFilterRecord: Record; disabled?: boolean; defaultImmutableKeys?: string[]; - checkIsInvalidateExpr: (expr: string) => boolean; + checkIsInvalidateExpr: (expr: Left | undefined) => boolean; } export const OperatorRenderer = ({ @@ -40,11 +42,14 @@ export const OperatorRenderer = ({ defaultImmutableKeys, checkIsInvalidateExpr, }: OperatorRendererProps) => { - const { left = '', operator, right } = expr; + const { left, operator, right } = expr; const tagOperatorOption: OptionProps[] = useMemo( () => - tagFilterRecord[left]?.filter_types?.map(item => ({ + ( + MANUAL_FEEDBACK_OPERATORS[left?.extraInfo?.content_type ?? ''] ?? + tagFilterRecord[left?.type ?? '']?.filter_types + )?.map((item: string) => ({ label: I18n.unsafeT(LOGIC_OPERATOR_RECORDS[item]?.label ?? ''), value: item, })) ?? [], @@ -53,10 +58,10 @@ export const OperatorRenderer = ({ const valueOperator = useMemo( () => - !isEmpty(tagOperatorOption) && !operator + !isEmpty(tagOperatorOption) && !operator && left?.type !== MANUAL_FEEDBACK ? tagOperatorOption[0].value : operator, - [tagOperatorOption, operator], + [tagOperatorOption, operator, left?.type], ) as string | undefined; const handleChange = useCallback( @@ -80,11 +85,12 @@ export const OperatorRenderer = ({ // ---------------- 这里实现了默认填充下拉框第一个 start ---------------- useEffect(() => { - if (!left) { + if (!left?.type) { return; } + handleChange(valueOperator); - }, [left, valueOperator]); + }, [left?.type, valueOperator]); // ---------------- 这里实现了默认填充下拉框第一个 end ---------------- const isInvalidateExpr = checkIsInvalidateExpr(left); @@ -94,10 +100,12 @@ export const OperatorRenderer = ({ [styles['expr-op-item-content-invalidate']]: isInvalidateExpr, })} > - {left ? ( + {left?.type ? ( { onRightValueChange?.(v as string[] | number[] | string | number); - onValueChangeStatus?.(left, true); + onValueChangeStatus?.(fieldKey, true); }} optionList={options?.map(item => ({ - label: getOptionCopywriting(left, item), + label: getOptionCopywriting(fieldKey, item), value: item, }))} {...(isMultiple ? multipleSelectProps : {})} /> ) : isNumberInput ? ( `${v}`.replace(/\D/g, '')} + formatter={numberInputFormatter} disabled={disabled} hideButtons - value={right as ReactText} + value={right?.[0] as string} max={Number.MAX_SAFE_INTEGER} min={Number.MIN_SAFE_INTEGER} onChange={v => { - onRightValueChange?.(`${v}`.replace(/\D/g, '')); - onValueChangeStatus?.(left, true); + onRightValueChange?.(numberInputFormatter(`${v}`) as string); + onValueChangeStatus?.(fieldKey, true); }} - suffix={getLabelUnit(left)} + suffix={getLabelUnit(fieldKey)} /> ) : ( = props => { value={right as ReactText} onChange={v => { onRightValueChange?.(v); - onValueChangeStatus?.(left, true); + onValueChangeStatus?.(fieldKey, true); }} /> )} diff --git a/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/utils.ts b/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/utils.ts index 1ed1c40a0..fe990fe6a 100644 --- a/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/utils.ts +++ b/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/utils.ts @@ -1,5 +1,6 @@ // Copyright (c) 2025 coze-dev Authors // SPDX-License-Identifier: Apache-2.0 +/* eslint-disable max-params */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { isArray, isEmpty, isNil } from 'lodash-es'; import { type ExprGroup } from '@cozeloop/components'; @@ -21,7 +22,14 @@ import { THREADS_STATUS_RECORDS, TimeUnit, FilterFields, + THREADS_FEEDBACK_COZE_RECORDS, } from './consts'; +import { + AUTO_EVAL_FEEDBACK_PREFIX, + AUTO_EVAL_FEEDBACK, + MANUAL_FEEDBACK, + MANUAL_FEEDBACK_PREFIX, +} from './const'; const assignValueWithKind = (params: { value: R; valueKind: string }) => { const { value, valueKind } = params; @@ -69,11 +77,13 @@ export const getOptionsWithKind = (params: { }; export const formatExprValue = ( - originValue?: Record, + originValue?: LogicValue, tagFilterRecord?: Record, defaultImmutableKeys?: string[], + ignoreKeys: string[] = [], ): ExprGroup | undefined => { const { query_and_or, filter_fields, sub_filter } = originValue || {}; + if (!originValue || !filter_fields) { return undefined; } @@ -81,15 +91,31 @@ export const formatExprValue = ( const exprOpNode: ExprGroup = { logicOperator: query_and_or === 'or' ? 'or' : 'and', disableDeletion: Boolean(defaultImmutableKeys?.length), - exprs: filter_fields.map(fieldFilter => { - const { field_name, query_type, values } = fieldFilter || {}; - return { - left: field_name as L, - operator: query_type as O, - disableDeletion: defaultImmutableKeys?.includes(field_name ?? ''), - right: values as R, - }; - }), + exprs: filter_fields + .filter( + fieldFilter => + !ignoreKeys.includes( + fieldFilter.logic_field_name_type ?? fieldFilter.field_name, + ), + ) + .map(fieldFilter => { + const { field_name, query_type, values } = fieldFilter || {}; + const leftValue = { + value: field_name, + type: field_name?.startsWith(AUTO_EVAL_FEEDBACK_PREFIX) + ? AUTO_EVAL_FEEDBACK + : field_name?.startsWith(MANUAL_FEEDBACK_PREFIX) + ? MANUAL_FEEDBACK + : (fieldFilter.logic_field_name_type ?? field_name), + extraInfo: fieldFilter.extraInfo, + }; + return { + left: leftValue as L, + operator: query_type as O, + disableDeletion: defaultImmutableKeys?.includes(field_name ?? ''), + right: values as R, + }; + }), }; if (sub_filter && sub_filter.length > 0) { @@ -101,6 +127,7 @@ export const formatExprValue = ( child, tagFilterRecord, defaultImmutableKeys, + ignoreKeys, ) as ExprGroup, ), ]; @@ -121,11 +148,23 @@ export const formatSpanFilterValue = ( const spanFilterNode: any = { query_and_or: logicOperator === 'or' ? 'or' : 'and', filter_fields: exprs?.map(item => { + const left = item.left as { + type: string; + value: string; + extraInfo?: Record; + }; + const fieldName = left?.value ?? left?.type; + const valueKind = - tagFilterRecord?.[item.left as string]?.value_type ?? 'string'; + tagFilterRecord?.[left?.type as string]?.value_type ?? 'string'; return { - field_name: item.left as string, + field_name: + fieldName === AUTO_EVAL_FEEDBACK || fieldName === MANUAL_FEEDBACK + ? '' + : fieldName, + logic_field_name_type: left?.type, + extraInfo: left?.extraInfo, query_type: item.operator, values: assignValueWithKind({ value: @@ -195,6 +234,14 @@ export const getKeyCopywriting = (key: string) => { return 'BotName'; case FilterFields.APP_ID: return 'AppName'; + case FilterFields.FEEDBACK: + return 'Feedback-自动评测'; + case FilterFields.FEEDBACK_MANUAL: + return 'Feedback-人工标注'; + case FilterFields.FEEDBACK_COZE: + return 'Feedback-Coze 对话'; + case FilterFields.WORKFLOW_ID: + return 'WorkflowName'; default: return snakeToPascalCase(key); } @@ -204,6 +251,8 @@ export const getOptionCopywriting = (key: string, option: string | number) => { switch (key) { case FilterFields.STATUS_KEY: return THREADS_STATUS_RECORDS[option]?.label; + case FilterFields.FEEDBACK_COZE: + return THREADS_FEEDBACK_COZE_RECORDS[option]?.label; default: return option; } diff --git a/frontend/packages/cozeloop/observation/trace-list/src/components/queries/table/columns/index.tsx b/frontend/packages/cozeloop/observation/trace-list/src/components/queries/table/columns/index.tsx index 7d18924ef..84c47cb35 100644 --- a/frontend/packages/cozeloop/observation/trace-list/src/components/queries/table/columns/index.tsx +++ b/frontend/packages/cozeloop/observation/trace-list/src/components/queries/table/columns/index.tsx @@ -58,7 +58,11 @@ export const COLUMN_RECORD = { displayName: QUERY_PROPERTY_LABEL_MAP[QUERY_PROPERTY.Status], }, [QUERY_PROPERTY.TraceId]: { - title: QUERY_PROPERTY_LABEL_MAP[QUERY_PROPERTY.TraceId], + title: ( + + {QUERY_PROPERTY_LABEL_MAP[QUERY_PROPERTY.TraceId]} + + ), dataIndex: QUERY_PROPERTY.TraceId, width: 158, disabled: true, @@ -71,7 +75,11 @@ export const COLUMN_RECORD = { ), }, [QUERY_PROPERTY.Input]: { - title: QUERY_PROPERTY_LABEL_MAP[QUERY_PROPERTY.Input], + title: ( + + {QUERY_PROPERTY_LABEL_MAP[QUERY_PROPERTY.Input]} + + ), dataIndex: QUERY_PROPERTY.Input, width: 320, disabled: true, @@ -106,7 +114,11 @@ export const COLUMN_RECORD = { }, [QUERY_PROPERTY.Output]: { - title: QUERY_PROPERTY_LABEL_MAP[QUERY_PROPERTY.Output], + title: ( + + {QUERY_PROPERTY_LABEL_MAP[QUERY_PROPERTY.Output]} + + ), dataIndex: QUERY_PROPERTY.Output, width: 320, render: (_, record: Span) => { @@ -141,9 +153,9 @@ export const COLUMN_RECORD = { [QUERY_PROPERTY.Tokens]: { title: ( -
+ {QUERY_PROPERTY_LABEL_MAP[QUERY_PROPERTY.Tokens]} -
+ ), dataIndex: QUERY_PROPERTY.Tokens, width: 108, @@ -166,7 +178,11 @@ export const COLUMN_RECORD = { }, [QUERY_PROPERTY.Latency]: { - title: QUERY_PROPERTY_LABEL_MAP[QUERY_PROPERTY.Latency], + title: ( + + {QUERY_PROPERTY_LABEL_MAP[QUERY_PROPERTY.Latency]} + + ), dataIndex: QUERY_PROPERTY.Latency, width: 108, render: (_, record) => , @@ -192,7 +208,11 @@ export const COLUMN_RECORD = { }, }, [QUERY_PROPERTY.StartTime]: { - title: QUERY_PROPERTY_LABEL_MAP[QUERY_PROPERTY.StartTime], + title: ( + + {QUERY_PROPERTY_LABEL_MAP[QUERY_PROPERTY.StartTime]} + + ), dataIndex: QUERY_PROPERTY.StartTime, width: 146, checked: true, @@ -208,7 +228,7 @@ export const COLUMN_RECORD = { [QUERY_PROPERTY.InputTokens]: { title: ( - + {QUERY_PROPERTY_LABEL_MAP[QUERY_PROPERTY.InputTokens]} ), @@ -231,7 +251,7 @@ export const COLUMN_RECORD = { }, [QUERY_PROPERTY.OutputTokens]: { title: ( - + {QUERY_PROPERTY_LABEL_MAP[QUERY_PROPERTY.OutputTokens]} ), @@ -253,7 +273,11 @@ export const COLUMN_RECORD = { }, }, [QUERY_PROPERTY.SpanId]: { - title: QUERY_PROPERTY_LABEL_MAP[QUERY_PROPERTY.SpanId], + title: ( + + {QUERY_PROPERTY_LABEL_MAP[QUERY_PROPERTY.SpanId]} + + ), dataIndex: QUERY_PROPERTY.SpanId, width: 120, checked: true, @@ -265,7 +289,11 @@ export const COLUMN_RECORD = { ), }, [QUERY_PROPERTY.SpanType]: { - title: QUERY_PROPERTY_LABEL_MAP[QUERY_PROPERTY.SpanType], + title: ( + + {QUERY_PROPERTY_LABEL_MAP[QUERY_PROPERTY.SpanType]} + + ), dataIndex: QUERY_PROPERTY.SpanType, width: 120, checked: true, @@ -277,7 +305,11 @@ export const COLUMN_RECORD = { ), }, [QUERY_PROPERTY.SpanName]: { - title: QUERY_PROPERTY_LABEL_MAP[QUERY_PROPERTY.SpanName], + title: ( + + {QUERY_PROPERTY_LABEL_MAP[QUERY_PROPERTY.SpanName]} + + ), dataIndex: QUERY_PROPERTY.SpanName, width: 120, checked: true, @@ -289,7 +321,11 @@ export const COLUMN_RECORD = { ), }, [QUERY_PROPERTY.PromptKey]: { - title: QUERY_PROPERTY_LABEL_MAP[QUERY_PROPERTY.PromptKey], + title: ( + + {QUERY_PROPERTY_LABEL_MAP[QUERY_PROPERTY.PromptKey]} + + ), dataIndex: QUERY_PROPERTY.PromptKey, width: 110, checked: true, @@ -302,7 +338,11 @@ export const COLUMN_RECORD = { }, [QUERY_PROPERTY.LogicDeleteDate]: { - title: QUERY_PROPERTY_LABEL_MAP[QUERY_PROPERTY.LogicDeleteDate], + title: ( + + {QUERY_PROPERTY_LABEL_MAP[QUERY_PROPERTY.LogicDeleteDate]} + + ), dataIndex: QUERY_PROPERTY.LogicDeleteDate, width: 176, checked: true, diff --git a/frontend/packages/cozeloop/observation/trace-list/src/components/queries/table/hooks/use-fetch-traces.tsx b/frontend/packages/cozeloop/observation/trace-list/src/components/queries/table/hooks/use-fetch-traces.tsx index 062617042..9c130ac45 100644 --- a/frontend/packages/cozeloop/observation/trace-list/src/components/queries/table/hooks/use-fetch-traces.tsx +++ b/frontend/packages/cozeloop/observation/trace-list/src/components/queries/table/hooks/use-fetch-traces.tsx @@ -1,20 +1,23 @@ // Copyright (c) 2025 coze-dev Authors // SPDX-License-Identifier: Apache-2.0 +/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @coze-arch/max-line-per-function */ import { useEffect, useMemo, useRef, useState } from 'react'; import { isEmpty, keyBy, keys } from 'lodash-es'; import { useInfiniteScroll } from 'ahooks'; -import { useSpace } from '@cozeloop/biz-hooks-adapter'; import { sendEvent, EVENT_NAMES } from '@cozeloop/tea-adapter'; +import { useSpace } from '@cozeloop/biz-hooks-adapter'; import { type PlatformType, type SpanListType, type ListSpansRequest, type QueryType, QueryRelation, + FieldType, } from '@cozeloop/api-schema/observation'; +import { tag } from '@cozeloop/api-schema/data'; import { observabilityTrace } from '@cozeloop/api-schema'; import { logger } from '@coze-arch/logger'; import { Toast } from '@coze-arch/coze-design'; @@ -22,9 +25,47 @@ import { Toast } from '@coze-arch/coze-design'; import { type ConvertSpan } from '@/typings/span'; import { useTraceStore } from '@/stores/trace'; import { usePerformance } from '@/hooks/use-performance'; +import { + AUTO_EVAL_FEEDBACK, + AUTO_EVAL_FEEDBACK_PREFIX, + MANUAL_FEEDBACK, + MANUAL_FEEDBACK_PREFIX, +} from '@/components/logic-expr/const'; import { TRACE_EXPIRED_CODE } from '..'; +const TAG_CONTENT_MAPPDING_TYPE = { + [tag.TagContentType.Categorical]: FieldType.Long, + [tag.TagContentType.Boolean]: FieldType.Long, + [tag.TagContentType.FreeText]: FieldType.String, + [tag.TagContentType.ContinuousNumber]: FieldType.Double, +}; + +const getFieldType = ( + fieldName: string, + fieldMetas?: Record, + item?: any, +) => { + if (item?.extraInfo) { + const { content_type } = item.extraInfo ?? {}; + + return TAG_CONTENT_MAPPDING_TYPE[content_type] ?? FieldType.String; + } + if (!fieldMetas) { + return FieldType.String; + } + + let filedKey = fieldName; + if (fieldName.startsWith(AUTO_EVAL_FEEDBACK_PREFIX)) { + filedKey = AUTO_EVAL_FEEDBACK; + } + + if (fieldName.startsWith(MANUAL_FEEDBACK_PREFIX)) { + filedKey = MANUAL_FEEDBACK; + } + return fieldMetas?.[filedKey]?.value_type ?? FieldType.String; +}; + export const useFetchTraces = () => { const { spaceID } = useSpace(); @@ -47,7 +88,7 @@ export const useFetchTraces = () => { filter_fields: applyFilters?.filter_fields?.map(item => ({ field_name: item.field_name, - field_type: fieldMetas?.[item.field_name]?.value_type, + field_type: getFieldType(item.field_name, fieldMetas, item), values: item.values, query_type: item.query_type as QueryType, query_and_or: (applyFilters?.query_and_or ?? @@ -142,6 +183,16 @@ export const useFetchTraces = () => { filters: JSON.stringify(keys(standardFilters)), }); + if ( + keys(standardFilters).some(key => + key.startsWith(AUTO_EVAL_FEEDBACK_PREFIX), + ) + ) { + sendEvent(EVENT_NAMES.trace_filter_by_evaluator, { + space_id: spaceID, + }); + } + if (requestId === latestCountRef.current) { const convertSpans: ConvertSpan[] = spans.map(span => ({ ...span, diff --git a/frontend/packages/cozeloop/observation/trace-list/src/components/queries/table/index.tsx b/frontend/packages/cozeloop/observation/trace-list/src/components/queries/table/index.tsx index acd4ae00f..b30576724 100644 --- a/frontend/packages/cozeloop/observation/trace-list/src/components/queries/table/index.tsx +++ b/frontend/packages/cozeloop/observation/trace-list/src/components/queries/table/index.tsx @@ -1,6 +1,6 @@ // Copyright (c) 2025 coze-dev Authors // SPDX-License-Identifier: Apache-2.0 -/* eslint-disable max-lines-per-function */ + /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable complexity */ @@ -60,6 +60,7 @@ export interface QueryTableProps { loadMore: () => void; loadingMore: boolean; traceListCode: number; + rowSelection?: any; } interface SelectedSpan { @@ -88,6 +89,7 @@ export const QueryTable = ({ loadMore, loadingMore, traceListCode, + rowSelection, }: QueryTableProps) => { const containerRef = useRef(null); const [detailVisible, setDetailVisible] = useState(false); @@ -326,13 +328,14 @@ export const QueryTable = ({ } }, }), - rowKey: 'id', + rowKey: 'span_id', sticky: true, loading: loading || loadingMore, virtualized: spans?.length > 0 ? virtualized : false, dataSource: spans, columns: sizedColumns, pagination: false, + rowSelection, scroll: { x: width, y: height - TABLE_HEADER_HEIGHT - 13, // 13 是底部的 padding diff --git a/frontend/packages/cozeloop/observation/trace-list/src/components/table-header-text/index.tsx b/frontend/packages/cozeloop/observation/trace-list/src/components/table-header-text/index.tsx index a009210ff..a4a850b9c 100644 --- a/frontend/packages/cozeloop/observation/trace-list/src/components/table-header-text/index.tsx +++ b/frontend/packages/cozeloop/observation/trace-list/src/components/table-header-text/index.tsx @@ -8,23 +8,35 @@ import { Typography } from '@coze-arch/coze-design'; interface TableHeaderText { className?: string; children: React.ReactElement | string; + align?: 'left' | 'right'; } -export const TableHeaderText = ({ children, className }: TableHeaderText) => ( - ( +
- {children} - + + {children} + +
); diff --git a/frontend/packages/cozeloop/observation/trace-list/src/consts/filter.ts b/frontend/packages/cozeloop/observation/trace-list/src/consts/filter.ts index 9ff487d40..2bd6736a0 100644 --- a/frontend/packages/cozeloop/observation/trace-list/src/consts/filter.ts +++ b/frontend/packages/cozeloop/observation/trace-list/src/consts/filter.ts @@ -1,6 +1,7 @@ // Copyright (c) 2025 coze-dev Authors // SPDX-License-Identifier: Apache-2.0 import { I18n } from '@cozeloop/i18n-adapter'; + import { QUERY_PROPERTY } from './trace-attrs'; export const SPAN_COLUMNS = [ @@ -43,6 +44,7 @@ export const QUERY_PROPERTY_LABEL_MAP: Record< [QUERY_PROPERTY.OutputTokens]: 'Output Tokens', [QUERY_PROPERTY.LogicDeleteDate]: I18n.t('data_expiration_time'), [QUERY_PROPERTY.StartTime]: 'Start Time', + [QUERY_PROPERTY.Feedback]: 'Feedback', }; export const SPAN_TAB_OPTION_LIST = [ diff --git a/frontend/packages/cozeloop/observation/trace-list/src/consts/index.ts b/frontend/packages/cozeloop/observation/trace-list/src/consts/index.ts index bde5c09c2..be0a72f29 100644 --- a/frontend/packages/cozeloop/observation/trace-list/src/consts/index.ts +++ b/frontend/packages/cozeloop/observation/trace-list/src/consts/index.ts @@ -12,7 +12,7 @@ export { type QueryPropertyEnum, } from './trace-attrs'; -export { COLUMN_RECORD } from '../components/queries/table/columns/index'; +export { COLUMN_RECORD } from '../components/queries/table/columns'; export { DEFAULT_SELECTED_KEYS } from './col'; export enum PlatformType { @@ -26,3 +26,8 @@ export enum SpanType { LlmSpan = 'llm_span', } export { jsonViewerConfig } from './json-view'; + +export { + AUTO_EVAL_FEEDBACK, + AUTO_EVAL_FEEDBACK_PREFIX, +} from '../components/logic-expr/const'; diff --git a/frontend/packages/cozeloop/observation/trace-list/src/consts/trace-attrs.ts b/frontend/packages/cozeloop/observation/trace-list/src/consts/trace-attrs.ts index c95a56960..de0c15469 100644 --- a/frontend/packages/cozeloop/observation/trace-list/src/consts/trace-attrs.ts +++ b/frontend/packages/cozeloop/observation/trace-list/src/consts/trace-attrs.ts @@ -16,6 +16,7 @@ export const QUERY_PROPERTY = { PromptKey: 'prompt_key', LogicDeleteDate: 'logic_delete_date', StartTime: 'start_time', + Feedback: 'feedback', } as const; export const FILTER_INVALIDATE = { diff --git a/frontend/packages/cozeloop/observation/trace-list/src/contexts/trace-context.tsx b/frontend/packages/cozeloop/observation/trace-list/src/contexts/trace-context.tsx index 097ed805d..6c013b443 100644 --- a/frontend/packages/cozeloop/observation/trace-list/src/contexts/trace-context.tsx +++ b/frontend/packages/cozeloop/observation/trace-list/src/contexts/trace-context.tsx @@ -111,7 +111,7 @@ const useTraceActions = ( const [lastUserRecord, setLastUserRecordState] = useState< TraceContextState['lastUserRecord'] >({ - filters: {}, + filters: { filter_fields: [] }, selectedPlatform: initPlatform, selectedSpanType: initSelectedSpanType, }); diff --git a/frontend/packages/cozeloop/observation/trace-list/src/hooks/index.tsx b/frontend/packages/cozeloop/observation/trace-list/src/hooks/index.tsx index 75d8f0aff..8b43da0e0 100644 --- a/frontend/packages/cozeloop/observation/trace-list/src/hooks/index.tsx +++ b/frontend/packages/cozeloop/observation/trace-list/src/hooks/index.tsx @@ -4,3 +4,5 @@ export { useFetchTraces } from '@/components/queries/table/hooks/use-fetch-trace export { usePerformance } from './use-performance'; export { usePageStay } from './use-page-stay'; export { useColumns } from './use-column'; +export { useFetchMetaInfo } from './use-fetch-meta-info'; +export { useGetMetaInfo, fetchMetaInfo } from './use-get-meta-info'; diff --git a/frontend/packages/cozeloop/observation/trace-list/src/hooks/use-fetch-meta-info.ts b/frontend/packages/cozeloop/observation/trace-list/src/hooks/use-fetch-meta-info.ts index c8025cdcf..f550406ec 100644 --- a/frontend/packages/cozeloop/observation/trace-list/src/hooks/use-fetch-meta-info.ts +++ b/frontend/packages/cozeloop/observation/trace-list/src/hooks/use-fetch-meta-info.ts @@ -1,43 +1,28 @@ // Copyright (c) 2025 coze-dev Authors // SPDX-License-Identifier: Apache-2.0 -import { useRequest } from 'ahooks'; -import { I18n } from '@cozeloop/i18n-adapter'; +import { useEffect } from 'react'; + import { useSpace } from '@cozeloop/biz-hooks-adapter'; import { type PlatformType, type SpanListType, } from '@cozeloop/api-schema/observation'; -import { observabilityTrace } from '@cozeloop/api-schema'; -import { Toast } from '@coze-arch/coze-design'; import { useTraceStore } from '../stores/trace'; +import { useGetMetaInfo } from './use-get-meta-info'; export const useFetchMetaInfo = () => { - const { spaceID } = useSpace(); const { selectedPlatform, selectedSpanType, setFieldMetas } = useTraceStore(); - useRequest( - async () => { - const result = await observabilityTrace.GetTracesMetaInfo( - { - platform_type: selectedPlatform as PlatformType, - span_list_type: selectedSpanType as SpanListType, - workspace_id: spaceID, - }, - { - __disableErrorToast: true, - }, - ); - setFieldMetas(result?.field_metas ?? {}); - }, - { - refreshDeps: [selectedPlatform, selectedSpanType], - onError(e) { - Toast.error( - I18n.t('observation_fetch_meta_error', { - msg: e.message || '', - }), - ); - }, - }, - ); + const { spaceID } = useSpace(); + const { metaInfo, loading } = useGetMetaInfo({ + selectedPlatform: selectedPlatform as PlatformType, + selectedSpanType: selectedSpanType as SpanListType, + spaceID, + }); + + useEffect(() => { + if (!loading) { + setFieldMetas(metaInfo); + } + }, [loading, metaInfo, setFieldMetas]); }; diff --git a/frontend/packages/cozeloop/observation/trace-list/src/hooks/use-get-meta-info.ts b/frontend/packages/cozeloop/observation/trace-list/src/hooks/use-get-meta-info.ts new file mode 100644 index 000000000..1cfc27687 --- /dev/null +++ b/frontend/packages/cozeloop/observation/trace-list/src/hooks/use-get-meta-info.ts @@ -0,0 +1,78 @@ +// Copyright (c) 2025 coze-dev Authors +// SPDX-License-Identifier: Apache-2.0 +import { useRequest } from 'ahooks'; +import { I18n } from '@cozeloop/i18n-adapter'; +import { useSpace } from '@cozeloop/biz-hooks-adapter'; +import { + type PlatformType, + type SpanListType, +} from '@cozeloop/api-schema/observation'; +import { observabilityTrace } from '@cozeloop/api-schema'; +import { Toast } from '@coze-arch/coze-design'; + +interface UseGetMetaInfoParams { + selectedPlatform: string | number; + selectedSpanType: string | number; + spaceID: string; +} + +export async function fetchMetaInfo({ + selectedPlatform, + selectedSpanType, + spaceID, +}: UseGetMetaInfoParams) { + try { + const result = await observabilityTrace.GetTracesMetaInfo( + { + platform_type: selectedPlatform as PlatformType, + span_list_type: selectedSpanType as SpanListType, + workspace_id: spaceID, + }, + { + __disableErrorToast: true, + }, + ); + + const { msg, code } = result as unknown as { msg: string; code: number }; + + if (code === 0) { + return result.field_metas || {}; + } else { + Toast.error( + I18n.t('analytics_fetch_meta_error', { + msg: msg || '', + }), + ); + return {}; + } + } catch (e) { + Toast.error( + I18n.t('analytics_fetch_meta_error', { + msg: (e as unknown as { message: string }).message || '', + }), + ); + } +} + +export const useGetMetaInfo = ({ + selectedPlatform, + selectedSpanType, +}: UseGetMetaInfoParams) => { + const { spaceID } = useSpace(); + const { data: metaInfo, loading } = useRequest( + () => + fetchMetaInfo({ + selectedPlatform, + selectedSpanType, + spaceID, + }), + { + refreshDeps: [selectedPlatform, selectedSpanType], + }, + ); + + return { + metaInfo, + loading, + }; +}; diff --git a/frontend/packages/cozeloop/observation/trace-list/src/hooks/use-sync-url-params.ts b/frontend/packages/cozeloop/observation/trace-list/src/hooks/use-sync-url-params.ts index 1c5add2f7..1d2bac95c 100644 --- a/frontend/packages/cozeloop/observation/trace-list/src/hooks/use-sync-url-params.ts +++ b/frontend/packages/cozeloop/observation/trace-list/src/hooks/use-sync-url-params.ts @@ -2,7 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 import { useEffect } from 'react'; -import { getUrlParamsFromPersistentFilters } from '../utils/url'; +import { + getUrlParamsFromPersistentFilters, + saveTraceFilterToStorage, +} from '../utils/url'; import { encodeJSON } from '../utils/json'; import { type TraceFilter } from '../typings/filter'; import { useTraceStore } from '../stores/trace'; @@ -28,18 +31,20 @@ export const useSyncUrlParams = (disableUrlParams?: boolean) => { } = useTraceStore(); useEffect(() => { + const urlParams = { + selected_span_type: selectedSpanType.toString(), + trace_platform: selectedPlatform.toString(), + trace_filters: filters ? encodeJSON(filters) : undefined, + trace_start_time: startTime.toString(), + trace_end_time: endTime.toString(), + trace_preset_time_range: presetTimeRange, + relation: relation.toString(), + ...getUrlParamsFromPersistentFilters(persistentFilters), + }; if (!disableUrlParams) { - setSearchValue({ - selected_span_type: selectedSpanType.toString(), - trace_platform: selectedPlatform.toString(), - trace_filters: filters ? encodeJSON(filters) : undefined, - trace_start_time: startTime.toString(), - trace_end_time: endTime.toString(), - trace_preset_time_range: presetTimeRange, - relation: relation.toString(), - ...getUrlParamsFromPersistentFilters(persistentFilters), - }); + setSearchValue(urlParams); } + saveTraceFilterToStorage(urlParams); }, [ disableUrlParams, selectedSpanType, diff --git a/frontend/packages/cozeloop/observation/trace-list/src/index.tsx b/frontend/packages/cozeloop/observation/trace-list/src/index.tsx index 4a2a961b3..ee910924f 100644 --- a/frontend/packages/cozeloop/observation/trace-list/src/index.tsx +++ b/frontend/packages/cozeloop/observation/trace-list/src/index.tsx @@ -1,5 +1,6 @@ // Copyright (c) 2025 coze-dev Authors // SPDX-License-Identifier: Apache-2.0 +/* eslint-disable @typescript-eslint/naming-convention */ import { I18n } from '@cozeloop/i18n-adapter'; import { PrimaryPage } from '@cozeloop/components'; @@ -17,6 +18,12 @@ import { PLATFORM_ENUM_OPTION_LIST } from './consts/filter'; import { COLUMN_RECORD, SPAN_COLUMNS } from './consts'; import { useFetchTraces } from './components/queries/table/hooks/use-fetch-traces'; import { Queries } from './components/queries'; +import { MANUAL_FEEDBACK } from './components/logic-expr/const'; +import { + LeftManualExpr, + type LeftManualExprProps, +} from './components/left-manual-expr'; +import { PromptSelect } from './components/filter-bar/prompt-select'; import { QueryFilterBar } from './components/filter-bar'; import { CozeLoopTraceBanner } from './components/banner'; @@ -62,6 +69,14 @@ const TraceListApp = () => { platformEnumOptionList={PLATFORM_ENUM_OPTION_LIST} spanListTypeEnumOptionList={SPAN_TAB_OPTION_LIST} tooltipContent={getTooltipContent()} + customLeftRenderMap={{ + [MANUAL_FEEDBACK]: v => ( + + ), + }} + customRightRenderMap={{ + prompt_key: v => , + }} /> } className="!pb-0" diff --git a/frontend/packages/cozeloop/observation/trace-list/src/utils/url.ts b/frontend/packages/cozeloop/observation/trace-list/src/utils/url.ts index 7806fdac2..06e5a00f2 100644 --- a/frontend/packages/cozeloop/observation/trace-list/src/utils/url.ts +++ b/frontend/packages/cozeloop/observation/trace-list/src/utils/url.ts @@ -3,6 +3,7 @@ /* eslint-disable complexity */ import queryString from 'query-string'; import { isNil } from 'lodash-es'; +import { CozeLoopStorage } from '@cozeloop/toolkit'; import { decodeJSON } from '@/utils/json'; import { PresetRange, timePickerPresets } from '@/consts/time'; @@ -11,6 +12,12 @@ import { type LogicValue } from '@/components/logic-expr'; import { type PersistentFilter } from '../typings/index'; import { type TraceFilter } from '../typings/filter'; import { TRACES_PERSISTENT_FILTER_PROPERTY } from '../consts/filter'; + +const traceListStorage = new CozeLoopStorage({ + field: 'trace', +}); + +// const TRACE_FILTER_STORAGE_KEY = 'trace-filter-storage'; export const getPersistentFiltersFromUrl = ( value: Record, ) => { @@ -45,6 +52,13 @@ export const getUrlTraceFilterData = (): TraceFilter => { arrayFormat: 'bracket', }); + const hasUrlParams = Object.keys(urlParams).length > 0; + + if (!hasUrlParams) { + const storedFilter = getTraceFilterFromStorage(); + return { ...storedFilter } as unknown as TraceFilter; + } + return urlParams as TraceFilter; }; @@ -119,3 +133,34 @@ export const initTraceUrlSearchInfo = ( initRelation, }; }; + +export const saveTraceFilterToStorage = (filter: Partial) => { + try { + const filteredData: Partial = {}; + Object.keys(filter).forEach(key => { + const value = filter[key as keyof TraceFilter]; + if (value !== undefined && value !== null && value !== '') { + filteredData[key as keyof TraceFilter] = value; + } + }); + + if (Object.keys(filteredData).length > 0) { + traceListStorage.setItem( + 'trace-filter-storage', + JSON.stringify(filteredData), + ); + } + } catch (error) { + console.warn('Failed to save trace filter to localStorage:', error); + } +}; + +export const getTraceFilterFromStorage = (): Partial => { + try { + const stored = traceListStorage.getItem('trace-filter-storage'); + return stored ? JSON.parse(stored) : {}; + } catch (error) { + console.warn('Failed to get trace filter from localStorage:', error); + return {}; + } +}; diff --git a/frontend/packages/cozeloop/observation/trace-list/tsconfig.build.json b/frontend/packages/cozeloop/observation/trace-list/tsconfig.build.json index 3a5af9c86..bebed9c08 100644 --- a/frontend/packages/cozeloop/observation/trace-list/tsconfig.build.json +++ b/frontend/packages/cozeloop/observation/trace-list/tsconfig.build.json @@ -51,6 +51,9 @@ { "path": "../../i18n/tsconfig.build.json" }, + { + "path": "../../tag-components/tsconfig.build.json" + }, { "path": "../../tea/tsconfig.build.json" }, @@ -58,7 +61,7 @@ "path": "../../toolkit/tsconfig.build.json" }, { - "path": "../trace-detail/tsconfig.build.json" + "path": "../trace-detail-open/tsconfig.build.json" } ] } diff --git a/frontend/packages/cozeloop/observation/trace-list/tsconfig.misc.json b/frontend/packages/cozeloop/observation/trace-list/tsconfig.misc.json index 267b83c4f..7bc9c63e7 100644 --- a/frontend/packages/cozeloop/observation/trace-list/tsconfig.misc.json +++ b/frontend/packages/cozeloop/observation/trace-list/tsconfig.misc.json @@ -10,7 +10,7 @@ "target": "ES2020", "moduleResolution": "bundler" }, - "include": ["__tests__", "vitest.config.ts"], + "include": ["__tests__", "vitest.config.ts", "stories"], "exclude": ["./dist"], "references": [ { diff --git a/frontend/packages/cozeloop/observation/trace-struct-data/src/components/message-parts.tsx b/frontend/packages/cozeloop/observation/trace-struct-data/src/components/message-parts.tsx index 42c77cc42..8b7884fd8 100644 --- a/frontend/packages/cozeloop/observation/trace-struct-data/src/components/message-parts.tsx +++ b/frontend/packages/cozeloop/observation/trace-struct-data/src/components/message-parts.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { isEmpty } from 'lodash-es'; import { IconCozLink } from '@coze-arch/coze-design/icons'; -import { Typography } from '@coze-arch/coze-design'; +import { Tag, Typography } from '@coze-arch/coze-design'; import { getPartUrl } from '../utils/span'; import { type Span, type RawMessage } from '../types'; @@ -15,6 +15,24 @@ interface MessagePartsProps { attrTos?: Span['attr_tos']; } +function IconImageVariable() { + return ( + + + + ); +} + export const MessageParts = (props: MessagePartsProps) => { const { raw, attrTos } = props; if (isEmpty(raw.parts)) { @@ -58,6 +76,17 @@ export const MessageParts = (props: MessagePartsProps) => { ); } + if (part.type === 'multi_part_variable') { + return ( +
+
+ }> + {part.text} + +
+
+ ); + } return (
diff --git a/frontend/packages/cozeloop/observation/trace-struct-data/src/components/multi-part-render/index.tsx b/frontend/packages/cozeloop/observation/trace-struct-data/src/components/multi-part-render/index.tsx new file mode 100644 index 000000000..2cd16292f --- /dev/null +++ b/frontend/packages/cozeloop/observation/trace-struct-data/src/components/multi-part-render/index.tsx @@ -0,0 +1,78 @@ +// Copyright (c) 2025 coze-dev Authors +// SPDX-License-Identifier: Apache-2.0 +import cn from 'classnames'; +import { Typography, Image } from '@coze-arch/coze-design'; + +import { type MultiPartSchema } from '../../span-definition/prompt/schema'; +import { MultiPartType, type DatasetItemProps } from './type'; + +const ImageDatasetItem: React.FC = ({ + fieldContent, + className, +}) => { + const { image_url } = fieldContent || {}; + + return ( + {image_url?.name} + ); +}; + +function StringDatasetItem({ fieldContent, className }: DatasetItemProps) { + const { text } = fieldContent || {}; + + return ( + + {text} + + ); +} + +const MultipartItemComponentMap = { + [MultiPartType.Text]: StringDatasetItem, + [MultiPartType.ImageUrl]: ImageDatasetItem, +}; + +export function MultipartRender(props: { + parts?: MultiPartSchema[]; + className?: string; +}) { + const { parts } = props; + + return ( +
+ {parts?.map((item, index) => { + if (!item.type) { + return; + } + const className = + item.type === MultiPartType.Text + ? 'w-full max-h-[auto] !border-0 !p-0' + : ''; + const Component = + MultipartItemComponentMap[item.type] || StringDatasetItem; + return ( + + ); + })} +
+ ); +} diff --git a/frontend/packages/cozeloop/observation/trace-struct-data/src/components/multi-part-render/type.ts b/frontend/packages/cozeloop/observation/trace-struct-data/src/components/multi-part-render/type.ts new file mode 100644 index 000000000..662e41895 --- /dev/null +++ b/frontend/packages/cozeloop/observation/trace-struct-data/src/components/multi-part-render/type.ts @@ -0,0 +1,14 @@ +// Copyright (c) 2025 coze-dev Authors +// SPDX-License-Identifier: Apache-2.0 +import { type MultiPartSchema } from '../../span-definition/prompt/schema'; + +export interface DatasetItemProps { + fieldContent?: MultiPartSchema; + className?: string; + expand?: boolean; +} + +export enum MultiPartType { + Text = 'text', + ImageUrl = 'image_url', +} diff --git a/frontend/packages/cozeloop/observation/trace-struct-data/src/components/plain-text.tsx b/frontend/packages/cozeloop/observation/trace-struct-data/src/components/plain-text.tsx index e61d39343..fe949b78c 100644 --- a/frontend/packages/cozeloop/observation/trace-struct-data/src/components/plain-text.tsx +++ b/frontend/packages/cozeloop/observation/trace-struct-data/src/components/plain-text.tsx @@ -3,21 +3,19 @@ import { isObject } from 'lodash-es'; import classNames from 'classnames'; import { JsonViewer, type JsonViewerProps } from '@textea/json-viewer'; -import { I18n } from '@cozeloop/i18n-adapter'; -import { Tag } from '@coze-arch/coze-design'; import { JSON_VIEW_CONFIG } from '../consts/json-view'; import styles from './index.module.less'; -export const PlantText = ({ content }: { content: string }) => ( +export const PlantText = ({ content }: { content: string | null }) => ( {content || '-'} ); export const renderPlainText = ( - content: string | object, + content: string | object | null, config?: Partial, ) => isObject(content) ? ( @@ -27,16 +25,11 @@ export const renderPlainText = ( ); export const renderJsonContent = ( - content: string | object, + content: string | object | null, config?: Partial, ) => isObject(content) ? ( ) : ( - <> - - {I18n.t('invalid_json')} - - - + ); diff --git a/frontend/packages/cozeloop/observation/trace-struct-data/src/components/span-field-render.tsx b/frontend/packages/cozeloop/observation/trace-struct-data/src/components/span-field-render.tsx index 010670a74..de3246b36 100644 --- a/frontend/packages/cozeloop/observation/trace-struct-data/src/components/span-field-render.tsx +++ b/frontend/packages/cozeloop/observation/trace-struct-data/src/components/span-field-render.tsx @@ -5,6 +5,7 @@ import { useState } from 'react'; import { isEmpty } from 'lodash-es'; import classNames from 'classnames'; +import { I18n } from '@cozeloop/i18n-adapter'; import { Typography } from '@coze-arch/coze-design'; import { capitalizeFirstLetter } from '../utils/letter'; @@ -15,7 +16,6 @@ import { renderPlainText } from './plain-text'; import { MessageParts } from './message-parts'; import styles from './index.module.less'; -import { I18n } from '@cozeloop/i18n-adapter'; interface SpanFieldRenderProps { messages?: RawMessage[]; @@ -59,15 +59,23 @@ export const SpanFieldRender = (props: SpanFieldRenderProps) => { !content && !tool_calls && !reasoning_content, })} > - {!isEmpty(rawContent.tool_calls) || - !isEmpty(rawContent.parts) ? ( - <> - - - - ) : ( - <>{renderPlainText(rawContent.content ?? '')} - )} + <> + {(!isEmpty(rawContent.tool_calls) || + !isEmpty(rawContent.parts)) && ( + <> + + + + )} + {rawContent.content + ? renderPlainText(rawContent.content ?? '') + : null} + {/* 全部都是空的时候渲染 - */} + {isEmpty(rawContent.tool_calls) && + isEmpty(rawContent.parts) && + !rawContent.content && + renderPlainText('')} +
diff --git a/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/model/index.tsx b/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/model/index.tsx index 49633d808..08a315e47 100644 --- a/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/model/index.tsx +++ b/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/model/index.tsx @@ -116,6 +116,7 @@ const getOutputAndReasoningContent = (output: string) => { role: c.message.role, content: c.message.content, reasoning_content: c.message.reasoning_content, + parts: c.message.parts, }, })), }; diff --git a/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/model/render.tsx b/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/model/render.tsx index 9c8a4fdce..81014cd80 100644 --- a/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/model/render.tsx +++ b/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/model/render.tsx @@ -52,17 +52,23 @@ const ModelTool = (tool?: Tool) => { +
- } > - {raw?.function?.name} + + + + + + {raw?.function?.name} + diff --git a/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/model/schema.ts b/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/model/schema.ts index d280b52a0..b122bdc8f 100644 --- a/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/model/schema.ts +++ b/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/model/schema.ts @@ -2,118 +2,121 @@ // SPDX-License-Identifier: Apache-2.0 import { z } from 'zod'; +// 基础文件URL schema +const fileUrlSchema = z.object({ + name: z.string().optional(), + url: z.string().optional(), + detail: z.string().optional(), + suffix: z.string().optional(), +}); + +// 基础图片URL schema +const imageUrlSchema = z.object({ + name: z.string().optional(), + url: z.string().optional(), + detail: z.string().optional(), +}); + +// 工具函数参数 schema +const toolFunctionSchema = z.object({ + name: z.string(), + arguments: z.string().optional(), +}); + +// 工具调用 schema +const toolCallSchema = z.object({ + type: z.string(), + function: toolFunctionSchema, +}); + +// 工具定义参数属性 schema +const toolParameterPropertySchema = z.object({ + description: z.string().optional(), + type: z.string().optional(), +}); + +// 工具定义参数 schema +const toolParametersSchema = z.object({ + required: z.array(z.string()).optional(), + properties: z.record(toolParameterPropertySchema).optional(), +}); + +// 工具定义 schema +const toolSchema = z.object({ + type: z.string(), + function: z.object({ + name: z.string(), + description: z.string().optional(), + parameters: z.union([z.null(), toolParametersSchema]), + }), +}); + +// 消息部分内容 schema +const messagePartSchema = z.object({ + type: z.string(), + text: z.string().optional(), + image_url: imageUrlSchema.optional(), + file_url: fileUrlSchema.optional(), +}); + +// 基础消息 schema +const messageSchema = z.object({ + role: z.string(), + content: z.union([z.string(), z.null()]).optional(), + reasoning_content: z.string().optional(), + tool_calls: z.array(toolCallSchema).optional(), + parts: z.array(messagePartSchema).optional(), +}); + export const modelInputSchema = z.object({ - tools: z - .array( - z.object({ - type: z.string(), - function: z.object({ - name: z.string(), - description: z.string().optional(), - parameters: z.object({ - required: z.array(z.string()).optional(), - properties: z - .record( - z.object({ - description: z.string().optional(), - type: z.string().optional(), - }), - ) - .optional(), - }), - }), - }), - ) - .optional(), - messages: z.array( - z.object({ - role: z.string(), - content: z.string().optional(), - reasoning_content: z.string().optional(), - tool_calls: z - .array( - z.object({ - type: z.string(), - function: z.object({ - name: z.string(), - arguments: z.string().optional(), - }), - }), - ) - .optional(), - parts: z - .array( - z.object({ - type: z.string(), - text: z.string().optional(), - image_url: z - .object({ - name: z.string().optional(), - url: z.string(), - detail: z.string().optional(), - }) - .optional(), - file_url: z - .object({ - name: z.string().optional(), - url: z.string(), - detail: z.string().optional(), - suffix: z.string().optional(), - }) - .optional(), - }), - ) - .optional(), - }), - ), + tools: z.array(toolSchema).optional(), + messages: z.array(messageSchema), +}); + +// 模型输出的工具调用 schema(可能有 id 字段) +const outputToolCallSchema = z.object({ + type: z.string(), + function: toolFunctionSchema, +}); + +// 模型输出的消息部分 schema +const outputMessagePartSchema = z.object({ + type: z.string(), + text: z.string().optional(), + file_url: fileUrlSchema.optional(), + image_url: imageUrlSchema.optional(), +}); + +// 模型输出的消息 schema +const outputMessageSchema = z.object({ + role: z.string(), + content: z.union([z.string(), z.null()]).optional(), + reasoning_content: z.string().optional(), + tool_calls: z.array(outputToolCallSchema).optional(), + parts: z.array(outputMessagePartSchema).optional(), +}); + +// 选择项 schema +const choiceSchema = z.object({ + index: z.number().optional(), + message: outputMessageSchema, }); export const modelOutputSchema = z.object({ - choices: z.array( - z.object({ - index: z.number().optional(), - message: z.object({ - role: z.string(), - content: z.string().optional(), - reasoning_content: z.string().optional(), - tool_calls: z - .array( - z.object({ - type: z.string(), - function: z.object({ - name: z.string(), - arguments: z.string().optional(), - }), - }), - ) - .optional(), - parts: z - .array( - z.object({ - type: z.string(), - text: z.string().optional(), - file_url: z - .object({ - name: z.string().optional(), - url: z.string(), - detail: z.string().optional(), - suffix: z.string().optional(), - }) - .optional(), - image_url: z - .object({ - name: z.string().optional(), - url: z.string().optional(), - detail: z.string().optional(), - }) - .optional(), - }), - ) - .optional(), - }), - }), - ), + choices: z.array(choiceSchema), }); +// 导出类型 +export type FileUrl = z.infer; +export type ImageUrl = z.infer; +export type ToolFunction = z.infer; +export type ToolCall = z.infer; +export type ToolParameterProperty = z.infer; +export type ToolParameters = z.infer; +export type Tool = z.infer; +export type MessagePart = z.infer; +export type Message = z.infer; +export type OutputMessage = z.infer; +export type Choice = z.infer; export type ModelInputSchema = z.infer; export type ModelOutputSchema = z.infer; diff --git a/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/prompt/argument-value-render.tsx b/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/prompt/argument-value-render.tsx new file mode 100644 index 000000000..54d45e187 --- /dev/null +++ b/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/prompt/argument-value-render.tsx @@ -0,0 +1,51 @@ +// Copyright (c) 2025 coze-dev Authors +// SPDX-License-Identifier: Apache-2.0 +import { isObject, truncate } from 'lodash-es'; +import { JsonViewer } from '@textea/json-viewer'; + +import { JSON_VIEW_CONFIG } from '../../consts/json-view'; +import { MultipartRender } from '../../components/multi-part-render'; +import { type MultiPartSchema } from './schema'; + +export enum PromptArgumentValueType { + Text = 'text', + Message = 'model_message', + MessagePart = 'model_message_part', +} + +export interface PromptArgument { + key: string; + value?: string | { content?: string | null } | MultiPartSchema[] | undefined; + source?: string; + value_type?: PromptArgumentValueType; +} + +export function ArgumentValueRender({ + promptArgument, +}: { + promptArgument: PromptArgument | undefined; +}) { + if (!promptArgument) { + return ''; + } + const { value, value_type } = promptArgument; + if (value_type === PromptArgumentValueType.MessagePart) { + if (!Array.isArray(value)) { + return ; + } + const multiPart = value as MultiPartSchema[]; + return ; + } + if (isObject(value)) { + return ; + } + return ( + + {value + ? truncate(value, { + length: 1000, + }) + : '-'} + + ); +} diff --git a/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/prompt/index.module.less b/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/prompt/index.module.less index 2d578afab..0385a6b66 100644 --- a/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/prompt/index.module.less +++ b/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/prompt/index.module.less @@ -1,5 +1,8 @@ // Copyright (c) 2025 coze-dev Authors // SPDX-License-Identifier: Apache-2.0 +/* stylelint-disable */ +@border-color: #1D1C2314; + .prompt-collapse{ .prompt-panel-content{ margin-bottom: 12px; @@ -27,3 +30,37 @@ } } } + +.argu-container{ + overflow: auto; + + max-height: 340px; + + + font-family: Menlo; + font-size: 12px; + + border: 1px solid @border-color; + border-radius: 8px; + + &.io-error { + border: 1px solid #FF441E; + } + + &:not(:last-child) { + margin-bottom: 20px; + } +} + +.argu-item-container{ + &:not(:last-child){ + border-bottom: 1px solid #1D1C2314; + } +} + +.argu-item{ + display: flex; + gap: 4px; + align-items: center; + padding: 8px 12px; +} diff --git a/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/prompt/index.tsx b/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/prompt/index.tsx index 6897097df..592cd8a3e 100644 --- a/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/prompt/index.tsx +++ b/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/prompt/index.tsx @@ -12,7 +12,7 @@ import { } from './schema'; import { PromptDataRender } from './render'; export type Input = Pick | string; -export type Output = { role: string; content?: string }[] | string; +export type Output = { role: string; content?: string | null }[] | string; const getInputAndTools = (input: string) => { const parsedInput = safeJsonParse(input); @@ -90,7 +90,7 @@ const getOutputAndReasoningContent = (output: string) => { const prompts = validateOutput.data; - let promptsContent: { role: string; content?: string }[] = []; + let promptsContent: { role: string; content?: string | null }[] = []; if (Array.isArray(prompts)) { promptsContent = prompts; diff --git a/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/prompt/render.tsx b/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/prompt/render.tsx index cf668cc66..782d396a2 100644 --- a/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/prompt/render.tsx +++ b/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/prompt/render.tsx @@ -2,8 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import React from 'react'; -import { isEmpty, isObject, truncate } from 'lodash-es'; -import { JsonViewer } from '@textea/json-viewer'; +import { isEmpty } from 'lodash-es'; import { I18n } from '@cozeloop/i18n-adapter'; import { handleCopy as copy } from '@cozeloop/components'; import { IconCozCopy } from '@coze-arch/coze-design/icons'; @@ -11,10 +10,10 @@ import { Button, Collapse, Typography } from '@coze-arch/coze-design'; import { type RemoveUndefinedOrString } from '../../types/utils'; import { type RawMessage, type Span, TagType } from '../../types'; -import { JSON_VIEW_CONFIG } from '../../consts/json-view'; import { SpanFieldRender } from '../../components/span-field-render'; import { RawContent } from '../../components/raw-content'; import type { Input, Output } from './index'; +import { ArgumentValueRender } from './argument-value-render'; import styles from './index.module.less'; @@ -77,35 +76,16 @@ export const PromptDataRender: PromptDataRender = { {input.arguments?.map((argu, ind: number) => (
{argu.key}
- {isObject(argu.value) ? ( - - ) : ( - - {argu.value - ? truncate(argu.value, { - length: 1000, - }) - : '-'} - - )} +
diff --git a/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/prompt/schema.ts b/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/prompt/schema.ts index 34eab4c07..fbebbfb18 100644 --- a/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/prompt/schema.ts +++ b/frontend/packages/cozeloop/observation/trace-struct-data/src/span-definition/prompt/schema.ts @@ -2,96 +2,116 @@ // SPDX-License-Identifier: Apache-2.0 import { z } from 'zod'; +import { PromptArgumentValueType } from './argument-value-render'; + +// 基础文件URL schema +const fileUrlSchema = z.object({ + name: z.string().optional(), + url: z.string().optional(), + detail: z.string().optional(), + suffix: z.string().optional(), +}); + +// 基础图片URL schema +const imageUrlSchema = z.object({ + name: z.string().optional(), + url: z.string().optional(), + detail: z.string().optional(), +}); + +// 基础部分内容 schema +const partSchema = z.object({ + type: z.string(), + text: z.string().optional(), + image_url: imageUrlSchema.optional(), + file_url: fileUrlSchema.optional(), +}); + +// 工具调用 schema +const toolCallSchema = z.object({ + id: z.string().optional(), + type: z.string(), + function: z.object({ + name: z.string(), + arguments: z.string().optional(), + }), +}); + +// 基础消息 schema +const baseMessageSchema = z.object({ + role: z.string(), + content: z.union([z.string(), z.null()]).optional(), + reasoning_content: z.string().optional(), + parts: z.array(partSchema).optional(), + name: z.string().optional(), + tool_calls: z.array(toolCallSchema).optional(), +}); + +// 输入参数 schema const inputArgumentSchema = z.array( z.object({ key: z.string(), value: z .union([ z.string(), - z.object({ - content: z.string(), - }), + z.object({ content: z.union([z.string(), z.null()]) }).optional(), + z.array(partSchema).optional(), ]) .optional(), source: z.string(), + value_type: z + .union([ + z.literal(PromptArgumentValueType.Text), + z.literal(PromptArgumentValueType.Message), + z.literal(PromptArgumentValueType.MessagePart), + ]) + .optional(), }), ); export const promptInputSchema = z.object({ arguments: z.union([inputArgumentSchema, z.null()]).optional(), - templates: z.array( - z.object({ - role: z.string(), - content: z.string().optional(), - reasoning_content: z.string().optional(), - parts: z - .array( - z.object({ - type: z.string(), - text: z.string().optional(), - image_url: z - .object({ - name: z.string().optional(), - url: z.string(), - detail: z.string().optional(), - }) - .optional(), - file_url: z - .object({ - name: z.string().optional(), - url: z.string(), - detail: z.string().optional(), - suffix: z.string().optional(), - }) - .optional(), - }), - ) - .optional(), - name: z.string().optional(), - tool_calls: z - .array( - z.object({ - id: z.string().optional(), - type: z.string(), - function: z.object({ - name: z.string(), - arguments: z.string().optional(), - }), - }), - ) - .optional(), - }), - ), + templates: z.array(baseMessageSchema), }); -const userPromptOutputSchema = z.object({ - prompts: z.union([ - z - .array( - z.object({ - role: z.string(), - content: z.string().optional(), - }), - ) - .optional(), - z.null(), - ]), +// 输出消息 schema(与输入稍有不同) +const outputPartSchema = z.object({ + type: z.string().optional(), + text: z.string().optional(), + image_url: imageUrlSchema.optional(), + file_url: fileUrlSchema.optional(), }); -const servicePromptOutputSchema = z.union([ - z.array( - z.object({ - role: z.string(), - content: z.string().optional(), - }), - ), +const outputMessageSchema = z.object({ + role: z.string(), + content: z.union([z.string(), z.null()]).optional(), + reasoning_content: z.string().optional(), + parts: z.array(outputPartSchema).optional(), +}); + +const messageArrayOrNullSchema = z.union([ + z.array(outputMessageSchema), z.null(), ]); +const userPromptOutputSchema = z.object({ + prompts: messageArrayOrNullSchema, +}); + +const servicePromptOutputSchema = messageArrayOrNullSchema; + export const promptOutputSchema = z.union([ userPromptOutputSchema, servicePromptOutputSchema, ]); +// 导出类型 +export type FileUrl = z.infer; +export type ImageUrl = z.infer; +export type Part = z.infer; +export type ToolCall = z.infer; +export type BaseMessage = z.infer; +export type OutputMessage = z.infer; export type PromptInputSchema = z.infer; export type PromptOutputSchema = z.infer; +export type MultiPartSchema = z.infer; diff --git a/frontend/packages/cozeloop/observation/trace-struct-data/src/types/index.ts b/frontend/packages/cozeloop/observation/trace-struct-data/src/types/index.ts index 3eb71fa7f..b59855f70 100644 --- a/frontend/packages/cozeloop/observation/trace-struct-data/src/types/index.ts +++ b/frontend/packages/cozeloop/observation/trace-struct-data/src/types/index.ts @@ -121,7 +121,7 @@ interface Part { export interface RawMessage { role: string; - content?: string | object; + content?: string | object | null; tool_calls?: ToolCall[]; parts?: Part[]; reasoning_content?: string; diff --git a/frontend/packages/cozeloop/observation/trace-struct-data/tsconfig.build.json b/frontend/packages/cozeloop/observation/trace-struct-data/tsconfig.build.json index 502fe6db5..cae750eba 100644 --- a/frontend/packages/cozeloop/observation/trace-struct-data/tsconfig.build.json +++ b/frontend/packages/cozeloop/observation/trace-struct-data/tsconfig.build.json @@ -16,27 +16,12 @@ { "path": "../../api-schema/tsconfig.build.json" }, - { - "path": "../../../arch/bot-typings/tsconfig.build.json" - }, { "path": "../../../arch/logger/tsconfig.build.json" }, { "path": "../../components/tsconfig.build.json" }, - { - "path": "../../../../config/eslint-config/tsconfig.build.json" - }, - { - "path": "../../../../config/stylelint-config/tsconfig.build.json" - }, - { - "path": "../../../../config/ts-config/tsconfig.build.json" - }, - { - "path": "../../../../config/vitest-config/tsconfig.build.json" - }, { "path": "../../env/tsconfig.build.json" }, diff --git a/frontend/packages/cozeloop/observation/trace-struct-data/tsconfig.misc.json b/frontend/packages/cozeloop/observation/trace-struct-data/tsconfig.misc.json index e56f732bb..f0d94b631 100644 --- a/frontend/packages/cozeloop/observation/trace-struct-data/tsconfig.misc.json +++ b/frontend/packages/cozeloop/observation/trace-struct-data/tsconfig.misc.json @@ -10,7 +10,7 @@ "target": "ES2020", "moduleResolution": "bundler" }, - "include": ["__tests__", "vitest.config.ts", "tailwind.config.ts"], + "include": ["__tests__", "vitest.config.ts", "stories", "tailwind.config.ts"], "exclude": ["./dist"], "references": [ { diff --git a/frontend/packages/cozeloop/resources/loop-lng/src/locales/observation/en-US.json b/frontend/packages/cozeloop/resources/loop-lng/src/locales/observation/en-US.json index 922bca246..28e141476 100644 --- a/frontend/packages/cozeloop/resources/loop-lng/src/locales/observation/en-US.json +++ b/frontend/packages/cozeloop/resources/loop-lng/src/locales/observation/en-US.json @@ -98,5 +98,8 @@ "task_filter_lte": "Less than or equal to", "task_filter_not_eq": "Not equal to", "task_filter_not_in": "Does not belong to", - "task_filter_not_null": "Is not empty" + "task_filter_not_null": "Is not empty", + "task_filter_thumbs_up": "Thumbs up", + "task_filter_thumbs_down": "Thumbs down", + "analytics_fetch_meta_error": "Fail to fetch metadata: {msg}" } diff --git a/frontend/packages/cozeloop/resources/loop-lng/src/locales/observation/zh-CN.json b/frontend/packages/cozeloop/resources/loop-lng/src/locales/observation/zh-CN.json index 784e3c7b4..b51c51110 100644 --- a/frontend/packages/cozeloop/resources/loop-lng/src/locales/observation/zh-CN.json +++ b/frontend/packages/cozeloop/resources/loop-lng/src/locales/observation/zh-CN.json @@ -98,5 +98,8 @@ "task_filter_lte": "小于等于", "task_filter_not_eq": "不等于", "task_filter_not_in": "不属于", - "task_filter_not_null": "不为空" + "task_filter_not_null": "不为空", + "task_filter_thumbs_up": "点赞", + "task_filter_thumbs_down": "点踩", + "analytics_fetch_meta_error": "获取元数据信息失败:{msg}" } diff --git a/rush.json b/rush.json index 850821ae1..4b0b573fd 100644 --- a/rush.json +++ b/rush.json @@ -213,10 +213,15 @@ "tags": ["team-devops", "level-3"] }, { - "packageName": "@cozeloop/observation-component-adapter", + "packageName": "@cozeloop/observation-component", "projectFolder": "frontend/packages/cozeloop/observation/trace-detail", "tags": ["team-devops", "level-3"] }, + { + "packageName": "@cozeloop/observation-component-adapter", + "projectFolder": "frontend/packages/cozeloop/observation/trace-detail-open", + "tags": ["team-devops", "level-3"] + }, { "packageName": "@cozeloop/community-base", "projectFolder": "frontend/apps/cozeloop", From 3470cc9d7b7d97e57eeccf8e81b1d1e037d7f48b Mon Sep 17 00:00:00 2001 From: madinah <497350746@qq.com> Date: Fri, 5 Sep 2025 15:29:28 +0800 Subject: [PATCH 2/2] feat(trace): add observation i18n --- frontend/apps/cozeloop/tailwind.config.ts | 1 + .../feedback/trace-detail-table.tsx | 27 ++++++++++--------- .../src/components/logic-expr/utils.ts | 8 +++--- .../observation/trace-list/src/consts/time.ts | 2 +- .../trace-list/src/utils/name-validate.ts | 9 ++++--- .../src/locales/observation/en-US.json | 21 ++++++++++++++- .../src/locales/observation/zh-CN.json | 21 ++++++++++++++- 7 files changed, 67 insertions(+), 22 deletions(-) diff --git a/frontend/apps/cozeloop/tailwind.config.ts b/frontend/apps/cozeloop/tailwind.config.ts index 78f324e31..f7133945e 100644 --- a/frontend/apps/cozeloop/tailwind.config.ts +++ b/frontend/apps/cozeloop/tailwind.config.ts @@ -19,5 +19,6 @@ export default createTailwindcssConfig({ '@cozeloop/observation-component-adapter', '@cozeloop/tag-pages', '@cozeloop/observation-component', + '@cozeloop/tag-components', ], }) as TailwindConfig; diff --git a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/feedback/trace-detail-table.tsx b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/feedback/trace-detail-table.tsx index 1749c5e6a..62f020a24 100644 --- a/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/feedback/trace-detail-table.tsx +++ b/frontend/packages/cozeloop/observation/trace-detail/src/trace-detail/components/feedback/trace-detail-table.tsx @@ -6,6 +6,7 @@ import React, { useEffect, useState } from 'react'; import dayjs from 'dayjs'; import classNames from 'classnames'; import { useUpdate } from 'ahooks'; +import { I18n } from '@cozeloop/i18n-adapter'; import LoopTableSortIcon from '@cozeloop/components/src/table/sort-icon'; import { LoopTable, UserProfile } from '@cozeloop/components'; import { useBaseURL } from '@cozeloop/biz-hooks-adapter'; @@ -30,9 +31,9 @@ import { useListAnnotations } from './hooks/use-list-annotations'; const { Text } = Typography; const SOURCE_TEXT = { - [AnnotationType.AnnotationType.AutoEvaluate]: '自动评测', - [AnnotationType.AnnotationType.ManualFeedback]: '人工标注', - [AnnotationType.AnnotationType.CozeFeedback]: 'Coze 对话', + [AnnotationType.AnnotationType.AutoEvaluate]: 'auto_evaluate', + [AnnotationType.AnnotationType.ManualFeedback]: 'manual_annotation', + [AnnotationType.AnnotationType.CozeFeedback]: 'coze_conversation', }; export const Source = ({ @@ -55,7 +56,7 @@ export const Source = ({ }} > - {SOURCE_TEXT[annotation.type ?? ''] ?? '-'} + {I18n.unsafeT(SOURCE_TEXT[annotation.type ?? ''] ?? '-')} {annotation.type === AnnotationType.AnnotationType.AutoEvaluate && (
@@ -87,9 +88,9 @@ const FeedbackResult = (props: FeedbackResultProps) => { return (
- 反馈结果 + {I18n.t('feedback_results')} - +
); @@ -142,7 +143,7 @@ export const TraceFeedBack = ({ }, [annotationRefreshKey]); const columns = [ { - title: '来源', + title: I18n.t('source'), dataIndex: 'source', width: 120, render: (_, annotation: AnnotationType.Annotation) => ( @@ -172,7 +173,7 @@ export const TraceFeedBack = ({ ), }, { - title: '更新人', + title: I18n.t('updater'), dataIndex: 'updater', width: 170, render: (_, annotation: AnnotationType.Annotation) => { @@ -186,7 +187,7 @@ export const TraceFeedBack = ({ }, }, { - title: '创建时间', + title: I18n.t('create_time'), dataIndex: 'createTime', width: 170, render: (_, annotation: AnnotationType.Annotation) => { @@ -238,7 +239,7 @@ export const TraceFeedBack = ({ }, { - title: () =>
原因
, + title: () =>
{I18n.t('reason')}
, dataIndex: 'reasoning', width: 372, render: (_, annotation: AnnotationType.Annotation) => ( @@ -279,12 +280,12 @@ export const TraceFeedBack = ({ } - title="暂无 Feedback" + title={I18n.t('no_feedback')} description={ <> {description ?? (
- 点击右上方标注数据按钮进行创建 + {I18n.t('click_annotation_button_to_create')}
)} diff --git a/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/utils.ts b/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/utils.ts index fe990fe6a..a0cad6c0c 100644 --- a/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/utils.ts +++ b/frontend/packages/cozeloop/observation/trace-list/src/components/logic-expr/utils.ts @@ -31,6 +31,8 @@ import { MANUAL_FEEDBACK_PREFIX, } from './const'; +import { I18n } from '@cozeloop/i18n-adapter'; + const assignValueWithKind = (params: { value: R; valueKind: string }) => { const { value, valueKind } = params; const defaultFieldValue = []; @@ -235,11 +237,11 @@ export const getKeyCopywriting = (key: string) => { case FilterFields.APP_ID: return 'AppName'; case FilterFields.FEEDBACK: - return 'Feedback-自动评测'; + return I18n.t('feedback_auto_evaluate'); case FilterFields.FEEDBACK_MANUAL: - return 'Feedback-人工标注'; + return I18n.t('feedback_manual_annotation'); case FilterFields.FEEDBACK_COZE: - return 'Feedback-Coze 对话'; + return I18n.t('feedback_coze_conversation'); case FilterFields.WORKFLOW_ID: return 'WorkflowName'; default: diff --git a/frontend/packages/cozeloop/observation/trace-list/src/consts/time.ts b/frontend/packages/cozeloop/observation/trace-list/src/consts/time.ts index f378fb6f6..9c99fdcb7 100644 --- a/frontend/packages/cozeloop/observation/trace-list/src/consts/time.ts +++ b/frontend/packages/cozeloop/observation/trace-list/src/consts/time.ts @@ -257,7 +257,7 @@ export const timePickerPresets = { end: () => dayjs().toDate(), }, [PresetRange.AllTime]: { - text: '全部时间', + text: I18n.t('time_all_time'), start: () => dayjs().subtract(365, 'd').toDate(), end: () => dayjs().toDate(), }, diff --git a/frontend/packages/cozeloop/observation/trace-list/src/utils/name-validate.ts b/frontend/packages/cozeloop/observation/trace-list/src/utils/name-validate.ts index cc0ab49cf..f43090697 100644 --- a/frontend/packages/cozeloop/observation/trace-list/src/utils/name-validate.ts +++ b/frontend/packages/cozeloop/observation/trace-list/src/utils/name-validate.ts @@ -1,24 +1,27 @@ // Copyright (c) 2025 coze-dev Authors // SPDX-License-Identifier: Apache-2.0 + +import { I18n } from '@cozeloop/i18n-adapter'; + const MAX_NAME_LENGTH = 20; export const validateViewName = (name: string, viewNames: string[]) => { if (name.trim() === '') { return { isValid: false, - message: '不允许为空', + message: I18n.t('validation_not_allowed_to_be_empty'), }; } if (name.trim().length > MAX_NAME_LENGTH) { return { isValid: false, - message: `名称长度不能超过${MAX_NAME_LENGTH}个字符`, + message: I18n.t('validation_name_length_limit', { num: MAX_NAME_LENGTH }), }; } if (viewNames.includes(name.trim())) { return { isValid: false, - message: '视图名称已存在', + message: I18n.t('validation_view_name_exists'), }; } return { diff --git a/frontend/packages/cozeloop/resources/loop-lng/src/locales/observation/en-US.json b/frontend/packages/cozeloop/resources/loop-lng/src/locales/observation/en-US.json index 28e141476..54ccc0a10 100644 --- a/frontend/packages/cozeloop/resources/loop-lng/src/locales/observation/en-US.json +++ b/frontend/packages/cozeloop/resources/loop-lng/src/locales/observation/en-US.json @@ -101,5 +101,24 @@ "task_filter_not_null": "Is not empty", "task_filter_thumbs_up": "Thumbs up", "task_filter_thumbs_down": "Thumbs down", - "analytics_fetch_meta_error": "Fail to fetch metadata: {msg}" + "analytics_fetch_meta_error": "Fail to fetch metadata: {msg}", + "validation_not_allowed_to_be_empty": "Empty is not allowed", + "validation_name_length_limit": "The name length cannot exceed {num} characters", + "validation_view_name_exists": "View name already exists", + "time_all_time": "All time", + "feedback_auto_evaluate": "Feedback-Auto Evaluate", + "feedback_manual_annotation": "Feedback-Manual Annotation", + "feedback_coze_conversation": "Feedback-Coze Conversation", + "auto_evaluate": "Auto Evaluate", + "manual_annotation": "Manual Annotation", + "coze_conversation": "Coze Conversation", + "feedback_results": "Feedback Results", + "refresh": "Refresh", + "update_time": "Update Time", + "source": "Source", + "updater": "Updater", + "create_time": "Create Time", + "reason": "Reason", + "no_feedback": "No Feedback", + "click_annotation_button_to_create": "Click the annotation button in the top right corner to create" } diff --git a/frontend/packages/cozeloop/resources/loop-lng/src/locales/observation/zh-CN.json b/frontend/packages/cozeloop/resources/loop-lng/src/locales/observation/zh-CN.json index b51c51110..595f787d4 100644 --- a/frontend/packages/cozeloop/resources/loop-lng/src/locales/observation/zh-CN.json +++ b/frontend/packages/cozeloop/resources/loop-lng/src/locales/observation/zh-CN.json @@ -101,5 +101,24 @@ "task_filter_not_null": "不为空", "task_filter_thumbs_up": "点赞", "task_filter_thumbs_down": "点踩", - "analytics_fetch_meta_error": "获取元数据信息失败:{msg}" + "analytics_fetch_meta_error": "获取元数据信息失败:{msg}", + "validation_not_allowed_to_be_empty": "不允许为空", + "validation_name_length_limit": "名称长度不能超过{num}个字符", + "validation_view_name_exists": "视图名称已存在", + "time_all_time": "全部时间", + "feedback_auto_evaluate": "Feedback-自动评测", + "feedback_manual_annotation": "Feedback-人工标注", + "feedback_coze_conversation": "Feedback-Coze 对话", + "auto_evaluate": "自动评测", + "manual_annotation": "人工标注", + "coze_conversation": "Coze 对话", + "feedback_results": "反馈结果", + "refresh": "刷新", + "update_time": "更新时间", + "source": "来源", + "updater": "更新人", + "create_time": "创建时间", + "reason": "原因", + "no_feedback": "暂无 Feedback", + "click_annotation_button_to_create": "点击右上方标注数据按钮进行创建" }