自定义prompt功能

This commit is contained in:
IndieKKY
2023-05-20 09:52:03 +08:00
parent d9d905facb
commit d6a41df2d0
6 changed files with 162 additions and 95 deletions

View File

@@ -31,7 +31,6 @@ const Body = () => {
const floatKeyPointsSegIdx = useAppSelector(state => state.env.floatKeyPointsSegIdx) const floatKeyPointsSegIdx = useAppSelector(state => state.env.floatKeyPointsSegIdx)
const translateEnable = useAppSelector(state => state.env.envData.translateEnable) const translateEnable = useAppSelector(state => state.env.envData.translateEnable)
const summarizeEnable = useAppSelector(state => state.env.envData.summarizeEnable) const summarizeEnable = useAppSelector(state => state.env.envData.summarizeEnable)
const title = useAppSelector(state => state.env.title)
const {addSummarizeTask} = useTranslate() const {addSummarizeTask} = useTranslate()
const bodyRef = useRef<any>() const bodyRef = useRef<any>()
const curOffsetTop = useAppSelector(state => state.env.curOffsetTop) const curOffsetTop = useAppSelector(state => state.env.curOffsetTop)
@@ -71,11 +70,11 @@ const Body = () => {
} }
if (segments_.length < SUMMARIZE_ALL_THRESHOLD || confirm(`确定总结${segments_.length}个段落?`)) { if (segments_.length < SUMMARIZE_ALL_THRESHOLD || confirm(`确定总结${segments_.length}个段落?`)) {
for (const segment of segments_) { for (const segment of segments_) {
addSummarizeTask(title, curSummaryType, segment).catch(console.error) addSummarizeTask(curSummaryType, segment).catch(console.error)
} }
toast.success(`已添加${segments_.length}个总结任务!`) toast.success(`已添加${segments_.length}个总结任务!`)
} }
}, [addSummarizeTask, apiKey, curSummaryType, dispatch, segments, title]) }, [addSummarizeTask, apiKey, curSummaryType, dispatch, segments])
const onFoldAll = useCallback(() => { const onFoldAll = useCallback(() => {
dispatch(setFoldAll(!foldAll)) dispatch(setFoldAll(!foldAll))

View File

@@ -68,18 +68,17 @@ const Summarize = (props: {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const apiKey = useAppSelector(state => state.env.envData.apiKey) const apiKey = useAppSelector(state => state.env.envData.apiKey)
const fontSize = useAppSelector(state => state.env.envData.fontSize) const fontSize = useAppSelector(state => state.env.envData.fontSize)
const title = useAppSelector(state => state.env.title)
const curSummaryType = useAppSelector(state => state.env.tempData.curSummaryType) const curSummaryType = useAppSelector(state => state.env.tempData.curSummaryType)
const {addSummarizeTask} = useTranslate() const {addSummarizeTask} = useTranslate()
const onGenerate = useCallback(() => { const onGenerate = useCallback(() => {
if (apiKey) { if (apiKey) {
addSummarizeTask(title, curSummaryType, segment).catch(console.error) addSummarizeTask(curSummaryType, segment).catch(console.error)
} else { } else {
dispatch(setPage(PAGE_SETTINGS)) dispatch(setPage(PAGE_SETTINGS))
toast.error('需要先设置ApiKey!') toast.error('需要先设置ApiKey!')
} }
}, [addSummarizeTask, apiKey, curSummaryType, dispatch, segment, title]) }, [addSummarizeTask, apiKey, curSummaryType, dispatch, segment])
const onCopy = useCallback(() => { const onCopy = useCallback(() => {
if (summary != null) { if (summary != null) {

View File

@@ -6,6 +6,8 @@ import {
LANGUAGE_DEFAULT, LANGUAGE_DEFAULT,
LANGUAGES, LANGUAGES,
PAGE_MAIN, PAGE_MAIN,
PROMPT_DEFAULTS,
PROMPT_TYPES,
SERVER_URL_THIRD, SERVER_URL_THIRD,
SUMMARIZE_LANGUAGE_DEFAULT, SUMMARIZE_LANGUAGE_DEFAULT,
TRANSLATE_FETCH_DEFAULT, TRANSLATE_FETCH_DEFAULT,
@@ -69,8 +71,10 @@ const Settings = () => {
const [wordsValue, setWordsValue] = useState(envData.words??WORDS_DEFAULT) const [wordsValue, setWordsValue] = useState(envData.words??WORDS_DEFAULT)
const [fetchAmountValue, setFetchAmountValue] = useState(envData.fetchAmount??TRANSLATE_FETCH_DEFAULT) const [fetchAmountValue, setFetchAmountValue] = useState(envData.fetchAmount??TRANSLATE_FETCH_DEFAULT)
const [moreFold, {toggle: toggleMoreFold}] = useBoolean(true) const [moreFold, {toggle: toggleMoreFold}] = useBoolean(true)
const [promptsFold, {toggle: togglePromptsFold}] = useBoolean(true)
const fold = useAppSelector(state => state.env.fold) const fold = useAppSelector(state => state.env.fold)
const totalHeight = useAppSelector(state => state.env.totalHeight) const totalHeight = useAppSelector(state => state.env.totalHeight)
const [promptsValue, setPromptsValue] = useState<{[key: string]: string}>(envData.prompts??{})
const wordsList = useMemo(() => { const wordsList = useMemo(() => {
const list = [] const list = []
for (let i = WORDS_MIN; i <= WORDS_MAX; i += WORDS_STEP) { for (let i = WORDS_MIN; i <= WORDS_MAX; i += WORDS_STEP) {
@@ -106,10 +110,11 @@ const Settings = () => {
words: wordsValue, words: wordsValue,
fetchAmount: fetchAmountValue, fetchAmount: fetchAmountValue,
fontSize: fontSizeValue, fontSize: fontSizeValue,
prompts: promptsValue,
})) }))
dispatch(setPage(PAGE_MAIN)) dispatch(setPage(PAGE_MAIN))
toast.success('保存成功') toast.success('保存成功')
}, [fontSizeValue, apiKeyValue, autoExpandValue, dispatch, fetchAmountValue, hideOnDisableAutoTranslateValue, languageValue, serverUrlValue, summarizeEnableValue, summarizeFloatValue, summarizeLanguageValue, themeValue, transDisplayValue, translateEnableValue, wordsValue]) }, [promptsValue, fontSizeValue, apiKeyValue, autoExpandValue, dispatch, fetchAmountValue, hideOnDisableAutoTranslateValue, languageValue, serverUrlValue, summarizeEnableValue, summarizeFloatValue, summarizeLanguageValue, themeValue, transDisplayValue, translateEnableValue, wordsValue])
const onCancel = useCallback(() => { const onCancel = useCallback(() => {
dispatch(setPage(PAGE_MAIN)) dispatch(setPage(PAGE_MAIN))
@@ -202,6 +207,28 @@ const Settings = () => {
<li></li> <li></li>
</ul> </ul>
</div>} </div>}
<div className='flex justify-center'>
<a className='link text-xs' onClick={togglePromptsFold}>{promptsFold?'点击查看提示词':'点击折叠提示词'}</a>
</div>
{!promptsFold && <div>
{PROMPT_TYPES.map((item, idx) => <FormItem key={item.type} title={<div>
<div>{item.name}</div>
<div className='link text-xs' onClick={() => {
setPromptsValue({
...promptsValue,
// @ts-expect-error
[item.type]: PROMPT_DEFAULTS[item.type]??''
})
}}></div>
</div>} htmlFor={`prompt-${item.type}`}>
<textarea id={`prompt-${item.type}`} className='mt-2 textarea input-bordered w-full' placeholder='留空使用默认提示词' value={promptsValue[item.type]??''} onChange={(e) => {
setPromptsValue({
...promptsValue,
[item.type]: e.target.value
})
}}/>
</FormItem>)}
</div>}
</Section> </Section>
<Section title={<div className='flex items-center'> <Section title={<div className='flex items-center'>

View File

@@ -5,6 +5,108 @@ export const IFRAME_ID = 'bilibili-subtitle-iframe'
export const STORAGE_ENV = 'bilibili-subtitle_env' export const STORAGE_ENV = 'bilibili-subtitle_env'
export const STORAGE_TEMP = 'bilibili-subtitle_temp' export const STORAGE_TEMP = 'bilibili-subtitle_temp'
export const PROMPT_TYPE_TRANSLATE = 'translate'
export const PROMPT_TYPE_SUMMARIZE_OVERVIEW = 'summarize_overview'
export const PROMPT_TYPE_SUMMARIZE_KEYPOINT = 'summarize_keypoint'
export const PROMPT_TYPE_SUMMARIZE_BRIEF = 'summarize_brief'
export const PROMPT_TYPES = [{
name: '翻译',
type: PROMPT_TYPE_TRANSLATE,
}, {
name: '概览',
type: PROMPT_TYPE_SUMMARIZE_OVERVIEW,
}, {
name: '要点',
type: PROMPT_TYPE_SUMMARIZE_KEYPOINT,
}, {
name: '总结',
type: PROMPT_TYPE_SUMMARIZE_BRIEF,
}]
export const SUMMARIZE_TYPE_TO_PROMPT_TYPE = {
overview: PROMPT_TYPE_SUMMARIZE_OVERVIEW,
keypoint: PROMPT_TYPE_SUMMARIZE_KEYPOINT,
brief: PROMPT_TYPE_SUMMARIZE_BRIEF,
}
export const PROMPT_DEFAULTS = {
[PROMPT_TYPE_TRANSLATE]: `You are a professional translator. Translate following video subtitles to language '{{language}}'.
Preserve incomplete sentence.
Translate in the same json format.
Answer in markdown json format.
video subtitles:
\`\`\`
{{subtitles}}
\`\`\``,
[PROMPT_TYPE_SUMMARIZE_OVERVIEW]: `You are a helpful assistant that summarize key points of video subtitle.
Summarize 3 to 8 brief key points in language '{{language}}'.
Answer in markdown json format.
The emoji should be related to the key point and 1 char length.
example output format:
\`\`\`json
[
{
"time": "03:00",
"emoji": "👍",
"key": "key point 1"
},
{
"time": "10:05",
"emoji": "😊",
"key": "key point 2"
}
]
\`\`\`
The video's title: '''{{title}}'''.
The video's subtitles:
'''
{{subtitles}}
'''`,
[PROMPT_TYPE_SUMMARIZE_KEYPOINT]: `You are a helpful assistant that summarize key points of video subtitle.
Summarize brief key points in language '{{language}}'.
Answer in markdown json format.
example output format:
\`\`\`json
[
"key point 1",
"key point 2"
]
\`\`\`
The video's title: '''{{title}}'''.
The video's subtitles:
'''
{{segment}}
'''`,
[PROMPT_TYPE_SUMMARIZE_BRIEF]: `You are a helpful assistant that summarize video subtitle.
Summarize in language '{{language}}'.
Answer in markdown json format.
example output format:
\`\`\`json
{
"summary": "brief summary"
}
\`\`\`
The video's title: '''{{title}}'''.
The video's subtitles:
'''
{{segment}}
'''`
}
export const EVENT_EXPAND = 'expand' export const EVENT_EXPAND = 'expand'
export const TASK_EXPIRE_TIME = 15*60*1000 export const TASK_EXPIRE_TIME = 15*60*1000

View File

@@ -13,8 +13,11 @@ import {
import { import {
LANGUAGE_DEFAULT, LANGUAGE_DEFAULT,
LANGUAGES_MAP, LANGUAGES_MAP,
PROMPT_DEFAULTS,
PROMPT_TYPE_TRANSLATE,
SUMMARIZE_LANGUAGE_DEFAULT, SUMMARIZE_LANGUAGE_DEFAULT,
SUMMARIZE_THRESHOLD, SUMMARIZE_THRESHOLD,
SUMMARIZE_TYPE_TO_PROMPT_TYPE,
TRANSLATE_COOLDOWN, TRANSLATE_COOLDOWN,
TRANSLATE_FETCH_DEFAULT, TRANSLATE_FETCH_DEFAULT,
} from '../const' } from '../const'
@@ -32,6 +35,7 @@ const useTranslate = () => {
const envData = useAppSelector(state => state.env.envData) const envData = useAppSelector(state => state.env.envData)
const language = LANGUAGES_MAP[envData.language??LANGUAGE_DEFAULT] const language = LANGUAGES_MAP[envData.language??LANGUAGE_DEFAULT]
const summarizeLanguage = LANGUAGES_MAP[envData.summarizeLanguage??SUMMARIZE_LANGUAGE_DEFAULT] const summarizeLanguage = LANGUAGES_MAP[envData.summarizeLanguage??SUMMARIZE_LANGUAGE_DEFAULT]
const title = useAppSelector(state => state.env.title)
/** /**
* 获取下一个需要翻译的行 * 获取下一个需要翻译的行
@@ -69,28 +73,22 @@ const useTranslate = () => {
}) })
let lineStr = JSON.stringify(linesMap).replaceAll('\n', '') let lineStr = JSON.stringify(linesMap).replaceAll('\n', '')
lineStr = '```' + lineStr + '```' lineStr = '```' + lineStr + '```'
let prompt: string = envData.prompts?.[PROMPT_TYPE_TRANSLATE]??PROMPT_DEFAULTS[PROMPT_TYPE_TRANSLATE]
// replace params
prompt = prompt.replaceAll('{{language}}', language.name)
prompt = prompt.replaceAll('{{title}}', title??'')
prompt = prompt.replaceAll('{{subtitles}}', lineStr)
const taskDef: TaskDef = { const taskDef: TaskDef = {
type: 'chatComplete', type: 'chatComplete',
serverUrl: envData.serverUrl, serverUrl: envData.serverUrl,
data: { data: {
model: 'gpt-3.5-turbo', model: 'gpt-3.5-turbo',
messages: [ messages: [
{
role: 'system',
content: 'You are a professional translator.'
},
{ {
role: 'user', role: 'user',
content: `Translate following video subtitles to language '${language.name}'. content: prompt,
Preserve incomplete sentence.
Translate in the same json format.
Answer in markdown json format.
video subtitles:
\`\`\`
${lineStr}
\`\`\``
} }
], ],
temperature: 0, temperature: 0,
@@ -118,85 +116,23 @@ ${lineStr}
dispatch(addTaskId(task.id)) dispatch(addTaskId(task.id))
} }
} }
}, [data?.body, dispatch, envData.apiKey, envData.fetchAmount, envData.serverUrl, language.name]) }, [data?.body, dispatch, envData.apiKey, envData.fetchAmount, envData.serverUrl, envData.prompts, title, language.name])
const addSummarizeTask = useCallback(async (title: string | undefined, type: SummaryType, segment: Segment) => { const addSummarizeTask = useCallback(async (type: SummaryType, segment: Segment) => {
if (segment.text.length >= SUMMARIZE_THRESHOLD && envData.apiKey) { if (segment.text.length >= SUMMARIZE_THRESHOLD && envData.apiKey) {
const title_ = title?`The video's title is '${title}'.`:''
let subtitles = '' let subtitles = ''
for (const item of segment.items) { for (const item of segment.items) {
subtitles += formatTime(item.from) + ' ' + item.content + '\n' subtitles += formatTime(item.from) + ' ' + item.content + '\n'
} }
let content // @ts-expect-error
if (type === 'overview') { const promptType: keyof typeof PROMPT_DEFAULTS = SUMMARIZE_TYPE_TO_PROMPT_TYPE[type]
content = `You are a helpful assistant that summarize key points of video subtitle. let prompt: string = envData.prompts?.[promptType]??PROMPT_DEFAULTS[promptType]
Summarize 3 to 8 brief key points in language '${summarizeLanguage.name}'. // replace params
Answer in markdown json format. prompt = prompt.replaceAll('{{language}}', summarizeLanguage.name)
The emoji should be related to the key point and 1 char length. prompt = prompt.replaceAll('{{title}}', title??'')
prompt = prompt.replaceAll('{{subtitles}}', subtitles)
prompt = prompt.replaceAll('{{segment}}', segment.text)
example output format:
\`\`\`json
[
{
"time": "03:00",
"emoji": "👍",
"key": "key point 1"
},
{
"time": "10:05",
"emoji": "😊",
"key": "key point 2"
}
]
\`\`\`
The video's title: '''${title_}'''.
The video's subtitles:
'''
${subtitles}
'''`
} else if (type === 'keypoint') {
content = `You are a helpful assistant that summarize key points of video subtitle.
Summarize brief key points in language '${summarizeLanguage.name}'.
Answer in markdown json format.
example output format:
\`\`\`json
[
"key point 1",
"key point 2"
]
\`\`\`
The video's title: '''${title_}'''.
The video's subtitles:
'''
${segment.text}
'''`
} else if (type === 'brief') {
content = `You are a helpful assistant that summarize video subtitle.
Summarize in language '${summarizeLanguage.name}'.
Answer in markdown json format.
example output format:
\`\`\`json
{
"summary": "brief summary"
}
\`\`\`
The video's title: '''${title_}'''.
The video's subtitles:
'''
${segment.text}
'''`
}
const taskDef: TaskDef = { const taskDef: TaskDef = {
type: 'chatComplete', type: 'chatComplete',
serverUrl: envData.serverUrl, serverUrl: envData.serverUrl,
@@ -205,7 +141,7 @@ ${segment.text}
messages: [ messages: [
{ {
role: 'user', role: 'user',
content, content: prompt,
} }
], ],
temperature: 0, temperature: 0,
@@ -225,7 +161,7 @@ ${segment.text}
const task = await chrome.runtime.sendMessage({type: 'addTask', taskDef}) const task = await chrome.runtime.sendMessage({type: 'addTask', taskDef})
dispatch(addTaskId(task.id)) dispatch(addTaskId(task.id))
} }
}, [dispatch, envData.apiKey, envData.serverUrl, summarizeLanguage.name]) }, [dispatch, envData.apiKey, envData.prompts, envData.serverUrl, summarizeLanguage.name, title])
const handleTranslate = useMemoizedFn((task: Task, content: string) => { const handleTranslate = useMemoizedFn((task: Task, content: string) => {
let map: {[key: string]: string} = {} let map: {[key: string]: string} = {}

4
src/typings.d.ts vendored
View File

@@ -14,6 +14,10 @@ interface EnvData {
summarizeFloat?: boolean summarizeFloat?: boolean
theme?: 'system' | 'light' | 'dark' theme?: 'system' | 'light' | 'dark'
fontSize?: 'normal' | 'large' fontSize?: 'normal' | 'large'
prompts?: {
[key: string]: string
}
} }
interface TempData { interface TempData {