SettingClaudeModel.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. import React, { useEffect, useState, useRef } from 'react';
  2. import { Button, Col, Form, Row, Spin } from '@douyinfe/semi-ui';
  3. import {
  4. compareObjects,
  5. API,
  6. showError,
  7. showSuccess,
  8. showWarning,
  9. verifyJSON,
  10. } from '../../../helpers';
  11. import { useTranslation } from 'react-i18next';
  12. import Text from '@douyinfe/semi-ui/lib/es/typography/text';
  13. const CLAUDE_HEADER = {
  14. 'claude-3-7-sonnet-20250219-thinking': {
  15. 'anthropic-beta': [
  16. 'output-128k-2025-02-19',
  17. 'token-efficient-tools-2025-02-19',
  18. ],
  19. },
  20. };
  21. const CLAUDE_DEFAULT_MAX_TOKENS = {
  22. default: 8192,
  23. 'claude-3-haiku-20240307': 4096,
  24. 'claude-3-opus-20240229': 4096,
  25. 'claude-3-7-sonnet-20250219-thinking': 8192,
  26. };
  27. export default function SettingClaudeModel(props) {
  28. const { t } = useTranslation();
  29. const [loading, setLoading] = useState(false);
  30. const [inputs, setInputs] = useState({
  31. 'claude.model_headers_settings': '',
  32. 'claude.thinking_adapter_enabled': true,
  33. 'claude.default_max_tokens': '',
  34. 'claude.thinking_adapter_budget_tokens_percentage': 0.8,
  35. });
  36. const refForm = useRef();
  37. const [inputsRow, setInputsRow] = useState(inputs);
  38. function onSubmit() {
  39. const updateArray = compareObjects(inputs, inputsRow);
  40. if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
  41. const requestQueue = updateArray.map((item) => {
  42. let value = String(inputs[item.key]);
  43. return API.put('/api/option/', {
  44. key: item.key,
  45. value,
  46. });
  47. });
  48. setLoading(true);
  49. Promise.all(requestQueue)
  50. .then((res) => {
  51. if (requestQueue.length === 1) {
  52. if (res.includes(undefined)) return;
  53. } else if (requestQueue.length > 1) {
  54. if (res.includes(undefined))
  55. return showError(t('部分保存失败,请重试'));
  56. }
  57. showSuccess(t('保存成功'));
  58. props.refresh();
  59. })
  60. .catch(() => {
  61. showError(t('保存失败,请重试'));
  62. })
  63. .finally(() => {
  64. setLoading(false);
  65. });
  66. }
  67. useEffect(() => {
  68. const currentInputs = {};
  69. for (let key in props.options) {
  70. if (Object.keys(inputs).includes(key)) {
  71. currentInputs[key] = props.options[key];
  72. }
  73. }
  74. setInputs(currentInputs);
  75. setInputsRow(structuredClone(currentInputs));
  76. refForm.current.setValues(currentInputs);
  77. }, [props.options]);
  78. return (
  79. <>
  80. <Spin spinning={loading}>
  81. <Form
  82. values={inputs}
  83. getFormApi={(formAPI) => (refForm.current = formAPI)}
  84. style={{ marginBottom: 15 }}
  85. >
  86. <Form.Section text={t('Claude设置')}>
  87. <Row>
  88. <Col xs={24} sm={12} md={8} lg={8} xl={8}>
  89. <Form.TextArea
  90. label={t('Claude请求头覆盖')}
  91. field={'claude.model_headers_settings'}
  92. placeholder={
  93. t('为一个 JSON 文本,例如:') +
  94. '\n' +
  95. JSON.stringify(CLAUDE_HEADER, null, 2)
  96. }
  97. extraText={
  98. t('示例') + '\n' + JSON.stringify(CLAUDE_HEADER, null, 2)
  99. }
  100. autosize={{ minRows: 6, maxRows: 12 }}
  101. trigger='blur'
  102. stopValidateWithError
  103. rules={[
  104. {
  105. validator: (rule, value) => verifyJSON(value),
  106. message: t('不是合法的 JSON 字符串'),
  107. },
  108. ]}
  109. onChange={(value) =>
  110. setInputs({
  111. ...inputs,
  112. 'claude.model_headers_settings': value,
  113. })
  114. }
  115. />
  116. </Col>
  117. </Row>
  118. <Row>
  119. <Col xs={24} sm={12} md={8} lg={8} xl={8}>
  120. <Form.TextArea
  121. label={t('缺省 MaxTokens')}
  122. field={'claude.default_max_tokens'}
  123. placeholder={
  124. t('为一个 JSON 文本,例如:') +
  125. '\n' +
  126. JSON.stringify(CLAUDE_DEFAULT_MAX_TOKENS, null, 2)
  127. }
  128. extraText={
  129. t('示例') +
  130. '\n' +
  131. JSON.stringify(CLAUDE_DEFAULT_MAX_TOKENS, null, 2)
  132. }
  133. autosize={{ minRows: 6, maxRows: 12 }}
  134. trigger='blur'
  135. stopValidateWithError
  136. rules={[
  137. {
  138. validator: (rule, value) => verifyJSON(value),
  139. message: t('不是合法的 JSON 字符串'),
  140. },
  141. ]}
  142. onChange={(value) =>
  143. setInputs({ ...inputs, 'claude.default_max_tokens': value })
  144. }
  145. />
  146. </Col>
  147. </Row>
  148. <Row>
  149. <Col span={16}>
  150. <Form.Switch
  151. label={t('启用Claude思考适配(-thinking后缀)')}
  152. field={'claude.thinking_adapter_enabled'}
  153. onChange={(value) =>
  154. setInputs({
  155. ...inputs,
  156. 'claude.thinking_adapter_enabled': value,
  157. })
  158. }
  159. />
  160. </Col>
  161. </Row>
  162. <Row>
  163. <Col span={16}>
  164. {/*//展示MaxTokens和BudgetTokens的计算公式, 并展示实际数字*/}
  165. <Text>
  166. {t(
  167. 'Claude思考适配 BudgetTokens = MaxTokens * BudgetTokens 百分比',
  168. )}
  169. </Text>
  170. </Col>
  171. </Row>
  172. <Row>
  173. <Col xs={24} sm={12} md={8} lg={8} xl={8}>
  174. <Form.InputNumber
  175. label={t('思考适配 BudgetTokens 百分比')}
  176. field={'claude.thinking_adapter_budget_tokens_percentage'}
  177. initValue={''}
  178. extraText={t('0.1-1之间的小数')}
  179. min={0.1}
  180. max={1}
  181. onChange={(value) =>
  182. setInputs({
  183. ...inputs,
  184. 'claude.thinking_adapter_budget_tokens_percentage': value,
  185. })
  186. }
  187. />
  188. </Col>
  189. </Row>
  190. <Row>
  191. <Button size='default' onClick={onSubmit}>
  192. {t('保存')}
  193. </Button>
  194. </Row>
  195. </Form.Section>
  196. </Form>
  197. </Spin>
  198. </>
  199. );
  200. }