Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
a9982ef2
authored
Feb 05, 2026
by
CaIon
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Remove deprecated components and hooks
parent
d13fa743
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
0 additions
and
1347 deletions
+0
-1347
.gitattributes
+0
-1
web/src/components/common/examples/ChannelKeyViewExample.jsx
+0
-113
web/src/components/common/modals/TwoFactorAuthModal.jsx
+0
-148
web/src/components/playground/index.js
+0
-40
web/src/components/settings/personal/cards/ModelsList.jsx
+0
-280
web/src/components/table/models/ModelsDescription.jsx
+0
-44
web/src/components/table/users/modals/BindSubscriptionModal.jsx
+0
-123
web/src/hooks/model-deployments/useDeploymentResources.js
+0
-312
web/src/hooks/model-deployments/useEnhancedDeploymentActions.jsx
+0
-286
web/src/pages/Setting/Personal/SettingsSidebarModulesUser.jsx
+0
-0
No files found.
.gitattributes
View file @
a9982ef2
...
@@ -35,5 +35,4 @@
...
@@ -35,5 +35,4 @@
# GitHub Linguist - Language Detection
# GitHub Linguist - Language Detection
# ============================================
# ============================================
# Mark web frontend as vendored so GitHub recognizes this as a Go project
# Mark web frontend as vendored so GitHub recognizes this as a Go project
web/** linguist-vendored
electron/** linguist-vendored
electron/** linguist-vendored
web/src/components/common/examples/ChannelKeyViewExample.jsx
deleted
100644 → 0
View file @
d13fa743
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import
React
,
{
useState
}
from
'react'
;
import
{
useTranslation
}
from
'react-i18next'
;
import
{
Button
,
Modal
}
from
'@douyinfe/semi-ui'
;
import
{
useSecureVerification
}
from
'../../../hooks/common/useSecureVerification'
;
import
{
createApiCalls
}
from
'../../../services/secureVerification'
;
import
SecureVerificationModal
from
'../modals/SecureVerificationModal'
;
import
ChannelKeyDisplay
from
'../ui/ChannelKeyDisplay'
;
/**
* 渠道密钥查看组件使用示例
* 展示如何使用通用安全验证系统
*/
const
ChannelKeyViewExample
=
({
channelId
})
=>
{
const
{
t
}
=
useTranslation
();
const
[
keyData
,
setKeyData
]
=
useState
(
''
);
const
[
showKeyModal
,
setShowKeyModal
]
=
useState
(
false
);
// 使用通用安全验证 Hook
const
{
isModalVisible
,
verificationMethods
,
verificationState
,
startVerification
,
executeVerification
,
cancelVerification
,
setVerificationCode
,
switchVerificationMethod
,
}
=
useSecureVerification
({
onSuccess
:
(
result
)
=>
{
// 验证成功后处理结果
if
(
result
.
success
&&
result
.
data
?.
key
)
{
setKeyData
(
result
.
data
.
key
);
setShowKeyModal
(
true
);
}
},
successMessage
:
t
(
'密钥获取成功'
),
});
// 开始查看密钥流程
const
handleViewKey
=
async
()
=>
{
const
apiCall
=
createApiCalls
.
viewChannelKey
(
channelId
);
await
startVerification
(
apiCall
,
{
title
:
t
(
'查看渠道密钥'
),
description
:
t
(
'为了保护账户安全,请验证您的身份。'
),
preferredMethod
:
'passkey'
,
// 可以指定首选验证方式
});
};
return
(
<>
{
/* 查看密钥按钮 */
}
<
Button
type=
'primary'
theme=
'outline'
onClick=
{
handleViewKey
}
>
{
t
(
'查看密钥'
)
}
</
Button
>
{
/* 安全验证模态框 */
}
<
SecureVerificationModal
visible=
{
isModalVisible
}
verificationMethods=
{
verificationMethods
}
verificationState=
{
verificationState
}
onVerify=
{
executeVerification
}
onCancel=
{
cancelVerification
}
onCodeChange=
{
setVerificationCode
}
onMethodSwitch=
{
switchVerificationMethod
}
title=
{
verificationState
.
title
}
description=
{
verificationState
.
description
}
/>
{
/* 密钥显示模态框 */
}
<
Modal
title=
{
t
(
'渠道密钥信息'
)
}
visible=
{
showKeyModal
}
onCancel=
{
()
=>
setShowKeyModal
(
false
)
}
footer=
{
<
Button
type=
'primary'
onClick=
{
()
=>
setShowKeyModal
(
false
)
}
>
{
t
(
'完成'
)
}
</
Button
>
}
width=
{
700
}
style=
{
{
maxWidth
:
'90vw'
}
}
>
<
ChannelKeyDisplay
keyData=
{
keyData
}
showSuccessIcon=
{
true
}
successText=
{
t
(
'密钥获取成功'
)
}
showWarning=
{
true
}
/>
</
Modal
>
</>
);
};
export
default
ChannelKeyViewExample
;
web/src/components/common/modals/TwoFactorAuthModal.jsx
deleted
100644 → 0
View file @
d13fa743
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import
React
from
'react'
;
import
{
useTranslation
}
from
'react-i18next'
;
import
{
Modal
,
Button
,
Input
,
Typography
}
from
'@douyinfe/semi-ui'
;
/**
* 可复用的两步验证模态框组件
* @param {Object} props
* @param {boolean} props.visible - 是否显示模态框
* @param {string} props.code - 验证码值
* @param {boolean} props.loading - 是否正在验证
* @param {Function} props.onCodeChange - 验证码变化回调
* @param {Function} props.onVerify - 验证回调
* @param {Function} props.onCancel - 取消回调
* @param {string} props.title - 模态框标题
* @param {string} props.description - 验证描述文本
* @param {string} props.placeholder - 输入框占位文本
*/
const
TwoFactorAuthModal
=
({
visible
,
code
,
loading
,
onCodeChange
,
onVerify
,
onCancel
,
title
,
description
,
placeholder
,
})
=>
{
const
{
t
}
=
useTranslation
();
const
handleKeyDown
=
(
e
)
=>
{
if
(
e
.
key
===
'Enter'
&&
code
&&
!
loading
)
{
onVerify
();
}
};
return
(
<
Modal
title=
{
<
div
className=
'flex items-center'
>
<
div
className=
'w-8 h-8 rounded-full bg-blue-100 dark:bg-blue-900 flex items-center justify-center mr-3'
>
<
svg
className=
'w-4 h-4 text-blue-600 dark:text-blue-400'
fill=
'currentColor'
viewBox=
'0 0 20 20'
>
<
path
fillRule=
'evenodd'
d=
'M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z'
clipRule=
'evenodd'
/>
</
svg
>
</
div
>
{
title
||
t
(
'安全验证'
)
}
</
div
>
}
visible=
{
visible
}
onCancel=
{
onCancel
}
footer=
{
<>
<
Button
onClick=
{
onCancel
}
>
{
t
(
'取消'
)
}
</
Button
>
<
Button
type=
'primary'
loading=
{
loading
}
disabled=
{
!
code
||
loading
}
onClick=
{
onVerify
}
>
{
t
(
'验证'
)
}
</
Button
>
</>
}
width=
{
500
}
style=
{
{
maxWidth
:
'90vw'
}
}
>
<
div
className=
'space-y-6'
>
{
/* 安全提示 */
}
<
div
className=
'bg-blue-50 dark:bg-blue-900 rounded-lg p-4'
>
<
div
className=
'flex items-start'
>
<
svg
className=
'w-5 h-5 text-blue-600 dark:text-blue-400 mt-0.5 mr-3 flex-shrink-0'
fill=
'currentColor'
viewBox=
'0 0 20 20'
>
<
path
fillRule=
'evenodd'
d=
'M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z'
clipRule=
'evenodd'
/>
</
svg
>
<
div
>
<
Typography
.
Text
strong
className=
'text-blue-800 dark:text-blue-200'
>
{
t
(
'安全验证'
)
}
</
Typography
.
Text
>
<
Typography
.
Text
className=
'block text-blue-700 dark:text-blue-300 text-sm mt-1'
>
{
description
||
t
(
'为了保护账户安全,请验证您的两步验证码。'
)
}
</
Typography
.
Text
>
</
div
>
</
div
>
</
div
>
{
/* 验证码输入 */
}
<
div
>
<
Typography
.
Text
strong
className=
'block mb-2'
>
{
t
(
'验证身份'
)
}
</
Typography
.
Text
>
<
Input
placeholder=
{
placeholder
||
t
(
'请输入认证器验证码或备用码'
)
}
value=
{
code
}
onChange=
{
onCodeChange
}
size=
'large'
maxLength=
{
8
}
onKeyDown=
{
handleKeyDown
}
autoFocus
/>
<
Typography
.
Text
type=
'tertiary'
size=
'small'
className=
'mt-2 block'
>
{
t
(
'支持6位TOTP验证码或8位备用码,可到`个人设置-安全设置-两步验证设置`配置或查看。'
,
)
}
</
Typography
.
Text
>
</
div
>
</
div
>
</
Modal
>
);
};
export
default
TwoFactorAuthModal
;
web/src/components/playground/index.js
deleted
100644 → 0
View file @
d13fa743
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
export
{
default
as
SettingsPanel
}
from
'./SettingsPanel'
;
export
{
default
as
ChatArea
}
from
'./ChatArea'
;
export
{
default
as
DebugPanel
}
from
'./DebugPanel'
;
export
{
default
as
MessageContent
}
from
'./MessageContent'
;
export
{
default
as
MessageActions
}
from
'./MessageActions'
;
export
{
default
as
CustomInputRender
}
from
'./CustomInputRender'
;
export
{
default
as
SSEViewer
}
from
'./SSEViewer'
;
export
{
default
as
ParameterControl
}
from
'./ParameterControl'
;
export
{
default
as
ImageUrlInput
}
from
'./ImageUrlInput'
;
export
{
default
as
FloatingButtons
}
from
'./FloatingButtons'
;
export
{
default
as
ConfigManager
}
from
'./ConfigManager'
;
export
{
saveConfig
,
loadConfig
,
clearConfig
,
hasStoredConfig
,
getConfigTimestamp
,
exportConfig
,
importConfig
,
}
from
'./configStorage'
;
web/src/components/settings/personal/cards/ModelsList.jsx
deleted
100644 → 0
View file @
d13fa743
This diff is collapsed.
Click to expand it.
web/src/components/table/models/ModelsDescription.jsx
deleted
100644 → 0
View file @
d13fa743
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import
React
from
'react'
;
import
{
Typography
}
from
'@douyinfe/semi-ui'
;
import
{
Layers
}
from
'lucide-react'
;
import
CompactModeToggle
from
'../../common/ui/CompactModeToggle'
;
const
{
Text
}
=
Typography
;
const
ModelsDescription
=
({
compactMode
,
setCompactMode
,
t
})
=>
{
return
(
<
div
className=
'flex flex-col md:flex-row justify-between items-start md:items-center gap-2 w-full'
>
<
div
className=
'flex items-center text-green-500'
>
<
Layers
size=
{
16
}
className=
'mr-2'
/>
<
Text
>
{
t
(
'模型管理'
)
}
</
Text
>
</
div
>
<
CompactModeToggle
compactMode=
{
compactMode
}
setCompactMode=
{
setCompactMode
}
t=
{
t
}
/>
</
div
>
);
};
export
default
ModelsDescription
;
web/src/components/table/users/modals/BindSubscriptionModal.jsx
deleted
100644 → 0
View file @
d13fa743
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import
React
,
{
useEffect
,
useMemo
,
useState
}
from
'react'
;
import
{
Modal
,
Select
,
Space
,
Typography
}
from
'@douyinfe/semi-ui'
;
import
{
API
,
showError
,
showSuccess
}
from
'../../../../helpers'
;
const
{
Text
}
=
Typography
;
const
BindSubscriptionModal
=
({
visible
,
onCancel
,
user
,
t
,
onSuccess
})
=>
{
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
plans
,
setPlans
]
=
useState
([]);
const
[
selectedPlanId
,
setSelectedPlanId
]
=
useState
(
null
);
const
loadPlans
=
async
()
=>
{
setLoading
(
true
);
try
{
const
res
=
await
API
.
get
(
'/api/subscription/admin/plans'
);
if
(
res
.
data
?.
success
)
{
setPlans
(
res
.
data
.
data
||
[]);
}
else
{
showError
(
res
.
data
?.
message
||
t
(
'加载失败'
));
}
}
catch
(
e
)
{
showError
(
t
(
'请求失败'
));
}
finally
{
setLoading
(
false
);
}
};
useEffect
(()
=>
{
if
(
visible
)
{
setSelectedPlanId
(
null
);
loadPlans
();
}
},
[
visible
]);
const
planOptions
=
useMemo
(()
=>
{
return
(
plans
||
[]).
map
((
p
)
=>
({
label
:
`
${
p
?.
plan
?.
title
||
''
}
(
$
{
p
?.
plan
?.
currency
||
'USD'
}
$
{
Number
(
p
?.
plan
?.
price_amount
||
0
)})
`,
value: p?.plan?.id,
}));
}, [plans]);
const bind = async () => {
if (!user?.id) {
showError(t('用户信息缺失'));
return;
}
if (!selectedPlanId) {
showError(t('请选择订阅套餐'));
return;
}
setLoading(true);
try {
const res = await API.post('/api/subscription/admin/bind', {
user_id: user.id,
plan_id: selectedPlanId,
});
if (res.data?.success) {
showSuccess(t('绑定成功'));
onSuccess?.();
onCancel?.();
} else {
showError(res.data?.message || t('绑定失败'));
}
} catch (e) {
showError(t('请求失败'));
} finally {
setLoading(false);
}
};
return (
<Modal
title={t('绑定订阅套餐')}
visible={visible}
onCancel={onCancel}
onOk={bind}
confirmLoading={loading}
maskClosable={false}
centered
>
<Space vertical style={{ width: '100%' }} spacing='medium'>
<div className='text-sm'>
<Text strong>{t('用户')}:</Text>
<Text>{user?.username}</Text>
<Text type='tertiary'> (ID: {user?.id})</Text>
</div>
<Select
placeholder={t('选择订阅套餐')}
optionList={planOptions}
value={selectedPlanId}
onChange={setSelectedPlanId}
loading={loading}
filter
style={{ width: '100%' }}
/>
<div className='text-xs text-gray-500'>
{t('绑定后会立即生成用户订阅(无需支付),有效期按套餐配置计算。')}
</div>
</Space>
</Modal>
);
};
export default BindSubscriptionModal;
web/src/hooks/model-deployments/useDeploymentResources.js
deleted
100644 → 0
View file @
d13fa743
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import
{
useState
,
useCallback
}
from
'react'
;
import
{
API
}
from
'../../helpers'
;
import
{
showError
}
from
'../../helpers'
;
export
const
useDeploymentResources
=
()
=>
{
const
[
hardwareTypes
,
setHardwareTypes
]
=
useState
([]);
const
[
hardwareTotalAvailable
,
setHardwareTotalAvailable
]
=
useState
(
0
);
const
[
locations
,
setLocations
]
=
useState
([]);
const
[
locationsTotalAvailable
,
setLocationsTotalAvailable
]
=
useState
(
0
);
const
[
availableReplicas
,
setAvailableReplicas
]
=
useState
([]);
const
[
priceEstimation
,
setPriceEstimation
]
=
useState
(
null
);
const
[
loadingHardware
,
setLoadingHardware
]
=
useState
(
false
);
const
[
loadingLocations
,
setLoadingLocations
]
=
useState
(
false
);
const
[
loadingReplicas
,
setLoadingReplicas
]
=
useState
(
false
);
const
[
loadingPrice
,
setLoadingPrice
]
=
useState
(
false
);
const
fetchHardwareTypes
=
useCallback
(
async
()
=>
{
try
{
setLoadingHardware
(
true
);
const
response
=
await
API
.
get
(
'/api/deployments/hardware-types'
);
if
(
response
.
data
.
success
)
{
const
{
hardware_types
:
hardwareList
=
[],
total_available
}
=
response
.
data
.
data
||
{};
const
normalizedHardware
=
hardwareList
.
map
((
hardware
)
=>
{
const
availableCountValue
=
Number
(
hardware
.
available_count
);
const
availableCount
=
Number
.
isNaN
(
availableCountValue
)
?
0
:
availableCountValue
;
const
availableBool
=
typeof
hardware
.
available
===
'boolean'
?
hardware
.
available
:
availableCount
>
0
;
return
{
...
hardware
,
available
:
availableBool
,
available_count
:
availableCount
,
};
});
const
providedTotal
=
Number
(
total_available
);
const
fallbackTotal
=
normalizedHardware
.
reduce
(
(
acc
,
item
)
=>
acc
+
(
Number
.
isNaN
(
item
.
available_count
)
?
0
:
item
.
available_count
),
0
,
);
const
hasProvidedTotal
=
total_available
!==
undefined
&&
total_available
!==
null
&&
total_available
!==
''
&&
!
Number
.
isNaN
(
providedTotal
);
setHardwareTypes
(
normalizedHardware
);
setHardwareTotalAvailable
(
hasProvidedTotal
?
providedTotal
:
fallbackTotal
,
);
return
normalizedHardware
;
}
else
{
showError
(
'获取硬件类型失败: '
+
response
.
data
.
message
);
setHardwareTotalAvailable
(
0
);
return
[];
}
}
catch
(
error
)
{
showError
(
'获取硬件类型失败: '
+
error
.
message
);
setHardwareTotalAvailable
(
0
);
return
[];
}
finally
{
setLoadingHardware
(
false
);
}
},
[]);
const
fetchLocations
=
useCallback
(
async
(
hardwareId
,
gpuCount
=
1
)
=>
{
if
(
!
hardwareId
)
{
setLocations
([]);
setLocationsTotalAvailable
(
0
);
return
[];
}
try
{
setLoadingLocations
(
true
);
const
response
=
await
API
.
get
(
`/api/deployments/available-replicas?hardware_id=
${
hardwareId
}
&gpu_count=
${
gpuCount
}
`
,
);
if
(
response
.
data
.
success
)
{
const
replicas
=
response
.
data
.
data
?.
replicas
||
[];
const
nextLocationsMap
=
new
Map
();
replicas
.
forEach
((
replica
)
=>
{
const
rawId
=
replica
?.
location_id
??
replica
?.
location
?.
id
;
if
(
rawId
===
null
||
rawId
===
undefined
)
return
;
const
mapKey
=
String
(
rawId
);
if
(
nextLocationsMap
.
has
(
mapKey
))
return
;
const
rawIso2
=
replica
?.
iso2
??
replica
?.
location_iso2
??
replica
?.
location
?.
iso2
;
const
iso2
=
rawIso2
?
String
(
rawIso2
).
toUpperCase
()
:
''
;
const
name
=
replica
?.
location_name
??
replica
?.
location
?.
name
??
replica
?.
name
??
String
(
rawId
);
nextLocationsMap
.
set
(
mapKey
,
{
id
:
rawId
,
name
:
String
(
name
),
iso2
,
region
:
replica
?.
region
??
replica
?.
location_region
??
replica
?.
location
?.
region
,
country
:
replica
?.
country
??
replica
?.
location_country
??
replica
?.
location
?.
country
,
code
:
replica
?.
code
??
replica
?.
location_code
??
replica
?.
location
?.
code
,
available
:
Number
(
replica
?.
available_count
)
||
0
,
});
});
const
normalizedLocations
=
Array
.
from
(
nextLocationsMap
.
values
());
setLocations
(
normalizedLocations
);
setLocationsTotalAvailable
(
normalizedLocations
.
reduce
(
(
acc
,
item
)
=>
acc
+
(
item
.
available
||
0
),
0
,
),
);
return
normalizedLocations
;
}
else
{
showError
(
'获取部署位置失败: '
+
response
.
data
.
message
);
setLocationsTotalAvailable
(
0
);
return
[];
}
}
catch
(
error
)
{
showError
(
'获取部署位置失败: '
+
error
.
message
);
setLocationsTotalAvailable
(
0
);
return
[];
}
finally
{
setLoadingLocations
(
false
);
}
},
[]);
const
fetchAvailableReplicas
=
useCallback
(
async
(
hardwareId
,
gpuCount
=
1
)
=>
{
if
(
!
hardwareId
)
{
setAvailableReplicas
([]);
return
[];
}
try
{
setLoadingReplicas
(
true
);
const
response
=
await
API
.
get
(
`/api/deployments/available-replicas?hardware_id=
${
hardwareId
}
&gpu_count=
${
gpuCount
}
`
,
);
if
(
response
.
data
.
success
)
{
const
replicas
=
response
.
data
.
data
.
replicas
||
[];
setAvailableReplicas
(
replicas
);
return
replicas
;
}
else
{
showError
(
'获取可用资源失败: '
+
response
.
data
.
message
);
setAvailableReplicas
([]);
return
[];
}
}
catch
(
error
)
{
console
.
error
(
'Load available replicas error:'
,
error
);
setAvailableReplicas
([]);
return
[];
}
finally
{
setLoadingReplicas
(
false
);
}
},
[],
);
const
calculatePrice
=
useCallback
(
async
(
params
)
=>
{
const
{
locationIds
,
hardwareId
,
gpusPerContainer
,
durationHours
,
replicaCount
,
}
=
params
;
if
(
!
locationIds
?.
length
||
!
hardwareId
||
!
gpusPerContainer
||
!
durationHours
||
!
replicaCount
)
{
setPriceEstimation
(
null
);
return
null
;
}
try
{
setLoadingPrice
(
true
);
const
requestData
=
{
location_ids
:
locationIds
,
hardware_id
:
hardwareId
,
gpus_per_container
:
gpusPerContainer
,
duration_hours
:
durationHours
,
replica_count
:
replicaCount
,
};
const
response
=
await
API
.
post
(
'/api/deployments/price-estimation'
,
requestData
,
);
if
(
response
.
data
.
success
)
{
const
estimation
=
response
.
data
.
data
;
setPriceEstimation
(
estimation
);
return
estimation
;
}
else
{
showError
(
'价格计算失败: '
+
response
.
data
.
message
);
setPriceEstimation
(
null
);
return
null
;
}
}
catch
(
error
)
{
console
.
error
(
'Price calculation error:'
,
error
);
setPriceEstimation
(
null
);
return
null
;
}
finally
{
setLoadingPrice
(
false
);
}
},
[]);
const
checkClusterNameAvailability
=
useCallback
(
async
(
name
)
=>
{
if
(
!
name
?.
trim
())
return
false
;
try
{
const
response
=
await
API
.
get
(
`/api/deployments/check-name?name=
${
encodeURIComponent
(
name
.
trim
())}
`
,
);
if
(
response
.
data
.
success
)
{
return
response
.
data
.
data
.
available
;
}
else
{
showError
(
'检查名称可用性失败: '
+
response
.
data
.
message
);
return
false
;
}
}
catch
(
error
)
{
console
.
error
(
'Check cluster name availability error:'
,
error
);
return
false
;
}
},
[]);
const
createDeployment
=
useCallback
(
async
(
deploymentData
)
=>
{
try
{
const
response
=
await
API
.
post
(
'/api/deployments'
,
deploymentData
);
if
(
response
.
data
.
success
)
{
return
response
.
data
.
data
;
}
else
{
throw
new
Error
(
response
.
data
.
message
||
'创建部署失败'
);
}
}
catch
(
error
)
{
throw
error
;
}
},
[]);
return
{
// Data
hardwareTypes
,
hardwareTotalAvailable
,
locations
,
locationsTotalAvailable
,
availableReplicas
,
priceEstimation
,
// Loading states
loadingHardware
,
loadingLocations
,
loadingReplicas
,
loadingPrice
,
// Functions
fetchHardwareTypes
,
fetchLocations
,
fetchAvailableReplicas
,
calculatePrice
,
checkClusterNameAvailability
,
createDeployment
,
// Clear functions
clearPriceEstimation
:
()
=>
setPriceEstimation
(
null
),
clearAvailableReplicas
:
()
=>
setAvailableReplicas
([]),
};
};
export
default
useDeploymentResources
;
web/src/hooks/model-deployments/useEnhancedDeploymentActions.jsx
deleted
100644 → 0
View file @
d13fa743
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import
{
useState
}
from
'react'
;
import
{
API
,
showError
,
showSuccess
}
from
'../../helpers'
;
export
const
useEnhancedDeploymentActions
=
(
t
)
=>
{
const
[
loading
,
setLoading
]
=
useState
({});
// Set loading state for specific operation
const
setOperationLoading
=
(
operation
,
deploymentId
,
isLoading
)
=>
{
setLoading
((
prev
)
=>
({
...
prev
,
[
`
${
operation
}
_
${
deploymentId
}
`
]:
isLoading
,
}));
};
// Get loading state for specific operation
const
isOperationLoading
=
(
operation
,
deploymentId
)
=>
{
return
loading
[
`
${
operation
}
_
${
deploymentId
}
`
]
||
false
;
};
// Extend deployment duration
const
extendDeployment
=
async
(
deploymentId
,
durationHours
)
=>
{
try
{
setOperationLoading
(
'extend'
,
deploymentId
,
true
);
const
response
=
await
API
.
post
(
`/api/deployments/
${
deploymentId
}
/extend`
,
{
duration_hours
:
durationHours
,
},
);
if
(
response
.
data
.
success
)
{
showSuccess
(
t
(
'容器时长延长成功'
));
return
response
.
data
.
data
;
}
}
catch
(
error
)
{
showError
(
t
(
'延长时长失败'
)
+
': '
+
(
error
.
response
?.
data
?.
message
||
error
.
message
),
);
throw
error
;
}
finally
{
setOperationLoading
(
'extend'
,
deploymentId
,
false
);
}
};
// Get deployment details
const
getDeploymentDetails
=
async
(
deploymentId
)
=>
{
try
{
setOperationLoading
(
'details'
,
deploymentId
,
true
);
const
response
=
await
API
.
get
(
`/api/deployments/
${
deploymentId
}
`
);
if
(
response
.
data
.
success
)
{
return
response
.
data
.
data
;
}
}
catch
(
error
)
{
showError
(
t
(
'获取详情失败'
)
+
': '
+
(
error
.
response
?.
data
?.
message
||
error
.
message
),
);
throw
error
;
}
finally
{
setOperationLoading
(
'details'
,
deploymentId
,
false
);
}
};
// Get deployment logs
const
getDeploymentLogs
=
async
(
deploymentId
,
options
=
{})
=>
{
try
{
setOperationLoading
(
'logs'
,
deploymentId
,
true
);
const
params
=
new
URLSearchParams
();
if
(
options
.
containerId
)
params
.
append
(
'container_id'
,
options
.
containerId
);
if
(
options
.
level
)
params
.
append
(
'level'
,
options
.
level
);
if
(
options
.
limit
)
params
.
append
(
'limit'
,
options
.
limit
.
toString
());
if
(
options
.
cursor
)
params
.
append
(
'cursor'
,
options
.
cursor
);
if
(
options
.
follow
)
params
.
append
(
'follow'
,
'true'
);
if
(
options
.
startTime
)
params
.
append
(
'start_time'
,
options
.
startTime
);
if
(
options
.
endTime
)
params
.
append
(
'end_time'
,
options
.
endTime
);
const
response
=
await
API
.
get
(
`/api/deployments/
${
deploymentId
}
/logs?
${
params
}
`
,
);
if
(
response
.
data
.
success
)
{
return
response
.
data
.
data
;
}
}
catch
(
error
)
{
showError
(
t
(
'获取日志失败'
)
+
': '
+
(
error
.
response
?.
data
?.
message
||
error
.
message
),
);
throw
error
;
}
finally
{
setOperationLoading
(
'logs'
,
deploymentId
,
false
);
}
};
// Update deployment configuration
const
updateDeploymentConfig
=
async
(
deploymentId
,
config
)
=>
{
try
{
setOperationLoading
(
'config'
,
deploymentId
,
true
);
const
response
=
await
API
.
put
(
`/api/deployments/
${
deploymentId
}
`
,
config
,
);
if
(
response
.
data
.
success
)
{
showSuccess
(
t
(
'容器配置更新成功'
));
return
response
.
data
.
data
;
}
}
catch
(
error
)
{
showError
(
t
(
'更新配置失败'
)
+
': '
+
(
error
.
response
?.
data
?.
message
||
error
.
message
),
);
throw
error
;
}
finally
{
setOperationLoading
(
'config'
,
deploymentId
,
false
);
}
};
// Delete (destroy) deployment
const
deleteDeployment
=
async
(
deploymentId
)
=>
{
try
{
setOperationLoading
(
'delete'
,
deploymentId
,
true
);
const
response
=
await
API
.
delete
(
`/api/deployments/
${
deploymentId
}
`
);
if
(
response
.
data
.
success
)
{
showSuccess
(
t
(
'容器销毁请求已提交'
));
return
response
.
data
.
data
;
}
}
catch
(
error
)
{
showError
(
t
(
'销毁容器失败'
)
+
': '
+
(
error
.
response
?.
data
?.
message
||
error
.
message
),
);
throw
error
;
}
finally
{
setOperationLoading
(
'delete'
,
deploymentId
,
false
);
}
};
// Update deployment name
const
updateDeploymentName
=
async
(
deploymentId
,
newName
)
=>
{
try
{
setOperationLoading
(
'rename'
,
deploymentId
,
true
);
const
response
=
await
API
.
put
(
`/api/deployments/
${
deploymentId
}
/name`
,
{
name
:
newName
,
});
if
(
response
.
data
.
success
)
{
showSuccess
(
t
(
'容器名称更新成功'
));
return
response
.
data
.
data
;
}
}
catch
(
error
)
{
showError
(
t
(
'更新名称失败'
)
+
': '
+
(
error
.
response
?.
data
?.
message
||
error
.
message
),
);
throw
error
;
}
finally
{
setOperationLoading
(
'rename'
,
deploymentId
,
false
);
}
};
// Batch operations
const
batchDelete
=
async
(
deploymentIds
)
=>
{
try
{
setOperationLoading
(
'batch_delete'
,
'all'
,
true
);
const
results
=
await
Promise
.
allSettled
(
deploymentIds
.
map
((
id
)
=>
deleteDeployment
(
id
)),
);
const
successful
=
results
.
filter
((
r
)
=>
r
.
status
===
'fulfilled'
).
length
;
const
failed
=
results
.
filter
((
r
)
=>
r
.
status
===
'rejected'
).
length
;
if
(
successful
>
0
)
{
showSuccess
(
t
(
'批量操作完成: {{success}}个成功, {{failed}}个失败'
,
{
success
:
successful
,
failed
:
failed
,
}),
);
}
return
{
successful
,
failed
};
}
catch
(
error
)
{
showError
(
t
(
'批量操作失败'
)
+
': '
+
error
.
message
);
throw
error
;
}
finally
{
setOperationLoading
(
'batch_delete'
,
'all'
,
false
);
}
};
// Export logs
const
exportLogs
=
async
(
deploymentId
,
options
=
{})
=>
{
try
{
setOperationLoading
(
'export_logs'
,
deploymentId
,
true
);
const
logs
=
await
getDeploymentLogs
(
deploymentId
,
{
...
options
,
limit
:
10000
,
// Get more logs for export
});
if
(
logs
&&
logs
.
logs
)
{
const
logText
=
logs
.
logs
.
map
(
(
log
)
=>
`[
${
new
Date
(
log
.
timestamp
).
toISOString
()}
] [
${
log
.
level
}
]
${
log
.
source
?
`[
${
log
.
source
}
] `
:
''
}${
log
.
message
}
`
,
)
.
join
(
'\n'
);
const
blob
=
new
Blob
([
logText
],
{
type
:
'text/plain'
});
const
url
=
URL
.
createObjectURL
(
blob
);
const
a
=
document
.
createElement
(
'a'
);
a
.
href
=
url
;
a
.
download
=
`deployment-
${
deploymentId
}
-logs-
${
new
Date
().
toISOString
().
split
(
'T'
)[
0
]}
.txt`
;
document
.
body
.
appendChild
(
a
);
a
.
click
();
document
.
body
.
removeChild
(
a
);
URL
.
revokeObjectURL
(
url
);
showSuccess
(
t
(
'日志导出成功'
));
}
}
catch
(
error
)
{
showError
(
t
(
'导出日志失败'
)
+
': '
+
error
.
message
);
throw
error
;
}
finally
{
setOperationLoading
(
'export_logs'
,
deploymentId
,
false
);
}
};
return
{
// Actions
extendDeployment
,
getDeploymentDetails
,
getDeploymentLogs
,
updateDeploymentConfig
,
deleteDeployment
,
updateDeploymentName
,
batchDelete
,
exportLogs
,
// Loading states
isOperationLoading
,
loading
,
// Utility
setOperationLoading
,
};
};
export
default
useEnhancedDeploymentActions
;
web/src/pages/Setting/Personal/SettingsSidebarModulesUser.jsx
deleted
100644 → 0
View file @
d13fa743
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment