release: bump version to 0.50.0
This commit is contained in:
@@ -73,9 +73,13 @@ export const DOCS_METADATA: Record<string, DocsMetadataEntry> = {
|
||||
zh: { title: 'Planet 使用手册', group: 'Manual', order: 2 },
|
||||
en: { title: 'Planet Manual', group: 'Manual', order: 2 },
|
||||
},
|
||||
'faq.md': {
|
||||
zh: { title: '常见问题', group: 'Manual', order: 3 },
|
||||
en: { title: 'FAQ', group: 'Manual', order: 3 },
|
||||
},
|
||||
'location-pipeline-user.md': {
|
||||
zh: { title: 'Earth 位置候选采集使用手册', group: 'Manual', order: 3 },
|
||||
en: { title: 'Earth Location Candidate Collection User Guide', group: 'Manual', order: 3 },
|
||||
zh: { title: 'Earth 位置候选采集使用手册', group: 'Manual', order: 4 },
|
||||
en: { title: 'Earth Location Candidate Collection User Guide', group: 'Manual', order: 4 },
|
||||
},
|
||||
'earth-frontend-context.md': {
|
||||
zh: { title: 'Earth 前端结构', group: 'Earth', order: 10 },
|
||||
|
||||
@@ -244,6 +244,7 @@ function Playground() {
|
||||
const pollTimerRef = useRef<number | null>(null)
|
||||
const messagesContainerRef = useRef<HTMLDivElement | null>(null)
|
||||
const messagesShellRef = useRef<HTMLDivElement | null>(null)
|
||||
const composerRef = useRef<HTMLDivElement | null>(null)
|
||||
const forceScrollToBottomRef = useRef(true)
|
||||
|
||||
const selectedPreset = useMemo(
|
||||
@@ -256,6 +257,7 @@ function Playground() {
|
||||
)
|
||||
const streaming = Boolean(activeAssistantMessage)
|
||||
const sendButtonTooltip = requestPending ? '发送中' : streaming ? '停止生成' : '发送消息'
|
||||
const composerExpanded = composerFocused || !!inputValue
|
||||
|
||||
const applyThreadSnapshot = (thread: PlaygroundThreadRecord | null) => {
|
||||
if (!thread) {
|
||||
@@ -423,6 +425,7 @@ function Playground() {
|
||||
}, [])
|
||||
|
||||
const handleApplyPreset = (preset: PlaygroundPreset) => {
|
||||
setComposerFocused(true)
|
||||
setSelectedPresetKey(preset.key)
|
||||
setTitle(preset.values.title)
|
||||
setObjective(preset.values.objective)
|
||||
@@ -430,6 +433,15 @@ function Playground() {
|
||||
setInputValue(preset.values.message)
|
||||
}
|
||||
|
||||
const handleComposerBlur = () => {
|
||||
window.setTimeout(() => {
|
||||
const composer = composerRef.current
|
||||
if (!composer || !composer.contains(document.activeElement)) {
|
||||
setComposerFocused(false)
|
||||
}
|
||||
}, 0)
|
||||
}
|
||||
|
||||
const handleStop = async () => {
|
||||
if (!token || !activeAssistantMessage) {
|
||||
return
|
||||
@@ -864,9 +876,17 @@ function Playground() {
|
||||
</div>
|
||||
|
||||
<div className="playground-chat__composer">
|
||||
<div className={`playground-chat__input-wrap${composerFocused || !!inputValue ? ' playground-chat__input-wrap--expanded' : ''}`}>
|
||||
{(composerFocused || !!inputValue) && (
|
||||
<div className="playground-preset-strip__actions">
|
||||
<div
|
||||
ref={composerRef}
|
||||
className={`playground-chat__input-wrap${composerExpanded ? ' playground-chat__input-wrap--expanded' : ''}`}
|
||||
onFocusCapture={() => setComposerFocused(true)}
|
||||
onBlurCapture={handleComposerBlur}
|
||||
>
|
||||
{composerExpanded && (
|
||||
<div
|
||||
className="playground-preset-strip__actions playground-preset-strip__actions--composer"
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
>
|
||||
{PLAYGROUND_PRESETS.map((preset) => (
|
||||
<Tag.CheckableTag
|
||||
key={preset.key}
|
||||
@@ -882,11 +902,9 @@ function Playground() {
|
||||
<Input.TextArea
|
||||
value={inputValue}
|
||||
onChange={(event) => setInputValue(event.target.value)}
|
||||
autoSize={composerFocused || !!inputValue ? { minRows: 4, maxRows: 10 } : { minRows: 1, maxRows: 1 }}
|
||||
autoSize={composerExpanded ? { minRows: 4, maxRows: 10 } : { minRows: 1, maxRows: 1 }}
|
||||
placeholder="在这里输入本次分析请求..."
|
||||
className="playground-chat__input"
|
||||
onFocus={() => setComposerFocused(true)}
|
||||
onBlur={() => setComposerFocused(false)}
|
||||
onPressEnter={(event) => {
|
||||
if (!event.shiftKey) {
|
||||
event.preventDefault()
|
||||
@@ -894,7 +912,7 @@ function Playground() {
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{!(composerFocused || !!inputValue) && (
|
||||
{!composerExpanded && (
|
||||
<Tooltip title={sendButtonTooltip}>
|
||||
<Button
|
||||
type="text"
|
||||
@@ -907,7 +925,7 @@ function Playground() {
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
{(composerFocused || !!inputValue) && (
|
||||
{composerExpanded && (
|
||||
<div className="playground-chat__actions">
|
||||
<div className="playground-chat__hints">
|
||||
<Tag>{title}</Tag>
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
CheckCircleOutlined,
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
EyeInvisibleOutlined,
|
||||
EyeOutlined,
|
||||
PlayCircleOutlined,
|
||||
PlusOutlined,
|
||||
ReloadOutlined,
|
||||
@@ -129,17 +131,31 @@ interface TVSettings {
|
||||
interface SecretStatus {
|
||||
configured: boolean
|
||||
preview: string
|
||||
source?: string
|
||||
}
|
||||
|
||||
interface AIProviderConfig {
|
||||
provider: string
|
||||
provider_api: string
|
||||
base_url: string
|
||||
model: string
|
||||
api_key: SecretStatus
|
||||
max_tokens: number
|
||||
anthropic_version: string
|
||||
source?: string
|
||||
}
|
||||
|
||||
interface ExternalIntegrations {
|
||||
ai_provider: {
|
||||
service_url: string
|
||||
service_token: SecretStatus
|
||||
default_provider: string
|
||||
provider: string
|
||||
provider_api: string
|
||||
base_url: string
|
||||
model: string
|
||||
api_key: SecretStatus
|
||||
providers: Record<string, AIProviderConfig>
|
||||
max_tokens: number
|
||||
anthropic_version: string
|
||||
timeout_seconds: number
|
||||
@@ -330,6 +346,11 @@ function Settings() {
|
||||
const [refreshingAiPreset, setRefreshingAiPreset] = useState(false)
|
||||
const [savingTvSettings, setSavingTvSettings] = useState(false)
|
||||
const [savingIntegrations, setSavingIntegrations] = useState(false)
|
||||
const [aiProviderSaveFeedback, setAiProviderSaveFeedback] = useState<{ type: 'success' | 'error'; message: string } | null>(null)
|
||||
const [testingAiProviderConnection, setTestingAiProviderConnection] = useState(false)
|
||||
const [revealedAiProviderSecrets, setRevealedAiProviderSecrets] = useState<Record<string, { api_key: string; service_token: string }>>({})
|
||||
const [aiProviderApiKeyRevealed, setAiProviderApiKeyRevealed] = useState(false)
|
||||
const [serviceTokenRevealed, setServiceTokenRevealed] = useState(false)
|
||||
const [testingCredentialProvider, setTestingCredentialProvider] = useState<string | null>(null)
|
||||
const [credentialGuide, setCredentialGuide] = useState<CredentialGuide | null>(null)
|
||||
const [credentialGuideOpen, setCredentialGuideOpen] = useState(false)
|
||||
@@ -348,6 +369,9 @@ function Settings() {
|
||||
const [customSourceForm] = Form.useForm()
|
||||
const [tvEditForm] = Form.useForm<TVStreamSource>()
|
||||
const selectedAiProvider = Form.useWatch(['ai_provider', 'provider'], integrationForm)
|
||||
const selectedAiProviderSecret = selectedAiProvider
|
||||
? integrations?.ai_provider.providers?.[selectedAiProvider]?.api_key || integrations?.ai_provider.api_key
|
||||
: integrations?.ai_provider.api_key
|
||||
const customCollectors: CollectorSettings[] = useMemo(() => customSourceConfigs.map((config) => ({
|
||||
id: -(config.config_id || 0),
|
||||
name: config.name,
|
||||
@@ -458,18 +482,21 @@ function Settings() {
|
||||
integrationForm.setFieldsValue({
|
||||
ai_provider: {
|
||||
service_url: integrations.ai_provider.service_url,
|
||||
service_token: '',
|
||||
service_token: integrations.ai_provider.service_token.configured
|
||||
? integrations.ai_provider.service_token.preview
|
||||
: '',
|
||||
default_provider: integrations.ai_provider.default_provider || integrations.ai_provider.provider,
|
||||
provider: integrations.ai_provider.provider,
|
||||
provider_api: integrations.ai_provider.provider_api,
|
||||
base_url: integrations.ai_provider.base_url,
|
||||
model: integrations.ai_provider.model,
|
||||
api_key: '',
|
||||
api_key: integrations.ai_provider.api_key.configured
|
||||
? integrations.ai_provider.api_key.preview
|
||||
: '',
|
||||
max_tokens: integrations.ai_provider.max_tokens,
|
||||
anthropic_version: integrations.ai_provider.anthropic_version,
|
||||
timeout_seconds: integrations.ai_provider.timeout_seconds,
|
||||
retry_attempts: integrations.ai_provider.retry_attempts,
|
||||
clear_service_token: false,
|
||||
clear_api_key: false,
|
||||
},
|
||||
barentswatch: {
|
||||
endpoint: integrations.barentswatch.endpoint,
|
||||
@@ -607,8 +634,10 @@ function Settings() {
|
||||
const saveIntegrations = async (values: any) => {
|
||||
try {
|
||||
setSavingIntegrations(true)
|
||||
const aiProviderPayload = buildAiProviderDraftPayload(values)
|
||||
const payload = {
|
||||
...values,
|
||||
ai_provider: aiProviderPayload,
|
||||
barentswatch: {
|
||||
...values.barentswatch,
|
||||
client_secret:
|
||||
@@ -619,15 +648,149 @@ function Settings() {
|
||||
}
|
||||
const response = await axios.put('/api/v1/settings/integrations', payload)
|
||||
setIntegrations(response.data.integrations)
|
||||
setAiProviderSaveFeedback({ type: 'success', message: 'AI 配置已保存为全局默认配置' })
|
||||
message.success('外部集成配置已保存')
|
||||
await fetchSettings()
|
||||
} catch {
|
||||
setAiProviderSaveFeedback({ type: 'error', message: 'AI 配置保存失败' })
|
||||
message.error('外部集成配置保存失败')
|
||||
} finally {
|
||||
setSavingIntegrations(false)
|
||||
}
|
||||
}
|
||||
|
||||
const buildAiProviderDraftPayload = (values: any) => {
|
||||
const selectedProvider = String(values.ai_provider?.provider || integrations?.ai_provider.provider || 'minimax')
|
||||
const providerSecret = integrations?.ai_provider.providers?.[selectedProvider]?.api_key
|
||||
|| integrations?.ai_provider.api_key
|
||||
const revealedSecrets = revealedAiProviderSecrets[selectedProvider]
|
||||
const nextApiKey = String(values.ai_provider?.api_key || '').trim()
|
||||
const apiKeyUnchanged = !nextApiKey
|
||||
|| nextApiKey === providerSecret?.preview
|
||||
|| nextApiKey === revealedSecrets?.api_key
|
||||
|| nextApiKey.startsWith('••••')
|
||||
|| nextApiKey.includes('*')
|
||||
const nextServiceToken = String(values.ai_provider?.service_token || '').trim()
|
||||
const serviceTokenUnchanged = !nextServiceToken
|
||||
|| nextServiceToken === integrations?.ai_provider.service_token.preview
|
||||
|| nextServiceToken === revealedSecrets?.service_token
|
||||
|| nextServiceToken.startsWith('••••')
|
||||
|| nextServiceToken.includes('*')
|
||||
return {
|
||||
...values.ai_provider,
|
||||
default_provider: selectedProvider,
|
||||
api_key: apiKeyUnchanged ? '' : nextApiKey,
|
||||
service_token: serviceTokenUnchanged ? '' : nextServiceToken,
|
||||
}
|
||||
}
|
||||
|
||||
const revealAiProviderSecrets = async (provider: string) => {
|
||||
const cached = revealedAiProviderSecrets[provider]
|
||||
if (cached) return cached
|
||||
const response = await axios.get('/api/v1/settings/integrations/ai-provider/secrets', {
|
||||
params: { provider },
|
||||
})
|
||||
const secrets = {
|
||||
api_key: String(response.data.api_key || ''),
|
||||
service_token: String(response.data.service_token || ''),
|
||||
}
|
||||
setRevealedAiProviderSecrets((prev) => ({ ...prev, [provider]: secrets }))
|
||||
return secrets
|
||||
}
|
||||
|
||||
const handleAiProviderApiKeyVisibleChange = async (visible: boolean) => {
|
||||
const provider = String(integrationForm.getFieldValue(['ai_provider', 'provider']) || integrations?.ai_provider.provider || 'minimax')
|
||||
const providerSecret = integrations?.ai_provider.providers?.[provider]?.api_key || integrations?.ai_provider.api_key
|
||||
if (visible) {
|
||||
try {
|
||||
const secrets = await revealAiProviderSecrets(provider)
|
||||
if (secrets.api_key) {
|
||||
integrationForm.setFieldValue(['ai_provider', 'api_key'], secrets.api_key)
|
||||
}
|
||||
setAiProviderApiKeyRevealed(true)
|
||||
} catch {
|
||||
message.error('读取 LLM API Key 失败')
|
||||
}
|
||||
return
|
||||
}
|
||||
const currentValue = String(integrationForm.getFieldValue(['ai_provider', 'api_key']) || '')
|
||||
const revealedValue = revealedAiProviderSecrets[provider]?.api_key
|
||||
if (revealedValue && currentValue === revealedValue) {
|
||||
integrationForm.setFieldValue(['ai_provider', 'api_key'], providerSecret?.preview || '')
|
||||
}
|
||||
setAiProviderApiKeyRevealed(false)
|
||||
}
|
||||
|
||||
const handleServiceTokenVisibleChange = async (visible: boolean) => {
|
||||
const provider = String(integrationForm.getFieldValue(['ai_provider', 'provider']) || integrations?.ai_provider.provider || 'minimax')
|
||||
if (visible) {
|
||||
try {
|
||||
const secrets = await revealAiProviderSecrets(provider)
|
||||
if (secrets.service_token) {
|
||||
integrationForm.setFieldValue(['ai_provider', 'service_token'], secrets.service_token)
|
||||
}
|
||||
setServiceTokenRevealed(true)
|
||||
} catch {
|
||||
message.error('读取代理 Token 失败')
|
||||
}
|
||||
return
|
||||
}
|
||||
const currentValue = String(integrationForm.getFieldValue(['ai_provider', 'service_token']) || '')
|
||||
const revealedValue = revealedAiProviderSecrets[provider]?.service_token
|
||||
if (revealedValue && currentValue === revealedValue) {
|
||||
integrationForm.setFieldValue(['ai_provider', 'service_token'], integrations?.ai_provider.service_token.preview || '')
|
||||
}
|
||||
setServiceTokenRevealed(false)
|
||||
}
|
||||
|
||||
const testAiProviderConnection = async () => {
|
||||
try {
|
||||
setTestingAiProviderConnection(true)
|
||||
const values = integrationForm.getFieldsValue(true)
|
||||
const response = await axios.post(
|
||||
'/api/v1/settings/integrations/ai-provider/connect',
|
||||
buildAiProviderDraftPayload(values),
|
||||
)
|
||||
if (response.data.success || response.data.connected) {
|
||||
if (response.data.integrations) {
|
||||
setIntegrations(response.data.integrations)
|
||||
integrationForm.setFieldsValue({
|
||||
ai_provider: {
|
||||
service_url: response.data.integrations.ai_provider.service_url,
|
||||
service_token: response.data.integrations.ai_provider.service_token.configured
|
||||
? response.data.integrations.ai_provider.service_token.preview
|
||||
: '',
|
||||
default_provider: response.data.integrations.ai_provider.default_provider || response.data.integrations.ai_provider.provider,
|
||||
provider: response.data.integrations.ai_provider.provider,
|
||||
provider_api: response.data.integrations.ai_provider.provider_api,
|
||||
base_url: response.data.integrations.ai_provider.base_url,
|
||||
model: response.data.integrations.ai_provider.model,
|
||||
api_key: response.data.integrations.ai_provider.api_key.configured
|
||||
? response.data.integrations.ai_provider.api_key.preview
|
||||
: '',
|
||||
max_tokens: response.data.integrations.ai_provider.max_tokens,
|
||||
anthropic_version: response.data.integrations.ai_provider.anthropic_version,
|
||||
timeout_seconds: response.data.integrations.ai_provider.timeout_seconds,
|
||||
retry_attempts: response.data.integrations.ai_provider.retry_attempts,
|
||||
},
|
||||
})
|
||||
}
|
||||
setAiProviderSaveFeedback({ type: 'success', message: response.data.message || 'AI Provider 连接成功,已保存为全局默认配置' })
|
||||
message.success(response.data.message || 'AI Provider 连接成功')
|
||||
} else {
|
||||
setAiProviderSaveFeedback({ type: 'error', message: response.data.message || 'AI Provider 连接失败' })
|
||||
message.error(response.data.message || 'AI Provider 连接失败')
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
const err = error as { response?: { data?: { detail?: string; message?: string } } }
|
||||
const errorMessage = err.response?.data?.message || err.response?.data?.detail || 'AI Provider 连接失败'
|
||||
setAiProviderSaveFeedback({ type: 'error', message: errorMessage })
|
||||
message.error(errorMessage)
|
||||
} finally {
|
||||
setTestingAiProviderConnection(false)
|
||||
}
|
||||
}
|
||||
|
||||
const saveSelectedCollectorSettings = async () => {
|
||||
if (!selectedCollector || !selectedCollectorConfig) return
|
||||
try {
|
||||
@@ -1064,21 +1227,31 @@ function Settings() {
|
||||
}
|
||||
}
|
||||
|
||||
const applyAiProviderPreset = (preset: AIProviderPreset) => {
|
||||
const applyAiProviderSelection = (provider: string, presetOverride?: AIProviderPreset) => {
|
||||
const preset = presetOverride || aiProviderPresets.find((item) => item.provider === provider)
|
||||
const savedProvider = integrations?.ai_provider.providers?.[provider]
|
||||
const providerApi = savedProvider?.provider_api || preset?.provider_api || 'openai-completions'
|
||||
const apiKey = savedProvider?.api_key.configured ? savedProvider.api_key.preview : ''
|
||||
integrationForm.setFieldsValue({
|
||||
ai_provider: {
|
||||
provider: preset.provider,
|
||||
provider_api: preset.provider_api,
|
||||
base_url: preset.base_url,
|
||||
model: preset.model,
|
||||
max_tokens: preset.provider_api === 'anthropic-messages'
|
||||
default_provider: provider,
|
||||
provider,
|
||||
provider_api: providerApi,
|
||||
base_url: savedProvider?.base_url || preset?.base_url || '',
|
||||
model: savedProvider?.model || preset?.model || '',
|
||||
api_key: apiKey,
|
||||
max_tokens: savedProvider?.max_tokens || (providerApi === 'anthropic-messages'
|
||||
? ANTHROPIC_MESSAGES_MAX_TOKENS
|
||||
: DEFAULT_PROVIDER_MAX_TOKENS,
|
||||
anthropic_version: '2023-06-01',
|
||||
: DEFAULT_PROVIDER_MAX_TOKENS),
|
||||
anthropic_version: savedProvider?.anthropic_version || '2023-06-01',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const applyAiProviderPreset = (preset: AIProviderPreset) => {
|
||||
applyAiProviderSelection(preset.provider, preset)
|
||||
}
|
||||
|
||||
const refreshSelectedAiProviderPreset = async () => {
|
||||
const provider = integrationForm.getFieldValue(['ai_provider', 'provider'])
|
||||
if (!provider) return
|
||||
@@ -1624,7 +1797,12 @@ function Settings() {
|
||||
forceRender: true,
|
||||
children: (
|
||||
<SettingsPanel loading={loading}>
|
||||
<Form form={integrationForm} layout="vertical" onFinish={saveIntegrations}>
|
||||
<Form
|
||||
form={integrationForm}
|
||||
layout="vertical"
|
||||
onFinish={saveIntegrations}
|
||||
onValuesChange={() => setAiProviderSaveFeedback(null)}
|
||||
>
|
||||
<Card size="small" title={<Space><ApiOutlined />LLM Provider</Space>}>
|
||||
<Form.Item name={['ai_provider', 'provider']} label="Provider">
|
||||
<Select
|
||||
@@ -1635,8 +1813,7 @@ function Settings() {
|
||||
label: `${preset.label} · ${preset.provider_api}`,
|
||||
}))}
|
||||
onChange={(value) => {
|
||||
const preset = aiProviderPresets.find((item) => item.provider === value)
|
||||
if (preset) applyAiProviderPreset(preset)
|
||||
applyAiProviderSelection(value)
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
@@ -1646,13 +1823,22 @@ function Settings() {
|
||||
<Input placeholder="https://api.example.com/v1" />
|
||||
</Form.Item>
|
||||
<Form.Item label=" ">
|
||||
<Button
|
||||
icon={<SyncOutlined />}
|
||||
loading={refreshingAiPreset}
|
||||
onClick={refreshSelectedAiProviderPreset}
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
<Space>
|
||||
<Button
|
||||
icon={<SyncOutlined />}
|
||||
loading={refreshingAiPreset}
|
||||
onClick={refreshSelectedAiProviderPreset}
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
<Button
|
||||
icon={<PlayCircleOutlined />}
|
||||
loading={testingAiProviderConnection}
|
||||
onClick={() => { void testAiProviderConnection() }}
|
||||
>
|
||||
测试连接
|
||||
</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
@@ -1678,21 +1864,28 @@ function Settings() {
|
||||
<Form.Item label="LLM API Key">
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Space>
|
||||
<Tag color={integrations?.ai_provider.api_key.configured ? 'green' : 'default'}>
|
||||
{integrations?.ai_provider.api_key.configured
|
||||
? `已配置 ${integrations.ai_provider.api_key.preview}`
|
||||
: '未配置'}
|
||||
<Tag color={selectedAiProviderSecret?.configured ? 'green' : 'default'}>
|
||||
{selectedAiProviderSecret?.configured ? '已配置' : '未配置'}
|
||||
</Tag>
|
||||
<Text type="secondary">留空表示保留现有 key。</Text>
|
||||
<Text type="secondary">保存时只会更新当前 Provider 的 key。</Text>
|
||||
</Space>
|
||||
<Form.Item name={['ai_provider', 'api_key']} noStyle>
|
||||
<Input.Password autoComplete="new-password" placeholder="输入新的 LLM API key" />
|
||||
<Input
|
||||
autoComplete="new-password"
|
||||
placeholder="输入新的 LLM API key"
|
||||
suffix={(
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={aiProviderApiKeyRevealed ? <EyeInvisibleOutlined /> : <EyeOutlined />}
|
||||
onClick={() => { void handleAiProviderApiKeyVisibleChange(!aiProviderApiKeyRevealed) }}
|
||||
aria-label={aiProviderApiKeyRevealed ? '隐藏 LLM API key' : '显示 LLM API key'}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
<Form.Item name={['ai_provider', 'clear_api_key']} valuePropName="checked">
|
||||
<Checkbox>清除当前 LLM API key</Checkbox>
|
||||
</Form.Item>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0 16px' }}>
|
||||
<Form.Item name={['ai_provider', 'max_tokens']} label="最大输出 Tokens">
|
||||
<InputNumber min={1} max={200000} style={{ width: '100%' }} />
|
||||
@@ -1715,20 +1908,27 @@ function Settings() {
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Space>
|
||||
<Tag color={integrations?.ai_provider.service_token.configured ? 'green' : 'default'}>
|
||||
{integrations?.ai_provider.service_token.configured
|
||||
? `已配置 ${integrations.ai_provider.service_token.preview}`
|
||||
: '未配置'}
|
||||
{integrations?.ai_provider.service_token.configured ? '已配置' : '未配置'}
|
||||
</Tag>
|
||||
<Text type="secondary">通常不需要改;用于 backend 调本地 aiprovider。</Text>
|
||||
</Space>
|
||||
<Form.Item name={['ai_provider', 'service_token']} noStyle>
|
||||
<Input.Password autoComplete="new-password" placeholder="输入新的代理 token" />
|
||||
<Input
|
||||
autoComplete="new-password"
|
||||
placeholder="输入新的代理 token"
|
||||
suffix={(
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={serviceTokenRevealed ? <EyeInvisibleOutlined /> : <EyeOutlined />}
|
||||
onClick={() => { void handleServiceTokenVisibleChange(!serviceTokenRevealed) }}
|
||||
aria-label={serviceTokenRevealed ? '隐藏代理 token' : '显示代理 token'}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
<Form.Item name={['ai_provider', 'clear_service_token']} valuePropName="checked">
|
||||
<Checkbox>清除当前代理 token</Checkbox>
|
||||
</Form.Item>
|
||||
</Card>
|
||||
</Card>
|
||||
|
||||
@@ -1740,6 +1940,14 @@ function Settings() {
|
||||
>
|
||||
保存 AI 配置
|
||||
</Button>
|
||||
{aiProviderSaveFeedback ? (
|
||||
<Alert
|
||||
showIcon
|
||||
type={aiProviderSaveFeedback.type}
|
||||
message={aiProviderSaveFeedback.message}
|
||||
style={{ marginTop: 12 }}
|
||||
/>
|
||||
) : null}
|
||||
</Form>
|
||||
</SettingsPanel>
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user