feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type ReactNode } from 'react';
|
||||
|
||||
import {
|
||||
type CSpanSingle,
|
||||
type CSPanBatch,
|
||||
type CSpan,
|
||||
getTokens,
|
||||
getSpanProp,
|
||||
type FieldItem,
|
||||
fieldItemHandlers,
|
||||
} from '@coze-devops/common-modules/query-trace';
|
||||
import { checkIsBatchBasicCSpan } from '@coze-devops/common-modules/query-trace';
|
||||
import { I18n, type I18nKeysNoOptionsType } from '@coze-arch/i18n';
|
||||
import { Tooltip } from '@coze-arch/bot-semi';
|
||||
|
||||
import { SPAN_STATUS_CONFIG_MAP } from '../consts/span';
|
||||
import { EMPTY_TEXT } from '../consts';
|
||||
import { formatTime } from '.';
|
||||
|
||||
const getLatencyFirst = (_span: CSpan) => {
|
||||
if (_span === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (checkIsBatchBasicCSpan(_span)) {
|
||||
const span = _span as CSPanBatch;
|
||||
let startTimeFirstResp = Number.POSITIVE_INFINITY;
|
||||
span.spans.forEach(subSpan => {
|
||||
if (
|
||||
subSpan.extra !== undefined &&
|
||||
'start_time_first_resp' in subSpan.extra &&
|
||||
subSpan.extra?.start_time_first_resp !== '0'
|
||||
) {
|
||||
startTimeFirstResp = Math.min(
|
||||
startTimeFirstResp,
|
||||
Number(subSpan.extra?.start_time_first_resp),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if (startTimeFirstResp === Number.POSITIVE_INFINITY) {
|
||||
return undefined;
|
||||
}
|
||||
return startTimeFirstResp - span.start_time;
|
||||
} else {
|
||||
const span = _span as CSpanSingle;
|
||||
if (
|
||||
span.extra !== undefined &&
|
||||
'start_time_first_resp' in span.extra &&
|
||||
span.extra?.start_time_first_resp !== '0'
|
||||
) {
|
||||
return Number(span?.extra?.start_time_first_resp) - span.start_time;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getFieldStatus = (span: CSpan): FieldItem => {
|
||||
const { status } = span;
|
||||
const { label } = SPAN_STATUS_CONFIG_MAP[status] ?? {};
|
||||
return {
|
||||
key: I18n.t('analytic_query_status'),
|
||||
value: label ? I18n.t(label as I18nKeysNoOptionsType) : undefined,
|
||||
};
|
||||
};
|
||||
|
||||
const getFieldLatencyFirst = (span: CSpan): FieldItem => {
|
||||
const latencyFirst = getLatencyFirst(span);
|
||||
return {
|
||||
key: I18n.t('analytic_query_latencyfirst'),
|
||||
value: latencyFirst !== undefined ? `${latencyFirst}ms` : EMPTY_TEXT,
|
||||
};
|
||||
};
|
||||
|
||||
const getFieldFirstResponseTime = (span: CSpan): FieldItem => {
|
||||
const startTimeFirstResp = getSpanProp(span, 'start_time_first_resp');
|
||||
return {
|
||||
key: I18n.t('analytic_query_firstrestime'),
|
||||
value:
|
||||
!startTimeFirstResp || startTimeFirstResp === '0'
|
||||
? '-'
|
||||
: formatTime(Number(startTimeFirstResp)),
|
||||
};
|
||||
};
|
||||
|
||||
const getFieldTokens = (span: CSpan): FieldItem => {
|
||||
const genValueRender = (
|
||||
inputTokens?: number,
|
||||
outputTokens?: number,
|
||||
): ReactNode => {
|
||||
if (inputTokens !== undefined && outputTokens !== undefined) {
|
||||
return (
|
||||
<Tooltip
|
||||
content={
|
||||
<article>
|
||||
<div className="whitespace-nowrap">
|
||||
Input Tokens: {inputTokens}
|
||||
</div>
|
||||
<div className="whitespace-nowrap">
|
||||
Output Tokens: {outputTokens}
|
||||
</div>
|
||||
</article>
|
||||
}
|
||||
position="bottom"
|
||||
>
|
||||
<div style={{ fontSize: 12 }}>{inputTokens + outputTokens}</div>
|
||||
</Tooltip>
|
||||
);
|
||||
} else {
|
||||
return EMPTY_TEXT;
|
||||
}
|
||||
};
|
||||
const { input_tokens: inputTokens, output_tokens: outputTokens } =
|
||||
getTokens(span);
|
||||
return {
|
||||
key: I18n.t('analytic_query_tokens'),
|
||||
value: genValueRender(inputTokens, outputTokens),
|
||||
};
|
||||
};
|
||||
|
||||
const getFieldLogId = (span: CSpan): FieldItem => ({
|
||||
key: I18n.t('analytic_query_logid'),
|
||||
value: getSpanProp(span, 'log_id') as string,
|
||||
});
|
||||
|
||||
export const fieldHandlers = {
|
||||
...fieldItemHandlers,
|
||||
status: getFieldStatus,
|
||||
latency_first: getFieldLatencyFirst,
|
||||
first_response_time: getFieldFirstResponseTime,
|
||||
tokens: getFieldTokens,
|
||||
log_id: getFieldLogId,
|
||||
};
|
||||
|
||||
export type FieldType = keyof typeof fieldHandlers;
|
||||
135
frontend/packages/devops/debug/debug-panel/src/utils/index.ts
Normal file
135
frontend/packages/devops/debug/debug-panel/src/utils/index.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import JSONBig from 'json-bigint';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import dayjs from 'dayjs';
|
||||
import { type ob_query_trace } from '@coze-arch/bot-api/ob_query_api';
|
||||
|
||||
import { type QueryFilterItemId, type UTCTimeInfo } from '../typings';
|
||||
import {
|
||||
DATE_FILTERING_DAYS_NUMBER,
|
||||
FILTERING_OPTION_ALL,
|
||||
TIME_MINUTE,
|
||||
} from '../consts';
|
||||
|
||||
dayjs.extend(utc);
|
||||
|
||||
const jsonBig = JSONBig({ storeAsString: true });
|
||||
|
||||
/**
|
||||
* 转换时间戳为当前格式化当前时区时间
|
||||
* @param timestamp string | number
|
||||
* @returns UTCTimeInfo
|
||||
*/
|
||||
export const getTimeInCurrentTimeZone = (
|
||||
timestamp: string | number,
|
||||
): UTCTimeInfo => {
|
||||
const utcDate = dayjs.utc(timestamp);
|
||||
const localDate = utcDate.local();
|
||||
const offset = localDate.utcOffset();
|
||||
const offsetString = `UTC${offset >= 0 ? '+' : '-'}${Math.abs(
|
||||
offset / TIME_MINUTE,
|
||||
)}`;
|
||||
const dateString = localDate.format('MM-DD HH:mm');
|
||||
return {
|
||||
timeOffsetString: offsetString,
|
||||
dateString,
|
||||
};
|
||||
};
|
||||
|
||||
export const getPastWeekDates = (): string[] => {
|
||||
const today = dayjs();
|
||||
const dateList: string[] = [];
|
||||
for (let i = 0; i < DATE_FILTERING_DAYS_NUMBER; i++) {
|
||||
const pastDay = today.subtract(i, 'day');
|
||||
dateList.push(pastDay.format('YYYY-MM-DD'));
|
||||
}
|
||||
return dateList;
|
||||
};
|
||||
|
||||
/**
|
||||
* 从格式化时间提取其当前对应的开始/结束时间戳
|
||||
* @param formattedDate QueryFilterItemId
|
||||
* @returns DailyTime
|
||||
*/
|
||||
export const getDailyTimestampByDate = (
|
||||
formattedDate?: QueryFilterItemId,
|
||||
): Pick<ob_query_trace.ListDebugQueriesRequest, 'startAtMS' | 'endAtMS'> => {
|
||||
if (formattedDate === FILTERING_OPTION_ALL) {
|
||||
const today = dayjs();
|
||||
return {
|
||||
startAtMS: today
|
||||
.subtract(DATE_FILTERING_DAYS_NUMBER - 1, 'day')
|
||||
.startOf('day')
|
||||
.valueOf()
|
||||
.toString(),
|
||||
endAtMS: today.endOf('day').valueOf().toString(),
|
||||
};
|
||||
} else {
|
||||
const date = dayjs(formattedDate);
|
||||
return {
|
||||
startAtMS: date.startOf('day').valueOf().toString(),
|
||||
endAtMS: date.endOf('day').valueOf().toString(),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const textWithFallback = (text?: string | number) =>
|
||||
text && text !== '' ? text : '-';
|
||||
|
||||
export const formatTime = (timestamp?: number | string) =>
|
||||
dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss');
|
||||
|
||||
export const isJsonString = (str: string) => {
|
||||
try {
|
||||
const jsonData = JSON.parse(str);
|
||||
if (Object.prototype.toString.call(jsonData) !== '[object Object]') {
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export const isDebugShowJsonString = (str: string) => {
|
||||
try {
|
||||
const jsonData = JSON.parse(str);
|
||||
if (
|
||||
Object.prototype.toString.call(jsonData) !== '[object Object]' &&
|
||||
Object.prototype.toString.call(jsonData) !== '[object Array]'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export const jsonParseWithBigNumber = (jsonString: string) =>
|
||||
JSON.parse(JSON.stringify(jsonBig.parse(jsonString)));
|
||||
|
||||
export const jsonParse = (
|
||||
jsonString: string,
|
||||
): Record<string, unknown> | string | unknown[] => {
|
||||
if (isDebugShowJsonString(jsonString)) {
|
||||
return jsonParseWithBigNumber(jsonString);
|
||||
} else {
|
||||
return jsonString;
|
||||
}
|
||||
};
|
||||
74
frontend/packages/devops/debug/debug-panel/src/utils/span.ts
Normal file
74
frontend/packages/devops/debug/debug-panel/src/utils/span.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { span2CSpan } from '@coze-devops/common-modules/query-trace';
|
||||
import {
|
||||
checkIsBatchBasicCSpan,
|
||||
type CSPanBatch,
|
||||
type CSpan,
|
||||
type CSpanSingle,
|
||||
} from '@coze-devops/common-modules/query-trace';
|
||||
import {
|
||||
type Span,
|
||||
type TraceAdvanceInfo,
|
||||
} from '@coze-arch/bot-api/ob_query_api';
|
||||
|
||||
export const getSpanProp = (span: CSpan, key: string) => {
|
||||
if (checkIsBatchBasicCSpan(span)) {
|
||||
const batchSpan = span as CSPanBatch;
|
||||
return (
|
||||
batchSpan[key as keyof CSPanBatch] ??
|
||||
batchSpan.spans[0]?.extra?.[key as keyof CSPanBatch['spans'][0]['extra']]
|
||||
);
|
||||
} else {
|
||||
const singleSpan = span as CSpanSingle;
|
||||
return (
|
||||
singleSpan[key as keyof CSpanSingle] ??
|
||||
singleSpan.extra?.[key as keyof CSpanSingle['extra']]
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 加强原始Span信息(注入服务端采集的token、status等信息)
|
||||
* @param originSpans Span[]
|
||||
* @param traceAdvanceInfo TraceAdvanceInfo[]
|
||||
* @returns CSpan[]
|
||||
*/
|
||||
export const enhanceOriginalSpan = (
|
||||
originSpans: Span[],
|
||||
traceAdvanceInfo: TraceAdvanceInfo[],
|
||||
): CSpan[] => {
|
||||
const traceAdvanceInfoMap: Record<string, TraceAdvanceInfo> =
|
||||
traceAdvanceInfo.reduce<Record<string, TraceAdvanceInfo>>((pre, cur) => {
|
||||
pre[cur.trace_id] = cur;
|
||||
return pre;
|
||||
}, {});
|
||||
const traceCSpans = originSpans.map(item => span2CSpan(item));
|
||||
const enhancedOverallSpans: CSpan[] = traceCSpans.map(item => {
|
||||
const {
|
||||
tokens: { input, output },
|
||||
status,
|
||||
} = traceAdvanceInfoMap[item.trace_id];
|
||||
return {
|
||||
...item,
|
||||
status,
|
||||
input_tokens_sum: input,
|
||||
output_tokens_sum: output,
|
||||
};
|
||||
});
|
||||
return enhancedOverallSpans;
|
||||
};
|
||||
23
frontend/packages/devops/debug/debug-panel/src/utils/url.ts
Normal file
23
frontend/packages/devops/debug/debug-panel/src/utils/url.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//
|
||||
export function windowOpen({ url, target }: { url: string; target?: string }) {
|
||||
const element = document.createElement('a');
|
||||
element.target = target || '_blank';
|
||||
element.href = url;
|
||||
element.click();
|
||||
}
|
||||
Reference in New Issue
Block a user