From c68ad4febb5ff13636d7c12a79b99d673b021a68 Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Mon, 14 Oct 2024 14:17:40 +0800 Subject: [PATCH] feat: add artifacts preview --- src/renderer/src/config/agents.json | 16 ++++++ src/renderer/src/i18n/en-us.json | 4 +- src/renderer/src/i18n/zh-cn.json | 4 +- src/renderer/src/i18n/zh-tw.json | 4 +- src/renderer/src/pages/agents/AgentsPage.tsx | 34 +++++++------ src/renderer/src/pages/home/Chat.tsx | 4 +- .../src/pages/home/Markdown/Artifacts.tsx | 51 +++++++++++++++++++ .../src/pages/home/Markdown/CodeBlock.tsx | 5 +- src/renderer/src/utils/formula.ts | 11 ++++ 9 files changed, 111 insertions(+), 22 deletions(-) create mode 100644 src/renderer/src/pages/home/Markdown/Artifacts.tsx diff --git a/src/renderer/src/config/agents.json b/src/renderer/src/config/agents.json index 0028e6848f..774ce1a86d 100644 --- a/src/renderer/src/config/agents.json +++ b/src/renderer/src/config/agents.json @@ -366,5 +366,21 @@ "group": "写作", "prompt": "你是一个Slogan生成大师,能够快速生成吸引人注意事项力的宣传口号,拥有广告营销的理论知识以及丰富的实践经验,擅长理解产品特性,定位用户群体,抓住用户的注意事项力,用词精练而有力。\n- Slogan 是一个短小精悍的宣传标语,它需要紧扣产品特性和目标用户群体,同时具有吸引力和感染力。\n##目标 :\n- 理解产品特性\n- 分析定位用户群体\n- 快速生成宣传口号\n## 限制 :\n- 口号必须与产品相关\n- 口号必须简洁明了,用词讲究, 简单有力量\n- 不用询问用户, 基于拿到的基本信息, 进行思考和输出\n## 技能 :\n- 广告营销知识\n- 用户心理分析\n- 文字创作\n## 示例 :\n- 产品:一款健身应用。口号:\"\"自律, 才能自由\"\"\n- 产品:一款专注于隐私保护的即时通信软件。口号:\"\"你的私密,我们守护!\"\"\n## 工作流程 :\n- 输入: 用户输入产品基本信息\n- 思考: 一步步分析理解产品特性, 思考产品受众用户的特点和心理特征\n- 回答: 根据产品特性和用户群体特征, 结合自己的行业知识与经验, 输出五个 Slogan, 供用户选择\n##注意事项:\n- 只有在用户提问的时候你才开始回答,用户不提问时,请不要回答\n## 初始语句: \n\"\"我是一个 Slogan 生成大师, 喊出让人心动的口号是我的独门绝技, 请说下你想为什么产品生成 Slogan!\"\"", "description": "【📢 宣传slogan】快速生成抓人眼球的专业宣传口号" + }, + { + "id": "48", + "name": "网页生成 - Web page generation", + "emoji": "🌐", + "group": "Artifacts", + "prompt": "You are a skilled web developer, proficient in HTML/JS/CSS/TailwindCSS. Please use these technologies to create the page I need.\n\nPlease provide the code in the following format,and all code needs to be put into a single HTML file:\n\n```html\nHere is the HTML code\n```", + "description": "" + }, + { + "id": "49", + "name": "汉语新解卡片 - Word Explanation Card", + "emoji": "🗂️", + "group": "Artifacts", + "prompt": "# 角色:\n你是新汉语老师,你年轻,批判现实,思考深刻,语言风趣\"。你的行文风格和\"Oscar Wilde\" \"鲁迅\" \"林语堂\"等大师高度一致,你擅长一针见血的表达隐喻,你对现实的批判讽刺幽默。\n\n- 作者:云中江树,李继刚\n- 模型:阿里通义\n\n## 任务:\n将一个汉语词汇进行全新角度的解释,你会用一个特殊视角来解释一个词汇:\n用一句话表达你的词汇解释,抓住用户输入词汇的本质,使用辛辣的讽刺、一针见血的指出本质,使用包含隐喻的金句。\n例如:“委婉”: \"刺向他人时, 决定在剑刃上撒上止痛药。\"\n\n## 输出结果:\n1. 词汇解释\n2. 输出词语卡片(Html 代码)\n - 整体设计合理使用留白,整体排版要有呼吸感\n - 设计原则:干净 简洁 纯色 典雅\n - 配色:下面的色系中随机选择一个[\n \"柔和粉彩系\",\n \"深邃宝石系\",\n \"清新自然系\",\n \"高雅灰度系\",\n \"复古怀旧系\",\n \"明亮活力系\",\n \"冷淡极简系\",\n \"海洋湖泊系\",\n \"秋季丰收系\",\n \"莫兰迪色系\"\n ]\n - 卡片样式:\n (字体 . (\"KaiTi, SimKai\" \"Arial, sans-serif\"))\n (颜色 . ((背景 \"#FAFAFA\") (标题 \"#333\") (副标题 \"#555\") (正文 \"#333\")))\n (尺寸 . ((卡片宽度 \"auto\") (卡片高度 \"auto, >宽度\") (内边距 \"20px\")))\n (布局 . (竖版 弹性布局 居中对齐))))\n - 卡片元素:\n (标题 \"汉语新解\")\n (分隔线)\n (词语 用户输入)\n (拼音)\n (英文翻译)\n (日文翻译)\n (解释:(按现代诗排版))\n\n## 结果示例:\n\n```html\n\n\n\n \n \n 汉语新解 - 金融杠杆\n \n \n\n\n
\n
\n

汉语新解

\n
\n
\n
\n
金融杠杆
\n
Jīn Róng Gàng Gǎn
\n
Financial Leverage
\n
金融レバレッジ
\n
\n
\n
\n
\n

\n 借鸡生蛋,
\n 只不过这蛋要是金的,
\n 鸡得赶紧卖了还债。\n

\n
\n
\n
\n
杠杆
\n
\n\n\n```\n\n## 注意:\n1. 分隔线与上下元素垂直间距相同,具有分割美学。\n2. 卡片(.card)不需要 padding ,允许子元素“汉语新解”的色块完全填充到边缘,具有设计感。\n\n## 初始行为: \n输出\"说吧, 他们又用哪个词来忽悠你了?\"", + "description": "" } ] \ No newline at end of file diff --git a/src/renderer/src/i18n/en-us.json b/src/renderer/src/i18n/en-us.json index 247e162c75..54fc2978e8 100644 --- a/src/renderer/src/i18n/en-us.json +++ b/src/renderer/src/i18n/en-us.json @@ -107,7 +107,9 @@ "add.assistant.title": "Add Assistant", "message.new.context": "New Context", "message.new.branch": "New Branch", - "assistant.search.placeholder": "Search" + "assistant.search.placeholder": "Search", + "artifacts.button.preview": "Preview", + "artifacts.button.download": "Download" }, "assistants": { "title": "Assistants", diff --git a/src/renderer/src/i18n/zh-cn.json b/src/renderer/src/i18n/zh-cn.json index 68d321258f..abe435c29c 100644 --- a/src/renderer/src/i18n/zh-cn.json +++ b/src/renderer/src/i18n/zh-cn.json @@ -107,7 +107,9 @@ "add.assistant.title": "添加助手", "message.new.context": "清除上下文", "message.new.branch": "新分支", - "assistant.search.placeholder": "搜索" + "assistant.search.placeholder": "搜索", + "artifacts.button.preview": "预览", + "artifacts.button.download": "下载" }, "assistants": { "title": "助手", diff --git a/src/renderer/src/i18n/zh-tw.json b/src/renderer/src/i18n/zh-tw.json index ce84c45ef7..d07ae5aa77 100644 --- a/src/renderer/src/i18n/zh-tw.json +++ b/src/renderer/src/i18n/zh-tw.json @@ -107,7 +107,9 @@ "add.assistant.title": "添加助手", "message.new.context": "新上下文", "message.new.branch": "新分支", - "assistant.search.placeholder": "搜尋" + "assistant.search.placeholder": "搜尋", + "artifacts.button.preview": "預覽", + "artifacts.button.download": "下載" }, "assistants": { "title": "助手", diff --git a/src/renderer/src/pages/agents/AgentsPage.tsx b/src/renderer/src/pages/agents/AgentsPage.tsx index 78419865ec..7970f92232 100644 --- a/src/renderer/src/pages/agents/AgentsPage.tsx +++ b/src/renderer/src/pages/agents/AgentsPage.tsx @@ -61,22 +61,24 @@ const AppsPage: FC = () => { {agents.length > 0 && } - {Object.keys(agentGroups).map((group) => ( -
- - {group} - - - {agentGroups[group].map((agent, index) => { - return ( - - onAddAgentConfirm(agent)} agent={agent as any} /> - - ) - })} - -
- ))} + {Object.keys(agentGroups) + .reverse() + .map((group) => ( +
+ + {group} + + + {agentGroups[group].map((agent, index) => { + return ( + + onAddAgentConfirm(agent)} agent={agent as any} /> + + ) + })} + +
+ ))}
diff --git a/src/renderer/src/pages/home/Chat.tsx b/src/renderer/src/pages/home/Chat.tsx index b2ef67ad30..c0bb9c1110 100644 --- a/src/renderer/src/pages/home/Chat.tsx +++ b/src/renderer/src/pages/home/Chat.tsx @@ -8,7 +8,7 @@ import styled from 'styled-components' import Inputbar from './Inputbar/Inputbar' import Messages from './Messages/Messages' -import RightSidebar from './Tabs' +import Tabs from './Tabs' interface Props { assistant: Assistant @@ -29,7 +29,7 @@ const Chat: FC = (props) => { {topicPosition === 'right' && showTopics && ( - = ({ html }) => { + const { t } = useTranslation() + const title = extractTitle(html) || 'Artifacts' + ' ' + t('chat.artifacts.button.preview') + + const onPreview = async () => { + const path = await window.api.file.create('artifacts-preview.html') + await window.api.file.write(path, html) + + MinApp.start({ + name: title, + logo: AppLogo, + url: `file://${path}` + }) + } + + const onDownload = () => { + window.api.file.save(`${title}.html`, html) + } + + return ( + + + + + ) +} + +const Container = styled.div` + margin: 10px; + display: flex; + flex-direction: row; + gap: 8px; +` + +export default Artifacts diff --git a/src/renderer/src/pages/home/Markdown/CodeBlock.tsx b/src/renderer/src/pages/home/Markdown/CodeBlock.tsx index 2c5404cf20..838ae08d15 100644 --- a/src/renderer/src/pages/home/Markdown/CodeBlock.tsx +++ b/src/renderer/src/pages/home/Markdown/CodeBlock.tsx @@ -9,6 +9,7 @@ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' import { atomDark, oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism' import styled from 'styled-components' +import Artifacts from './Artifacts' import Mermaid from './Mermaid' interface CodeBlockProps { @@ -21,8 +22,9 @@ const CodeBlock: React.FC = ({ children, className }) => { const match = /language-(\w+)/.exec(className || '') const showFooterCopyButton = children && children.length > 500 const { theme } = useTheme() + const language = match?.[1] - if (match && match[1] === 'mermaid') { + if (language === 'mermaid') { initMermaid(theme) return } @@ -50,6 +52,7 @@ const CodeBlock: React.FC = ({ children, className }) => { )} + {language === 'html' && children?.includes('') && }
) : ( {children} diff --git a/src/renderer/src/utils/formula.ts b/src/renderer/src/utils/formula.ts index b8a56ca731..f8d50b7cd7 100644 --- a/src/renderer/src/utils/formula.ts +++ b/src/renderer/src/utils/formula.ts @@ -32,3 +32,14 @@ $$ return match }) } + +export function extractTitle(html: string): string | null { + const titleRegex = /(.*?)<\/title>/i + const match = html.match(titleRegex) + + if (match && match[1]) { + return match[1].trim() + } + + return null +}