diff --git a/package.json b/package.json index 83d01f6..0bd5c2a 100644 --- a/package.json +++ b/package.json @@ -48,4 +48,4 @@ "build": "cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=8192 tsup", "postinstall": "patch-package" } -} \ No newline at end of file +} diff --git a/src/common/store/settings.ts b/src/common/store/settings.ts index f2d5bfc..d6922d4 100644 --- a/src/common/store/settings.ts +++ b/src/common/store/settings.ts @@ -53,5 +53,5 @@ export const getSetting = async () => { res.url = 'https://api.openai.com/v1'; } - return res; + return res as Settings; }; diff --git a/src/components/icon-btn.tsx b/src/components/icon-btn.tsx new file mode 100644 index 0000000..ad401e2 --- /dev/null +++ b/src/components/icon-btn.tsx @@ -0,0 +1,45 @@ +import { PropsWithChildren, useMemo } from 'react'; +import cx from 'classnames'; + +export const IconBtn: React.FC< + PropsWithChildren<{ + disabled?: boolean; + color?: 'green' | 'gray' | 'orange' | 'red' | 'blue'; + className?: string; + onClick?: () => void; + }> +> = ({ color, className, onClick, children, disabled }) => { + const colorClass = useMemo(() => { + switch (color) { + case 'green': + return 'text-green-400 hover:text-green-500'; + case 'gray': + return 'text-gray-400 hover:text-gray-500'; + case 'orange': + return 'text-orange-400 hover:text-orange-500'; + case 'red': + return 'text-red-400 hover:text-red-500'; + case 'blue': + return 'text-blue-400 hover:text-blue-500'; + default: + return ''; + } + }, []); + + if (disabled) { + return null; + } + + return ( +
+ {children} +
+ ); +}; diff --git a/src/components/icon/delete.tsx b/src/components/icon/delete.tsx new file mode 100644 index 0000000..4bfd532 --- /dev/null +++ b/src/components/icon/delete.tsx @@ -0,0 +1,18 @@ +import { SVGProps } from 'react'; + +export function IcBaselineDeleteOutline(props: SVGProps) { + return ( + + + + ); +} diff --git a/src/content/container/ask-writely/prompts.tsx b/src/content/container/ask-writely/prompts.tsx index 1f7a425..65a1924 100644 --- a/src/content/container/ask-writely/prompts.tsx +++ b/src/content/container/ask-writely/prompts.tsx @@ -1,3 +1,4 @@ +import { getSetting, useSettings } from '@/common/store/settings'; import { IcBaselineAutoFixHigh, IcBaselineCheck, @@ -26,156 +27,202 @@ export const defaultPrompt = (params: { task: string }) => { }; }; -const getPrompts = () => [ - { - category: i18n.t('Edit or review selection'), - menus: [ - { - label: i18n.t('Improve writing'), - icon: , - }, - { - label: i18n.t('Fix spell and grammar'), - icon: , - }, - { - label: i18n.t('Make shorter'), - icon: , - }, - { - label: i18n.t('Make Longer'), - icon: , - }, - { - label: i18n.t('Change tone'), - icon: , - children: [ - i18n.t('Professional'), - i18n.t('Casual'), - i18n.t('Straightforward'), - i18n.t('Confident'), - i18n.t('Friendly'), - ].map((label) => { - return { - label, - instruction: i18n.t('Change tone to'), - }; - }), - }, - ], - }, - { - category: i18n.t('Generate from selection'), - menus: [ - { - label: i18n.t('Summarize'), - icon: , - }, - { - label: i18n.t('Translate to'), - icon: , - children: [ - { - label: i18n.t('English'), - icon: '🇎🇧 ', - }, - { - label: i18n.t('Chinese'), - icon: 'ðŸ‡ĻðŸ‡ģ ', - }, - { - label: i18n.t('Japanese'), - icon: 'ðŸ‡ŊðŸ‡ĩ ', - }, - { - label: i18n.t('Korean'), - icon: '🇰🇷 ', - }, - { - label: i18n.t('German'), - icon: 'ðŸ‡Đ🇊 ', - }, - { - label: i18n.t('French'), - icon: 'ðŸ‡Ŧ🇷 ', - }, - { - label: i18n.t('Italian'), - icon: 'ðŸ‡ŪðŸ‡đ ', - }, - ].map((item) => { - return { - ...item, - instruction: i18n.t('Translate to') + ' ' + item.label, - }; - }), - }, - { - label: i18n.t('Explain this'), - icon: , - }, - { - label: i18n.t('Find action items'), - icon: , - }, - ], - }, - { - category: i18n.t('Draft with AI'), - icon: , - menus: [ - { - label: i18n.t('Brain storm ideas'), - icon: , - }, - { - label: i18n.t('Blog post'), - icon: , - }, - { - label: i18n.t('Social media post'), - icon: , - }, - { - label: i18n.t('Press release'), - icon: , - }, - { - label: i18n.t('Creative story'), - icon: , - }, - { - label: i18n.t('Essay'), - icon: , - }, - { - label: i18n.t('Poem'), - icon: , - }, - { - label: i18n.t('Job description'), - icon: , - }, - { - label: i18n.t('Pros and cons list'), - icon: , - }, - ].map((item) => { - return { - ...item, - instruction: i18n.t('Write a') + ' ' + item.label, - }; - }), - }, -]; +function getRandomEmoji() { + const emojis = [ + '😀', + '😂', + '😍', + 'ðŸĪ”', + '🙄', + '😇', + 'ðŸĪĪ', + 'ðŸĨģ', + 'ðŸĨš', + '😎', + '😜', + 'ðŸĪŠ', + ]; + const randomIndex = Math.floor(Math.random() * emojis.length); + return emojis[randomIndex]; +} + +let settings = null; + +(async function () { + settings = await getSetting(); +})(); + +const getPrompts = () => { + const customInstructions = settings?.customInstructions || []; + + const predefined = [ + { + category: i18n.t('Edit or review selection'), + menus: [ + { + label: i18n.t('Improve writing'), + icon: , + }, + { + label: i18n.t('Fix spell and grammar'), + icon: , + }, + { + label: i18n.t('Make shorter'), + icon: , + }, + { + label: i18n.t('Make Longer'), + icon: , + }, + { + label: i18n.t('Change tone'), + icon: , + children: [ + i18n.t('Professional'), + i18n.t('Casual'), + i18n.t('Straightforward'), + i18n.t('Confident'), + i18n.t('Friendly'), + ].map((label) => { + return { + label, + instruction: i18n.t('Change tone to'), + }; + }), + }, + ], + }, + { + category: i18n.t('Generate from selection'), + menus: [ + { + label: i18n.t('Summarize'), + icon: , + }, + { + label: i18n.t('Translate to'), + icon: , + children: [ + { + label: i18n.t('English'), + icon: '🇎🇧 ', + }, + { + label: i18n.t('Chinese'), + icon: 'ðŸ‡ĻðŸ‡ģ ', + }, + { + label: i18n.t('Japanese'), + icon: 'ðŸ‡ŊðŸ‡ĩ ', + }, + { + label: i18n.t('Korean'), + icon: '🇰🇷 ', + }, + { + label: i18n.t('German'), + icon: 'ðŸ‡Đ🇊 ', + }, + { + label: i18n.t('French'), + icon: 'ðŸ‡Ŧ🇷 ', + }, + { + label: i18n.t('Italian'), + icon: 'ðŸ‡ŪðŸ‡đ ', + }, + ].map((item) => { + return { + ...item, + instruction: i18n.t('Translate to') + ' ' + item.label, + }; + }), + }, + { + label: i18n.t('Explain this'), + icon: , + }, + { + label: i18n.t('Find action items'), + icon: , + }, + ], + }, + { + category: i18n.t('Draft with AI'), + icon: , + menus: [ + { + label: i18n.t('Brain storm ideas'), + icon: , + }, + { + label: i18n.t('Blog post'), + icon: , + }, + { + label: i18n.t('Social media post'), + icon: , + }, + { + label: i18n.t('Press release'), + icon: , + }, + { + label: i18n.t('Creative story'), + icon: , + }, + { + label: i18n.t('Essay'), + icon: , + }, + { + label: i18n.t('Poem'), + icon: , + }, + { + label: i18n.t('Job description'), + icon: , + }, + { + label: i18n.t('Pros and cons list'), + icon: , + }, + ].map((item) => { + return { + ...item, + instruction: i18n.t('Write a') + ' ' + item.label, + }; + }), + }, + ]; + + if (customInstructions?.length) { + predefined.unshift({ + category: i18n.t('Custom instructions'), + menus: customInstructions?.map((i) => ({ + label: i, + icon: getRandomEmoji() as any, + instruction: i, + })), + }); + } + + return predefined; +}; export class PromptCenter { protected prompts; constructor() { - this.prompts = this.constructPrompts(getPrompts()); + this.initPrompts(); } + private initPrompts = () => { + this.prompts = this.constructPrompts(getPrompts()); + }; + private constructPrompts = (prompts, prefix: string = '') => { return prompts.map((p) => { if (!prompts.children?.length) { @@ -183,7 +230,6 @@ export class PromptCenter { ...p, prompt: defaultPrompt({ task: p.label, - role: i18n.t('Senior Writer'), }), }; } diff --git a/src/options/setting-form/custom-list.tsx b/src/options/setting-form/custom-list.tsx new file mode 100644 index 0000000..77071a6 --- /dev/null +++ b/src/options/setting-form/custom-list.tsx @@ -0,0 +1,61 @@ +import { IconBtn } from '@/components/icon-btn'; +import { IcBaselineDeleteOutline } from '@/components/icon/delete'; +import { IcOutlineCheck } from '@/components/icon/update'; +import { Input } from 'antd'; +import { useControllableValue } from 'ahooks'; +import { useState } from 'react'; + +export const CustomList: React.FC<{ + value?: string[]; + onChange?: (value: string[]) => void; +}> = (props) => { + const [value, setValue] = useControllableValue(props, { + defaultValue: [], + }); + const [inputValue, setInputValue] = useState(''); + + return ( +
+
+ setInputValue(e.target.value)} + onPressEnter={() => { + if (inputValue.trim()) { + setValue([inputValue.trim(), ...(value || [])]); + setInputValue(''); + } + }} + /> + { + if (inputValue.trim()) { + setValue([inputValue.trim(), ...(value || [])]); + setInputValue(''); + } + }} + > + + +
+
+ {(value || []).map((p) => { + return ( +
+
{p}
+ setValue((value || []).filter((i) => i !== p))} + > + + +
+ ); + })} +
+
+ ); +}; diff --git a/src/options/setting-form/system.tsx b/src/options/setting-form/system.tsx index 3f0560a..02e5840 100644 --- a/src/options/setting-form/system.tsx +++ b/src/options/setting-form/system.tsx @@ -1,5 +1,6 @@ import { Card, Form, Radio, Switch } from 'antd'; import { langs } from '../../common/langs'; +import { CustomList } from './custom-list'; export const SystemSetting: React.FC = () => { return ( @@ -14,6 +15,9 @@ export const SystemSetting: React.FC = () => { + + + ); }; diff --git a/src/options/types/settings.ts b/src/options/types/settings.ts index cf17426..b80aeec 100644 --- a/src/options/types/settings.ts +++ b/src/options/types/settings.ts @@ -3,4 +3,6 @@ export type Settings = { model?: string; lang?: string; url?: string; + customInstructions?: string[]; + debug?: boolean; };