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;
};