fix: expand the model after deploying from catalog
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
import { atom } from 'jotai';
|
||||||
|
|
||||||
|
// models expand keys: create, update , delete,
|
||||||
|
export const modelsExpandKeysAtom = atom<string[]>([]);
|
||||||
@@ -71,6 +71,9 @@ const AutoImage: React.FC<
|
|||||||
}, [props.onLoad]);
|
}, [props.onLoad]);
|
||||||
|
|
||||||
const handleOnError = () => {
|
const handleOnError = () => {
|
||||||
|
console.log('error===img=========', {
|
||||||
|
src: props.src
|
||||||
|
});
|
||||||
setIsError(true);
|
setIsError(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -496,6 +496,11 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = ({
|
|||||||
overlayCtx!.resetTransform();
|
overlayCtx!.resetTransform();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const updateCursorSize = () => {
|
||||||
|
cursorRef.current!.style.width = `${lineWidth * autoScale.current}px`;
|
||||||
|
cursorRef.current!.style.height = `${lineWidth * autoScale.current}px`;
|
||||||
|
};
|
||||||
|
|
||||||
const initializeImage = useCallback(async () => {
|
const initializeImage = useCallback(async () => {
|
||||||
if (imguid === preImguid.current) {
|
if (imguid === preImguid.current) {
|
||||||
return;
|
return;
|
||||||
@@ -518,6 +523,7 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = ({
|
|||||||
if (strokesRef.current.length) {
|
if (strokesRef.current.length) {
|
||||||
redrawStrokes(strokesRef.current);
|
redrawStrokes(strokesRef.current);
|
||||||
}
|
}
|
||||||
|
updateCursorSize();
|
||||||
}, [drawImage, onReset, redrawStrokes, imguid]);
|
}, [drawImage, onReset, redrawStrokes, imguid]);
|
||||||
|
|
||||||
const updateZoom = (scaleChange: number, mouseX: number, mouseY: number) => {
|
const updateZoom = (scaleChange: number, mouseX: number, mouseY: number) => {
|
||||||
@@ -557,11 +563,6 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = ({
|
|||||||
canvasRef.current!.style.transform = `scale(${autoScale.current})`;
|
canvasRef.current!.style.transform = `scale(${autoScale.current})`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateCursorSize = () => {
|
|
||||||
cursorRef.current!.style.width = `${lineWidth * autoScale.current}px`;
|
|
||||||
cursorRef.current!.style.height = `${lineWidth * autoScale.current}px`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOnWheel = (event: any) => {
|
const handleOnWheel = (event: any) => {
|
||||||
handleZoom(event);
|
handleZoom(event);
|
||||||
updateCursorSize();
|
updateCursorSize();
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
|
|
||||||
export default function useExpandedRowKeys() {
|
export default function useExpandedRowKeys(defaultKeys: React.Key[] = []) {
|
||||||
const [expandedRowKeys, setExpandedRowKeys] = useState<React.Key[]>([]);
|
const [expandedRowKeys, setExpandedRowKeys] =
|
||||||
|
useState<React.Key[]>(defaultKeys);
|
||||||
|
|
||||||
const handleExpandChange = useCallback(
|
const handleExpandChange = useCallback(
|
||||||
(expanded: boolean, record: any, rowKey: any) => {
|
(expanded: boolean, record: any, rowKey: any) => {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export default {
|
|||||||
'menu.playground.embedding': '文本嵌入',
|
'menu.playground.embedding': '文本嵌入',
|
||||||
'menu.playground.chat': '对话',
|
'menu.playground.chat': '对话',
|
||||||
'menu.playground.speech': '语音',
|
'menu.playground.speech': '语音',
|
||||||
'menu.playground.text2images': '文生图',
|
'menu.playground.text2images': '图像',
|
||||||
'menu.compare': '多模型对比',
|
'menu.compare': '多模型对比',
|
||||||
'menu.models': '模型',
|
'menu.models': '模型',
|
||||||
'menu.models.modelList': '部署与管理',
|
'menu.models.modelList': '部署与管理',
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export default {
|
|||||||
'playground.system': '系统',
|
'playground.system': '系统',
|
||||||
'playground.systemMessage': '系统消息',
|
'playground.systemMessage': '系统消息',
|
||||||
'playground.user': '用户',
|
'playground.user': '用户',
|
||||||
'playground.assistant': '小助手',
|
'playground.assistant': 'AI助手',
|
||||||
'playground.newMessage': '新消息',
|
'playground.newMessage': '新消息',
|
||||||
'playground.viewcode': '查看代码',
|
'playground.viewcode': '查看代码',
|
||||||
'playground.model': '模型',
|
'playground.model': '模型',
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
import { modelsExpandKeysAtom } from '@/atoms/models';
|
||||||
import PageTools from '@/components/page-tools';
|
import PageTools from '@/components/page-tools';
|
||||||
import { PageAction } from '@/config';
|
import { PageAction } from '@/config';
|
||||||
import { SyncOutlined } from '@ant-design/icons';
|
import { SyncOutlined } from '@ant-design/icons';
|
||||||
import { PageContainer } from '@ant-design/pro-components';
|
import { PageContainer } from '@ant-design/pro-components';
|
||||||
import { useIntl, useNavigate } from '@umijs/max';
|
import { useIntl, useNavigate } from '@umijs/max';
|
||||||
import { Button, Input, Pagination, Select, Space, message } from 'antd';
|
import { Button, Input, Pagination, Select, Space, message } from 'antd';
|
||||||
|
import { useAtom } from 'jotai';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { createModel, queryCatalogList } from './apis';
|
import { createModel, queryCatalogList } from './apis';
|
||||||
@@ -38,6 +40,7 @@ const Catalog: React.FC = () => {
|
|||||||
current: {},
|
current: {},
|
||||||
source: modelSourceMap.huggingface_value
|
source: modelSourceMap.huggingface_value
|
||||||
});
|
});
|
||||||
|
const [modelsExpandKeys, setModelsExpandKeys] = useAtom(modelsExpandKeysAtom);
|
||||||
const cacheData = React.useRef<CatalogItemType[]>([]);
|
const cacheData = React.useRef<CatalogItemType[]>([]);
|
||||||
|
|
||||||
const categoryOptions = [...modelCategories.filter((item) => item.value)];
|
const categoryOptions = [...modelCategories.filter((item) => item.value)];
|
||||||
@@ -53,7 +56,7 @@ const Catalog: React.FC = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (search) {
|
if (search) {
|
||||||
return _.toLower(item.name).includes(search);
|
return _.toLower(item.name).includes(_.toLower(search));
|
||||||
}
|
}
|
||||||
if (categories.length > 0) {
|
if (categories.length > 0) {
|
||||||
return categories.some((category) =>
|
return categories.some((category) =>
|
||||||
@@ -134,6 +137,7 @@ const Catalog: React.FC = () => {
|
|||||||
show: false
|
show: false
|
||||||
});
|
});
|
||||||
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
||||||
|
setModelsExpandKeys([modelData.id]);
|
||||||
navigate('/models/list');
|
navigate('/models/list');
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { modelsExpandKeysAtom } from '@/atoms/models';
|
||||||
import AutoTooltip from '@/components/auto-tooltip';
|
import AutoTooltip from '@/components/auto-tooltip';
|
||||||
import DeleteModal from '@/components/delete-modal';
|
import DeleteModal from '@/components/delete-modal';
|
||||||
import DropdownButtons from '@/components/drop-down-buttons';
|
import DropdownButtons from '@/components/drop-down-buttons';
|
||||||
@@ -30,6 +31,7 @@ import { PageContainer } from '@ant-design/pro-components';
|
|||||||
import { Access, useAccess, useIntl, useNavigate } from '@umijs/max';
|
import { Access, useAccess, useIntl, useNavigate } from '@umijs/max';
|
||||||
import { Button, Dropdown, Input, Select, Space, Tag, message } from 'antd';
|
import { Button, Dropdown, Input, Select, Space, Tag, message } from 'antd';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { useAtom } from 'jotai';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
@@ -125,6 +127,7 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
loading,
|
loading,
|
||||||
total
|
total
|
||||||
}) => {
|
}) => {
|
||||||
|
const [expandAtom] = useAtom(modelsExpandKeysAtom);
|
||||||
const access = useAccess();
|
const access = useAccess();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -134,7 +137,7 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
updateExpandedRowKeys,
|
updateExpandedRowKeys,
|
||||||
removeExpandedRowKey,
|
removeExpandedRowKey,
|
||||||
expandedRowKeys
|
expandedRowKeys
|
||||||
} = useExpandedRowKeys();
|
} = useExpandedRowKeys(expandAtom);
|
||||||
const { sortOrder, setSortOrder } = useTableSort({
|
const { sortOrder, setSortOrder } = useTableSort({
|
||||||
defaultSortOrder: 'descend'
|
defaultSortOrder: 'descend'
|
||||||
});
|
});
|
||||||
@@ -309,6 +312,7 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
||||||
|
updateExpandedRowKeys([row.id, ...expandedRowKeys]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// ingore
|
// ingore
|
||||||
}
|
}
|
||||||
@@ -329,6 +333,7 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
replicas: 0
|
replicas: 0
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
removeExpandedRowKey([row.id]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// ingore
|
// ingore
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -352,6 +352,7 @@ const GroundImages: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
if (item.b64_json && stream_options.chunk_results) {
|
if (item.b64_json && stream_options.chunk_results) {
|
||||||
imgItem.dataUrl += item.b64_json;
|
imgItem.dataUrl += item.b64_json;
|
||||||
} else if (item.b64_json) {
|
} else if (item.b64_json) {
|
||||||
|
console.log('item.b64_json:', item.b64_json, item.index);
|
||||||
imgItem.dataUrl = `data:image/png;base64,${item.b64_json}`;
|
imgItem.dataUrl = `data:image/png;base64,${item.b64_json}`;
|
||||||
}
|
}
|
||||||
const progress = _.round(item.progress, 0);
|
const progress = _.round(item.progress, 0);
|
||||||
|
|||||||
@@ -199,20 +199,32 @@ export const readStreamData = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Process the remainder of the buffer
|
// Process the remainder of the buffer
|
||||||
const processBuffer = (buffer: string, callback: (data: any) => void) => {
|
const processBuffer = async (buffer: string, callback: (data: any) => void) => {
|
||||||
|
if (!buffer) return;
|
||||||
|
|
||||||
const lines = buffer.split('\n');
|
const lines = buffer.split('\n');
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
if (line.startsWith('data: ')) {
|
const trimmedLine = line.trim();
|
||||||
const jsonStr = line.slice(6).trim();
|
|
||||||
|
if (trimmedLine.startsWith('data: ')) {
|
||||||
|
const jsonStr = trimmedLine.slice(6).trim();
|
||||||
try {
|
try {
|
||||||
const jsonData = JSON.parse(jsonStr);
|
if (jsonStr !== '[DONE]') {
|
||||||
callback(jsonData);
|
console.log('jsonStr>>>>>>>>>>>>>done:', jsonStr);
|
||||||
|
const jsonData = JSON.parse(jsonStr);
|
||||||
|
callback(jsonData);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(
|
console.error('Failed to parse JSON from line:', jsonStr, e);
|
||||||
'Failed to parse JSON from remaining buffer:',
|
}
|
||||||
jsonStr,
|
} else if (trimmedLine.startsWith('error:')) {
|
||||||
e
|
const errorStr = trimmedLine.slice(7).trim();
|
||||||
);
|
console.log('jsonStr>>>>>>>>>>>>>error:', errorStr);
|
||||||
|
try {
|
||||||
|
const jsonData = JSON.parse(errorStr);
|
||||||
|
callback({ error: jsonData });
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse error JSON from line:', errorStr, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,47 +233,89 @@ const processBuffer = (buffer: string, callback: (data: any) => void) => {
|
|||||||
export const readLargeStreamData = async (
|
export const readLargeStreamData = async (
|
||||||
reader: any,
|
reader: any,
|
||||||
decoder: TextDecoder,
|
decoder: TextDecoder,
|
||||||
callback: (data: any) => void
|
callback: (data: any) => void,
|
||||||
|
throttleDelay = 200
|
||||||
) => {
|
) => {
|
||||||
let buffer = ''; // cache incomplete line
|
let buffer = ''; // cache incomplete line
|
||||||
|
|
||||||
|
class BufferManager {
|
||||||
|
private buffer: any[] = [];
|
||||||
|
private failed: boolean = false;
|
||||||
|
private isFlushing: boolean = false;
|
||||||
|
private callback: (data: any) => void;
|
||||||
|
|
||||||
|
constructor(callback: (data: any) => void) {
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public add(data: any) {
|
||||||
|
this.buffer.push(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async flush() {
|
||||||
|
if (this.buffer.length === 0 || this.isFlushing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.failed = false;
|
||||||
|
this.isFlushing = true;
|
||||||
|
|
||||||
|
while (this.buffer.length > 0) {
|
||||||
|
const data = this.buffer.shift();
|
||||||
|
|
||||||
|
try {
|
||||||
|
processBuffer(data, this.callback);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error processing buffer:', error);
|
||||||
|
this.failed = true;
|
||||||
|
this.buffer.unshift(data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.isFlushing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getBuffer() {
|
||||||
|
return this.buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bufferManager = new BufferManager(callback);
|
||||||
|
|
||||||
|
const throttledCallback = throttle(async () => {
|
||||||
|
bufferManager.flush();
|
||||||
|
}, throttleDelay);
|
||||||
|
|
||||||
|
let isReading = true;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const { done, value } = await reader?.read?.();
|
const { done, value } = await reader?.read?.();
|
||||||
|
|
||||||
if (done) {
|
if (done) {
|
||||||
|
isReading = false;
|
||||||
// Process remaining buffered data
|
// Process remaining buffered data
|
||||||
if (buffer.trim()) {
|
if (buffer) {
|
||||||
processBuffer(buffer, callback);
|
bufferManager.add(buffer);
|
||||||
}
|
}
|
||||||
|
bufferManager.flush();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode new chunk of data and append to buffer
|
try {
|
||||||
buffer += decoder.decode(value, { stream: true });
|
// Decode new chunk of data and append to buffer
|
||||||
|
buffer += decoder.decode(value, { stream: true });
|
||||||
|
|
||||||
// Try to process the complete line in the buffer
|
// Try to process the complete line in the buffer
|
||||||
const lines = buffer.split('\n');
|
const lines = buffer.split('\n');
|
||||||
buffer = lines.pop() || ''; // Keep last line (may be incomplete)
|
buffer = lines.pop() || ''; // Keep last line (may be incomplete)
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
if (line.startsWith('data: ')) {
|
bufferManager.add(line);
|
||||||
const jsonStr = line.slice(6).trim();
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (jsonStr !== '[DONE]') {
|
|
||||||
const jsonData = JSON.parse(jsonStr);
|
|
||||||
callback(jsonData);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to parse JSON:', jsonStr, e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line.startsWith('error:')) {
|
throttledCallback();
|
||||||
const errorStr = line.slice(7).trim();
|
} catch (error) {
|
||||||
const jsonData = JSON.parse(errorStr);
|
console.log('Error reading stream data:', error);
|
||||||
callback({ error: jsonData });
|
// do nothing
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+32
-21
@@ -149,35 +149,46 @@ export const generateRandomNumber = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function base64ToBlob(base64: string, contentType = '', sliceSize = 512) {
|
function base64ToBlob(base64: string, contentType = '', sliceSize = 512) {
|
||||||
const base64Content = base64.replace(/^data:image\/(png|jpg);base64,/, '');
|
try {
|
||||||
const byteCharacters = atob(base64Content);
|
const base64Content = base64.replace(/^data:image\/(png|jpg);base64,/, '');
|
||||||
const byteArrays = [];
|
const byteCharacters = atob(base64Content);
|
||||||
|
const byteArrays = [];
|
||||||
|
|
||||||
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
|
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
|
||||||
const slice = byteCharacters.slice(offset, offset + sliceSize);
|
const slice = byteCharacters.slice(offset, offset + sliceSize);
|
||||||
|
|
||||||
const byteNumbers = new Array(slice.length);
|
const byteNumbers = new Array(slice.length);
|
||||||
for (let i = 0; i < slice.length; i++) {
|
for (let i = 0; i < slice.length; i++) {
|
||||||
byteNumbers[i] = slice.charCodeAt(i);
|
byteNumbers[i] = slice.charCodeAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
const byteArray = new Uint8Array(byteNumbers);
|
||||||
|
byteArrays.push(byteArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
const byteArray = new Uint8Array(byteNumbers);
|
return new Blob(byteArrays, { type: contentType });
|
||||||
byteArrays.push(byteArray);
|
} catch (error) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Blob(byteArrays, { type: contentType });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const base64ToFile = (base64String: string, fileName: string) => {
|
export const base64ToFile = (base64String: string, fileName: string) => {
|
||||||
if (!base64String) {
|
try {
|
||||||
|
if (!base64String) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
console.log('base64String:', base64String);
|
||||||
|
const match = base64String.match(/data:(.*?);base64,/);
|
||||||
|
if (!match) {
|
||||||
|
throw new Error('Invalid base64 string');
|
||||||
|
}
|
||||||
|
const contentType = match[1];
|
||||||
|
const blob = base64ToBlob(base64String, contentType);
|
||||||
|
if (!blob) {
|
||||||
|
throw new Error('Failed to convert base64 to blob');
|
||||||
|
}
|
||||||
|
return new File([blob], fileName || contentType, { type: contentType });
|
||||||
|
} catch (error) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
console.log('base64String:', base64String);
|
|
||||||
const match = base64String.match(/data:(.*?);base64,/);
|
|
||||||
if (!match) {
|
|
||||||
throw new Error('Invalid base64 string');
|
|
||||||
}
|
|
||||||
const contentType = match[1];
|
|
||||||
const blob = base64ToBlob(base64String, contentType);
|
|
||||||
return new File([blob], fileName || contentType, { type: contentType });
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user