diff --git a/src/components/image-editor/hooks/use-zoom.ts b/src/components/image-editor/hooks/use-zoom.ts index 184cca30..bfe9c3a0 100644 --- a/src/components/image-editor/hooks/use-zoom.ts +++ b/src/components/image-editor/hooks/use-zoom.ts @@ -107,7 +107,6 @@ export default function useZoom(props: { if (isLoadingMaskRef.current) { return; } - event.preventDefault(); // stop handleZoom(event); updateCursorSize(); diff --git a/src/locales/en-US/playground.ts b/src/locales/en-US/playground.ts index 5b6fc0b6..0169d575 100644 --- a/src/locales/en-US/playground.ts +++ b/src/locales/en-US/playground.ts @@ -152,5 +152,7 @@ export default { '1. After selection, no further masking can be drawn; therefore, you should draw the mask first and then check the option.\n 2. Once a mask image is uploaded, no further masks can be generated.', 'playground.model.noavailable.tips2': 'If the expected model isn’t showing up, make sure it’s running and correctly categorized. If the category is incorrect, you can manually adjust it in the model’s settings.', - 'playground.rerank.query.validate': 'The query is required.' + 'playground.rerank.query.validate': 'The query is required.', + 'playground.image.generate.error': + 'Something went wrong. The image could not be generated.' }; diff --git a/src/locales/ja-JP/playground.ts b/src/locales/ja-JP/playground.ts index ecc64a36..a72ffa50 100644 --- a/src/locales/ja-JP/playground.ts +++ b/src/locales/ja-JP/playground.ts @@ -155,9 +155,12 @@ export default { '1. 選択後は追加のマスクを描画できません。そのため、最初にマスクを描画してからオプションを選択してください。\n 2. マスク画像をアップロードすると、追加のマスクを生成することはできません。', 'playground.model.noavailable.tips2': '期待するモデルが表示されない場合は、モデルが実行中で正しく分類されていることを確認してください。カテゴリが間違っている場合は、モデルの設定で手動で調整できます。', - 'playground.rerank.query.validate': 'The query is required.' + 'playground.rerank.query.validate': 'The query is required.', + 'playground.image.generate.error': + 'Something went wrong. The image could not be generated.' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== // 1. 'playground.rerank.query.validate': 'The query is required.' +// 2. 'playground.image.generate.error': 'Something went wrong. The image could not be generated.' // ========== End of To-Do List ========== diff --git a/src/locales/ru-RU/playground.ts b/src/locales/ru-RU/playground.ts index dd90e7fa..a8cecd09 100644 --- a/src/locales/ru-RU/playground.ts +++ b/src/locales/ru-RU/playground.ts @@ -149,9 +149,12 @@ export default { '1. После выделения области создание маски станет недоступным; поэтому сначала нарисуйте маску, а затем активируйте опцию. \n 2. После загрузки изображения маски создание новых масок невозможно.', 'playground.model.noavailable.tips2': 'Если нужная модель не отображается, убедитесь, что она запущена и ей присвоена правильная категория. Если категория указана неверно, её можно изменить вручную в настройках модели.', - 'playground.rerank.query.validate': 'The query is required.' + 'playground.rerank.query.validate': 'The query is required.', + 'playground.image.generate.error': + 'Something went wrong. The image could not be generated.' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== // 1. 'playground.rerank.query.validate': 'The query is required.' +// 2. 'playground.image.generate.error': 'Something went wrong. The image could not be generated.' // ========== End of To-Do List ========== diff --git a/src/locales/zh-CN/playground.ts b/src/locales/zh-CN/playground.ts index 2c47e192..57ae4785 100644 --- a/src/locales/zh-CN/playground.ts +++ b/src/locales/zh-CN/playground.ts @@ -146,5 +146,6 @@ export default { '1. 选择后,将不可再绘制遮罩;因此,你应该先绘制遮罩然后再勾选.\n 2. 当上传了遮罩图片,将不可再生成遮罩', 'playground.model.noavailable.tips2': '若预期的模型未显示,请检查模型是否已正常运行并被正确分类。如分类不正确,请编辑模型并手动调整其类别。', - 'playground.rerank.query.validate': '查询内容不能为空' + 'playground.rerank.query.validate': '查询内容不能为空', + 'playground.image.generate.error': '出了一点问题,图片未能生成。' }; diff --git a/src/pages/llmodels/components/deploy-modal.tsx b/src/pages/llmodels/components/deploy-modal.tsx index 196aa7eb..17a35247 100644 --- a/src/pages/llmodels/components/deploy-modal.tsx +++ b/src/pages/llmodels/components/deploy-modal.tsx @@ -343,7 +343,7 @@ const AddModal: FC = (props) => { } if (manual) { - form.current?.form?.resetFields(resetFieldsByModel); + form.current?.form?.setFieldsValue(defaultFormValues); } // If the item is empty setIsGGUF(item.isGGUF); diff --git a/src/pages/playground/hooks/use-text-image.ts b/src/pages/playground/hooks/use-text-image.ts index cfa3aa0d..86eb7c05 100644 --- a/src/pages/playground/hooks/use-text-image.ts +++ b/src/pages/playground/hooks/use-text-image.ts @@ -5,6 +5,7 @@ import { fetchChunkedDataPostFormData, readLargeStreamData as readStreamData } from '@/utils/fetch-chunk-data'; +import { useIntl } from '@umijs/max'; import _ from 'lodash'; import { useEffect, useRef, useState } from 'react'; import { CREAT_IMAGE_API, EDIT_IMAGE_API } from '../apis'; @@ -12,6 +13,7 @@ import { CREAT_IMAGE_API, EDIT_IMAGE_API } from '../apis'; const ODD_STRING = 'AAAABJRU5ErkJgg==='; export default function useTextImage(props: any) { + const intl = useIntl(); const { scroller, paramsRef, chunkFields, API } = props; const [loading, setLoading] = useState(false); const [tokenResult, setTokenResult] = useState(null); @@ -34,6 +36,8 @@ export default function useTextImage(props: any) { const requestToken = useRef(null); const { initialize } = useOverlayScroller(); const { initialize: innitializeParams } = useOverlayScroller(); + const streamReaderRef = useRef(null); + const requestIdRef = useRef(0); useEffect(() => { if (scroller.current) { @@ -46,6 +50,11 @@ export default function useTextImage(props: any) { } }, [innitializeParams]); + const updateRequestId = () => { + requestIdRef.current = requestIdRef.current + 1; + return requestIdRef.current; + }; + const removeBase64Suffix = (str: string, suffix: string) => { return str.endsWith(suffix) ? str.slice(0, -suffix.length) : str; }; @@ -78,9 +87,18 @@ export default function useTextImage(props: any) { return Math.floor(Math.random() * (max - min + 1) + min); }; + const stopDebounce = _.debounce(() => { + setImageList([]); + setLoading(false); + }, 200); + const submitMessage = async (parameters: any) => { try { if (!parameters.model) return; + + requestToken.current?.abort?.('cancel'); + requestToken.current = new AbortController(); + const currentRequestId = updateRequestId(); const size: any = setImageSize(parameters); setLoading(true); setMessageId(); @@ -107,9 +125,6 @@ export default function useTextImage(props: any) { }); setImageList(newImageList); - requestToken.current?.abort?.('cancel'); - requestToken.current = new AbortController(); - let result: any = {}; if (API === CREAT_IMAGE_API) { result = await fetchChunkedData({ @@ -135,10 +150,15 @@ export default function useTextImage(props: any) { setImageList([]); return; } - + console.log('result:', result); const { reader, decoder } = result; - - await readStreamData(reader, decoder, (chunk: any) => { + streamReaderRef.current = reader; + await readStreamData(reader, decoder, (chunk: any, done?: boolean) => { + console.log('chunk done:', chunk, done); + if (requestIdRef.current !== currentRequestId) { + // If the request ID has changed, ignore this chunk + return; + } if (chunk?.error) { setTokenResult({ error: true, @@ -168,12 +188,25 @@ export default function useTextImage(props: any) { progress: progress }; }); + if ( + done && + !chunk?.error && + newImageList.some((item) => item.progress < 100) + ) { + setTokenResult({ + error: true, + errorMessage: intl.formatMessage({ + id: 'playground.image.generate.error' + }) + }); + } setImageList([...newImageList]); }); } catch (error) { console.log('error:', error); + updateRequestId(); + stopDebounce(); requestToken.current?.abort?.('cancel'); - setImageList([]); } finally { setLoading(false); } @@ -187,8 +220,6 @@ export default function useTextImage(props: any) { const handleStopConversation = () => { requestToken.current?.abort?.('stop'); - setImageList([]); - setLoading(false); }; useEffect(() => { diff --git a/src/pages/resources/components/add-worker-macos.tsx b/src/pages/resources/components/add-worker-macos.tsx new file mode 100644 index 00000000..c1a9c67e --- /dev/null +++ b/src/pages/resources/components/add-worker-macos.tsx @@ -0,0 +1,69 @@ +import { useIntl } from '@umijs/max'; +import { Space, Typography } from 'antd'; +import React, { useMemo } from 'react'; + +interface AddWorkerMacOSProps { + token?: string; +} + +const registerWorkerSteps = [ + 'Open the quick configuration file.', + `Select the "General" option.`, + `Select "Worker" as the service role.`, + 'Enter the Server URL.', + 'Paste the Token.', + `Click "Restart".` +]; + +const AddWorkerMacOS: React.FC = () => { + const intl = useIntl(); + const labels = useMemo( + () => ({ + step1: intl.formatMessage({ id: 'resources.worker.add.step1' }), + step2: intl.formatMessage({ id: 'resources.worker.add.step2' }), + step2Tips: intl.formatMessage({ id: 'resources.worker.add.step2.tips' }), + step3: intl.formatMessage({ id: 'resources.worker.add.step3' }), + linuxOrMac: intl.formatMessage({ id: 'resources.worker.linuxormaxos' }) + }), + [intl] + ); + return ( +
+

1. Install GPUStack

+ + Download Package and install it on + your macOS machine. + +

+ 2. +

+ + + Click Copy the token + + +

+ 3. {labels.step2}{' '} + +

+ + + {registerWorkerSteps.map((step, index) => ( + + {index + 1}. {step} + + ))} + + +

4. {labels.step3}

+
+ ); +}; + +export default AddWorkerMacOS; diff --git a/src/pages/resources/components/add-worker-windows.tsx b/src/pages/resources/components/add-worker-windows.tsx new file mode 100644 index 00000000..8f2a15ff --- /dev/null +++ b/src/pages/resources/components/add-worker-windows.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +interface AddWorkerWindowsProps { + token?: string; +} + +const AddWorkerWindows: React.FC = () => { + return
add worker in windows
; +}; + +export default AddWorkerWindows; diff --git a/src/pages/resources/components/add-worker.tsx b/src/pages/resources/components/add-worker.tsx index d7e12d84..e7047cee 100644 --- a/src/pages/resources/components/add-worker.tsx +++ b/src/pages/resources/components/add-worker.tsx @@ -23,9 +23,22 @@ const AddWorker: React.FC = (props) => { }, { key: 'container', - label: intl.formatMessage({ id: 'resources.worker.container.install' }), + label: 'Linux', + // icon: , children: } + // { + // key: 'macos', + // label: 'macOS', + // icon: , + // children: + // }, + // { + // key: 'windows', + // label: 'Windows', + // icon: , + // children: + // } ]; return ( diff --git a/src/utils/fetch-chunk-data.ts b/src/utils/fetch-chunk-data.ts index 1e37eca6..6b2b5871 100644 --- a/src/utils/fetch-chunk-data.ts +++ b/src/utils/fetch-chunk-data.ts @@ -193,7 +193,11 @@ export const readStreamData = async ( }; // Process the remainder of the buffer -const processBuffer = async (buffer: string, callback: (data: any) => void) => { +const processBuffer = async ( + buffer: string, + callback: (data: any, done?: boolean) => void, + done?: boolean +) => { if (!buffer) return; const lines = buffer.split('\n'); @@ -205,7 +209,7 @@ const processBuffer = async (buffer: string, callback: (data: any) => void) => { try { if (jsonStr !== '[DONE]') { const jsonData = JSON.parse(jsonStr); - callback(jsonData); + callback(jsonData, done); } } catch (e) { console.error('Failed to parse JSON from line:', jsonStr, e); @@ -214,7 +218,7 @@ const processBuffer = async (buffer: string, callback: (data: any) => void) => { const errorStr = trimmedLine.slice(7).trim(); try { const jsonData = JSON.parse(errorStr); - callback({ error: jsonData }); + callback({ error: jsonData, done }); } catch (e) { console.error('Failed to parse error JSON from line:', errorStr, e); } @@ -225,7 +229,7 @@ const processBuffer = async (buffer: string, callback: (data: any) => void) => { export const readLargeStreamData = async ( reader: any, decoder: TextDecoder, - callback: (data: any) => void, + callback: (data: any, done?: boolean) => void, throttleDelay = 200 ) => { let buffer = ''; // cache incomplete line @@ -234,9 +238,9 @@ export const readLargeStreamData = async ( private buffer: any[] = []; private failed: boolean = false; private isFlushing: boolean = false; - private callback: (data: any) => void; + private callback: (data: any, done?: boolean) => void; - constructor(callback: (data: any) => void) { + constructor(callback: (data: any, done?: boolean) => void) { this.callback = callback; } @@ -244,7 +248,12 @@ export const readLargeStreamData = async ( this.buffer.push(data); } - public async flush() { + public async flush(done?: boolean) { + if (this.buffer.length === 0 && done) { + // If buffer is empty and done is true, we can call the callback with an empty object + this.callback({}, done); + return; + } if (this.buffer.length === 0 || this.isFlushing) { return; } @@ -255,7 +264,7 @@ export const readLargeStreamData = async ( const data = this.buffer.shift(); try { - processBuffer(data, this.callback); + processBuffer(data, this.callback, done); } catch (error) { console.error('Error processing buffer:', error); this.failed = true; @@ -263,6 +272,10 @@ export const readLargeStreamData = async ( break; } } + if (done) { + // If done is true, we should call the callback with an empty object + this.callback({}, done); + } this.isFlushing = false; } @@ -282,13 +295,15 @@ export const readLargeStreamData = async ( while (true) { const { done, value } = await reader?.read?.(); + console.log('done:', done, 'value:', value); + if (done) { isReading = false; // Process remaining buffered data if (buffer) { bufferManager.add(buffer); } - bufferManager.flush(); + bufferManager.flush(done); break; }