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
fd6ae3ea
authored
Feb 27, 2025
by
1808837298@qq.com
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: Add Claude model configuration management #791
parent
fe9a3025
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
473 additions
and
4 deletions
+473
-4
setting/config/config.go
+259
-0
setting/model_setting/claude.go
+48
-0
web/src/components/ModelSetting.js
+14
-4
web/src/pages/Setting/Model/SettingClaudeModel.js
+152
-0
No files found.
setting/config/config.go
0 → 100644
View file @
fd6ae3ea
package
config
import
(
"encoding/json"
"one-api/common"
"reflect"
"strconv"
"strings"
"sync"
)
// ConfigManager 统一管理所有配置
type
ConfigManager
struct
{
configs
map
[
string
]
interface
{}
mutex
sync
.
RWMutex
}
var
GlobalConfig
=
NewConfigManager
()
func
NewConfigManager
()
*
ConfigManager
{
return
&
ConfigManager
{
configs
:
make
(
map
[
string
]
interface
{}),
}
}
// Register 注册一个配置模块
func
(
cm
*
ConfigManager
)
Register
(
name
string
,
config
interface
{})
{
cm
.
mutex
.
Lock
()
defer
cm
.
mutex
.
Unlock
()
cm
.
configs
[
name
]
=
config
}
// Get 获取指定配置模块
func
(
cm
*
ConfigManager
)
Get
(
name
string
)
interface
{}
{
cm
.
mutex
.
RLock
()
defer
cm
.
mutex
.
RUnlock
()
return
cm
.
configs
[
name
]
}
// LoadFromDB 从数据库加载配置
func
(
cm
*
ConfigManager
)
LoadFromDB
(
options
map
[
string
]
string
)
error
{
cm
.
mutex
.
Lock
()
defer
cm
.
mutex
.
Unlock
()
for
name
,
config
:=
range
cm
.
configs
{
prefix
:=
name
+
"."
configMap
:=
make
(
map
[
string
]
string
)
// 收集属于此配置的所有选项
for
key
,
value
:=
range
options
{
if
strings
.
HasPrefix
(
key
,
prefix
)
{
configKey
:=
strings
.
TrimPrefix
(
key
,
prefix
)
configMap
[
configKey
]
=
value
}
}
// 如果找到配置项,则更新配置
if
len
(
configMap
)
>
0
{
if
err
:=
updateConfigFromMap
(
config
,
configMap
);
err
!=
nil
{
common
.
SysError
(
"failed to update config "
+
name
+
": "
+
err
.
Error
())
continue
}
}
}
return
nil
}
// SaveToDB 将配置保存到数据库
func
(
cm
*
ConfigManager
)
SaveToDB
(
updateFunc
func
(
key
,
value
string
)
error
)
error
{
cm
.
mutex
.
RLock
()
defer
cm
.
mutex
.
RUnlock
()
for
name
,
config
:=
range
cm
.
configs
{
configMap
,
err
:=
configToMap
(
config
)
if
err
!=
nil
{
return
err
}
for
key
,
value
:=
range
configMap
{
dbKey
:=
name
+
"."
+
key
if
err
:=
updateFunc
(
dbKey
,
value
);
err
!=
nil
{
return
err
}
}
}
return
nil
}
// 辅助函数:将配置对象转换为map
func
configToMap
(
config
interface
{})
(
map
[
string
]
string
,
error
)
{
result
:=
make
(
map
[
string
]
string
)
val
:=
reflect
.
ValueOf
(
config
)
if
val
.
Kind
()
==
reflect
.
Ptr
{
val
=
val
.
Elem
()
}
if
val
.
Kind
()
!=
reflect
.
Struct
{
return
nil
,
nil
}
typ
:=
val
.
Type
()
for
i
:=
0
;
i
<
val
.
NumField
();
i
++
{
field
:=
val
.
Field
(
i
)
fieldType
:=
typ
.
Field
(
i
)
// 跳过未导出字段
if
!
fieldType
.
IsExported
()
{
continue
}
// 获取json标签作为键名
key
:=
fieldType
.
Tag
.
Get
(
"json"
)
if
key
==
""
||
key
==
"-"
{
key
=
fieldType
.
Name
}
// 处理不同类型的字段
var
strValue
string
switch
field
.
Kind
()
{
case
reflect
.
String
:
strValue
=
field
.
String
()
case
reflect
.
Bool
:
strValue
=
strconv
.
FormatBool
(
field
.
Bool
())
case
reflect
.
Int
,
reflect
.
Int8
,
reflect
.
Int16
,
reflect
.
Int32
,
reflect
.
Int64
:
strValue
=
strconv
.
FormatInt
(
field
.
Int
(),
10
)
case
reflect
.
Uint
,
reflect
.
Uint8
,
reflect
.
Uint16
,
reflect
.
Uint32
,
reflect
.
Uint64
:
strValue
=
strconv
.
FormatUint
(
field
.
Uint
(),
10
)
case
reflect
.
Float32
,
reflect
.
Float64
:
strValue
=
strconv
.
FormatFloat
(
field
.
Float
(),
'f'
,
-
1
,
64
)
case
reflect
.
Map
,
reflect
.
Slice
,
reflect
.
Struct
:
// 复杂类型使用JSON序列化
bytes
,
err
:=
json
.
Marshal
(
field
.
Interface
())
if
err
!=
nil
{
return
nil
,
err
}
strValue
=
string
(
bytes
)
default
:
// 跳过不支持的类型
continue
}
result
[
key
]
=
strValue
}
return
result
,
nil
}
// 辅助函数:从map更新配置对象
func
updateConfigFromMap
(
config
interface
{},
configMap
map
[
string
]
string
)
error
{
val
:=
reflect
.
ValueOf
(
config
)
if
val
.
Kind
()
!=
reflect
.
Ptr
{
return
nil
}
val
=
val
.
Elem
()
if
val
.
Kind
()
!=
reflect
.
Struct
{
return
nil
}
typ
:=
val
.
Type
()
for
i
:=
0
;
i
<
val
.
NumField
();
i
++
{
field
:=
val
.
Field
(
i
)
fieldType
:=
typ
.
Field
(
i
)
// 跳过未导出字段
if
!
fieldType
.
IsExported
()
{
continue
}
// 获取json标签作为键名
key
:=
fieldType
.
Tag
.
Get
(
"json"
)
if
key
==
""
||
key
==
"-"
{
key
=
fieldType
.
Name
}
// 检查map中是否有对应的值
strValue
,
ok
:=
configMap
[
key
]
if
!
ok
{
continue
}
// 根据字段类型设置值
if
!
field
.
CanSet
()
{
continue
}
switch
field
.
Kind
()
{
case
reflect
.
String
:
field
.
SetString
(
strValue
)
case
reflect
.
Bool
:
boolValue
,
err
:=
strconv
.
ParseBool
(
strValue
)
if
err
!=
nil
{
continue
}
field
.
SetBool
(
boolValue
)
case
reflect
.
Int
,
reflect
.
Int8
,
reflect
.
Int16
,
reflect
.
Int32
,
reflect
.
Int64
:
intValue
,
err
:=
strconv
.
ParseInt
(
strValue
,
10
,
64
)
if
err
!=
nil
{
continue
}
field
.
SetInt
(
intValue
)
case
reflect
.
Uint
,
reflect
.
Uint8
,
reflect
.
Uint16
,
reflect
.
Uint32
,
reflect
.
Uint64
:
uintValue
,
err
:=
strconv
.
ParseUint
(
strValue
,
10
,
64
)
if
err
!=
nil
{
continue
}
field
.
SetUint
(
uintValue
)
case
reflect
.
Float32
,
reflect
.
Float64
:
floatValue
,
err
:=
strconv
.
ParseFloat
(
strValue
,
64
)
if
err
!=
nil
{
continue
}
field
.
SetFloat
(
floatValue
)
case
reflect
.
Map
,
reflect
.
Slice
,
reflect
.
Struct
:
// 复杂类型使用JSON反序列化
err
:=
json
.
Unmarshal
([]
byte
(
strValue
),
field
.
Addr
()
.
Interface
())
if
err
!=
nil
{
continue
}
}
}
return
nil
}
// ConfigToMap 将配置对象转换为map(导出函数)
func
ConfigToMap
(
config
interface
{})
(
map
[
string
]
string
,
error
)
{
return
configToMap
(
config
)
}
// UpdateConfigFromMap 从map更新配置对象(导出函数)
func
UpdateConfigFromMap
(
config
interface
{},
configMap
map
[
string
]
string
)
error
{
return
updateConfigFromMap
(
config
,
configMap
)
}
// ExportAllConfigs 导出所有已注册的配置为扁平结构
func
(
cm
*
ConfigManager
)
ExportAllConfigs
()
map
[
string
]
string
{
cm
.
mutex
.
RLock
()
defer
cm
.
mutex
.
RUnlock
()
result
:=
make
(
map
[
string
]
string
)
for
name
,
cfg
:=
range
cm
.
configs
{
configMap
,
err
:=
ConfigToMap
(
cfg
)
if
err
!=
nil
{
continue
}
// 使用 "模块名.配置项" 的格式添加到结果中
for
key
,
value
:=
range
configMap
{
result
[
name
+
"."
+
key
]
=
value
}
}
return
result
}
setting/model_setting/claude.go
View file @
fd6ae3ea
package
model_setting
package
model_setting
import
(
"net/http"
"one-api/setting/config"
)
//var claudeHeadersSettings = map[string][]string{}
//
//var ClaudeThinkingAdapterEnabled = true
//var ClaudeThinkingAdapterMaxTokens = 8192
//var ClaudeThinkingAdapterBudgetTokensPercentage = 0.8
// ClaudeSettings 定义Claude模型的配置
type
ClaudeSettings
struct
{
HeadersSettings
map
[
string
][]
string
`json:"headers_settings"`
ThinkingAdapterEnabled
bool
`json:"thinking_adapter_enabled"`
ThinkingAdapterMaxTokens
int
`json:"thinking_adapter_max_tokens"`
ThinkingAdapterBudgetTokensPercentage
float64
`json:"thinking_adapter_budget_tokens_percentage"`
}
// 默认配置
var
defaultClaudeSettings
=
ClaudeSettings
{
HeadersSettings
:
map
[
string
][]
string
{},
ThinkingAdapterEnabled
:
true
,
ThinkingAdapterMaxTokens
:
8192
,
ThinkingAdapterBudgetTokensPercentage
:
0.8
,
}
// 全局实例
var
claudeSettings
=
defaultClaudeSettings
func
init
()
{
// 注册到全局配置管理器
config
.
GlobalConfig
.
Register
(
"claude"
,
&
claudeSettings
)
}
// GetClaudeSettings 获取Claude配置
func
GetClaudeSettings
()
*
ClaudeSettings
{
return
&
claudeSettings
}
func
(
c
*
ClaudeSettings
)
WriteHeaders
(
headers
*
http
.
Header
)
{
for
key
,
values
:=
range
c
.
HeadersSettings
{
for
_
,
value
:=
range
values
{
headers
.
Add
(
key
,
value
)
}
}
}
web/src/components/ModelSetting.js
View file @
fd6ae3ea
...
@@ -6,12 +6,17 @@ import { API, showError, showSuccess } from '../helpers';
...
@@ -6,12 +6,17 @@ import { API, showError, showSuccess } from '../helpers';
import
SettingsChats
from
'../pages/Setting/Operation/SettingsChats.js'
;
import
SettingsChats
from
'../pages/Setting/Operation/SettingsChats.js'
;
import
{
useTranslation
}
from
'react-i18next'
;
import
{
useTranslation
}
from
'react-i18next'
;
import
SettingGeminiModel
from
'../pages/Setting/Model/SettingGeminiModel.js'
;
import
SettingGeminiModel
from
'../pages/Setting/Model/SettingGeminiModel.js'
;
import
SettingClaudeModel
from
'../pages/Setting/Model/SettingClaudeModel.js'
;
const
ModelSetting
=
()
=>
{
const
ModelSetting
=
()
=>
{
const
{
t
}
=
useTranslation
();
const
{
t
}
=
useTranslation
();
let
[
inputs
,
setInputs
]
=
useState
({
let
[
inputs
,
setInputs
]
=
useState
({
GeminiSafetySettings
:
''
,
'gemini.safety_settings'
:
''
,
GeminiVersionSettings
:
''
,
'gemini.version_settings'
:
''
,
'claude.headers_settings'
:
''
,
'claude.thinking_adapter_enabled'
:
true
,
'claude.thinking_adapter_max_tokens'
:
8192
,
'claude.thinking_adapter_budget_tokens_percentage'
:
0.8
,
});
});
let
[
loading
,
setLoading
]
=
useState
(
false
);
let
[
loading
,
setLoading
]
=
useState
(
false
);
...
@@ -23,8 +28,9 @@ const ModelSetting = () => {
...
@@ -23,8 +28,9 @@ const ModelSetting = () => {
let
newInputs
=
{};
let
newInputs
=
{};
data
.
forEach
((
item
)
=>
{
data
.
forEach
((
item
)
=>
{
if
(
if
(
item
.
key
===
'GeminiSafetySettings'
||
item
.
key
===
'gemini.safety_settings'
||
item
.
key
===
'GeminiVersionSettings'
item
.
key
===
'gemini.version_settings'
||
item
.
key
===
'claude.headers_settings'
)
{
)
{
item
.
value
=
JSON
.
stringify
(
JSON
.
parse
(
item
.
value
),
null
,
2
);
item
.
value
=
JSON
.
stringify
(
JSON
.
parse
(
item
.
value
),
null
,
2
);
}
}
...
@@ -65,6 +71,10 @@ const ModelSetting = () => {
...
@@ -65,6 +71,10 @@ const ModelSetting = () => {
<
Card
style
=
{{
marginTop
:
'10px'
}}
>
<
Card
style
=
{{
marginTop
:
'10px'
}}
>
<
SettingGeminiModel
options
=
{
inputs
}
refresh
=
{
onRefresh
}
/
>
<
SettingGeminiModel
options
=
{
inputs
}
refresh
=
{
onRefresh
}
/
>
<
/Card
>
<
/Card
>
{
/* Claude */
}
<
Card
style
=
{{
marginTop
:
'10px'
}}
>
<
SettingClaudeModel
options
=
{
inputs
}
refresh
=
{
onRefresh
}
/
>
<
/Card
>
<
/Spin
>
<
/Spin
>
<
/
>
<
/
>
);
);
...
...
web/src/pages/Setting/Model/SettingClaudeModel.js
0 → 100644
View file @
fd6ae3ea
import
React
,
{
useEffect
,
useState
,
useRef
}
from
'react'
;
import
{
Button
,
Col
,
Form
,
Row
,
Spin
}
from
'@douyinfe/semi-ui'
;
import
{
compareObjects
,
API
,
showError
,
showSuccess
,
showWarning
,
verifyJSON
}
from
'../../../helpers'
;
import
{
useTranslation
}
from
'react-i18next'
;
import
Text
from
'@douyinfe/semi-ui/lib/es/typography/text'
;
const
CLAUDE_HEADER
=
{
'anthropic-beta'
:
[
'output-128k-2025-02-19'
,
'token-efficient-tools-2025-02-19'
],
};
export
default
function
SettingClaudeModel
(
props
)
{
const
{
t
}
=
useTranslation
();
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
inputs
,
setInputs
]
=
useState
({
'claude.headers_settings'
:
''
,
'claude.thinking_adapter_enabled'
:
true
,
'claude.thinking_adapter_max_tokens'
:
8192
,
'claude.thinking_adapter_budget_tokens_percentage'
:
0.8
,
});
const
refForm
=
useRef
();
const
[
inputsRow
,
setInputsRow
]
=
useState
(
inputs
);
function
onSubmit
()
{
const
updateArray
=
compareObjects
(
inputs
,
inputsRow
);
if
(
!
updateArray
.
length
)
return
showWarning
(
t
(
'你似乎并没有修改什么'
));
const
requestQueue
=
updateArray
.
map
((
item
)
=>
{
let
value
=
''
;
if
(
typeof
inputs
[
item
.
key
]
===
'boolean'
)
{
value
=
String
(
inputs
[
item
.
key
]);
}
else
{
value
=
inputs
[
item
.
key
];
}
return
API
.
put
(
'/api/option/'
,
{
key
:
item
.
key
,
value
,
});
});
setLoading
(
true
);
Promise
.
all
(
requestQueue
)
.
then
((
res
)
=>
{
if
(
requestQueue
.
length
===
1
)
{
if
(
res
.
includes
(
undefined
))
return
;
}
else
if
(
requestQueue
.
length
>
1
)
{
if
(
res
.
includes
(
undefined
))
return
showError
(
t
(
'部分保存失败,请重试'
));
}
showSuccess
(
t
(
'保存成功'
));
props
.
refresh
();
})
.
catch
(()
=>
{
showError
(
t
(
'保存失败,请重试'
));
})
.
finally
(()
=>
{
setLoading
(
false
);
});
}
useEffect
(()
=>
{
const
currentInputs
=
{};
for
(
let
key
in
props
.
options
)
{
if
(
Object
.
keys
(
inputs
).
includes
(
key
))
{
currentInputs
[
key
]
=
props
.
options
[
key
];
}
}
setInputs
(
currentInputs
);
setInputsRow
(
structuredClone
(
currentInputs
));
refForm
.
current
.
setValues
(
currentInputs
);
},
[
props
.
options
]);
return
(
<>
<
Spin
spinning
=
{
loading
}
>
<
Form
values
=
{
inputs
}
getFormApi
=
{(
formAPI
)
=>
(
refForm
.
current
=
formAPI
)}
style
=
{{
marginBottom
:
15
}}
>
<
Form
.
Section
text
=
{
t
(
'Claude设置'
)}
>
<
Row
>
<
Col
span
=
{
16
}
>
<
Form
.
TextArea
label
=
{
t
(
'Claude请求头覆盖'
)}
field
=
{
'claude.headers_settings'
}
placeholder
=
{
t
(
'为一个 JSON 文本,例如:'
)
+
'\n'
+
JSON
.
stringify
(
CLAUDE_HEADER
,
null
,
2
)}
extraText
=
{
t
(
'示例'
)
+
JSON
.
stringify
(
CLAUDE_HEADER
,
null
,
2
)}
autosize
=
{{
minRows
:
6
,
maxRows
:
12
}}
trigger
=
'blur'
stopValidateWithError
rules
=
{[
{
validator
:
(
rule
,
value
)
=>
verifyJSON
(
value
),
message
:
t
(
'不是合法的 JSON 字符串'
)
}
]}
onChange
=
{(
value
)
=>
setInputs
({
...
inputs
,
'claude.headers_settings'
:
value
})}
/
>
<
/Col
>
<
/Row
>
<
Row
>
<
Col
span
=
{
16
}
>
<
Form
.
Switch
label
=
{
t
(
'启用Claude思考适配(-thinking后缀)'
)}
field
=
{
'claude.thinking_adapter_enabled'
}
onChange
=
{(
value
)
=>
setInputs
({
...
inputs
,
'claude.thinking_adapter_enabled'
:
value
})}
/
>
<
/Col
>
<
/Row
>
<
Row
>
<
Col
span
=
{
16
}
>
{
/*//展示MaxTokens和BudgetTokens的计算公式, 并展示实际数字*/
}
<
Text
>
{
t
(
'Claude思考适配 BudgetTokens = MaxTokens * BudgetTokens 百分比'
)}
<
/Text
>
<
/Col
>
<
/Row
>
<
Row
>
<
Col
span
=
{
8
}
>
<
Form
.
InputNumber
label
=
{
t
(
'思考适配 MaxTokens'
)}
field
=
{
'claude.thinking_adapter_max_tokens'
}
initValue
=
{
''
}
onChange
=
{(
value
)
=>
setInputs
({
...
inputs
,
'claude.thinking_adapter_max_tokens'
:
value
})}
/
>
<
/Col
>
<
Col
span
=
{
8
}
>
<
Form
.
InputNumber
label
=
{
t
(
'思考适配 BudgetTokens 百分比'
)}
field
=
{
'claude.thinking_adapter_budget_tokens_percentage'
}
initValue
=
{
''
}
suffix
=
{
t
(
'%'
)}
onChange
=
{(
value
)
=>
setInputs
({
...
inputs
,
'claude.thinking_adapter_budget_tokens_percentage'
:
value
})}
/
>
<
/Col
>
<
/Row
>
<
Row
>
<
Button
size
=
'default'
onClick
=
{
onSubmit
}
>
{
t
(
'保存'
)}
<
/Button
>
<
/Row
>
<
/Form.Section
>
<
/Form
>
<
/Spin
>
<
/
>
);
}
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