mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-06 05:09:09 +08:00
feat: add artifacts preview
This commit is contained in:
parent
0026293e69
commit
21a13d54c8
File diff suppressed because one or more lines are too long
@ -107,7 +107,9 @@
|
|||||||
"add.assistant.title": "Add Assistant",
|
"add.assistant.title": "Add Assistant",
|
||||||
"message.new.context": "New Context",
|
"message.new.context": "New Context",
|
||||||
"message.new.branch": "New Branch",
|
"message.new.branch": "New Branch",
|
||||||
"assistant.search.placeholder": "Search"
|
"assistant.search.placeholder": "Search",
|
||||||
|
"artifacts.button.preview": "Preview",
|
||||||
|
"artifacts.button.download": "Download"
|
||||||
},
|
},
|
||||||
"assistants": {
|
"assistants": {
|
||||||
"title": "Assistants",
|
"title": "Assistants",
|
||||||
|
|||||||
@ -107,7 +107,9 @@
|
|||||||
"add.assistant.title": "添加助手",
|
"add.assistant.title": "添加助手",
|
||||||
"message.new.context": "清除上下文",
|
"message.new.context": "清除上下文",
|
||||||
"message.new.branch": "新分支",
|
"message.new.branch": "新分支",
|
||||||
"assistant.search.placeholder": "搜索"
|
"assistant.search.placeholder": "搜索",
|
||||||
|
"artifacts.button.preview": "预览",
|
||||||
|
"artifacts.button.download": "下载"
|
||||||
},
|
},
|
||||||
"assistants": {
|
"assistants": {
|
||||||
"title": "助手",
|
"title": "助手",
|
||||||
|
|||||||
@ -107,7 +107,9 @@
|
|||||||
"add.assistant.title": "添加助手",
|
"add.assistant.title": "添加助手",
|
||||||
"message.new.context": "新上下文",
|
"message.new.context": "新上下文",
|
||||||
"message.new.branch": "新分支",
|
"message.new.branch": "新分支",
|
||||||
"assistant.search.placeholder": "搜尋"
|
"assistant.search.placeholder": "搜尋",
|
||||||
|
"artifacts.button.preview": "預覽",
|
||||||
|
"artifacts.button.download": "下載"
|
||||||
},
|
},
|
||||||
"assistants": {
|
"assistants": {
|
||||||
"title": "助手",
|
"title": "助手",
|
||||||
|
|||||||
@ -61,22 +61,24 @@ const AppsPage: FC = () => {
|
|||||||
{agents.length > 0 && <ManageIcon onClick={ManageAgentsPopup.show} />}
|
{agents.length > 0 && <ManageIcon onClick={ManageAgentsPopup.show} />}
|
||||||
</HStack>
|
</HStack>
|
||||||
<UserAgents onAdd={onAddAgentConfirm} />
|
<UserAgents onAdd={onAddAgentConfirm} />
|
||||||
{Object.keys(agentGroups).map((group) => (
|
{Object.keys(agentGroups)
|
||||||
<div key={group}>
|
.reverse()
|
||||||
<Title level={4} key={group} style={{ marginBottom: 16 }}>
|
.map((group) => (
|
||||||
{group}
|
<div key={group}>
|
||||||
</Title>
|
<Title level={4} key={group} style={{ marginBottom: 16 }}>
|
||||||
<Row gutter={16}>
|
{group}
|
||||||
{agentGroups[group].map((agent, index) => {
|
</Title>
|
||||||
return (
|
<Row gutter={16}>
|
||||||
<Col span={8} key={group + index}>
|
{agentGroups[group].map((agent, index) => {
|
||||||
<AgentCard onClick={() => onAddAgentConfirm(agent)} agent={agent as any} />
|
return (
|
||||||
</Col>
|
<Col span={8} key={group + index}>
|
||||||
)
|
<AgentCard onClick={() => onAddAgentConfirm(agent)} agent={agent as any} />
|
||||||
})}
|
</Col>
|
||||||
</Row>
|
)
|
||||||
</div>
|
})}
|
||||||
))}
|
</Row>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
<div style={{ minHeight: 20 }} />
|
<div style={{ minHeight: 20 }} />
|
||||||
</AssistantsContainer>
|
</AssistantsContainer>
|
||||||
</ContentContainer>
|
</ContentContainer>
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import styled from 'styled-components'
|
|||||||
|
|
||||||
import Inputbar from './Inputbar/Inputbar'
|
import Inputbar from './Inputbar/Inputbar'
|
||||||
import Messages from './Messages/Messages'
|
import Messages from './Messages/Messages'
|
||||||
import RightSidebar from './Tabs'
|
import Tabs from './Tabs'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
assistant: Assistant
|
assistant: Assistant
|
||||||
@ -29,7 +29,7 @@ const Chat: FC<Props> = (props) => {
|
|||||||
<Inputbar assistant={assistant} setActiveTopic={props.setActiveTopic} />
|
<Inputbar assistant={assistant} setActiveTopic={props.setActiveTopic} />
|
||||||
</Main>
|
</Main>
|
||||||
{topicPosition === 'right' && showTopics && (
|
{topicPosition === 'right' && showTopics && (
|
||||||
<RightSidebar
|
<Tabs
|
||||||
activeAssistant={assistant}
|
activeAssistant={assistant}
|
||||||
activeTopic={props.activeTopic}
|
activeTopic={props.activeTopic}
|
||||||
setActiveAssistant={props.setActiveAssistant}
|
setActiveAssistant={props.setActiveAssistant}
|
||||||
|
|||||||
51
src/renderer/src/pages/home/Markdown/Artifacts.tsx
Normal file
51
src/renderer/src/pages/home/Markdown/Artifacts.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import MinApp from '@renderer/components/MinApp'
|
||||||
|
import { AppLogo } from '@renderer/config/env'
|
||||||
|
import { extractTitle } from '@renderer/utils/formula'
|
||||||
|
import { Button } from 'antd'
|
||||||
|
import { FC } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
html: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Artifacts: FC<Props> = ({ 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 (
|
||||||
|
<Container>
|
||||||
|
<Button type="primary" size="middle" onClick={onPreview}>
|
||||||
|
{t('chat.artifacts.button.preview')}
|
||||||
|
</Button>
|
||||||
|
<Button size="middle" onClick={onDownload}>
|
||||||
|
{t('chat.artifacts.button.download')}
|
||||||
|
</Button>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
margin: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 8px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export default Artifacts
|
||||||
@ -9,6 +9,7 @@ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
|
|||||||
import { atomDark, oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism'
|
import { atomDark, oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
import Artifacts from './Artifacts'
|
||||||
import Mermaid from './Mermaid'
|
import Mermaid from './Mermaid'
|
||||||
|
|
||||||
interface CodeBlockProps {
|
interface CodeBlockProps {
|
||||||
@ -21,8 +22,9 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ children, className }) => {
|
|||||||
const match = /language-(\w+)/.exec(className || '')
|
const match = /language-(\w+)/.exec(className || '')
|
||||||
const showFooterCopyButton = children && children.length > 500
|
const showFooterCopyButton = children && children.length > 500
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
const language = match?.[1]
|
||||||
|
|
||||||
if (match && match[1] === 'mermaid') {
|
if (language === 'mermaid') {
|
||||||
initMermaid(theme)
|
initMermaid(theme)
|
||||||
return <Mermaid chart={children} />
|
return <Mermaid chart={children} />
|
||||||
}
|
}
|
||||||
@ -50,6 +52,7 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ children, className }) => {
|
|||||||
<CopyButton text={children} style={{ marginTop: -40, marginRight: 10 }} />
|
<CopyButton text={children} style={{ marginTop: -40, marginRight: 10 }} />
|
||||||
</CodeFooter>
|
</CodeFooter>
|
||||||
)}
|
)}
|
||||||
|
{language === 'html' && children?.includes('</html>') && <Artifacts html={children} />}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<code className={className}>{children}</code>
|
<code className={className}>{children}</code>
|
||||||
|
|||||||
@ -32,3 +32,14 @@ $$
|
|||||||
return match
|
return match
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function extractTitle(html: string): string | null {
|
||||||
|
const titleRegex = /<title>(.*?)<\/title>/i
|
||||||
|
const match = html.match(titleRegex)
|
||||||
|
|
||||||
|
if (match && match[1]) {
|
||||||
|
return match[1].trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user