SettingClaudeModel.js 5.9 KB

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