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:
80
src/utils/pick-attrs.ts
Normal file
80
src/utils/pick-attrs.ts
Normal 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
90
src/utils/querySSE.ts
Normal 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(),
|
||||
};
|
||||
};
|
||||
@ -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
13
src/utils/type.ts
Normal 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 };
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user