Merge remote-tracking branch 'origin/feature/v1.3_主agent_rxd' into test

# Conflicts:
#	src/views/agent/work-flow/components/DynamicForm.vue
This commit is contained in:
rd
2025-08-28 15:47:55 +08:00
327 changed files with 19899 additions and 1703 deletions

80
src/utils/pick-attrs.ts Normal file
View File

@ -0,0 +1,80 @@
/*
* @Author: RenXiaoDong
* @Date: 2025-08-20 23:43:17
*/
const attributes = `accept acceptcharset accesskey action allowfullscreen allowtransparency
alt async autocomplete autofocus autoplay capture cellpadding cellspacing challenge
charset checked classid class colspan cols content contenteditable contextmenu
controls coords crossorigin data datetime default defer dir disabled download draggable
enctype form formaction formenctype formmethod formnovalidate formtarget frameborder
headers height hidden high href hreflang htmlfor for httpequiv icon id inputmode integrity
is keyparams keytype kind label lang list loop low manifest marginheight marginwidth max maxlength media
mediagroup method min minlength multiple muted name novalidate nonce open
optimum pattern placeholder poster preload radiogroup readonly rel required
reversed role rowspan rows sandbox scope scoped scrolling seamless selected
shape size sizes span spellcheck src srcdoc srclang srcset start step style
summary tabindex target title type usemap value width wmode wrap`;
const eventsName = `onCopy onCut onPaste onCompositionend onCompositionstart onCompositionupdate onKeydown
onKeypress onKeyup onFocus onBlur onChange onInput onSubmit onClick onContextmenu onDoubleclick onDblclick
onDrag onDragend onDragenter onDragexit onDragleave onDragover onDragstart onDrop onMousedown
onMouseenter onMouseleave onMousemove onMouseout onMouseover onMouseup onSelect onTouchcancel
onTouchend onTouchmove onTouchstart onTouchstartPassive onTouchmovePassive onScroll onWheel onAbort onCanplay onCanplaythrough
onDurationchange onEmptied onEncrypted onEnded onError onLoadeddata onLoadedmetadata
onLoadstart onPause onPlay onPlaying onProgress onRatechange onSeeked onSeeking onStalled onSuspend onTimeupdate onVolumechange onWaiting onLoad onError`;
const propList = `${attributes} ${eventsName}`.split(/[\s\n]+/);
/* eslint-enable max-len */
const ariaPrefix = 'aria-';
const dataPrefix = 'data-';
function match(key: string, prefix: string) {
return key.indexOf(prefix) === 0;
}
export interface PickConfig {
aria?: boolean;
data?: boolean;
attr?: boolean;
}
/**
* Picker props from exist props with filter
* @param props Passed props
* @param ariaOnly boolean | { aria?: boolean; data?: boolean; attr?: boolean; } filter config
*/
export default function pickAttrs(props: object, ariaOnly: boolean | PickConfig = false) {
let mergedConfig;
if (ariaOnly === false) {
mergedConfig = {
aria: true,
data: true,
attr: true,
};
} else if (ariaOnly === true) {
mergedConfig = {
aria: true,
};
} else {
mergedConfig = {
...ariaOnly,
};
}
const attrs = {};
Object.keys(props).forEach((key) => {
if (
// Aria
(mergedConfig.aria && (key === 'role' || match(key, ariaPrefix))) ||
// Data
(mergedConfig.data && match(key, dataPrefix)) ||
// Attr
(mergedConfig.attr && (propList.includes(key) || propList.includes(key.toLowerCase())))
) {
// @ts-ignore
attrs[key] = props[key];
}
});
return attrs;
}

90
src/utils/querySSE.ts Normal file
View File

@ -0,0 +1,90 @@
import { fetchEventSource } from '@microsoft/fetch-event-source';
import type { EventSourceMessage } from '@microsoft/fetch-event-source';
import { useEnterpriseStore } from '@/stores/modules/enterprise';
import { glsWithCatch } from '@/utils/stroage';
import { BASE_PYTHON_URL } from '@/api/all/chat';
import { genRandomId } from '@/utils/tools';
const DEFAULT_SSE_URL = `${BASE_PYTHON_URL}/api/agent/runs`;
const SSE_HEADERS = {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
Accept: 'text/event-stream',
requestid: genRandomId(),
};
interface SSEConfig {
headers?: Record<string, string | number>;
method?: string;
body?: any;
handleMessage?: (data: any) => void;
handleError?: (err: any) => number | null | undefined | void;
handleClose?: () => void;
handleOpen?: (response: Response) => void;
}
/**
* 创建服务器发送事件SSE连接
* @param config SSE 配置
* @param url 可选的自定义 URL
* @returns 包含abort方法的对象用于中断SSE连接
*/
export default async (config: SSEConfig, url: string = DEFAULT_SSE_URL): Promise<{ abort: () => void }> => {
const {
body = undefined,
headers = {},
method = 'post',
handleMessage,
handleError,
handleOpen,
handleClose,
} = config;
const store = useEnterpriseStore();
// 创建AbortController实例用于中断请求
const abortController = new AbortController();
fetchEventSource(url, {
method,
// credentials: 'include',
headers: {
...SSE_HEADERS,
Authorization: glsWithCatch('accessToken'),
'enterprise-id': store.enterpriseInfo?.id?.toString(),
...headers,
},
body,
signal: abortController.signal, // 传递signal给fetchEventSource
openWhenHidden: true, // 用户切换到另一个页面后仍能保持SSE连接
onmessage(event: EventSourceMessage) {
if (event.data) {
try {
const parsedData = JSON.parse(event.data);
handleMessage?.({ ...event, data: parsedData });
} catch (error) {
console.error('Error parsing SSE message:', error);
handleError(new Error('Failed to parse SSE message'));
}
}
},
onerror(error: Error) {
// console.error('SSE error:', error);
handleError?.(error);
},
onclose() {
// console.log('SSE connection closed');
handleClose?.();
},
async onopen(response: Response) {
// console.log('onopen', response);
handleOpen?.(response);
},
});
// 返回abort方法供外部调用
return {
abort: () => abortController.abort(),
};
};

View File

@ -108,14 +108,14 @@ export function downloadByUrl(url: string, filename?: string) {
}
export function genRandomId() {
return `id_${Date.now()}_${Math.floor(Math.random() * 10000)}`;
return `id_${dayjs().unix()}_${Math.floor(Math.random() * 10000)}`;
}
export function formatFileSize(bytes: number): string {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const sizes = ['Bytes', 'kb', 'mB', 'gB', 'tB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
@ -397,7 +397,7 @@ export function getImageMainColor(imageUrl: string): Promise<string> {
const avgColor = {
r: Math.round(maxGroup.sumR / maxGroup.count),
g: Math.round(maxGroup.sumG / maxGroup.count),
b: Math.round(maxGroup.sumB / maxGroup.count)
b: Math.round(maxGroup.sumB / maxGroup.count),
};
resolve(`rgb(${avgColor.r},${avgColor.g},${avgColor.b})`);
@ -427,28 +427,38 @@ function medianCut(data: Uint8ClampedArray, levels: number): any[] {
if (a < 128) continue;
colors.push({
r, g, b,
r,
g,
b,
count: 1,
sumR: r, sumG: g, sumB: b
sumR: r,
sumG: g,
sumB: b,
});
}
// 如果没有颜色数据,返回默认白色
if (colors.length === 0) {
return [{
count: 1,
sumR: 255, sumG: 255, sumB: 255
}];
return [
{
count: 1,
sumR: 255,
sumG: 255,
sumB: 255,
},
];
}
// 开始中位数切分
let colorGroups = [{
colors,
count: colors.length,
sumR: colors.reduce((sum, c) => sum + c.r, 0),
sumG: colors.reduce((sum, c) => sum + c.g, 0),
sumB: colors.reduce((sum, c) => sum + c.b, 0)
}];
let colorGroups = [
{
colors,
count: colors.length,
sumR: colors.reduce((sum, c) => sum + c.r, 0),
sumG: colors.reduce((sum, c) => sum + c.g, 0),
sumB: colors.reduce((sum, c) => sum + c.b, 0),
},
];
for (let i = 0; i < levels; i++) {
const newGroups = [];
@ -460,12 +470,12 @@ function medianCut(data: Uint8ClampedArray, levels: number): any[] {
}
// 找出颜色范围最大的通道
const rMin = Math.min(...group.colors.map(c => c.r));
const rMax = Math.max(...group.colors.map(c => c.r));
const gMin = Math.min(...group.colors.map(c => c.g));
const gMax = Math.max(...group.colors.map(c => c.g));
const bMin = Math.min(...group.colors.map(c => c.b));
const bMax = Math.max(...group.colors.map(c => c.b));
const rMin = Math.min(...group.colors.map((c) => c.r));
const rMax = Math.max(...group.colors.map((c) => c.r));
const gMin = Math.min(...group.colors.map((c) => c.g));
const gMax = Math.max(...group.colors.map((c) => c.g));
const bMin = Math.min(...group.colors.map((c) => c.b));
const bMax = Math.max(...group.colors.map((c) => c.b));
const rRange = rMax - rMin;
const gRange = gMax - gMin;
@ -500,7 +510,7 @@ function medianCut(data: Uint8ClampedArray, levels: number): any[] {
count: group1.length,
sumR: group1.reduce((sum, c) => sum + c.r, 0),
sumG: group1.reduce((sum, c) => sum + c.g, 0),
sumB: group1.reduce((sum, c) => sum + c.b, 0)
sumB: group1.reduce((sum, c) => sum + c.b, 0),
});
newGroups.push({
@ -508,7 +518,7 @@ function medianCut(data: Uint8ClampedArray, levels: number): any[] {
count: group2.length,
sumR: group2.reduce((sum, c) => sum + c.r, 0),
sumG: group2.reduce((sum, c) => sum + c.g, 0),
sumB: group2.reduce((sum, c) => sum + c.b, 0)
sumB: group2.reduce((sum, c) => sum + c.b, 0),
});
}

13
src/utils/type.ts Normal file
View File

@ -0,0 +1,13 @@
/*
* @Author: RenXiaoDong
* @Date: 2025-08-20 23:18:36
*/
import type { PropType, VNode } from 'vue';
declare type VNodeChildAtom = VNode | string | number | boolean | null | undefined | void;
export type VueNode = VNodeChildAtom | VNodeChildAtom[] | VNode;
export function objectType<T = {}>(defaultVal?: T) {
return { type: Object as PropType<T>, default: defaultVal as T };
}

View File

@ -7,6 +7,7 @@ import router from '@/router';
import { useUserStore } from '@/stores';
import { useEnterpriseStore } from '@/stores/modules/enterprise';
import { useSidebarStore } from '@/stores/modules/side-bar';
import { useChatStore } from '@/stores/modules/chat';
// 登录
export function goUserLogin(query?: any) {
@ -15,29 +16,37 @@ export function goUserLogin(query?: any) {
export const getUserEnterpriseInfo = async () => {
const enterpriseStore = useEnterpriseStore();
const sidebarStore = useSidebarStore();
const userStore = useUserStore();
// const sidebarStore = useSidebarStore();
// const userStore = useUserStore();
await enterpriseStore.getEnterpriseInfo(); // 初始化企业信息
sidebarStore.getUserNavbarMenuList(); // 初始化navbar菜单
userStore.getUserAllowAccessRoutes(); // 初始化允许访问的路由
// sidebarStore.getUserNavbarMenuList(); // 初始化navbar菜单
// userStore.getUserAllowAccessRoutes(); // 初始化允许访问的路由
};
export async function initApp() {
const userStore = useUserStore();
const chatStore = useChatStore();
await chatStore.getAgentInfo(); // 初始化智能体信息
await userStore.getUserInfo(); // 初始化用户信息
await getUserEnterpriseInfo(); // 初始化企业信息、navbar菜单、允许访问的路由
}
// 登录处理
export async function handleUserLogin() {
const userStore = useUserStore();
const sidebarStore = useSidebarStore();
await userStore.getUserInfo(); // 初始化用户信息
await getUserEnterpriseInfo(); // 初始化企业信息、navbar菜单、允许访问的路由
await initApp();
sidebarStore.startUnreadInfoPolling(); // 初始化未读信息
handleUserHome();
}
// 首页
export function handleUserHome() {
router.push({ name: 'Home' });
export function handleUserHome(params?: any) {
router.push({ name: 'Home', params });
}
// 登出处理
@ -45,14 +54,17 @@ export function handleUserLogout() {
const userStore = useUserStore();
const enterpriseStore = useEnterpriseStore();
const sidebarStore = useSidebarStore();
const chatStore = useChatStore();
chatStore.clearAgentInfo(); // 清除智能体信息
userStore.clearUserInfo(); // 清除用户信息
userStore.clearToken(); // 清除token
enterpriseStore.clearUserEnterpriseInfo(); // 清除企业信息
sidebarStore.clearUserNavbarMenuList(); // 清除navbar菜单信息
userStore.clearUserAllowAccessRoutes(); // 清除权限路由列表
// sidebarStore.clearUserNavbarMenuList(); // 清除navbar菜单信息
// userStore.clearUserAllowAccessRoutes(); // 清除权限路由列表
sidebarStore.stopUnreadInfoPolling(); // 清除未读消息
sidebarStore.clearActiveMenuId(); // 清除active菜单id
sidebarStore.clearActiveMenuKey(); // 清除active菜单id
sidebarStore.clearMenuCollapse(); // 清除active菜单id
goUserLogin();
}