mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 10:06:51 +08:00
Merge branch 'jzh' into deploy/dev
This commit is contained in:
commit
39b4cb9008
@ -151,6 +151,12 @@ def deserialize_response(raw_data: bytes) -> Response:
|
||||
|
||||
response = Response(response=body, status=status_code)
|
||||
|
||||
# Replace Flask's default headers (e.g. Content-Type, Content-Length) with the
|
||||
# parsed ones so we faithfully reproduce the original response. Use Headers.add
|
||||
# rather than dict-style assignment so that repeated headers such as Set-Cookie
|
||||
# (and any other multi-valued header per RFC 9110) are preserved instead of
|
||||
# being overwritten.
|
||||
response.headers.clear()
|
||||
for line in lines[1:]:
|
||||
if not line:
|
||||
continue
|
||||
@ -158,6 +164,6 @@ def deserialize_response(raw_data: bytes) -> Response:
|
||||
if ":" not in line_str:
|
||||
continue
|
||||
name, value = line_str.split(":", 1)
|
||||
response.headers[name] = value.strip()
|
||||
response.headers.add(name, value.strip())
|
||||
|
||||
return response
|
||||
|
||||
@ -323,6 +323,50 @@ class TestDeserializeResponse:
|
||||
with pytest.raises(ValueError, match="Invalid status line"):
|
||||
deserialize_response(raw_data)
|
||||
|
||||
def test_deserialize_response_preserves_duplicate_set_cookie_headers(self):
|
||||
# Regression test for https://github.com/langgenius/dify/issues/35722
|
||||
# Multiple Set-Cookie headers must be preserved per RFC 9110, not collapsed
|
||||
# into a single value by dict-style assignment.
|
||||
raw_data = (
|
||||
b"HTTP/1.1 200 OK\r\n"
|
||||
b"Content-Type: text/plain\r\n"
|
||||
b"Set-Cookie: session=abc; Path=/; HttpOnly\r\n"
|
||||
b"Set-Cookie: tracking=xyz; Path=/; Secure\r\n"
|
||||
b"\r\n"
|
||||
b"ok"
|
||||
)
|
||||
|
||||
response = deserialize_response(raw_data)
|
||||
|
||||
cookies = response.headers.getlist("Set-Cookie")
|
||||
assert cookies == [
|
||||
"session=abc; Path=/; HttpOnly",
|
||||
"tracking=xyz; Path=/; Secure",
|
||||
]
|
||||
# Single-valued headers should still be readable normally.
|
||||
assert response.headers.get("Content-Type") == "text/plain"
|
||||
|
||||
def test_deserialize_response_preserves_duplicate_generic_headers(self):
|
||||
# Any header name (not just Set-Cookie) may legitimately repeat; verify the
|
||||
# parser preserves all values rather than overwriting earlier ones.
|
||||
raw_data = b"HTTP/1.1 200 OK\r\nX-Custom: first\r\nX-Custom: second\r\n\r\n"
|
||||
|
||||
response = deserialize_response(raw_data)
|
||||
|
||||
assert response.headers.getlist("X-Custom") == ["first", "second"]
|
||||
|
||||
def test_deserialize_response_does_not_inject_default_content_type(self):
|
||||
# Flask's Response constructor adds a default Content-Type header. When the
|
||||
# raw response has no Content-Type, the parsed response should not silently
|
||||
# gain one from the framework default.
|
||||
raw_data = b"HTTP/1.1 204 No Content\r\nX-Trace-Id: abc\r\n\r\n"
|
||||
|
||||
response = deserialize_response(raw_data)
|
||||
|
||||
header_names = [name for name, _ in response.headers.items()]
|
||||
assert "Content-Type" not in header_names
|
||||
assert response.headers.get("X-Trace-Id") == "abc"
|
||||
|
||||
def test_roundtrip_response(self):
|
||||
# Test that serialize -> deserialize produces equivalent response
|
||||
original_response = Response(
|
||||
|
||||
@ -1744,11 +1744,6 @@
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/base/prompt-editor/plugins/component-picker-block/menu.tsx": {
|
||||
"erasable-syntax-only/parameter-properties": {
|
||||
"count": 1
|
||||
|
||||
@ -29,6 +29,7 @@ import {
|
||||
} from 'lexical'
|
||||
import * as React from 'react'
|
||||
import { GeneratorType } from '@/app/components/app/configuration/config/automatic/types'
|
||||
import { VAR_REFERENCE_CHILD_POPUP_CLASS_NAME } from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars'
|
||||
import { VarType } from '@/app/components/workflow/types'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { EventEmitterContextProvider } from '@/context/event-emitter-provider'
|
||||
@ -928,5 +929,46 @@ describe('ComponentPicker (component-picker-block/index.tsx)', () => {
|
||||
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('does not hide the menu when focus moves into a variable child popup', async () => {
|
||||
const captures: Captures = { editor: null, eventEmitter: null }
|
||||
|
||||
render((
|
||||
<MinimalEditor
|
||||
triggerString="/"
|
||||
workflowVariableBlock={makeWorkflowVariableBlock({}, [
|
||||
makeWorkflowVarNode('node-1', 'Node 1', [
|
||||
makeWorkflowNodeVar('payload', VarType.object, [makeWorkflowNodeVar('child', VarType.string)]),
|
||||
]),
|
||||
])}
|
||||
captures={captures}
|
||||
/>
|
||||
))
|
||||
|
||||
const editor = await waitForEditor(captures)
|
||||
await setEditorText(editor, '/', true)
|
||||
expect(await screen.findByText('payload')).toBeInTheDocument()
|
||||
|
||||
vi.useFakeTimers()
|
||||
|
||||
const popupTarget = document.createElement('button')
|
||||
const popup = document.createElement('div')
|
||||
popup.classList.add(VAR_REFERENCE_CHILD_POPUP_CLASS_NAME)
|
||||
popup.appendChild(popupTarget)
|
||||
document.body.appendChild(popup)
|
||||
|
||||
act(() => {
|
||||
editor.dispatchCommand(BLUR_COMMAND, new FocusEvent('blur-sm', { relatedTarget: popupTarget }))
|
||||
})
|
||||
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(200)
|
||||
})
|
||||
|
||||
expect(screen.queryByText('payload')).toBeInTheDocument()
|
||||
|
||||
popup.remove()
|
||||
vi.useRealTimers()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -14,6 +14,7 @@ import type {
|
||||
WorkflowVariableBlockType,
|
||||
} from '../../types'
|
||||
import type { PickerBlockMenuOption } from './menu'
|
||||
import type { EventEmitterValue } from '@/context/event-emitter'
|
||||
import {
|
||||
flip,
|
||||
offset,
|
||||
@ -39,7 +40,7 @@ import {
|
||||
} from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { GeneratorType } from '@/app/components/app/configuration/config/automatic/types'
|
||||
import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars'
|
||||
import VarReferenceVars, { VAR_REFERENCE_CHILD_POPUP_CLASS_NAME } from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { useBasicTypeaheadTriggerMatch } from '../../hooks'
|
||||
import { $splitNodeContainingQuery } from '../../utils'
|
||||
@ -119,7 +120,9 @@ const ComponentPicker = ({
|
||||
(event) => {
|
||||
clearBlurTimer()
|
||||
const target = event?.relatedTarget as HTMLElement
|
||||
if (!target?.classList?.contains('var-search-input'))
|
||||
const isVariableMenuTarget = target?.classList?.contains('var-search-input')
|
||||
|| target?.closest?.(`.${VAR_REFERENCE_CHILD_POPUP_CLASS_NAME}`)
|
||||
if (!isVariableMenuTarget)
|
||||
blurTimerRef.current = setTimeout(() => setBlurHidden(true), 200)
|
||||
return false
|
||||
},
|
||||
@ -143,8 +146,8 @@ const ComponentPicker = ({
|
||||
}
|
||||
}, [editor, clearBlurTimer])
|
||||
|
||||
eventEmitter?.useSubscription((v: any) => {
|
||||
if (v.type === INSERT_VARIABLE_VALUE_BLOCK_COMMAND)
|
||||
eventEmitter?.useSubscription((v: EventEmitterValue) => {
|
||||
if (typeof v !== 'string' && v.type === INSERT_VARIABLE_VALUE_BLOCK_COMMAND && typeof v.payload === 'string')
|
||||
editor.dispatchCommand(INSERT_VARIABLE_VALUE_BLOCK_COMMAND, `{{${v.payload}}}`)
|
||||
})
|
||||
|
||||
@ -303,7 +306,7 @@ const ComponentPicker = ({
|
||||
}
|
||||
</>
|
||||
)
|
||||
}, [blurHidden, allFlattenOptions.length, workflowVariableBlock?.show, floatingStyles, isPositioned, refs, workflowVariableOptions, isSupportFileVar, handleClose, currentBlock?.generatorType, handleSelectWorkflowVariable, queryString, workflowVariableBlock?.showManageInputField, workflowVariableBlock?.onManageInputField])
|
||||
}, [blurHidden, allFlattenOptions.length, workflowVariableBlock?.show, floatingStyles, isPositioned, refs, workflowVariableOptions, isSupportFileVar, handleClose, currentBlock?.generatorType, handleSelectWorkflowVariable, queryString, triggerString, workflowVariableBlock?.showManageInputField, workflowVariableBlock?.onManageInputField])
|
||||
|
||||
return (
|
||||
<LexicalTypeaheadMenuPlugin
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { EvaluationResourceProps } from '../../types'
|
||||
import type { EvaluationLog, EvaluationLogFile } from '@/types/evaluation'
|
||||
import type { EvaluationLog, EvaluationRunStatus } from '@/types/evaluation'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
DropdownMenu,
|
||||
@ -11,22 +11,35 @@ import { keepPreviousData, useMutation, useQuery } from '@tanstack/react-query'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Pagination from '@/app/components/base/pagination'
|
||||
import useTimestamp from '@/hooks/use-timestamp'
|
||||
import { consoleClient, consoleQuery } from '@/service/client'
|
||||
import { useMembers } from '@/service/use-common'
|
||||
import { downloadUrl } from '@/utils/download'
|
||||
import { useEvaluationResource, useEvaluationStore } from '../../store'
|
||||
|
||||
const PAGE_SIZE = 16
|
||||
const LOADING_ROW_IDS = ['1', '2', '3', '4', '5', '6']
|
||||
const CREATED_AT_FORMAT = 'YYYY-MM-DD'
|
||||
|
||||
const formatCreatedAt = (createdAt: string) => {
|
||||
if (!createdAt)
|
||||
type FormatTimestamp = (value: number, format: string) => string
|
||||
|
||||
const STATUS_ICON_CLASS_NAMES: Record<EvaluationRunStatus, string> = {
|
||||
pending: 'i-ri-time-line text-text-tertiary',
|
||||
running: 'i-ri-loader-4-line animate-spin text-text-accent',
|
||||
completed: 'i-ri-checkbox-circle-fill text-util-colors-green-green-600',
|
||||
failed: 'i-ri-close-circle-fill text-text-destructive',
|
||||
cancelled: 'i-ri-forbid-2-line text-text-tertiary',
|
||||
}
|
||||
|
||||
const formatCreatedAt = (createdAt: number | null | undefined, formatTime: FormatTimestamp) => {
|
||||
if (createdAt == null)
|
||||
return '-'
|
||||
|
||||
return createdAt.includes('T') ? createdAt.slice(0, 10) : createdAt
|
||||
return formatTime(createdAt, CREATED_AT_FORMAT)
|
||||
}
|
||||
|
||||
const getLogRunId = (record: EvaluationLog) => {
|
||||
return record.run_id ?? record.evaluation_run_id ?? record.id ?? null
|
||||
return record.id
|
||||
}
|
||||
|
||||
const HistoryTab = ({
|
||||
@ -34,6 +47,8 @@ const HistoryTab = ({
|
||||
resourceId,
|
||||
}: EvaluationResourceProps) => {
|
||||
const { t } = useTranslation('evaluation')
|
||||
const { formatTime } = useTimestamp()
|
||||
const { data: membersData } = useMembers()
|
||||
const [page, setPage] = useState(0)
|
||||
const resource = useEvaluationResource(resourceType, resourceId)
|
||||
const setSelectedRunId = useEvaluationStore(state => state.setSelectedRunId)
|
||||
@ -54,19 +69,22 @@ const HistoryTab = ({
|
||||
placeholderData: keepPreviousData,
|
||||
})
|
||||
const fileDownloadMutation = useMutation({
|
||||
mutationFn: async (file: EvaluationLogFile) => {
|
||||
mutationFn: async (fileId: string) => {
|
||||
const fileInfo = await consoleClient.evaluation.file({
|
||||
params: {
|
||||
targetType: resourceType,
|
||||
targetId: resourceId,
|
||||
fileId: file.id,
|
||||
fileId,
|
||||
},
|
||||
})
|
||||
|
||||
downloadUrl({ url: fileInfo.download_url, fileName: file.name })
|
||||
downloadUrl({ url: fileInfo.download_url, fileName: fileInfo.name })
|
||||
},
|
||||
})
|
||||
const records = useMemo(() => logsQuery.data?.data ?? [], [logsQuery.data?.data])
|
||||
const memberNameById = useMemo(() => {
|
||||
return new Map((membersData?.accounts ?? []).map(member => [member.id, member.name]))
|
||||
}, [membersData?.accounts])
|
||||
const total = logsQuery.data?.total ?? 0
|
||||
const isInitialLoading = logsQuery.isLoading && !logsQuery.data
|
||||
|
||||
@ -85,8 +103,7 @@ const HistoryTab = ({
|
||||
<table className="w-full table-fixed border-collapse overflow-hidden rounded-md">
|
||||
<colgroup>
|
||||
<col className="w-[120px]" />
|
||||
<col className="w-[95px]" />
|
||||
<col className="w-[80px]" />
|
||||
<col className="w-[120px]" />
|
||||
<col className="w-[67px]" />
|
||||
<col className="w-[40px]" />
|
||||
</colgroup>
|
||||
@ -99,7 +116,6 @@ const HistoryTab = ({
|
||||
</span>
|
||||
</th>
|
||||
<th className="h-7 px-3 text-left system-xs-medium-uppercase text-text-tertiary">{t('history.columns.creator')}</th>
|
||||
<th className="h-7 px-3 text-left system-xs-medium-uppercase text-text-tertiary">{t('history.columns.version')}</th>
|
||||
<th className="h-7 px-3 text-left system-xs-medium-uppercase text-text-tertiary">{t('history.columns.status')}</th>
|
||||
<th className="h-7 text-center text-text-tertiary">
|
||||
<span aria-hidden="true" className="i-ri-download-2-line inline-block h-3.5 w-3.5" />
|
||||
@ -109,14 +125,14 @@ const HistoryTab = ({
|
||||
<tbody>
|
||||
{isInitialLoading && LOADING_ROW_IDS.map(rowId => (
|
||||
<tr key={rowId} className="border-b border-divider-subtle">
|
||||
<td colSpan={5} className="h-10 px-3">
|
||||
<td colSpan={4} className="h-10 px-3">
|
||||
<div className="h-4 animate-pulse rounded bg-background-section" />
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{!isInitialLoading && records.map(record => (
|
||||
<tr
|
||||
key={`${record.created_at}-${record.test_file.id}`}
|
||||
key={record.id}
|
||||
className={cn(
|
||||
'border-b border-divider-subtle',
|
||||
getLogRunId(record) && 'cursor-pointer hover:bg-state-base-hover',
|
||||
@ -128,13 +144,12 @@ const HistoryTab = ({
|
||||
setSelectedRunId(resourceType, resourceId, runId)
|
||||
}}
|
||||
>
|
||||
<td className="h-10 truncate px-3 system-sm-regular text-text-secondary">{formatCreatedAt(record.created_at)}</td>
|
||||
<td className="h-10 truncate px-3 system-sm-regular text-text-secondary">{record.created_by}</td>
|
||||
<td className="h-10 truncate px-3 system-sm-regular text-text-secondary">{record.version || '-'}</td>
|
||||
<td className="h-10 px-3 text-center">
|
||||
{record.result_file
|
||||
? <span aria-label={t('history.status.completed')} className="i-ri-checkbox-circle-fill inline-block h-4 w-4 text-util-colors-green-green-600" />
|
||||
: <span aria-label={t('history.status.running')} className="i-ri-loader-4-line inline-block h-4 w-4 animate-spin text-text-accent" />}
|
||||
<td className="h-10 truncate px-3 system-sm-regular text-text-secondary">{formatCreatedAt(record.created_at, formatTime)}</td>
|
||||
<td className="h-10 truncate px-3 system-sm-regular text-text-secondary">{memberNameById.get(record.created_by) ?? record.created_by}</td>
|
||||
<td className="h-10 px-3">
|
||||
<div className="flex h-10 items-center justify-center">
|
||||
<span aria-label={t(`history.status.${record.status}`)} className={cn('inline-block h-4 w-4', STATUS_ICON_CLASS_NAMES[record.status])} />
|
||||
</div>
|
||||
</td>
|
||||
<td className="h-10 text-center">
|
||||
<DropdownMenu>
|
||||
@ -153,9 +168,11 @@ const HistoryTab = ({
|
||||
<DropdownMenuContent popupClassName="w-[180px] rounded-lg border-[0.5px] border-components-panel-border py-1 shadow-lg">
|
||||
<DropdownMenuItem
|
||||
className="gap-2"
|
||||
disabled={!record.dataset_file_id}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
fileDownloadMutation.mutate(record.test_file)
|
||||
if (record.dataset_file_id)
|
||||
fileDownloadMutation.mutate(record.dataset_file_id)
|
||||
}}
|
||||
>
|
||||
<span aria-hidden="true" className="i-ri-file-download-line h-4 w-4" />
|
||||
@ -163,11 +180,11 @@ const HistoryTab = ({
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="gap-2"
|
||||
disabled={!record.result_file}
|
||||
disabled={!record.result_file_id}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
if (record.result_file)
|
||||
fileDownloadMutation.mutate(record.result_file)
|
||||
if (record.result_file_id)
|
||||
fileDownloadMutation.mutate(record.result_file_id)
|
||||
}}
|
||||
>
|
||||
<span aria-hidden="true" className="i-ri-download-2-line h-4 w-4" />
|
||||
|
||||
@ -43,7 +43,7 @@ const Field: FC<Props> = ({
|
||||
disabled={depth !== MAX_DEPTH + 1}
|
||||
render={(
|
||||
<div
|
||||
className={cn('flex items-center justify-between rounded-md pr-2', !readonly && 'hover:bg-state-base-hover', depth !== MAX_DEPTH + 1 && 'cursor-pointer')}
|
||||
className={cn('flex items-center justify-between rounded-md pr-2 outline-none focus:outline-none focus-visible:outline-none', !readonly && 'hover:bg-state-base-hover', depth !== MAX_DEPTH + 1 && 'cursor-pointer')}
|
||||
onMouseDown={() => !readonly && onSelect?.([...valueSelector, name])}
|
||||
>
|
||||
<div className="flex grow items-stretch">
|
||||
|
||||
@ -29,6 +29,7 @@ import {
|
||||
} from './var-reference-vars.helpers'
|
||||
|
||||
const VAR_SEARCH_INPUT_CLASS_NAME = 'var-search-input'
|
||||
export const VAR_REFERENCE_CHILD_POPUP_CLASS_NAME = 'var-reference-vars-child-popup'
|
||||
|
||||
const resolveValueSelector = ({
|
||||
itemData,
|
||||
@ -210,7 +211,7 @@ const Item: FC<ItemProps> = ({
|
||||
className={cn(
|
||||
(isObj || isStructureOutput) ? 'pr-1' : 'pr-[18px]',
|
||||
(isHovering || isSelected) && ((isObj || isStructureOutput) ? 'bg-components-panel-on-panel-item-bg-hover' : 'bg-state-base-hover'),
|
||||
'relative flex h-6 w-full cursor-pointer items-center rounded-md pl-3',
|
||||
'relative flex h-6 w-full cursor-pointer items-center rounded-md pl-3 outline-none focus:outline-none focus-visible:outline-none',
|
||||
className,
|
||||
)}
|
||||
data-selected={isSelected ? 'true' : 'false'}
|
||||
@ -263,7 +264,7 @@ const Item: FC<ItemProps> = ({
|
||||
<PopoverContent
|
||||
placement="left-start"
|
||||
sideOffset={0}
|
||||
popupClassName="border-none bg-transparent p-0 shadow-none backdrop-blur-none"
|
||||
popupClassName={cn(VAR_REFERENCE_CHILD_POPUP_CLASS_NAME, 'border-none bg-transparent p-0 shadow-none backdrop-blur-none')}
|
||||
positionerProps={{
|
||||
style: {
|
||||
zIndex: zIndex || 100,
|
||||
|
||||
@ -74,7 +74,10 @@
|
||||
"history.empty": "No test history yet",
|
||||
"history.latestVersion": "Latest",
|
||||
"history.searchPlaceholder": "Search",
|
||||
"history.status.cancelled": "Cancelled",
|
||||
"history.status.completed": "Completed",
|
||||
"history.status.failed": "Failed",
|
||||
"history.status.pending": "Pending",
|
||||
"history.status.running": "Running",
|
||||
"history.title": "Test History",
|
||||
"judgeModel.description": "Choose the model used to score your evaluation results.",
|
||||
|
||||
@ -74,7 +74,10 @@
|
||||
"history.empty": "还没有测试历史",
|
||||
"history.latestVersion": "最新",
|
||||
"history.searchPlaceholder": "搜索",
|
||||
"history.status.cancelled": "已取消",
|
||||
"history.status.completed": "已完成",
|
||||
"history.status.failed": "失败",
|
||||
"history.status.pending": "等待中",
|
||||
"history.status.running": "运行中",
|
||||
"history.title": "测试历史",
|
||||
"judgeModel.description": "选择用于打分和判定评测结果的模型。",
|
||||
|
||||
@ -61,6 +61,20 @@ export type EvaluationRunRequest = EvaluationConfigData & {
|
||||
|
||||
export type EvaluationRunStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'
|
||||
|
||||
export type EvaluationJudgmentMetricsSummary = {
|
||||
enabled: boolean
|
||||
logical_operator: 'and' | 'or'
|
||||
configured_conditions: number
|
||||
evaluated_items: number
|
||||
passed_items: number
|
||||
failed_items: number
|
||||
pass_rate: number
|
||||
}
|
||||
|
||||
export type EvaluationMetricsSummary = Record<string, unknown> & {
|
||||
_judgment?: EvaluationJudgmentMetricsSummary
|
||||
}
|
||||
|
||||
export type EvaluationRun = {
|
||||
id: string
|
||||
tenant_id: string
|
||||
@ -74,7 +88,7 @@ export type EvaluationRun = {
|
||||
completed_items: number
|
||||
failed_items: number
|
||||
progress: number
|
||||
metrics_summary: Record<string, unknown>
|
||||
metrics_summary: EvaluationMetricsSummary
|
||||
error: string | null
|
||||
created_by: string
|
||||
started_at: number | null
|
||||
@ -82,21 +96,7 @@ export type EvaluationRun = {
|
||||
created_at: number
|
||||
}
|
||||
|
||||
export type EvaluationLogFile = {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export type EvaluationLog = {
|
||||
id?: string
|
||||
run_id?: string
|
||||
evaluation_run_id?: string
|
||||
created_at: string
|
||||
created_by: string
|
||||
test_file: EvaluationLogFile
|
||||
result_file: EvaluationLogFile | null
|
||||
version: string
|
||||
}
|
||||
export type EvaluationLog = EvaluationRun
|
||||
|
||||
export type EvaluationRunMetric = {
|
||||
name?: string
|
||||
|
||||
Loading…
Reference in New Issue
Block a user