mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-10 07:19:02 +08:00
feat: 增加模型备注功能 (#5392)
* feat: 增加模型备注功能 * fix: 移除未使用变量 --------- Co-authored-by: liutao <>
This commit is contained in:
parent
dfd957434c
commit
f5a7258229
1
src/renderer/src/components/MarkdownEditor/README.md
Normal file
1
src/renderer/src/components/MarkdownEditor/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
||||||
93
src/renderer/src/components/MarkdownEditor/index.tsx
Normal file
93
src/renderer/src/components/MarkdownEditor/index.tsx
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import 'katex/dist/katex.min.css'
|
||||||
|
|
||||||
|
import React, { FC, useEffect, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import ReactMarkdown from 'react-markdown'
|
||||||
|
import rehypeKatex from 'rehype-katex'
|
||||||
|
import rehypeRaw from 'rehype-raw'
|
||||||
|
import remarkCjkFriendly from 'remark-cjk-friendly'
|
||||||
|
import remarkGfm from 'remark-gfm'
|
||||||
|
import remarkMath from 'remark-math'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
interface MarkdownEditorProps {
|
||||||
|
value: string
|
||||||
|
onChange: (value: string) => void
|
||||||
|
placeholder?: string
|
||||||
|
height?: string | number
|
||||||
|
autoFocus?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const MarkdownEditor: FC<MarkdownEditorProps> = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
placeholder = '请输入Markdown格式文本...',
|
||||||
|
height = '300px',
|
||||||
|
autoFocus = false
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [inputValue, setInputValue] = useState(value || '')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setInputValue(value || '')
|
||||||
|
}, [value])
|
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
const newValue = e.target.value
|
||||||
|
setInputValue(newValue)
|
||||||
|
onChange(newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EditorContainer style={{ height }}>
|
||||||
|
<InputArea value={inputValue} onChange={handleChange} placeholder={placeholder} autoFocus={autoFocus} />
|
||||||
|
<PreviewArea>
|
||||||
|
<ReactMarkdown
|
||||||
|
remarkPlugins={[remarkGfm, remarkCjkFriendly, remarkMath]}
|
||||||
|
rehypePlugins={[rehypeRaw, rehypeKatex]}
|
||||||
|
className="markdown">
|
||||||
|
{inputValue || t('settings.provider.notes.markdown_editor_default_value')}
|
||||||
|
</ReactMarkdown>
|
||||||
|
</PreviewArea>
|
||||||
|
</EditorContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditorContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
`
|
||||||
|
|
||||||
|
const InputArea = styled.textarea`
|
||||||
|
flex: 1;
|
||||||
|
padding: 12px;
|
||||||
|
border: none;
|
||||||
|
resize: none;
|
||||||
|
font-family: var(--font-family);
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--color-text);
|
||||||
|
background-color: var(--color-bg-1);
|
||||||
|
border-right: 1px solid var(--color-border);
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: var(--color-text-3);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const PreviewArea = styled.div`
|
||||||
|
flex: 1;
|
||||||
|
padding: 12px;
|
||||||
|
overflow: auto;
|
||||||
|
background-color: var(--color-bg-1);
|
||||||
|
`
|
||||||
|
|
||||||
|
export default MarkdownEditor
|
||||||
@ -1329,7 +1329,12 @@
|
|||||||
"remove_invalid_keys": "Remove Invalid Keys",
|
"remove_invalid_keys": "Remove Invalid Keys",
|
||||||
"search": "Search Providers...",
|
"search": "Search Providers...",
|
||||||
"search_placeholder": "Search model id or name",
|
"search_placeholder": "Search model id or name",
|
||||||
"title": "Model Provider"
|
"title": "Model Provider",
|
||||||
|
"notes": {
|
||||||
|
"title": "Model Notes",
|
||||||
|
"placeholder": "Enter Markdown content...",
|
||||||
|
"markdown_editor_default_value": "Preview area"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"proxy": {
|
"proxy": {
|
||||||
"mode": {
|
"mode": {
|
||||||
|
|||||||
@ -1327,7 +1327,12 @@
|
|||||||
"remove_invalid_keys": "無効なキーを削除",
|
"remove_invalid_keys": "無効なキーを削除",
|
||||||
"search": "プロバイダーを検索...",
|
"search": "プロバイダーを検索...",
|
||||||
"search_placeholder": "モデルIDまたは名前を検索",
|
"search_placeholder": "モデルIDまたは名前を検索",
|
||||||
"title": "モデルプロバイダー"
|
"title": "モデルプロバイダー",
|
||||||
|
"notes": {
|
||||||
|
"title": "モデルノート",
|
||||||
|
"placeholder": "Markdown形式の内容を入力してください...",
|
||||||
|
"markdown_editor_default_value": "プレビュー領域"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"proxy": {
|
"proxy": {
|
||||||
"mode": {
|
"mode": {
|
||||||
|
|||||||
@ -1327,7 +1327,12 @@
|
|||||||
"remove_invalid_keys": "Удалить недействительные ключи",
|
"remove_invalid_keys": "Удалить недействительные ключи",
|
||||||
"search": "Поиск поставщиков...",
|
"search": "Поиск поставщиков...",
|
||||||
"search_placeholder": "Поиск по ID или имени модели",
|
"search_placeholder": "Поиск по ID или имени модели",
|
||||||
"title": "Провайдеры моделей"
|
"title": "Провайдеры моделей",
|
||||||
|
"notes": {
|
||||||
|
"title": "Заметки модели",
|
||||||
|
"placeholder": "Введите содержимое в формате Markdown...",
|
||||||
|
"markdown_editor_default_value": "Область предварительного просмотра"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"proxy": {
|
"proxy": {
|
||||||
"mode": {
|
"mode": {
|
||||||
|
|||||||
@ -1329,7 +1329,12 @@
|
|||||||
"remove_invalid_keys": "删除无效密钥",
|
"remove_invalid_keys": "删除无效密钥",
|
||||||
"search": "搜索模型平台...",
|
"search": "搜索模型平台...",
|
||||||
"search_placeholder": "搜索模型 ID 或名称",
|
"search_placeholder": "搜索模型 ID 或名称",
|
||||||
"title": "模型服务"
|
"title": "模型服务",
|
||||||
|
"notes": {
|
||||||
|
"title": "模型备注",
|
||||||
|
"placeholder": "请输入Markdown格式内容...",
|
||||||
|
"markdown_editor_default_value": "预览区域"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"proxy": {
|
"proxy": {
|
||||||
"mode": {
|
"mode": {
|
||||||
|
|||||||
@ -1328,7 +1328,12 @@
|
|||||||
"remove_invalid_keys": "刪除無效金鑰",
|
"remove_invalid_keys": "刪除無效金鑰",
|
||||||
"search": "搜尋模型平臺...",
|
"search": "搜尋模型平臺...",
|
||||||
"search_placeholder": "搜尋模型 ID 或名稱",
|
"search_placeholder": "搜尋模型 ID 或名稱",
|
||||||
"title": "模型提供者"
|
"title": "模型提供者",
|
||||||
|
"notes": {
|
||||||
|
"title": "模型備註",
|
||||||
|
"placeholder": "輸入Markdown格式內容...",
|
||||||
|
"markdown_editor_default_value": "預覽區域"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"proxy": {
|
"proxy": {
|
||||||
"mode": {
|
"mode": {
|
||||||
|
|||||||
@ -0,0 +1,84 @@
|
|||||||
|
import MarkdownEditor from '@renderer/components/MarkdownEditor'
|
||||||
|
import { TopView } from '@renderer/components/TopView'
|
||||||
|
import { useProvider } from '@renderer/hooks/useProvider'
|
||||||
|
import { Provider } from '@renderer/types'
|
||||||
|
import { Modal } from 'antd'
|
||||||
|
import { FC, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
interface ShowParams {
|
||||||
|
provider: Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props extends ShowParams {
|
||||||
|
resolve: (data: any) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const PopupContainer: FC<Props> = ({ provider: _provider, resolve }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [open, setOpen] = useState(true)
|
||||||
|
const { provider, updateProvider } = useProvider(_provider.id)
|
||||||
|
const [notes, setNotes] = useState<string>(provider.notes || '')
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
updateProvider({
|
||||||
|
...provider,
|
||||||
|
notes
|
||||||
|
})
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClose = () => {
|
||||||
|
resolve({})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={t('settings.provider.notes.title')}
|
||||||
|
open={open}
|
||||||
|
onOk={handleSave}
|
||||||
|
onCancel={onCancel}
|
||||||
|
afterClose={onClose}
|
||||||
|
width={800}
|
||||||
|
centered>
|
||||||
|
<EditorContainer>
|
||||||
|
<MarkdownEditor
|
||||||
|
value={notes}
|
||||||
|
onChange={setNotes}
|
||||||
|
placeholder={t('settings.provider.notes.placeholder')}
|
||||||
|
height="400px"
|
||||||
|
/>
|
||||||
|
</EditorContainer>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditorContainer = styled.div`
|
||||||
|
margin-top: 16px;
|
||||||
|
height: 400px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export default class ModelNotesPopup {
|
||||||
|
static hide() {
|
||||||
|
TopView.hide('ModelNotesPopup')
|
||||||
|
}
|
||||||
|
static show(props: ShowParams) {
|
||||||
|
return new Promise<any>((resolve) => {
|
||||||
|
TopView.show(
|
||||||
|
<PopupContainer
|
||||||
|
{...props}
|
||||||
|
resolve={(v) => {
|
||||||
|
resolve(v)
|
||||||
|
this.hide()
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
'ModelNotesPopup'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { CheckOutlined, LoadingOutlined } from '@ant-design/icons'
|
import { CheckOutlined, FileTextOutlined, LoadingOutlined } from '@ant-design/icons'
|
||||||
import { StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons/SVGIcon'
|
import { StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons/SVGIcon'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import { isEmbeddingModel, isRerankModel } from '@renderer/config/models'
|
import { isEmbeddingModel, isRerankModel } from '@renderer/config/models'
|
||||||
@ -35,6 +35,7 @@ import HealthCheckPopup from './HealthCheckPopup'
|
|||||||
import LMStudioSettings from './LMStudioSettings'
|
import LMStudioSettings from './LMStudioSettings'
|
||||||
import ModelList, { ModelStatus } from './ModelList'
|
import ModelList, { ModelStatus } from './ModelList'
|
||||||
import ModelListSearchBar from './ModelListSearchBar'
|
import ModelListSearchBar from './ModelListSearchBar'
|
||||||
|
import ModelNotesPopup from './ModelNotesPopup'
|
||||||
import ProviderOAuth from './ProviderOAuth'
|
import ProviderOAuth from './ProviderOAuth'
|
||||||
import ProviderSettingsPopup from './ProviderSettingsPopup'
|
import ProviderSettingsPopup from './ProviderSettingsPopup'
|
||||||
import SelectProviderModelPopup from './SelectProviderModelPopup'
|
import SelectProviderModelPopup from './SelectProviderModelPopup'
|
||||||
@ -273,6 +274,10 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
|
|||||||
return formatApiHost(apiHost) + 'chat/completions'
|
return formatApiHost(apiHost) + 'chat/completions'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onShowNotes = useCallback(() => {
|
||||||
|
ModelNotesPopup.show({ provider })
|
||||||
|
}, [provider])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (provider.id === 'copilot') {
|
if (provider.id === 'copilot') {
|
||||||
return
|
return
|
||||||
@ -308,6 +313,9 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
|
|||||||
onClick={() => ProviderSettingsPopup.show({ provider })}
|
onClick={() => ProviderSettingsPopup.show({ provider })}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<Tooltip title={t('settings.provider.notes.title')}>
|
||||||
|
<Button type="text" onClick={onShowNotes} icon={<FileTextOutlined />} />
|
||||||
|
</Tooltip>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Switch
|
<Switch
|
||||||
value={provider.enabled}
|
value={provider.enabled}
|
||||||
|
|||||||
@ -140,6 +140,7 @@ export type Provider = {
|
|||||||
isAuthed?: boolean
|
isAuthed?: boolean
|
||||||
rateLimit?: number
|
rateLimit?: number
|
||||||
isNotSupportArrayContent?: boolean
|
isNotSupportArrayContent?: boolean
|
||||||
|
notes?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProviderType = 'openai' | 'anthropic' | 'gemini' | 'qwenlm' | 'azure-openai'
|
export type ProviderType = 'openai' | 'anthropic' | 'gemini' | 'qwenlm' | 'azure-openai'
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user