diff --git a/README.md b/README.md index 675b22e6c..85f053390 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Refly is an open-source AI-native creation engine. It's intuitive free-form canvas interface combines multi-threaded conversations, knowledge base RAG integration, contextual memory, intelligent search, WYSIWYG AI editor and more, empowering you to effortlessly transform ideas into production-ready content. -[🚀 Refly v0.2.3 Released! Featuring Enhanced Product Onboarding!](https://docs.refly.ai/changelog/v0.2.3) +[🚀 Refly Chrome Extension web clipper launched! ⚡️!](https://docs.refly.ai/guide/chrome-extension) [Refly Cloud](https://refly.ai/) · [Self-hosting](https://refly.ai/) · [Forum](https://github.com/refly-ai/refly/discussions) · [Discord](https://discord.gg/bWjffrb89h) · [Twitter](https://x.com/reflyai) · [Documentation](https://docs.refly.ai/) diff --git a/README_CN.md b/README_CN.md index 651fb4e26..4e92cdb79 100644 --- a/README_CN.md +++ b/README_CN.md @@ -11,7 +11,7 @@ Refly 是一个开源的 AI 原生创作引擎。Refly 直观的自由画布界面集成了多线程对话、RAG 检索流程、上下文记忆、智能搜索和 AI 文档编辑等功能,让您轻松地将创意转化为完整作品。 -[🚀 Refly v0.2.3 正式发布!全新升级产品引导体验!](https://docs.refly.ai/changelog/v0.2.3) +[🚀 Refly 网页剪存 Chrome 插件发布! ⚡️! ](https://docs.refly.ai/guide/chrome-extension) [Refly Cloud](https://refly.ai/) · [Self-hosting](https://refly.ai/) · [Forum](https://github.com/refly-ai/refly/discussions) · [Discord](https://discord.gg/bWjffrb89h) · [Twitter](https://x.com/reflyai) · [Documentation](https://docs.refly.ai/) diff --git a/apps/extension/package.json b/apps/extension/package.json index 91db96786..7cd78e507 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -2,7 +2,7 @@ "name": "@refly/extension", "description": "A free-form canvas creation platform powered by multi-threaded dialogue, knowledge integration, context memory, intelligent search and AI documents, easily transforms ideas into quality content.", "private": true, - "version": "0.1.0", + "version": "0.3.6", "type": "module", "scripts": { "predev": "wxt prepare", diff --git a/apps/extension/src/components/content-clipper/index.tsx b/apps/extension/src/components/content-clipper/index.tsx index 0605e9235..f14a6430d 100644 --- a/apps/extension/src/components/content-clipper/index.tsx +++ b/apps/extension/src/components/content-clipper/index.tsx @@ -33,6 +33,7 @@ export const ContentClipper: React.FC = ({ className, onSav content: '', }); const [isSaving, setIsSaving] = useState(false); + const [isClipping, setIsClipping] = useState(false); const { saveSelectedContent } = useSaveSelectedContent(); const { handleSaveResourceAndNotify } = useSaveResourceNotify(); @@ -45,6 +46,7 @@ export const ContentClipper: React.FC = ({ className, onSav if (response?.body) { setPageInfo(response.body); } + setIsClipping(false); } }, getRuntime()); }, []); @@ -52,6 +54,7 @@ export const ContentClipper: React.FC = ({ className, onSav // Handle clip current page content const handleClipContent = useCallback(async () => { try { + setIsClipping(true); // Send message to content script to get page content const msg: BackgroundMessage = { source: getRuntime(), @@ -59,6 +62,7 @@ export const ContentClipper: React.FC = ({ className, onSav }; sendMessage(msg); } catch (err) { + setIsClipping(false); console.error('Failed to clip content:', err); message.error(t('extension.webClipper.error.clipContentFailed')); } @@ -176,6 +180,7 @@ export const ContentClipper: React.FC = ({ className, onSav size="large" icon={} onClick={handleClipContent} + loading={isClipping} className="flex-1" > {t('extension.webClipper.action.clip')} diff --git a/apps/extension/src/entrypoints/popup/index.html b/apps/extension/src/entrypoints/popup/index.html index ed4cb9494..c2f05e091 100644 --- a/apps/extension/src/entrypoints/popup/index.html +++ b/apps/extension/src/entrypoints/popup/index.html @@ -3,8 +3,11 @@ - Default Popup Title + The AI Native Creation Engine · Refly +
diff --git a/apps/extension/src/hooks/use-save-resource.ts b/apps/extension/src/hooks/use-save-resource.ts index 42f049219..ae4a880ab 100644 --- a/apps/extension/src/hooks/use-save-resource.ts +++ b/apps/extension/src/hooks/use-save-resource.ts @@ -3,7 +3,12 @@ import { getMarkdown, getReadabilityMarkdown } from '@refly/utils/html2md'; import getClient from '@refly-packages/ai-workspace-common/requests/proxiedRequest'; import { getClientOrigin } from '@refly/utils/url'; import { getRuntime } from '@refly/utils/env'; -import { ConnectionError } from '@refly/errors'; +import { ConnectionError, ContentTooLargeError, PayloadTooLargeError } from '@refly/errors'; + +// Maximum content length (100k characters) +const MAX_CONTENT_LENGTH = 100000; +// Maximum payload size (100KB) +const MAX_PAYLOAD_SIZE_BYTES = 100 * 1024; export const useSaveCurrentWeblinkAsResource = () => { const saveResource = async () => { @@ -13,6 +18,17 @@ export const useSaveCurrentWeblinkAsResource = () => { const pageContent = isWeb ? getMarkdown(document?.body) : getReadabilityMarkdown(document?.body ? document?.body : document); + + // Check content length + if (pageContent?.length > MAX_CONTENT_LENGTH) { + return { + url: '', + res: { + errCode: new ContentTooLargeError().code, + } as BaseResponse, + }; + } + const resource = { resourceId: 'tempResId', title: document?.title || '', @@ -36,6 +52,17 @@ export const useSaveCurrentWeblinkAsResource = () => { }, }; + // Check payload size + const payloadSize = new Blob([JSON.stringify(createResourceData)]).size; + if (payloadSize > MAX_PAYLOAD_SIZE_BYTES) { + return { + url: '', + res: { + errCode: new PayloadTooLargeError().code, + } as BaseResponse, + }; + } + const { error } = await getClient().createResource(createResourceData); // const resourceId = data?.data?.resourceId; // const url = `${getClientOrigin(false)}/resource/${resourceId}`; diff --git a/apps/extension/src/hooks/use-save-selected-content.ts b/apps/extension/src/hooks/use-save-selected-content.ts index 0f273bdb0..9f6b12e5e 100644 --- a/apps/extension/src/hooks/use-save-selected-content.ts +++ b/apps/extension/src/hooks/use-save-selected-content.ts @@ -1,7 +1,12 @@ import { UpsertResourceRequest, type BaseResponse } from '@refly/openapi-schema'; import getClient from '@refly-packages/ai-workspace-common/requests/proxiedRequest'; import { getClientOrigin } from '@refly/utils/url'; -import { ConnectionError } from '@refly/errors'; +import { ConnectionError, ContentTooLargeError, PayloadTooLargeError } from '@refly/errors'; + +// Maximum content length (100k characters) +const MAX_CONTENT_LENGTH = 100000; +// Maximum payload size (100KB) +const MAX_PAYLOAD_SIZE_BYTES = 100 * 1024; interface SaveContentMetadata { title?: string; @@ -19,6 +24,16 @@ export const useSaveSelectedContent = () => { const title = metadata?.title || document?.title || 'Untitled'; const url = metadata?.url || document?.location?.href || 'https://www.refly.ai'; + // Check content length + if (content?.length > MAX_CONTENT_LENGTH) { + return { + url: '', + res: { + errCode: new ContentTooLargeError().code, + } as BaseResponse, + }; + } + const createResourceData: UpsertResourceRequest = { resourceType: 'text', title, @@ -29,6 +44,17 @@ export const useSaveSelectedContent = () => { }, }; + // Check payload size + const payloadSize = new Blob([JSON.stringify(createResourceData)]).size; + if (payloadSize > MAX_PAYLOAD_SIZE_BYTES) { + return { + url: '', + res: { + errCode: new PayloadTooLargeError().code, + } as BaseResponse, + }; + } + const { error } = await getClient().createResource({ body: createResourceData, }); diff --git a/apps/extension/src/public/_locales/en/messages.json b/apps/extension/src/public/_locales/en/messages.json index b3077dd2c..b55d0d348 100644 --- a/apps/extension/src/public/_locales/en/messages.json +++ b/apps/extension/src/public/_locales/en/messages.json @@ -4,7 +4,7 @@ "description": "Name of the extension." }, "description": { - "message": "A free-form canvas creation platform powered by multi-threaded dialogue, knowledge integration, context memory, intelligent search and WYSIWYG AI editor, easily transforms ideas into quality content.", + "message": "An OpenSource AI-powered free-form canvas with multi-threaded dialogue, knowledge base, intelligent search, and WYSIWYG AI editor", "description": "Description of the extension." }, "displayName": { diff --git a/apps/extension/src/public/_locales/en_AU/messages.json b/apps/extension/src/public/_locales/en_AU/messages.json index b3077dd2c..b55d0d348 100644 --- a/apps/extension/src/public/_locales/en_AU/messages.json +++ b/apps/extension/src/public/_locales/en_AU/messages.json @@ -4,7 +4,7 @@ "description": "Name of the extension." }, "description": { - "message": "A free-form canvas creation platform powered by multi-threaded dialogue, knowledge integration, context memory, intelligent search and WYSIWYG AI editor, easily transforms ideas into quality content.", + "message": "An OpenSource AI-powered free-form canvas with multi-threaded dialogue, knowledge base, intelligent search, and WYSIWYG AI editor", "description": "Description of the extension." }, "displayName": { diff --git a/apps/extension/src/public/_locales/en_GB/messages.json b/apps/extension/src/public/_locales/en_GB/messages.json index b3077dd2c..b55d0d348 100644 --- a/apps/extension/src/public/_locales/en_GB/messages.json +++ b/apps/extension/src/public/_locales/en_GB/messages.json @@ -4,7 +4,7 @@ "description": "Name of the extension." }, "description": { - "message": "A free-form canvas creation platform powered by multi-threaded dialogue, knowledge integration, context memory, intelligent search and WYSIWYG AI editor, easily transforms ideas into quality content.", + "message": "An OpenSource AI-powered free-form canvas with multi-threaded dialogue, knowledge base, intelligent search, and WYSIWYG AI editor", "description": "Description of the extension." }, "displayName": { diff --git a/apps/extension/src/public/_locales/en_US/messages.json b/apps/extension/src/public/_locales/en_US/messages.json index b3077dd2c..b55d0d348 100644 --- a/apps/extension/src/public/_locales/en_US/messages.json +++ b/apps/extension/src/public/_locales/en_US/messages.json @@ -4,7 +4,7 @@ "description": "Name of the extension." }, "description": { - "message": "A free-form canvas creation platform powered by multi-threaded dialogue, knowledge integration, context memory, intelligent search and WYSIWYG AI editor, easily transforms ideas into quality content.", + "message": "An OpenSource AI-powered free-form canvas with multi-threaded dialogue, knowledge base, intelligent search, and WYSIWYG AI editor", "description": "Description of the extension." }, "displayName": { diff --git a/apps/extension/src/public/wxt.svg b/apps/extension/src/public/wxt.svg deleted file mode 100644 index 0e763206b..000000000 --- a/apps/extension/src/public/wxt.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/apps/extension/wxt.config.ts b/apps/extension/wxt.config.ts index d8dd599a3..be127ece8 100644 --- a/apps/extension/wxt.config.ts +++ b/apps/extension/wxt.config.ts @@ -68,8 +68,8 @@ export default defineConfig({ }, }) as WxtViteConfig, manifest: { - version: '0.1.0', - author: 'pftom', + version: '0.3.6', + author: 'support@refly.ai', name: '__MSG_displayName__', description: '__MSG_description__', default_locale: 'en', @@ -77,11 +77,6 @@ export default defineConfig({ content_security_policy: { extension_pages: "script-src 'self'; object-src 'self'", }, - commands: { - _execute_action: { - suggested_key: { default: 'Ctrl+J', mac: 'Command+J' }, - }, - }, key: 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlkdw0WXN0WT9YYu1nsWezZzSmWrGpny4gK0UhiL7nbz2NQkqq32KsW51Ag3wdvD/ccyS5VUUEnnlAwxmk0CfnO+TNEFM5lCtF+1/2j5HpmlqZMUlu3tUx+SiY3mF6R9cpbfts3IjWomuRVMfHXmWEu3Gctv4T5hSTNKd44Z3SOPj5KeUxYryJmL/y8LR6lj9F/a5Gfblf5t214GKeFXjewgQOmAGT+v5NurIu3xuwPkYqmkrNcRrQHqkdREH4AFp4TjlNpx5W+AR6Qh9FRkGjXTlcVMQ62KqPlIV29Y/VTO/4oUVhPMhVxXH91ojoA7Vzgr76OtnjaysNZbBapxgFQIDAQAB', externally_connectable: { matches: [ @@ -95,7 +90,6 @@ export default defineConfig({ permissions: [ 'storage', 'scripting', - 'history', 'activeTab', 'tabs', 'cookies', diff --git a/apps/web/src/components/landing-page-partials/FeaturesBlocks.tsx b/apps/web/src/components/landing-page-partials/FeaturesBlocks.tsx index c34673183..cb4b7c542 100644 --- a/apps/web/src/components/landing-page-partials/FeaturesBlocks.tsx +++ b/apps/web/src/components/landing-page-partials/FeaturesBlocks.tsx @@ -2,6 +2,7 @@ import { ReactNode } from 'react'; import { useTranslation } from 'react-i18next'; import { AiOutlineAppstore, AiOutlineExperiment } from 'react-icons/ai'; import { FaRegPaperPlane } from 'react-icons/fa'; +import { HiOutlineDocumentDownload } from 'react-icons/hi'; import { LuSearch } from 'react-icons/lu'; import { MdOutlineNoteAlt } from 'react-icons/md'; @@ -143,6 +144,19 @@ function FeaturesBlocks() { color: '#3B82F6', tagShadow: '0 2px 4px 0 rgba(0,0,0,0.10), inset 0 -4px 0 0 rgba(59,130,246,0.20)', }, + { + tag: t('landingPage.features.featureFive.tag'), + tagIcon: , + title: t('landingPage.features.featureFive.title'), + bulletPoints: t('landingPage.features.featureFive.bulletPoints', { + returnObjects: true, + }) as string[], + imageSrc: 'https://static.refly.ai/landing/features-clip.webp', + isReversed: false, + background: 'linear-gradient(180deg, #E8F5E9 0%, #FFFFFF 100%)', + color: '#4CAF50', + tagShadow: '0 2px 4px 0 rgba(0,0,0,0.10), inset 0 -4px 0 0 rgba(76,175,80,0.20)', + }, { tag: t('landingPage.features.featureThree.tag'), tagIcon: , diff --git a/apps/web/src/components/landing-page-partials/Footer.tsx b/apps/web/src/components/landing-page-partials/Footer.tsx index 28c1443d7..ec62185fc 100644 --- a/apps/web/src/components/landing-page-partials/Footer.tsx +++ b/apps/web/src/components/landing-page-partials/Footer.tsx @@ -14,6 +14,7 @@ import { IconLanguage, } from '@refly-packages/ai-workspace-common/components/common/icon'; import { FaGithub } from 'react-icons/fa6'; +import { EXTENSION_DOWNLOAD_LINK, getClientOrigin } from '@refly/utils/url'; const resources = [ { @@ -34,6 +35,17 @@ const resources = [ }, ]; +const platforms = [ + { + title: 'chrome', + link: EXTENSION_DOWNLOAD_LINK, + }, + { + title: 'web', + link: getClientOrigin(), + }, +]; + function Footer() { const { t } = useTranslation(); const { setLoginModalOpen } = useAuthStoreShallow((state) => ({ @@ -245,6 +257,26 @@ function Footer() { + {/* Platforms Section */} +
+
+ {t('landingPage.footer.platforms.title')} +
+
    + {platforms.map((item) => ( +
  • + + {t(`landingPage.footer.platforms.${item.title}`)} + +
  • + ))} +
+
+ {/* Contact Us Section */}
diff --git a/apps/web/src/components/landing-page-partials/Header.tsx b/apps/web/src/components/landing-page-partials/Header.tsx index 9c26719fe..0eee8c8e3 100644 --- a/apps/web/src/components/landing-page-partials/Header.tsx +++ b/apps/web/src/components/landing-page-partials/Header.tsx @@ -1,9 +1,7 @@ import Logo from '@/assets/logo.svg'; -import { Button } from 'antd'; +import { Button, Badge } from 'antd'; import { useTranslation } from 'react-i18next'; import { useAuthStoreShallow } from '@refly-packages/ai-workspace-common/stores/auth'; -import { UILocaleList } from '@refly-packages/ai-workspace-common/components/ui-locale-list'; -import { IconDown, IconLanguage } from '@refly-packages/ai-workspace-common/components/common/icon'; import { useState, useEffect } from 'react'; import { useNavigate, @@ -12,6 +10,11 @@ import { } from '@refly-packages/ai-workspace-common/utils/router'; import './header.scss'; import { FaDiscord, FaGithub } from 'react-icons/fa6'; +import { EXTENSION_DOWNLOAD_LINK } from '@refly/utils/url'; +import { + IconChrome, + MemoizedIcon, +} from '@refly-packages/ai-workspace-common/components/common/icon'; function Header() { const navigate = useNavigate(); @@ -23,7 +26,7 @@ function Header() { })); const [value, setValue] = useState('product'); - const [starCount, setStarCount] = useState('614'); + const [starCount, setStarCount] = useState('913'); const tabOptions = [ { @@ -31,7 +34,19 @@ function Header() { value: 'product', }, { - label: t('landingPage.tab.price'), + label: ( + + {t('landingPage.tab.price')} + + ), value: 'pricing', }, { @@ -121,24 +136,24 @@ function Header() {
- - - - + diff --git a/apps/web/src/components/landing-page-partials/HeroHome.tsx b/apps/web/src/components/landing-page-partials/HeroHome.tsx index 3b56d0bf7..4d8b81572 100644 --- a/apps/web/src/components/landing-page-partials/HeroHome.tsx +++ b/apps/web/src/components/landing-page-partials/HeroHome.tsx @@ -11,11 +11,9 @@ import { Button, Modal } from 'antd'; import BlurImage from '@/components/common/BlurImage'; import { useAuthStoreShallow } from '@refly-packages/ai-workspace-common/stores/auth'; import { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; function HeroHome() { const { t, i18n } = useTranslation(); - const navigate = useNavigate(); const { setLoginModalOpen } = useAuthStoreShallow((state) => ({ setLoginModalOpen: state.setLoginModalOpen, })); @@ -72,20 +70,21 @@ function HeroHome() {
{ - navigate('/pricing'); + window.open('https://docs.refly.ai/changelog/v0.3.0', '_blank'); }} className={cn( 'group inline-flex items-center justify-center rounded-lg border border-black/5 bg-white text-base hover:cursor-pointer hover:bg-neutral-50 dark:border-white/5', 'px-4', 'py-2', + 'w-auto', )} > - + 🚀 {t('landingPage.messageText')} - +
diff --git a/apps/web/src/components/layout/sider.tsx b/apps/web/src/components/layout/sider.tsx index 5d0c83cde..9a5336fca 100644 --- a/apps/web/src/components/layout/sider.tsx +++ b/apps/web/src/components/layout/sider.tsx @@ -237,7 +237,7 @@ const getSelectedKey = (pathname: string) => { export const SiderLayout = (props: { source: 'sider' | 'popover' }) => { const { source = 'sider' } = props; - const [starCount, setStarCount] = useState('851'); + const [starCount, setStarCount] = useState('913'); const [searchParams] = useSearchParams(); const navigate = useNavigate(); const { updateLibraryModalActiveKey } = useKnowledgeBaseStoreShallow((state) => ({ diff --git a/packages/ai-workspace-common/src/assets/chrome.svg b/packages/ai-workspace-common/src/assets/chrome.svg new file mode 100644 index 000000000..db8307169 --- /dev/null +++ b/packages/ai-workspace-common/src/assets/chrome.svg @@ -0,0 +1,11 @@ + + + Google Chrome + + + + + + + + diff --git a/packages/ai-workspace-common/src/components/common/icon.tsx b/packages/ai-workspace-common/src/components/common/icon.tsx index c6ffa15f6..eab0f87ca 100644 --- a/packages/ai-workspace-common/src/components/common/icon.tsx +++ b/packages/ai-workspace-common/src/components/common/icon.tsx @@ -66,7 +66,9 @@ import QwenIcon from '@refly-packages/ai-workspace-common/assets/qwen.svg'; import MetaLlamaIcon from '@refly-packages/ai-workspace-common/assets/meta.svg'; import DeepSeekIcon from '@refly-packages/ai-workspace-common/assets/deepseek.svg'; import MistralIcon from '@refly-packages/ai-workspace-common/assets/mistral.svg'; +import ChromeIcon from '@refly-packages/ai-workspace-common/assets/chrome.svg'; import { MdOutlineMouse } from 'react-icons/md'; +import { memo } from 'react'; export const IconCanvas = TfiBlackboard; export const IconAskAI = LuSparkles; @@ -124,7 +126,7 @@ export const IconEmail = BsEnvelope; export const IconGuideLine = RiGuideLine; export const IconLanguage = IoLanguage; export const IconDeleteFile = TiDocumentDelete; - +export const IconChrome = ChromeIcon; export const ModelProviderIcons = { openai: OpenAIIcon, anthropic: ClaudeIcon, @@ -161,3 +163,7 @@ export const getSkillIcon = (skillName: string, className?: string) => { return ; } }; + +export const MemoizedIcon = memo(({ icon, className }: { icon: string; className?: string }) => ( + {icon} +)); diff --git a/packages/ai-workspace-common/src/components/import-resource/index.tsx b/packages/ai-workspace-common/src/components/import-resource/index.tsx index 858eaca14..e5cdb8853 100644 --- a/packages/ai-workspace-common/src/components/import-resource/index.tsx +++ b/packages/ai-workspace-common/src/components/import-resource/index.tsx @@ -8,6 +8,7 @@ import { import { ImportFromWeblink } from './intergrations/import-from-weblink'; import { ImportFromText } from './intergrations/import-from-text'; +import { ImportFromExtension } from './intergrations/import-from-extension'; import { useTranslation } from 'react-i18next'; import './index.scss'; @@ -15,7 +16,7 @@ import { useEffect } from 'react'; import { getPopupContainer } from '@refly-packages/ai-workspace-common/utils/ui'; import { getRuntime } from '@refly/utils/env'; import MultilingualSearch from '@refly-packages/ai-workspace-common/modules/multilingual-search'; -import { TbClipboard, TbWorldSearch } from 'react-icons/tb'; +import { TbClipboard, TbWorldSearch, TbBrowserPlus } from 'react-icons/tb'; import { IconImportResource } from '@refly-packages/ai-workspace-common/components/common/icon'; const MenuItem = Menu.Item; @@ -100,6 +101,12 @@ export const ImportResourceModal = () => { {t('resource.import.fromText')} + + + + + {t('resource.import.fromExtension')} + @@ -110,6 +117,7 @@ export const ImportResourceModal = () => { {selectedMenuItem === 'import-from-weblink' ? : null} {selectedMenuItem === 'import-from-paste-text' ? : null} {selectedMenuItem === 'import-from-web-search' ? : null} + {selectedMenuItem === 'import-from-extension' ? : null} diff --git a/packages/ai-workspace-common/src/components/import-resource/intergrations/import-from-extension.tsx b/packages/ai-workspace-common/src/components/import-resource/intergrations/import-from-extension.tsx new file mode 100644 index 000000000..e99d006fb --- /dev/null +++ b/packages/ai-workspace-common/src/components/import-resource/intergrations/import-from-extension.tsx @@ -0,0 +1,338 @@ +import { Button } from 'antd'; +import { useTranslation } from 'react-i18next'; +import { TbBrowserPlus } from 'react-icons/tb'; +import { IconResourceFilled } from '@refly-packages/ai-workspace-common/components/common/icon'; +import { EXTENSION_DOWNLOAD_LINK } from '@refly/utils/url'; +import { NODE_COLORS } from '@refly-packages/ai-workspace-common/components/canvas/nodes/shared/colors'; +import { useState } from 'react'; + +const SOCIAL_PLATFORMS = [ + { + name: 'Wikipedia', + icon: 'https://www.wikipedia.org/static/favicon/wikipedia.ico', + url: 'https://www.wikipedia.org', + }, + // Global Social Media + { + name: 'X/Twitter', + icon: 'https://abs.twimg.com/responsive-web/client-web/icon-ios.b1fc727a.png', + url: 'https://twitter.com', + }, + { + name: 'Reddit', + icon: 'https://www.redditstatic.com/desktop2x/img/favicon/favicon-32x32.png', + url: 'https://reddit.com', + }, + // { + // name: 'YouTube', + // icon: 'https://www.youtube.com/s/desktop/e4d15d2c/img/favicon_144x144.png', + // url: 'https://youtube.com', + // }, + { + name: 'Medium', + icon: 'https://miro.medium.com/v2/1*m-R_BkNf1Qjr1YbyOIJY2w.png', + url: 'https://medium.com', + }, + + // Chinese Social Media & Communities + { + name: 'Red Book', + icon: 'https://ci.xiaohongshu.com/fe-platform/f148480444782b0e55bac65a9c9360f7e5ed6e7d.png', + url: 'https://xiaohongshu.com/explore', + }, + { + name: 'Zhihu', + icon: 'https://static.zhihu.com/heifetz/favicon.ico', + url: 'https://www.zhihu.com', + }, + { + name: 'Douban', + icon: 'https://www.douban.com/favicon.ico', + url: 'https://www.douban.com', + }, + { + name: 'Juejin', + icon: 'https://lf3-cdn-tos.bytescm.com/obj/static/xitu_juejin_web/static/favicons/favicon-32x32.png', + url: 'https://juejin.cn', + }, + + // Product & Design + { + name: 'Product Hunt', + icon: 'https://ph-static.imgix.net/ph-favicon-32x32.png', + url: 'https://www.producthunt.com', + }, + // { + // name: 'Dribbble', + // icon: 'https://cdn.dribbble.com/assets/favicon-b38525134603b9513174ec887944bde1a869eb6cd414f4d640ee48ab2a15a26b.ico', + // url: 'https://dribbble.com', + // }, + // { + // name: 'Behance', + // icon: 'https://a5.behance.net/2acd763b44e2249b6cc54674210d66e5fd08e523/img/site/favicon.ico', + // url: 'https://www.behance.net', + // }, + // { + // name: 'UI Garage', + // icon: 'https://uigarage.net/wp-content/uploads/2019/05/favicon-150x150.png', + // url: 'https://uigarage.net', + // }, + + // Academic & Research + { + name: 'arXiv', + icon: 'https://static.arxiv.org/static/browse/0.3.4/images/icons/favicon-32x32.png', + url: 'https://arxiv.org', + }, + { + name: 'Google Scholar', + icon: 'https://scholar.google.com/favicon.ico', + url: 'https://scholar.google.com', + }, + { + name: 'ResearchGate', + icon: 'https://c5.rgstatic.net/m/4177159727632/images/favicon/favicon-32x32.png', + url: 'https://www.researchgate.net', + }, + { + name: 'SSRN', + icon: 'https://www.ssrn.com/favicon.ico', + url: 'https://www.ssrn.com', + }, + + // Tech & Development + { + name: 'GitHub', + icon: 'https://github.githubassets.com/favicons/favicon.png', + url: 'https://github.com', + }, + { + name: 'Stack Overflow', + icon: 'https://cdn.sstatic.net/Sites/stackoverflow/Img/apple-touch-icon.png', + url: 'https://stackoverflow.com', + }, + { + name: 'Dev.to', + icon: 'https://dev-to-uploads.s3.amazonaws.com/uploads/logos/resized_logo_UQww2soKuUsjaOGNB38o.png', + url: 'https://dev.to', + }, + { + name: 'HackerNews', + icon: 'https://news.ycombinator.com/favicon.ico', + url: 'https://news.ycombinator.com', + }, + + // Legal & Professional + { + name: 'LexisNexis', + icon: 'https://www.lexisnexis.com/favicon.ico', + url: 'https://www.lexisnexis.com', + }, + { + name: 'Westlaw', + icon: 'https://www.westlaw.com/favicon.ico', + url: 'https://www.westlaw.com', + }, + { + name: 'LinkedIn', + icon: 'https://static.licdn.com/sc/h/al2o9zrvru7aqj8e1x2rzsrca', + url: 'https://www.linkedin.com', + }, + { + name: 'Harvard Business Review', + icon: 'https://hbr.org/resources/images/favicon.ico', + url: 'https://hbr.org', + }, + + // Books & Reviews + { + name: 'Goodreads', + icon: 'https://www.goodreads.com/favicon.ico', + url: 'https://www.goodreads.com', + }, + { + name: 'Book Review (NYT)', + icon: 'https://www.nytimes.com/vi-assets/static-assets/favicon-4bf96cb6a1093748bf5b3c429accb9b4.ico', + url: 'https://www.nytimes.com/section/books/review', + }, + { + name: 'Library Genesis', + icon: 'https://libgen.rs/favicon.ico', + url: 'https://libgen.rs', + }, + { + name: 'Z-Library', + icon: 'https://z-lib.org/favicon.ico', + url: 'https://z-lib.org', + }, + + // Media & Content Creation + { + name: 'Substack', + icon: 'https://substack.com/favicon.ico', + url: 'https://substack.com', + }, + { + name: 'Ghost', + icon: 'https://ghost.org/favicon.ico', + url: 'https://ghost.org', + }, + { + name: 'Buffer', + icon: 'https://buffer.com/favicon.ico', + url: 'https://buffer.com', + }, + // { + // name: 'Canva', + // icon: 'https://static.canva.com/static/images/favicon-1.ico', + // url: 'https://www.canva.com', + // }, + + // Productivity & Notes + { + name: 'Notion', + icon: 'https://www.notion.so/images/favicon.ico', + url: 'https://notion.so', + }, + { + name: 'Flomo', + icon: 'https://flomoapp.com/images/favicon.ico', + url: 'https://flomoapp.com', + }, + { + name: 'Obsidian', + icon: 'https://obsidian.md/favicon.ico', + url: 'https://obsidian.md', + }, + { + name: 'Logseq', + icon: 'https://logseq.com/icons/icon-144x144.png', + url: 'https://logseq.com', + }, + { + name: 'Readwise', + icon: 'https://readwise.io/favicon.ico', + url: 'https://readwise.io', + }, + + // Discussion Forums + { + name: 'Quora', + icon: 'https://qsf.fs.quoracdn.net/-4-images.favicon.ico-26-ae77b637b1e7ed2c.ico', + url: 'https://www.quora.com', + }, + { + name: 'V2EX', + icon: 'https://www.v2ex.com/static/icon-192.png', + url: 'https://www.v2ex.com', + }, + { + name: 'Hupu', + icon: 'https://w1.hoopchina.com.cn/images/pc/old/favicon.ico', + url: 'https://bbs.hupu.com', + }, + { + name: 'Discord', + icon: 'https://discord.com/assets/847541504914fd33810e70a0ea73177e.ico', + url: 'https://discord.com', + }, +]; + +const PlatformButton = ({ platform }: { platform: (typeof SOCIAL_PLATFORMS)[0] }) => { + const [showFallbackIcon, setShowFallbackIcon] = useState(false); + + return ( + + ); +}; + +export const ImportFromExtension = () => { + const { t, i18n } = useTranslation(); + const locale = i18n?.languages?.[0]; + + return ( +
+ {/* header - fixed height */} +
+ + + +
{t('resource.import.fromExtension')}
+
+ + {/* scrollable content area */} +
+
+ {/* Video Demo */} +
+ +
+ + {/* Action Buttons */} +
+ + +
+ + {/* Platform List */} +
+

+ {t('resource.import.recommendedPlatforms')} +

+
+ {SOCIAL_PLATFORMS.map((platform) => ( + + ))} +
+
+
+
+
+ ); +}; diff --git a/packages/ai-workspace-common/src/components/sider-menu-setting-list/index.tsx b/packages/ai-workspace-common/src/components/sider-menu-setting-list/index.tsx index 5188030e7..3db13c897 100644 --- a/packages/ai-workspace-common/src/components/sider-menu-setting-list/index.tsx +++ b/packages/ai-workspace-common/src/components/sider-menu-setting-list/index.tsx @@ -5,7 +5,9 @@ import { useUserStore } from '@refly-packages/ai-workspace-common/stores/user'; import { useSiderStoreShallow } from '@refly-packages/ai-workspace-common/stores/sider'; import { useLogout } from '@refly-packages/ai-workspace-common/hooks/use-logout'; import { GrGroup } from 'react-icons/gr'; - +import { MemoizedIcon } from '@refly-packages/ai-workspace-common/components/common/icon'; +import { IconChrome } from '@refly-packages/ai-workspace-common/components/common/icon'; +import { EXTENSION_DOWNLOAD_LINK } from '@refly/utils/url'; export const SiderMenuSettingList = (props: { children: React.ReactNode }) => { const { t } = useTranslation(); const userStore = useUserStore(); @@ -40,6 +42,11 @@ export const SiderMenuSettingList = (props: { children: React.ReactNode }) => { icon: , label: t('loggedHomePage.siderMenu.contactUs'), }, + { + key: 'addToChrome', + icon: , + label: t('loggedHomePage.siderMenu.addToChrome'), + }, { type: 'divider', }, @@ -57,6 +64,8 @@ export const SiderMenuSettingList = (props: { children: React.ReactNode }) => { setShowSettingModal(true); } else if (key === 'logout') { handleLogout(); + } else if (key === 'addToChrome') { + window.open(EXTENSION_DOWNLOAD_LINK, '_blank'); } }; diff --git a/packages/ai-workspace-common/src/stores/import-resource.ts b/packages/ai-workspace-common/src/stores/import-resource.ts index f11b4c501..6ac5a5f82 100644 --- a/packages/ai-workspace-common/src/stores/import-resource.ts +++ b/packages/ai-workspace-common/src/stores/import-resource.ts @@ -17,7 +17,8 @@ export interface LinkMeta { export type ImportResourceMenuItem = | 'import-from-weblink' | 'import-from-paste-text' - | 'import-from-web-search'; + | 'import-from-web-search' + | 'import-from-extension'; interface ImportResourceState { importResourceModalVisible: boolean; diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index 81c49c2b7..30bda3be3 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -8,6 +8,22 @@ export class UnknownError extends BaseError { }; } +export class ContentTooLargeError extends BaseError { + code = 'E2004'; + messageDict = { + en: 'Content is too large. Maximum length is 100k characters.', + 'zh-CN': '内容过长。最大长度为 10 万字符。', + }; +} + +export class PayloadTooLargeError extends BaseError { + code = 'E2005'; + messageDict = { + en: 'Request payload is too large. Maximum size is 100KB.', + 'zh-CN': '请求数据过大。最大大小为 100KB。', + }; +} + export class ConnectionError extends BaseError { code = 'E0001'; messageDict = { @@ -218,6 +234,8 @@ const errorMap = { E2001: StorageQuotaExceeded, E2002: ModelUsageQuotaExceeded, E2003: ModelNotSupportedError, + E2004: ContentTooLargeError, + E2005: PayloadTooLargeError, }; export function getErrorMessage(code: string, locale: string): string { diff --git a/packages/i18n/src/en-US/ui.ts b/packages/i18n/src/en-US/ui.ts index 74e0dd9d2..cc4baab04 100644 --- a/packages/i18n/src/en-US/ui.ts +++ b/packages/i18n/src/en-US/ui.ts @@ -112,8 +112,9 @@ const translations = { 'Powered by multi-threaded dialogue, knowledge integration, context memory, and intelligent search, ', second: 'Refly is the best way to transform ideas into quality content.', }, - messageText: 'New DeepSeek R1 inference model released! ⚡️ ', + messageText: 'v0.3.0 released! Supports Chrome Extension web clipper! ', tryForFree: 'Get Started', + addToChrome: 'Add to Chrome', contactUs: 'Contact Us', watchVideo: 'Watch Video', joinBtn: 'Join for free', @@ -162,6 +163,16 @@ const translations = { 'AI editing assistant for quality professional output', ], }, + featureFive: { + tag: 'Clip & Save', + title: 'One-click content saving from any webpage to build your second brain', + bulletPoints: [ + 'One-click save content from any webpage (Twitter, Notion, etc.)', + 'Support private webpage content saving and organization', + 'Continuously build your personal knowledge base', + 'Seamlessly integrate with your second brain', + ], + }, }, pricing: { title: 'Plans and Pricing', @@ -207,6 +218,11 @@ const translations = { community: 'Community', mail: 'Email', }, + platforms: { + title: 'Platforms', + chrome: 'Chrome Extension', + web: 'Web App', + }, }, loginFailed: { title: 'Login Failed', @@ -252,6 +268,7 @@ const translations = { price: 'Pricing', docs: 'Docs', discord: 'Discord', + priceTag: '-50%', }, faq: { title: 'Frequently Asked Questions', @@ -880,6 +897,13 @@ const translations = { storageLimited: 'Storage quota exceeded', storagePartialLimited: 'Insufficient storage: only {{count}} files can be saved', upgrade: 'Upgrade Now', + fromExtension: 'Web Clipper', + extensionTitle: 'Save Web Content with One Click', + extensionDescription: + 'Install our Chrome extension to easily save web content to your canvas.', + downloadExtension: 'Download Extension', + viewDocs: 'View Documentation', + recommendedPlatforms: 'Recommended Platforms', }, wait_parse: 'Parsing', parse_failed: 'Parse Failed, click to retry', @@ -960,6 +984,7 @@ const translations = { }, siderMenu: { contactUs: 'Contact Us', + addToChrome: 'Add to Chrome', canvasTitle: 'Canvas', canvasDescription: 'Create, edit, and view canvas, unlike traditional Chatbot applications, Refly completes the entire process of dialogue thinking, content consumption, and creation through an infinite two-dimensional canvas.', diff --git a/packages/i18n/src/zh-Hans/ui.ts b/packages/i18n/src/zh-Hans/ui.ts index 379853266..c286fee6e 100644 --- a/packages/i18n/src/zh-Hans/ui.ts +++ b/packages/i18n/src/zh-Hans/ui.ts @@ -123,8 +123,9 @@ const translations = { first: '由多线程对话、知识库集成、上下文记忆和智能搜索驱动,', second: 'Refly 是将创意转化为优质内容的最佳方式。', }, - messageText: '全新推理模型 DeepSeek R1 上线!⚡️ !', + messageText: 'v0.3.0 发布!支持 Chrome 插件剪存和保存网页内容⚡️ !', tryForFree: '开始使用', + addToChrome: '添加到 Chrome', contactUs: '联系我们', watchVideo: '观看视频', joinBtn: '免费使用', @@ -170,6 +171,16 @@ const translations = { 'AI 编辑助手,确保专业输出质量', ], }, + featureFive: { + tag: '剪存', + title: '一键剪存任意网页内容,持续打造第二大脑', + bulletPoints: [ + '一键保存任意网页内容(Twitter、小红书、Notion 等)', + '支持私有网页内容剪存与整理', + '持续积累个人知识库', + '无缝集成到第二大脑', + ], + }, }, pricing: { title: '计划与定价', @@ -207,6 +218,11 @@ const translations = { community: '社区', mail: '邮箱', }, + platforms: { + title: '平台', + chrome: 'Chrome 插件', + web: 'Web 应用', + }, }, loginFailed: { title: '登录失败', @@ -252,6 +268,7 @@ const translations = { price: '价格', docs: '文档', discord: 'Discord', + priceTag: '五折', }, faq: { title: '常见问题', @@ -865,6 +882,12 @@ const translations = { storageLimited: '存储空间已满', storagePartialLimited: '存储空间不足,仅能保存 {{count}} 个资源', upgrade: '升级订阅', + fromExtension: '插件剪存', + extensionTitle: '一键保存网页内容', + extensionDescription: '安装我们的 Chrome 插件,轻松保存网页内容到画布中。', + downloadExtension: '下载插件', + viewDocs: '查看文档', + recommendedPlatforms: '推荐平台', }, wait_parse: '内容解析中', parse_failed: '解析失败,点击重试', @@ -950,6 +973,7 @@ const translations = { }, siderMenu: { contactUs: '联系我们', + addToChrome: '添加到 Chrome', canvasTitle: '画布', canvasDescription: '创建、编辑和查看画布, 不同于传统的 Chatbot 应用,Refly 通过无限延伸的二维画布来完成对话思考、内容消费和创作的全流程。', diff --git a/packages/utils/src/url.ts b/packages/utils/src/url.ts index 4987bd05f..2d3c337f8 100644 --- a/packages/utils/src/url.ts +++ b/packages/utils/src/url.ts @@ -6,6 +6,9 @@ const overrideLocalDev = false; export const SENTRY_DSN = 'https://3a105c6104e4c4de3ead00dc11f16623@o4507205453414400.ingest.us.sentry.io/4507209639133184'; +export const EXTENSION_DOWNLOAD_LINK = + 'https://chromewebstore.google.com/detail/lecbjbapfkinmikhadakbclblnemmjpd'; + export const SERVER_PROD_DOMAIN = 'https://api.refly.ai'; export const SERVER_STAGING_DOMAIN = 'https://staging-api.refly.ai'; export const SERVER_DEV_DOMAIN = 'http://localhost:5800';