mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-27 07:44:48 +08:00
feat: add i18n support and add Chinese (#372)
* feat: add i18n support and add Chinese * fix: resolve conflicts * Update en.json with cancle settings * Update zh.json with settngs cancle --------- Co-authored-by: johnny0120 <15564476+johnny0120@users.noreply.github.com> Co-authored-by: Willem Jiang <willem.jiang@gmail.com> Co-authored-by: Willem Jiang <143703838+willem-bd@users.noreply.github.com>
This commit is contained in:
215
web/messages/en.json
Normal file
215
web/messages/en.json
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"save": "Save",
|
||||||
|
"settings": "Settings",
|
||||||
|
"getStarted": "Get Started",
|
||||||
|
"learnMore": "Learn More",
|
||||||
|
"starOnGitHub": "Star on GitHub",
|
||||||
|
"send": "Send",
|
||||||
|
"stop": "Stop",
|
||||||
|
"linkNotReliable": "This link might be a hallucination from AI model and may not be reliable.",
|
||||||
|
"noResult": "No result"
|
||||||
|
},
|
||||||
|
"messageInput": {
|
||||||
|
"placeholder": "What can I do for you?",
|
||||||
|
"placeholderWithRag": "What can I do for you? \nYou may refer to RAG resources by using @."
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"title": "DeerFlow"
|
||||||
|
},
|
||||||
|
"hero": {
|
||||||
|
"title": "Deep Research",
|
||||||
|
"subtitle": "at Your Fingertips",
|
||||||
|
"description": "Meet DeerFlow, your personal Deep Research assistant. With powerful tools like search engines, web crawlers, Python and MCP services, it delivers instant insights, comprehensive reports, and even captivating podcasts.",
|
||||||
|
"footnote": "* DEER stands for Deep Exploration and Efficient Research."
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"title": "DeerFlow Settings",
|
||||||
|
"description": "Manage your DeerFlow settings here.",
|
||||||
|
"addServers": "Add Servers",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"addNewMCPServers": "Add New MCP Servers",
|
||||||
|
"mcpConfigDescription": "DeerFlow uses the standard JSON MCP config to create a new server.",
|
||||||
|
"pasteConfigBelow": "Paste your config below and click \"Add\" to add new servers.",
|
||||||
|
"add": "Add",
|
||||||
|
"general": {
|
||||||
|
"title": "General",
|
||||||
|
"autoAcceptPlan": "Allow automatic acceptance of plans",
|
||||||
|
"maxPlanIterations": "Max plan iterations",
|
||||||
|
"maxPlanIterationsDescription": "Set to 1 for single-step planning. Set to 2 or more to enable re-planning.",
|
||||||
|
"maxStepsOfPlan": "Max steps of a research plan",
|
||||||
|
"maxStepsDescription": "By default, each research plan has 3 steps.",
|
||||||
|
"maxSearchResults": "Max search results",
|
||||||
|
"maxSearchResultsDescription": "By default, each search step has 3 results."
|
||||||
|
},
|
||||||
|
"about": {
|
||||||
|
"title": "About"
|
||||||
|
},
|
||||||
|
"reportStyle": {
|
||||||
|
"writingStyle": "Writing Style",
|
||||||
|
"chooseTitle": "Choose Writing Style",
|
||||||
|
"chooseDesc": "Select the writing style for your research reports. Different styles are optimized for different audiences and purposes.",
|
||||||
|
"academic": "Academic",
|
||||||
|
"academicDesc": "Formal, objective, and analytical with precise terminology",
|
||||||
|
"popularScience": "Popular Science",
|
||||||
|
"popularScienceDesc": "Engaging and accessible for general audience",
|
||||||
|
"news": "News",
|
||||||
|
"newsDesc": "Factual, concise, and impartial journalistic style",
|
||||||
|
"socialMedia": "Social Media",
|
||||||
|
"socialMediaDesc": "Concise, attention-grabbing, and shareable"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"quote": "Originated from Open Source, give back to Open Source.",
|
||||||
|
"license": "Licensed under MIT License",
|
||||||
|
"copyright": "DeerFlow"
|
||||||
|
},
|
||||||
|
"chat": {
|
||||||
|
"welcome": {
|
||||||
|
"greeting": "👋 Hello, there!",
|
||||||
|
"description": "Welcome to 🦌 DeerFlow, a deep research assistant built on cutting-edge language models, helps you search on web, browse information, and handle complex tasks."
|
||||||
|
},
|
||||||
|
"conversationStarters": [
|
||||||
|
"How many times taller is the Eiffel Tower than the tallest building in the world?",
|
||||||
|
"How many years does an average Tesla battery last compared to a gasoline engine?",
|
||||||
|
"How many liters of water are required to produce 1 kg of beef?",
|
||||||
|
"How many times faster is the speed of light compared to the speed of sound?"
|
||||||
|
],
|
||||||
|
"inputBox": {
|
||||||
|
"deepThinking": "Deep Thinking",
|
||||||
|
"deepThinkingTooltip": {
|
||||||
|
"title": "Deep Thinking Mode: {status}",
|
||||||
|
"description": "When enabled, DeerFlow will use reasoning model ({model}) to generate more thoughtful plans."
|
||||||
|
},
|
||||||
|
"investigation": "Investigation",
|
||||||
|
"investigationTooltip": {
|
||||||
|
"title": "Investigation Mode: {status}",
|
||||||
|
"description": "When enabled, DeerFlow will perform a quick search before planning. This is useful for researches related to ongoing events and news."
|
||||||
|
},
|
||||||
|
"enhancePrompt": "Enhance prompt with AI",
|
||||||
|
"on": "On",
|
||||||
|
"off": "Off"
|
||||||
|
},
|
||||||
|
"research": {
|
||||||
|
"deepResearch": "Deep Research",
|
||||||
|
"researching": "Researching...",
|
||||||
|
"generatingReport": "Generating report...",
|
||||||
|
"reportGenerated": "Report generated",
|
||||||
|
"open": "Open",
|
||||||
|
"close": "Close",
|
||||||
|
"deepThinking": "Deep Thinking",
|
||||||
|
"report": "Report",
|
||||||
|
"activities": "Activities",
|
||||||
|
"generatePodcast": "Generate podcast",
|
||||||
|
"edit": "Edit",
|
||||||
|
"copy": "Copy",
|
||||||
|
"downloadReport": "Download report as markdown",
|
||||||
|
"searchingFor": "Searching for",
|
||||||
|
"reading": "Reading",
|
||||||
|
"runningPythonCode": "Running Python code",
|
||||||
|
"errorExecutingCode": "Error when executing the above code",
|
||||||
|
"executionOutput": "Execution output",
|
||||||
|
"retrievingDocuments": "Retrieving documents from RAG",
|
||||||
|
"running": "Running",
|
||||||
|
"generatingPodcast": "Generating podcast...",
|
||||||
|
"nowPlayingPodcast": "Now playing podcast...",
|
||||||
|
"podcast": "Podcast",
|
||||||
|
"errorGeneratingPodcast": "Error when generating podcast. Please try again.",
|
||||||
|
"downloadPodcast": "Download podcast"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"replaying": "Replaying",
|
||||||
|
"replayDescription": "DeerFlow is now replaying the conversation...",
|
||||||
|
"replayHasStopped": "The replay has been stopped.",
|
||||||
|
"replayModeDescription": "You're now in DeerFlow's replay mode. Click the \"Play\" button on the right to start.",
|
||||||
|
"play": "Play",
|
||||||
|
"fastForward": "Fast Forward",
|
||||||
|
"demoNotice": "* This site is for demo purposes only. If you want to try your own question, please",
|
||||||
|
"clickHere": "click here",
|
||||||
|
"cloneLocally": "to clone it locally and run it."
|
||||||
|
},
|
||||||
|
"multiAgent": {
|
||||||
|
"moveToPrevious": "Move to the previous step",
|
||||||
|
"playPause": "Play / Pause",
|
||||||
|
"moveToNext": "Move to the next step",
|
||||||
|
"toggleFullscreen": "Toggle fullscreen"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"landing": {
|
||||||
|
"caseStudies": {
|
||||||
|
"title": "Case Studies",
|
||||||
|
"description": "See DeerFlow in action through replays.",
|
||||||
|
"clickToWatch": "Click to watch replay",
|
||||||
|
"cases": [
|
||||||
|
{
|
||||||
|
"title": "How tall is Eiffel Tower compared to tallest building?",
|
||||||
|
"description": "The research compares the heights and global significance of the Eiffel Tower and Burj Khalifa, and uses Python code to calculate the multiples."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "What are the top trending repositories on GitHub?",
|
||||||
|
"description": "The research utilized MCP services to identify the most popular GitHub repositories and documented them in detail using search engines."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Write an article about Nanjing's traditional dishes",
|
||||||
|
"description": "The study vividly showcases Nanjing's famous dishes through rich content and imagery, uncovering their hidden histories and cultural significance."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "How to decorate a small rental apartment?",
|
||||||
|
"description": "The study provides readers with practical and straightforward methods for decorating apartments, accompanied by inspiring images."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Introduce the movie 'Léon: The Professional'",
|
||||||
|
"description": "The research provides a comprehensive introduction to the movie 'Léon: The Professional', including its plot, characters, and themes."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "How do you view the takeaway war in China? (in Chinese)",
|
||||||
|
"description": "The research analyzes the intensifying competition between JD and Meituan, highlighting their strategies, technological innovations, and challenges."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Are ultra-processed foods linked to health?",
|
||||||
|
"description": "The research examines the health risks of rising ultra-processed food consumption, urging more research on long-term effects and individual differences."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Write an article on \"Would you insure your AI twin?\"",
|
||||||
|
"description": "The research explores the concept of insuring AI twins, highlighting their benefits, risks, ethical considerations, and the evolving regulatory."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"coreFeatures": {
|
||||||
|
"title": "Core Features",
|
||||||
|
"description": "Find out what makes DeerFlow effective.",
|
||||||
|
"features": [
|
||||||
|
{
|
||||||
|
"name": "Dive Deeper and Reach Wider",
|
||||||
|
"description": "Unlock deeper insights with advanced tools. Our powerful search + crawling and Python tools gathers comprehensive data, delivering in-depth reports to enhance your study."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Human-in-the-loop",
|
||||||
|
"description": "Refine your research plan, or adjust focus areas all through simple natural language."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Lang Stack",
|
||||||
|
"description": "Build with confidence using the LangChain and LangGraph frameworks."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "MCP Integrations",
|
||||||
|
"description": "Supercharge your research workflow and expand your toolkit with seamless MCP integrations."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Podcast Generation",
|
||||||
|
"description": "Instantly generate podcasts from reports. Perfect for on-the-go learning or sharing findings effortlessly."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"multiAgent": {
|
||||||
|
"title": "Multi-Agent Architecture",
|
||||||
|
"description": "Experience the agent teamwork with our Supervisor + Handoffs design pattern."
|
||||||
|
},
|
||||||
|
"joinCommunity": {
|
||||||
|
"title": "Join the DeerFlow Community",
|
||||||
|
"description": "Contribute brilliant ideas to shape the future of DeerFlow. Collaborate, innovate, and make impacts.",
|
||||||
|
"contributeNow": "Contribute Now"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
220
web/messages/zh.json
Normal file
220
web/messages/zh.json
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"cancel": "取消",
|
||||||
|
"save": "保存",
|
||||||
|
"settings": "设置",
|
||||||
|
"getStarted": "开始使用",
|
||||||
|
"learnMore": "了解更多",
|
||||||
|
"starOnGitHub": "在 GitHub 上点赞",
|
||||||
|
"send": "发送",
|
||||||
|
"stop": "停止",
|
||||||
|
"linkNotReliable": "此链接可能是 AI 生成的幻觉,可能并不可靠。",
|
||||||
|
"noResult": "无结果"
|
||||||
|
},
|
||||||
|
"messageInput": {
|
||||||
|
"placeholder": "我能帮你做什么?",
|
||||||
|
"placeholderWithRag": "我能帮你做什么?\n你可以通过 @ 引用 RAG 资源。"
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"title": "DeerFlow"
|
||||||
|
},
|
||||||
|
"hero": {
|
||||||
|
"title": "深度研究",
|
||||||
|
"subtitle": "触手可及",
|
||||||
|
"description": "认识 DeerFlow,您的个人深度研究助手。凭借搜索引擎、网络爬虫、Python 和 MCP 服务等强大工具,它能提供即时洞察、全面报告,甚至制作引人入胜的播客。",
|
||||||
|
"footnote": "* DEER 代表深度探索和高效研究。"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"title": "DeerFlow 设置",
|
||||||
|
"description": "在这里管理您的 DeerFlow 设置。",
|
||||||
|
"cancel": "取消",
|
||||||
|
"addServers": "添加服务器",
|
||||||
|
"addNewMCPServers": "添加新的 MCP 服务器",
|
||||||
|
"mcpConfigDescription": "DeerFlow 使用标准 JSON MCP 配置来创建新服务器。",
|
||||||
|
"pasteConfigBelow": "将您的配置粘贴到下面,然后点击\"添加\"来添加新服务器。",
|
||||||
|
"add": "添加",
|
||||||
|
"general": {
|
||||||
|
"title": "通用",
|
||||||
|
"autoAcceptPlan": "允许自动接受计划",
|
||||||
|
"maxPlanIterations": "最大计划迭代次数",
|
||||||
|
"maxPlanIterationsDescription": "设置为 1 进行单步规划。设置为 2 或更多以启用重新规划。",
|
||||||
|
"maxStepsOfPlan": "研究计划的最大步骤数",
|
||||||
|
"maxStepsDescription": "默认情况下,每个研究计划有 3 个步骤。",
|
||||||
|
"maxSearchResults": "最大搜索结果数",
|
||||||
|
"maxSearchResultsDescription": "默认情况下,每个搜索步骤有 3 个结果。"
|
||||||
|
},
|
||||||
|
"about": {
|
||||||
|
"title": "关于"
|
||||||
|
},
|
||||||
|
"reportStyle": {
|
||||||
|
"writingStyle": "写作风格",
|
||||||
|
"chooseTitle": "选择写作风格",
|
||||||
|
"chooseDesc": "请选择您的研究报告的写作风格。不同风格适用于不同受众和用途。",
|
||||||
|
"academic": "学术",
|
||||||
|
"academicDesc": "正式、客观、分析性强,术语精确",
|
||||||
|
"popularScience": "科普",
|
||||||
|
"popularScienceDesc": "生动有趣,适合大众阅读",
|
||||||
|
"news": "新闻",
|
||||||
|
"newsDesc": "事实、简明、公正的新闻风格",
|
||||||
|
"socialMedia": "社交媒体",
|
||||||
|
"socialMediaDesc": "简洁有趣,易于传播"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"quote": "源于开源,回馈开源。",
|
||||||
|
"license": "基于 MIT 许可证授权",
|
||||||
|
"copyright": "DeerFlow"
|
||||||
|
},
|
||||||
|
"chat": {
|
||||||
|
"page": {
|
||||||
|
"loading": "正在加载 DeerFlow...",
|
||||||
|
"welcomeUser": "欢迎,{username}",
|
||||||
|
"starOnGitHub": "在 GitHub 上点赞"
|
||||||
|
},
|
||||||
|
"welcome": {
|
||||||
|
"greeting": "👋 你好!",
|
||||||
|
"description": "欢迎来到 🦌 DeerFlow,一个基于前沿语言模型构建的深度研究助手,帮助您搜索网络、浏览信息并处理复杂任务。"
|
||||||
|
},
|
||||||
|
"conversationStarters": [
|
||||||
|
"埃菲尔铁塔比世界最高建筑高多少倍?",
|
||||||
|
"特斯拉电池的平均寿命比汽油发动机长多少年?",
|
||||||
|
"生产1公斤牛肉需要多少升水?",
|
||||||
|
"光速比声速快多少倍?"
|
||||||
|
],
|
||||||
|
"inputBox": {
|
||||||
|
"deepThinking": "深度思考",
|
||||||
|
"deepThinkingTooltip": {
|
||||||
|
"title": "深度思考模式:{status}",
|
||||||
|
"description": "启用后,DeerFlow 将使用推理模型({model})生成更深思熟虑的计划。"
|
||||||
|
},
|
||||||
|
"investigation": "调研",
|
||||||
|
"investigationTooltip": {
|
||||||
|
"title": "调研模式:{status}",
|
||||||
|
"description": "启用后,DeerFlow 将在规划前进行快速搜索。这对于与时事和新闻相关的研究很有用。"
|
||||||
|
},
|
||||||
|
"enhancePrompt": "用 AI 增强提示",
|
||||||
|
"on": "开启",
|
||||||
|
"off": "关闭"
|
||||||
|
},
|
||||||
|
"research": {
|
||||||
|
"deepResearch": "深度研究",
|
||||||
|
"researching": "研究中...",
|
||||||
|
"generatingReport": "生成报告中...",
|
||||||
|
"reportGenerated": "报告已生成",
|
||||||
|
"open": "打开",
|
||||||
|
"close": "关闭",
|
||||||
|
"deepThinking": "深度思考",
|
||||||
|
"report": "报告",
|
||||||
|
"activities": "活动",
|
||||||
|
"generatePodcast": "生成播客",
|
||||||
|
"edit": "编辑",
|
||||||
|
"copy": "复制",
|
||||||
|
"downloadReport": "下载报告为 Markdown",
|
||||||
|
"searchingFor": "搜索",
|
||||||
|
"reading": "阅读中",
|
||||||
|
"runningPythonCode": "运行 Python 代码",
|
||||||
|
"errorExecutingCode": "执行上述代码时出错",
|
||||||
|
"executionOutput": "执行输出",
|
||||||
|
"retrievingDocuments": "从 RAG 检索文档",
|
||||||
|
"running": "运行",
|
||||||
|
"generatingPodcast": "生成播客中...",
|
||||||
|
"nowPlayingPodcast": "正在播放播客...",
|
||||||
|
"podcast": "播客",
|
||||||
|
"errorGeneratingPodcast": "生成播客时出错。请重试。",
|
||||||
|
"downloadPodcast": "下载播客"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"replaying": "回放中",
|
||||||
|
"replayDescription": "DeerFlow 正在回放对话...",
|
||||||
|
"replayHasStopped": "回放已停止。",
|
||||||
|
"replayModeDescription": "您现在处于 DeerFlow 的回放模式。点击右侧的\"播放\"按钮开始。",
|
||||||
|
"play": "播放",
|
||||||
|
"fastForward": "快进",
|
||||||
|
"demoNotice": "* 此网站仅用于演示目的。如果您想尝试自己的问题,请",
|
||||||
|
"clickHere": "点击这里",
|
||||||
|
"cloneLocally": "在本地克隆并运行它。"
|
||||||
|
},
|
||||||
|
"multiAgent": {
|
||||||
|
"moveToPrevious": "移动到上一步",
|
||||||
|
"playPause": "播放 / 暂停",
|
||||||
|
"moveToNext": "移动到下一步",
|
||||||
|
"toggleFullscreen": "切换全屏"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"landing": {
|
||||||
|
"caseStudies": {
|
||||||
|
"title": "案例研究",
|
||||||
|
"description": "通过回放查看 DeerFlow 的实际应用。",
|
||||||
|
"clickToWatch": "点击观看回放",
|
||||||
|
"cases": [
|
||||||
|
{
|
||||||
|
"title": "埃菲尔铁塔与最高建筑相比有多高?",
|
||||||
|
"description": "该研究比较了埃菲尔铁塔和哈利法塔的高度和全球意义,并使用 Python 代码计算倍数。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "GitHub 上最热门的仓库有哪些?",
|
||||||
|
"description": "该研究利用 MCP 服务识别最受欢迎的 GitHub 仓库,并使用搜索引擎详细记录它们。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "写一篇关于南京传统菜肴的文章",
|
||||||
|
"description": "该研究通过丰富的内容和图像生动地展示了南京的著名菜肴,揭示了它们隐藏的历史和文化意义。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "如何装饰小型出租公寓?",
|
||||||
|
"description": "该研究为读者提供了实用而直接的公寓装饰方法,并配有鼓舞人心的图像。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "介绍电影《这个杀手不太冷》",
|
||||||
|
"description": "该研究全面介绍了电影《这个杀手不太冷》,包括其情节、角色和主题。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "你如何看待中国的外卖大战?(中文)",
|
||||||
|
"description": "该研究分析了京东和美团之间日益激烈的竞争,突出了它们的策略、技术创新和挑战。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "超加工食品与健康有关吗?",
|
||||||
|
"description": "该研究检查了超加工食品消费增加的健康风险,敦促对长期影响和个体差异进行更多研究。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "写一篇关于\"你会为你的 AI 双胞胎投保吗?\"的文章",
|
||||||
|
"description": "该研究探讨了为 AI 双胞胎投保的概念,突出了它们的好处、风险、伦理考虑和不断发展的监管。"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"coreFeatures": {
|
||||||
|
"title": "核心功能",
|
||||||
|
"description": "了解是什么让 DeerFlow 如此有效。",
|
||||||
|
"features": [
|
||||||
|
{
|
||||||
|
"name": "深入挖掘,触及更广",
|
||||||
|
"description": "使用高级工具解锁更深层的洞察。我们强大的搜索+爬取和 Python 工具收集全面的数据,提供深入的报告来增强您的研究。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "人机协作",
|
||||||
|
"description": "通过简单的自然语言完善您的研究计划或调整重点领域。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Lang 技术栈",
|
||||||
|
"description": "使用 LangChain 和 LangGraph 框架自信地构建。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "MCP 集成",
|
||||||
|
"description": "通过无缝的 MCP 集成增强您的研究工作流程并扩展您的工具包。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "播客生成",
|
||||||
|
"description": "从报告中即时生成播客。非常适合移动学习或轻松分享发现。"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"multiAgent": {
|
||||||
|
"title": "多智能体架构",
|
||||||
|
"description": "通过我们的监督者 + 交接设计模式体验智能体团队合作。"
|
||||||
|
},
|
||||||
|
"joinCommunity": {
|
||||||
|
"title": "加入 DeerFlow 社区",
|
||||||
|
"description": "贡献精彩想法,塑造 DeerFlow 的未来。协作、创新并产生影响。",
|
||||||
|
"contributeNow": "立即贡献"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,9 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
import "./src/env.js";
|
import "./src/env.js";
|
||||||
|
import createNextIntlPlugin from 'next-intl/plugin';
|
||||||
|
|
||||||
|
const withNextIntl = createNextIntlPlugin('./src/i18n.ts');
|
||||||
|
|
||||||
/** @type {import("next").NextConfig} */
|
/** @type {import("next").NextConfig} */
|
||||||
|
|
||||||
@@ -39,4 +42,4 @@ const config = {
|
|||||||
output: "standalone",
|
output: "standalone",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default withNextIntl(config);
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
"@tiptap/extension-table-row": "^2.11.7",
|
"@tiptap/extension-table-row": "^2.11.7",
|
||||||
"@tiptap/extension-text": "^2.12.0",
|
"@tiptap/extension-text": "^2.12.0",
|
||||||
"@tiptap/react": "^2.11.7",
|
"@tiptap/react": "^2.11.7",
|
||||||
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@xyflow/react": "^12.6.0",
|
"@xyflow/react": "^12.6.0",
|
||||||
"best-effort-json-parser": "^1.1.3",
|
"best-effort-json-parser": "^1.1.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
@@ -55,6 +56,7 @@
|
|||||||
"hast": "^1.0.0",
|
"hast": "^1.0.0",
|
||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
"immer": "^10.1.1",
|
"immer": "^10.1.1",
|
||||||
|
"js-cookie": "^3.0.5",
|
||||||
"katex": "^0.16.21",
|
"katex": "^0.16.21",
|
||||||
"lowlight": "^3.3.0",
|
"lowlight": "^3.3.0",
|
||||||
"lru-cache": "^11.1.0",
|
"lru-cache": "^11.1.0",
|
||||||
@@ -62,6 +64,7 @@
|
|||||||
"motion": "^12.7.4",
|
"motion": "^12.7.4",
|
||||||
"nanoid": "^5.1.5",
|
"nanoid": "^5.1.5",
|
||||||
"next": "^15.2.3",
|
"next": "^15.2.3",
|
||||||
|
"next-intl": "^4.3.1",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"novel": "^1.0.2",
|
"novel": "^1.0.2",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
|
|||||||
126
web/pnpm-lock.yaml
generated
126
web/pnpm-lock.yaml
generated
@@ -95,6 +95,9 @@ importers:
|
|||||||
'@tiptap/react':
|
'@tiptap/react':
|
||||||
specifier: ^2.11.7
|
specifier: ^2.11.7
|
||||||
version: 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
version: 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@types/js-cookie':
|
||||||
|
specifier: ^3.0.6
|
||||||
|
version: 3.0.6
|
||||||
'@xyflow/react':
|
'@xyflow/react':
|
||||||
specifier: ^12.6.0
|
specifier: ^12.6.0
|
||||||
version: 12.6.0(@types/react@19.1.2)(immer@10.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
version: 12.6.0(@types/react@19.1.2)(immer@10.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
@@ -122,6 +125,9 @@ importers:
|
|||||||
immer:
|
immer:
|
||||||
specifier: ^10.1.1
|
specifier: ^10.1.1
|
||||||
version: 10.1.1
|
version: 10.1.1
|
||||||
|
js-cookie:
|
||||||
|
specifier: ^3.0.5
|
||||||
|
version: 3.0.5
|
||||||
katex:
|
katex:
|
||||||
specifier: ^0.16.21
|
specifier: ^0.16.21
|
||||||
version: 0.16.21
|
version: 0.16.21
|
||||||
@@ -143,6 +149,9 @@ importers:
|
|||||||
next:
|
next:
|
||||||
specifier: ^15.2.3
|
specifier: ^15.2.3
|
||||||
version: 15.3.0(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
version: 15.3.0(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
next-intl:
|
||||||
|
specifier: ^4.3.1
|
||||||
|
version: 4.3.1(next@15.3.0(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(typescript@5.8.3)
|
||||||
next-themes:
|
next-themes:
|
||||||
specifier: ^0.4.6
|
specifier: ^0.4.6
|
||||||
version: 0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
version: 0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
@@ -367,6 +376,24 @@ packages:
|
|||||||
'@floating-ui/utils@0.2.9':
|
'@floating-ui/utils@0.2.9':
|
||||||
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
|
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
|
||||||
|
|
||||||
|
'@formatjs/ecma402-abstract@2.3.4':
|
||||||
|
resolution: {integrity: sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==}
|
||||||
|
|
||||||
|
'@formatjs/fast-memoize@2.2.7':
|
||||||
|
resolution: {integrity: sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==}
|
||||||
|
|
||||||
|
'@formatjs/icu-messageformat-parser@2.11.2':
|
||||||
|
resolution: {integrity: sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA==}
|
||||||
|
|
||||||
|
'@formatjs/icu-skeleton-parser@1.8.14':
|
||||||
|
resolution: {integrity: sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ==}
|
||||||
|
|
||||||
|
'@formatjs/intl-localematcher@0.5.10':
|
||||||
|
resolution: {integrity: sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==}
|
||||||
|
|
||||||
|
'@formatjs/intl-localematcher@0.6.1':
|
||||||
|
resolution: {integrity: sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==}
|
||||||
|
|
||||||
'@hookform/resolvers@5.0.1':
|
'@hookform/resolvers@5.0.1':
|
||||||
resolution: {integrity: sha512-u/+Jp83luQNx9AdyW2fIPGY6Y7NG68eN2ZW8FOJYL+M0i4s49+refdJdOp/A9n9HFQtQs3HIDHQvX3ZET2o7YA==}
|
resolution: {integrity: sha512-u/+Jp83luQNx9AdyW2fIPGY6Y7NG68eN2ZW8FOJYL+M0i4s49+refdJdOp/A9n9HFQtQs3HIDHQvX3ZET2o7YA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1285,6 +1312,9 @@ packages:
|
|||||||
'@scena/matrix@1.1.1':
|
'@scena/matrix@1.1.1':
|
||||||
resolution: {integrity: sha512-JVKBhN0tm2Srl+Yt+Ywqu0oLgLcdemDQlD1OxmN9jaCTwaFPZ7tY8n6dhVgMEaR9qcR7r+kAlMXnSfNyYdE+Vg==}
|
resolution: {integrity: sha512-JVKBhN0tm2Srl+Yt+Ywqu0oLgLcdemDQlD1OxmN9jaCTwaFPZ7tY8n6dhVgMEaR9qcR7r+kAlMXnSfNyYdE+Vg==}
|
||||||
|
|
||||||
|
'@schummar/icu-type-parser@1.21.5':
|
||||||
|
resolution: {integrity: sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==}
|
||||||
|
|
||||||
'@standard-schema/utils@0.3.0':
|
'@standard-schema/utils@0.3.0':
|
||||||
resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
|
resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
|
||||||
|
|
||||||
@@ -1680,6 +1710,9 @@ packages:
|
|||||||
'@types/hast@3.0.4':
|
'@types/hast@3.0.4':
|
||||||
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
|
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
|
||||||
|
|
||||||
|
'@types/js-cookie@3.0.6':
|
||||||
|
resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==}
|
||||||
|
|
||||||
'@types/json-schema@7.0.15':
|
'@types/json-schema@7.0.15':
|
||||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||||
|
|
||||||
@@ -2264,6 +2297,9 @@ packages:
|
|||||||
supports-color:
|
supports-color:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
decimal.js@10.5.0:
|
||||||
|
resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==}
|
||||||
|
|
||||||
decode-named-character-reference@1.1.0:
|
decode-named-character-reference@1.1.0:
|
||||||
resolution: {integrity: sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==}
|
resolution: {integrity: sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==}
|
||||||
|
|
||||||
@@ -2752,6 +2788,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
|
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
intl-messageformat@10.7.16:
|
||||||
|
resolution: {integrity: sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug==}
|
||||||
|
|
||||||
is-alphabetical@1.0.4:
|
is-alphabetical@1.0.4:
|
||||||
resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==}
|
resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==}
|
||||||
|
|
||||||
@@ -2912,6 +2951,10 @@ packages:
|
|||||||
react:
|
react:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
js-cookie@3.0.5:
|
||||||
|
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
js-tokens@4.0.0:
|
js-tokens@4.0.0:
|
||||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||||
|
|
||||||
@@ -3305,9 +3348,23 @@ packages:
|
|||||||
natural-compare@1.4.0:
|
natural-compare@1.4.0:
|
||||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||||
|
|
||||||
|
negotiator@1.0.0:
|
||||||
|
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
neo-async@2.6.2:
|
neo-async@2.6.2:
|
||||||
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
|
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
|
||||||
|
|
||||||
|
next-intl@4.3.1:
|
||||||
|
resolution: {integrity: sha512-FylHpOoQw5MpOyJt4cw8pNEGba7r3jKDSqt112fmBqXVceGR5YncmqpxS5MvSHsWRwbjqpOV8OsZCIY/4f4HWg==}
|
||||||
|
peerDependencies:
|
||||||
|
next: ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
|
||||||
|
typescript: ^5.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
typescript:
|
||||||
|
optional: true
|
||||||
|
|
||||||
next-themes@0.4.6:
|
next-themes@0.4.6:
|
||||||
resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
|
resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -4128,6 +4185,11 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: '*'
|
react: '*'
|
||||||
|
|
||||||
|
use-intl@4.3.1:
|
||||||
|
resolution: {integrity: sha512-8Xn5RXzeHZhWqqZimi1wi2pKFqm0NxRUOB41k1QdjbPX+ysoeLW3Ey+fi603D/e5EGb0fYw8WzjgtUagJdlIvg==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
|
||||||
|
|
||||||
use-sidecar@1.1.3:
|
use-sidecar@1.1.3:
|
||||||
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
|
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -4378,6 +4440,36 @@ snapshots:
|
|||||||
|
|
||||||
'@floating-ui/utils@0.2.9': {}
|
'@floating-ui/utils@0.2.9': {}
|
||||||
|
|
||||||
|
'@formatjs/ecma402-abstract@2.3.4':
|
||||||
|
dependencies:
|
||||||
|
'@formatjs/fast-memoize': 2.2.7
|
||||||
|
'@formatjs/intl-localematcher': 0.6.1
|
||||||
|
decimal.js: 10.5.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@formatjs/fast-memoize@2.2.7':
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@formatjs/icu-messageformat-parser@2.11.2':
|
||||||
|
dependencies:
|
||||||
|
'@formatjs/ecma402-abstract': 2.3.4
|
||||||
|
'@formatjs/icu-skeleton-parser': 1.8.14
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@formatjs/icu-skeleton-parser@1.8.14':
|
||||||
|
dependencies:
|
||||||
|
'@formatjs/ecma402-abstract': 2.3.4
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@formatjs/intl-localematcher@0.5.10':
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@formatjs/intl-localematcher@0.6.1':
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@hookform/resolvers@5.0.1(react-hook-form@7.56.1(react@19.1.0))':
|
'@hookform/resolvers@5.0.1(react-hook-form@7.56.1(react@19.1.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@standard-schema/utils': 0.3.0
|
'@standard-schema/utils': 0.3.0
|
||||||
@@ -5248,6 +5340,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@daybrush/utils': 1.13.0
|
'@daybrush/utils': 1.13.0
|
||||||
|
|
||||||
|
'@schummar/icu-type-parser@1.21.5': {}
|
||||||
|
|
||||||
'@standard-schema/utils@0.3.0': {}
|
'@standard-schema/utils@0.3.0': {}
|
||||||
|
|
||||||
'@swc/counter@0.1.3': {}
|
'@swc/counter@0.1.3': {}
|
||||||
@@ -5633,6 +5727,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/unist': 3.0.3
|
'@types/unist': 3.0.3
|
||||||
|
|
||||||
|
'@types/js-cookie@3.0.6': {}
|
||||||
|
|
||||||
'@types/json-schema@7.0.15': {}
|
'@types/json-schema@7.0.15': {}
|
||||||
|
|
||||||
'@types/json5@0.0.29': {}
|
'@types/json5@0.0.29': {}
|
||||||
@@ -6256,6 +6352,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
|
|
||||||
|
decimal.js@10.5.0: {}
|
||||||
|
|
||||||
decode-named-character-reference@1.1.0:
|
decode-named-character-reference@1.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
character-entities: 2.0.2
|
character-entities: 2.0.2
|
||||||
@@ -6919,6 +7017,13 @@ snapshots:
|
|||||||
hasown: 2.0.2
|
hasown: 2.0.2
|
||||||
side-channel: 1.1.0
|
side-channel: 1.1.0
|
||||||
|
|
||||||
|
intl-messageformat@10.7.16:
|
||||||
|
dependencies:
|
||||||
|
'@formatjs/ecma402-abstract': 2.3.4
|
||||||
|
'@formatjs/fast-memoize': 2.2.7
|
||||||
|
'@formatjs/icu-messageformat-parser': 2.11.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
is-alphabetical@1.0.4: {}
|
is-alphabetical@1.0.4: {}
|
||||||
|
|
||||||
is-alphabetical@2.0.1: {}
|
is-alphabetical@2.0.1: {}
|
||||||
@@ -7081,6 +7186,8 @@ snapshots:
|
|||||||
'@types/react': 19.1.2
|
'@types/react': 19.1.2
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
|
|
||||||
|
js-cookie@3.0.5: {}
|
||||||
|
|
||||||
js-tokens@4.0.0: {}
|
js-tokens@4.0.0: {}
|
||||||
|
|
||||||
js-yaml@4.1.0:
|
js-yaml@4.1.0:
|
||||||
@@ -7660,8 +7767,20 @@ snapshots:
|
|||||||
|
|
||||||
natural-compare@1.4.0: {}
|
natural-compare@1.4.0: {}
|
||||||
|
|
||||||
|
negotiator@1.0.0: {}
|
||||||
|
|
||||||
neo-async@2.6.2: {}
|
neo-async@2.6.2: {}
|
||||||
|
|
||||||
|
next-intl@4.3.1(next@15.3.0(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(typescript@5.8.3):
|
||||||
|
dependencies:
|
||||||
|
'@formatjs/intl-localematcher': 0.5.10
|
||||||
|
negotiator: 1.0.0
|
||||||
|
next: 15.3.0(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
use-intl: 4.3.1(react@19.1.0)
|
||||||
|
optionalDependencies:
|
||||||
|
typescript: 5.8.3
|
||||||
|
|
||||||
next-themes@0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
next-themes@0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
@@ -8737,6 +8856,13 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
|
|
||||||
|
use-intl@4.3.1(react@19.1.0):
|
||||||
|
dependencies:
|
||||||
|
'@formatjs/fast-memoize': 2.2.7
|
||||||
|
'@schummar/icu-type-parser': 1.21.5
|
||||||
|
intl-messageformat: 10.7.16
|
||||||
|
react: 19.1.0
|
||||||
|
|
||||||
use-sidecar@1.1.3(@types/react@19.1.2)(react@19.1.0):
|
use-sidecar@1.1.3(@types/react@19.1.2)(react@19.1.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
detect-node-es: 1.1.0
|
detect-node-es: 1.1.0
|
||||||
|
|||||||
@@ -2,17 +2,12 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
|
|
||||||
import { Welcome } from "./welcome";
|
import { Welcome } from "./welcome";
|
||||||
|
|
||||||
const questions = [
|
|
||||||
"How many times taller is the Eiffel Tower than the tallest building in the world?",
|
|
||||||
"How many years does an average Tesla battery last compared to a gasoline engine?",
|
|
||||||
"How many liters of water are required to produce 1 kg of beef?",
|
|
||||||
"How many times faster is the speed of light compared to the speed of sound?",
|
|
||||||
];
|
|
||||||
export function ConversationStarter({
|
export function ConversationStarter({
|
||||||
className,
|
className,
|
||||||
onSend,
|
onSend,
|
||||||
@@ -20,6 +15,9 @@ export function ConversationStarter({
|
|||||||
className?: string;
|
className?: string;
|
||||||
onSend?: (message: string) => void;
|
onSend?: (message: string) => void;
|
||||||
}) {
|
}) {
|
||||||
|
const t = useTranslations("chat");
|
||||||
|
const questions = t.raw("conversationStarters") as string[];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("flex flex-col items-center", className)}>
|
<div className={cn("flex flex-col items-center", className)}>
|
||||||
<div className="pointer-events-none fixed inset-0 flex items-center justify-center">
|
<div className="pointer-events-none fixed inset-0 flex items-center justify-center">
|
||||||
@@ -41,7 +39,7 @@ export function ConversationStarter({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="bg-card text-muted-foreground cursor-pointer rounded-2xl border px-4 py-4 opacity-75 transition-all duration-300 hover:opacity-100 hover:shadow-md"
|
className="bg-card text-muted-foreground h-full w-full cursor-pointer rounded-2xl border px-4 py-4 opacity-75 transition-all duration-300 hover:opacity-100 hover:shadow-md"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onSend?.(question);
|
onSend?.(question);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import { MagicWandIcon } from "@radix-ui/react-icons";
|
import { MagicWandIcon } from "@radix-ui/react-icons";
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
import { ArrowUp, Lightbulb, X } from "lucide-react";
|
import { ArrowUp, Lightbulb, X } from "lucide-react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { useCallback, useMemo, useRef, useState } from "react";
|
import { useCallback, useMemo, useRef, useState } from "react";
|
||||||
|
|
||||||
import { Detective } from "~/components/deer-flow/icons/detective";
|
import { Detective } from "~/components/deer-flow/icons/detective";
|
||||||
@@ -46,6 +47,8 @@ export function InputBox({
|
|||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
onRemoveFeedback?: () => void;
|
onRemoveFeedback?: () => void;
|
||||||
}) {
|
}) {
|
||||||
|
const t = useTranslations("chat.inputBox");
|
||||||
|
const tCommon = useTranslations("common");
|
||||||
const enableDeepThinking = useSettingsStore(
|
const enableDeepThinking = useSettingsStore(
|
||||||
(state) => state.general.enableDeepThinking,
|
(state) => state.general.enableDeepThinking,
|
||||||
);
|
);
|
||||||
@@ -217,12 +220,14 @@ export function InputBox({
|
|||||||
title={
|
title={
|
||||||
<div>
|
<div>
|
||||||
<h3 className="mb-2 font-bold">
|
<h3 className="mb-2 font-bold">
|
||||||
Deep Thinking Mode: {enableDeepThinking ? "On" : "Off"}
|
{t("deepThinkingTooltip.title", {
|
||||||
|
status: enableDeepThinking ? t("on") : t("off"),
|
||||||
|
})}
|
||||||
</h3>
|
</h3>
|
||||||
<p>
|
<p>
|
||||||
When enabled, DeerFlow will use reasoning model (
|
{t("deepThinkingTooltip.description", {
|
||||||
{config.models.reasoning?.[0]}) to generate more thoughtful
|
model: config.models.reasoning?.[0] ?? "",
|
||||||
plans.
|
})}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -237,7 +242,7 @@ export function InputBox({
|
|||||||
setEnableDeepThinking(!enableDeepThinking);
|
setEnableDeepThinking(!enableDeepThinking);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Lightbulb /> Deep Thinking
|
<Lightbulb /> {t("deepThinking")}
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
@@ -247,13 +252,11 @@ export function InputBox({
|
|||||||
title={
|
title={
|
||||||
<div>
|
<div>
|
||||||
<h3 className="mb-2 font-bold">
|
<h3 className="mb-2 font-bold">
|
||||||
Investigation Mode: {backgroundInvestigation ? "On" : "Off"}
|
{t("investigationTooltip.title", {
|
||||||
|
status: backgroundInvestigation ? t("on") : t("off"),
|
||||||
|
})}
|
||||||
</h3>
|
</h3>
|
||||||
<p>
|
<p>{t("investigationTooltip.description")}</p>
|
||||||
When enabled, DeerFlow will perform a quick search before
|
|
||||||
planning. This is useful for researches related to ongoing
|
|
||||||
events and news.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -267,13 +270,13 @@ export function InputBox({
|
|||||||
setEnableBackgroundInvestigation(!backgroundInvestigation)
|
setEnableBackgroundInvestigation(!backgroundInvestigation)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Detective /> Investigation
|
<Detective /> {t("investigation")}
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<ReportStyleDialog />
|
<ReportStyleDialog />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex shrink-0 items-center gap-2">
|
<div className="flex shrink-0 items-center gap-2">
|
||||||
<Tooltip title="Enhance prompt with AI">
|
<Tooltip title={t("enhancePrompt")}>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
@@ -293,7 +296,7 @@ export function InputBox({
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title={responding ? "Stop" : "Send"}>
|
<Tooltip title={responding ? tCommon("stop") : tCommon("send")}>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
ChevronRight,
|
ChevronRight,
|
||||||
Lightbulb,
|
Lightbulb,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import React, { useCallback, useMemo, useRef, useState } from "react";
|
import React, { useCallback, useMemo, useRef, useState } from "react";
|
||||||
|
|
||||||
import { LoadingAnimation } from "~/components/deer-flow/loading-animation";
|
import { LoadingAnimation } from "~/components/deer-flow/loading-animation";
|
||||||
@@ -252,6 +253,7 @@ function ResearchCard({
|
|||||||
researchId: string;
|
researchId: string;
|
||||||
onToggleResearch?: () => void;
|
onToggleResearch?: () => void;
|
||||||
}) {
|
}) {
|
||||||
|
const t = useTranslations("chat.research");
|
||||||
const reportId = useStore((state) => state.researchReportIds.get(researchId));
|
const reportId = useStore((state) => state.researchReportIds.get(researchId));
|
||||||
const hasReport = reportId !== undefined;
|
const hasReport = reportId !== undefined;
|
||||||
const reportGenerating = useStore(
|
const reportGenerating = useStore(
|
||||||
@@ -260,10 +262,10 @@ function ResearchCard({
|
|||||||
const openResearchId = useStore((state) => state.openResearchId);
|
const openResearchId = useStore((state) => state.openResearchId);
|
||||||
const state = useMemo(() => {
|
const state = useMemo(() => {
|
||||||
if (hasReport) {
|
if (hasReport) {
|
||||||
return reportGenerating ? "Generating report..." : "Report generated";
|
return reportGenerating ? t("generatingReport") : t("reportGenerated");
|
||||||
}
|
}
|
||||||
return "Researching...";
|
return t("researching");
|
||||||
}, [hasReport, reportGenerating]);
|
}, [hasReport, reportGenerating, t]);
|
||||||
const msg = useResearchMessage(researchId);
|
const msg = useResearchMessage(researchId);
|
||||||
const title = useMemo(() => {
|
const title = useMemo(() => {
|
||||||
if (msg) {
|
if (msg) {
|
||||||
@@ -283,8 +285,8 @@ function ResearchCard({
|
|||||||
<Card className={cn("w-full", className)}>
|
<Card className={cn("w-full", className)}>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>
|
<CardTitle>
|
||||||
<RainbowText animated={state !== "Report generated"}>
|
<RainbowText animated={state !== t("reportGenerated")}>
|
||||||
{title !== undefined && title !== "" ? title : "Deep Research"}
|
{title !== undefined && title !== "" ? title : t("deepResearch")}
|
||||||
</RainbowText>
|
</RainbowText>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -297,7 +299,7 @@ function ResearchCard({
|
|||||||
variant={!openResearchId ? "default" : "outline"}
|
variant={!openResearchId ? "default" : "outline"}
|
||||||
onClick={handleOpen}
|
onClick={handleOpen}
|
||||||
>
|
>
|
||||||
{researchId !== openResearchId ? "Open" : "Close"}
|
{researchId !== openResearchId ? t("open") : t("close")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
@@ -316,6 +318,7 @@ function ThoughtBlock({
|
|||||||
isStreaming?: boolean;
|
isStreaming?: boolean;
|
||||||
hasMainContent?: boolean;
|
hasMainContent?: boolean;
|
||||||
}) {
|
}) {
|
||||||
|
const t = useTranslations("chat.research");
|
||||||
const [isOpen, setIsOpen] = useState(true);
|
const [isOpen, setIsOpen] = useState(true);
|
||||||
|
|
||||||
const [hasAutoCollapsed, setHasAutoCollapsed] = useState(false);
|
const [hasAutoCollapsed, setHasAutoCollapsed] = useState(false);
|
||||||
@@ -359,7 +362,7 @@ function ThoughtBlock({
|
|||||||
isStreaming ? "text-primary" : "text-foreground",
|
isStreaming ? "text-primary" : "text-foreground",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Deep Thinking
|
{t("deepThinking")}
|
||||||
</span>
|
</span>
|
||||||
{isStreaming && <LoadingAnimation className="ml-2 scale-75" />}
|
{isStreaming && <LoadingAnimation className="ml-2 scale-75" />}
|
||||||
<div className="flex-grow" />
|
<div className="flex-grow" />
|
||||||
@@ -432,6 +435,7 @@ function PlanCard({
|
|||||||
) => void;
|
) => void;
|
||||||
waitForFeedback?: boolean;
|
waitForFeedback?: boolean;
|
||||||
}) {
|
}) {
|
||||||
|
const t = useTranslations("chat.research");
|
||||||
const plan = useMemo<{
|
const plan = useMemo<{
|
||||||
title?: string;
|
title?: string;
|
||||||
thought?: string;
|
thought?: string;
|
||||||
@@ -482,7 +486,7 @@ function PlanCard({
|
|||||||
{`### ${
|
{`### ${
|
||||||
plan.title !== undefined && plan.title !== ""
|
plan.title !== undefined && plan.title !== ""
|
||||||
? plan.title
|
? plan.title
|
||||||
: "Deep Research"
|
: t("deepResearch")
|
||||||
}`}
|
}`}
|
||||||
</Markdown>
|
</Markdown>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { FastForward, Play } from "lucide-react";
|
import { FastForward, Play } from "lucide-react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { useCallback, useRef, useState } from "react";
|
import { useCallback, useRef, useState } from "react";
|
||||||
|
|
||||||
import { RainbowText } from "~/components/deer-flow/rainbow-text";
|
import { RainbowText } from "~/components/deer-flow/rainbow-text";
|
||||||
@@ -27,6 +28,7 @@ import { MessageListView } from "./message-list-view";
|
|||||||
import { Welcome } from "./welcome";
|
import { Welcome } from "./welcome";
|
||||||
|
|
||||||
export function MessagesBlock({ className }: { className?: string }) {
|
export function MessagesBlock({ className }: { className?: string }) {
|
||||||
|
const t = useTranslations("chat.messages");
|
||||||
const messageIds = useMessageIds();
|
const messageIds = useMessageIds();
|
||||||
const messageCount = messageIds.length;
|
const messageCount = messageIds.length;
|
||||||
const responding = useStore((state) => state.responding);
|
const responding = useStore((state) => state.responding);
|
||||||
@@ -152,16 +154,16 @@ export function MessagesBlock({ className }: { className?: string }) {
|
|||||||
<CardHeader className={cn("flex-grow", responding && "pl-3")}>
|
<CardHeader className={cn("flex-grow", responding && "pl-3")}>
|
||||||
<CardTitle>
|
<CardTitle>
|
||||||
<RainbowText animated={responding}>
|
<RainbowText animated={responding}>
|
||||||
{responding ? "Replaying" : `${replayTitle}`}
|
{responding ? t("replaying") : `${replayTitle}`}
|
||||||
</RainbowText>
|
</RainbowText>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
<RainbowText animated={responding}>
|
<RainbowText animated={responding}>
|
||||||
{responding
|
{responding
|
||||||
? "DeerFlow is now replaying the conversation..."
|
? t("replayDescription")
|
||||||
: replayStarted
|
: replayStarted
|
||||||
? "The replay has been stopped."
|
? t("replayHasStopped")
|
||||||
: `You're now in DeerFlow's replay mode. Click the "Play" button on the right to start.`}
|
: t("replayModeDescription")}
|
||||||
</RainbowText>
|
</RainbowText>
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -175,13 +177,13 @@ export function MessagesBlock({ className }: { className?: string }) {
|
|||||||
onClick={handleFastForwardReplay}
|
onClick={handleFastForwardReplay}
|
||||||
>
|
>
|
||||||
<FastForward size={16} />
|
<FastForward size={16} />
|
||||||
Fast Forward
|
{t("fastForward")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{!replayStarted && (
|
{!replayStarted && (
|
||||||
<Button className="w-24" onClick={handleStartReplay}>
|
<Button className="w-24" onClick={handleStartReplay}>
|
||||||
<Play size={16} />
|
<Play size={16} />
|
||||||
Play
|
{t("play")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -190,17 +192,16 @@ export function MessagesBlock({ className }: { className?: string }) {
|
|||||||
</Card>
|
</Card>
|
||||||
{!replayStarted && env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY && (
|
{!replayStarted && env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY && (
|
||||||
<div className="text-muted-foreground w-full text-center text-xs">
|
<div className="text-muted-foreground w-full text-center text-xs">
|
||||||
* This site is for demo purposes only. If you want to try your
|
{t("demoNotice")}{" "}
|
||||||
own question, please{" "}
|
|
||||||
<a
|
<a
|
||||||
className="underline"
|
className="underline"
|
||||||
href="https://github.com/bytedance/deer-flow"
|
href="https://github.com/bytedance/deer-flow"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
click here
|
{t("clickHere")}
|
||||||
</a>{" "}
|
</a>{" "}
|
||||||
to clone it locally and run it.
|
{t("cloneLocally")}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { PythonOutlined } from "@ant-design/icons";
|
|||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { LRUCache } from "lru-cache";
|
import { LRUCache } from "lru-cache";
|
||||||
import { BookOpenText, FileText, PencilRuler, Search } from "lucide-react";
|
import { BookOpenText, FileText, PencilRuler, Search } from "lucide-react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import SyntaxHighlighter from "react-syntax-highlighter";
|
import SyntaxHighlighter from "react-syntax-highlighter";
|
||||||
@@ -122,6 +123,7 @@ type SearchResult =
|
|||||||
};
|
};
|
||||||
|
|
||||||
function WebSearchToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
function WebSearchToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
||||||
|
const t = useTranslations("chat.research");
|
||||||
const searching = useMemo(() => {
|
const searching = useMemo(() => {
|
||||||
return toolCall.result === undefined;
|
return toolCall.result === undefined;
|
||||||
}, [toolCall.result]);
|
}, [toolCall.result]);
|
||||||
@@ -159,7 +161,7 @@ function WebSearchToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
|||||||
animated={searchResults === undefined}
|
animated={searchResults === undefined}
|
||||||
>
|
>
|
||||||
<Search size={16} className={"mr-2"} />
|
<Search size={16} className={"mr-2"} />
|
||||||
<span>Searching for </span>
|
<span>{t("searchingFor")} </span>
|
||||||
<span className="max-w-[500px] overflow-hidden text-ellipsis whitespace-nowrap">
|
<span className="max-w-[500px] overflow-hidden text-ellipsis whitespace-nowrap">
|
||||||
{(toolCall.args as { query: string }).query}
|
{(toolCall.args as { query: string }).query}
|
||||||
</span>
|
</span>
|
||||||
@@ -238,6 +240,7 @@ function WebSearchToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function CrawlToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
function CrawlToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
||||||
|
const t = useTranslations("chat.research");
|
||||||
const url = useMemo(
|
const url = useMemo(
|
||||||
() => (toolCall.args as { url: string }).url,
|
() => (toolCall.args as { url: string }).url,
|
||||||
[toolCall.args],
|
[toolCall.args],
|
||||||
@@ -251,7 +254,7 @@ function CrawlToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
|||||||
animated={toolCall.result === undefined}
|
animated={toolCall.result === undefined}
|
||||||
>
|
>
|
||||||
<BookOpenText size={16} className={"mr-2"} />
|
<BookOpenText size={16} className={"mr-2"} />
|
||||||
<span>Reading</span>
|
<span>{t("reading")}</span>
|
||||||
</RainbowText>
|
</RainbowText>
|
||||||
</div>
|
</div>
|
||||||
<ul className="mt-2 flex flex-wrap gap-4">
|
<ul className="mt-2 flex flex-wrap gap-4">
|
||||||
@@ -279,6 +282,7 @@ function CrawlToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function RetrieverToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
function RetrieverToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
||||||
|
const t = useTranslations("chat.research");
|
||||||
const searching = useMemo(() => {
|
const searching = useMemo(() => {
|
||||||
return toolCall.result === undefined;
|
return toolCall.result === undefined;
|
||||||
}, [toolCall.result]);
|
}, [toolCall.result]);
|
||||||
@@ -292,7 +296,7 @@ function RetrieverToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
|||||||
<div className="font-medium italic">
|
<div className="font-medium italic">
|
||||||
<RainbowText className="flex items-center" animated={searching}>
|
<RainbowText className="flex items-center" animated={searching}>
|
||||||
<Search size={16} className={"mr-2"} />
|
<Search size={16} className={"mr-2"} />
|
||||||
<span>Retrieving documents from RAG </span>
|
<span>{t("retrievingDocuments")} </span>
|
||||||
<span className="max-w-[500px] overflow-hidden text-ellipsis whitespace-nowrap">
|
<span className="max-w-[500px] overflow-hidden text-ellipsis whitespace-nowrap">
|
||||||
{(toolCall.args as { keywords: string }).keywords}
|
{(toolCall.args as { keywords: string }).keywords}
|
||||||
</span>
|
</span>
|
||||||
@@ -337,6 +341,7 @@ function RetrieverToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function PythonToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
function PythonToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
||||||
|
const t = useTranslations("chat.research");
|
||||||
const code = useMemo<string | undefined>(() => {
|
const code = useMemo<string | undefined>(() => {
|
||||||
return (toolCall.args as { code?: string }).code;
|
return (toolCall.args as { code?: string }).code;
|
||||||
}, [toolCall.args]);
|
}, [toolCall.args]);
|
||||||
@@ -349,7 +354,7 @@ function PythonToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
|||||||
className="text-base font-medium italic"
|
className="text-base font-medium italic"
|
||||||
animated={toolCall.result === undefined}
|
animated={toolCall.result === undefined}
|
||||||
>
|
>
|
||||||
Running Python code
|
{t("runningPythonCode")}
|
||||||
</RainbowText>
|
</RainbowText>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -373,6 +378,7 @@ function PythonToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function PythonToolCallResult({ result }: { result: string }) {
|
function PythonToolCallResult({ result }: { result: string }) {
|
||||||
|
const t = useTranslations("chat.research");
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
const hasError = useMemo(
|
const hasError = useMemo(
|
||||||
() => result.includes("Error executing code:\n"),
|
() => result.includes("Error executing code:\n"),
|
||||||
@@ -399,7 +405,7 @@ function PythonToolCallResult({ result }: { result: string }) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mt-4 font-medium italic">
|
<div className="mt-4 font-medium italic">
|
||||||
{hasError ? "Error when executing the above code" : "Execution output"}
|
{hasError ? t("errorExecutingCode") : t("executionOutput")}
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-accent mt-2 max-h-[400px] max-w-[calc(100%-120px)] overflow-y-auto rounded-md p-2 text-sm">
|
<div className="bg-accent mt-2 max-h-[400px] max-w-[calc(100%-120px)] overflow-y-auto rounded-md p-2 text-sm">
|
||||||
<SyntaxHighlighter
|
<SyntaxHighlighter
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
import { Check, Copy, Headphones, Pencil, Undo2, X, Download } from "lucide-react";
|
import { Check, Copy, Headphones, Pencil, Undo2, X, Download } from "lucide-react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
import { ScrollContainer } from "~/components/deer-flow/scroll-container";
|
import { ScrollContainer } from "~/components/deer-flow/scroll-container";
|
||||||
@@ -23,6 +24,7 @@ export function ResearchBlock({
|
|||||||
className?: string;
|
className?: string;
|
||||||
researchId: string | null;
|
researchId: string | null;
|
||||||
}) {
|
}) {
|
||||||
|
const t = useTranslations("chat.research");
|
||||||
const reportId = useStore((state) =>
|
const reportId = useStore((state) =>
|
||||||
researchId ? state.researchReportIds.get(researchId) : undefined,
|
researchId ? state.researchReportIds.get(researchId) : undefined,
|
||||||
);
|
);
|
||||||
@@ -108,7 +110,7 @@ export function ResearchBlock({
|
|||||||
<div className="absolute right-4 flex h-9 items-center justify-center">
|
<div className="absolute right-4 flex h-9 items-center justify-center">
|
||||||
{hasReport && !reportStreaming && (
|
{hasReport && !reportStreaming && (
|
||||||
<>
|
<>
|
||||||
<Tooltip title="Generate podcast">
|
<Tooltip title={t("generatePodcast")}>
|
||||||
<Button
|
<Button
|
||||||
className="text-gray-400"
|
className="text-gray-400"
|
||||||
size="icon"
|
size="icon"
|
||||||
@@ -119,7 +121,7 @@ export function ResearchBlock({
|
|||||||
<Headphones />
|
<Headphones />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="Edit">
|
<Tooltip title={t("edit")}>
|
||||||
<Button
|
<Button
|
||||||
className="text-gray-400"
|
className="text-gray-400"
|
||||||
size="icon"
|
size="icon"
|
||||||
@@ -130,7 +132,7 @@ export function ResearchBlock({
|
|||||||
{editing ? <Undo2 /> : <Pencil />}
|
{editing ? <Undo2 /> : <Pencil />}
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="Copy">
|
<Tooltip title={t("copy")}>
|
||||||
<Button
|
<Button
|
||||||
className="text-gray-400"
|
className="text-gray-400"
|
||||||
size="icon"
|
size="icon"
|
||||||
@@ -140,7 +142,7 @@ export function ResearchBlock({
|
|||||||
{copied ? <Check /> : <Copy />}
|
{copied ? <Check /> : <Copy />}
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="Download report as markdown">
|
<Tooltip title={t("downloadReport")}>
|
||||||
<Button
|
<Button
|
||||||
className="text-gray-400"
|
className="text-gray-400"
|
||||||
size="icon"
|
size="icon"
|
||||||
@@ -152,7 +154,7 @@ export function ResearchBlock({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Tooltip title="Close">
|
<Tooltip title={t("close")}>
|
||||||
<Button
|
<Button
|
||||||
className="text-gray-400"
|
className="text-gray-400"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -177,10 +179,10 @@ export function ResearchBlock({
|
|||||||
value="report"
|
value="report"
|
||||||
disabled={!hasReport}
|
disabled={!hasReport}
|
||||||
>
|
>
|
||||||
Report
|
{t("report")}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger className="px-8" value="activities">
|
<TabsTrigger className="px-8" value="activities">
|
||||||
Activities
|
{t("activities")}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,12 +3,16 @@
|
|||||||
|
|
||||||
import { StarFilledIcon, GitHubLogoIcon } from "@radix-ui/react-icons";
|
import { StarFilledIcon, GitHubLogoIcon } from "@radix-ui/react-icons";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
import { LanguageSwitcher } from "~/components/deer-flow/language-switcher";
|
||||||
import { NumberTicker } from "~/components/magicui/number-ticker";
|
import { NumberTicker } from "~/components/magicui/number-ticker";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { env } from "~/env";
|
import { env } from "~/env";
|
||||||
|
|
||||||
export async function SiteHeader() {
|
export function SiteHeader() {
|
||||||
|
const t = useTranslations('common');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="supports-backdrop-blur:bg-background/80 bg-background/40 sticky top-0 left-0 z-40 flex h-15 w-full flex-col items-center backdrop-blur-lg">
|
<header className="supports-backdrop-blur:bg-background/80 bg-background/40 sticky top-0 left-0 z-40 flex h-15 w-full flex-col items-center backdrop-blur-lg">
|
||||||
<div className="container flex h-15 items-center justify-between px-3">
|
<div className="container flex h-15 items-center justify-between px-3">
|
||||||
@@ -16,7 +20,8 @@ export async function SiteHeader() {
|
|||||||
<span className="mr-1 text-2xl">🦌</span>
|
<span className="mr-1 text-2xl">🦌</span>
|
||||||
<span>DeerFlow</span>
|
<span>DeerFlow</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative flex items-center">
|
<div className="relative flex items-center gap-2">
|
||||||
|
<LanguageSwitcher />
|
||||||
<div
|
<div
|
||||||
className="pointer-events-none absolute inset-0 z-0 h-full w-full rounded-full opacity-60 blur-2xl"
|
className="pointer-events-none absolute inset-0 z-0 h-full w-full rounded-full opacity-60 blur-2xl"
|
||||||
style={{
|
style={{
|
||||||
@@ -32,7 +37,7 @@ export async function SiteHeader() {
|
|||||||
>
|
>
|
||||||
<Link href="https://github.com/bytedance/deer-flow" target="_blank">
|
<Link href="https://github.com/bytedance/deer-flow" target="_blank">
|
||||||
<GitHubLogoIcon className="size-4" />
|
<GitHubLogoIcon className="size-4" />
|
||||||
Star on GitHub
|
{t('starOnGitHub')}
|
||||||
{env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY &&
|
{env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY &&
|
||||||
env.GITHUB_OAUTH_TOKEN && <StarCounter />}
|
env.GITHUB_OAUTH_TOKEN && <StarCounter />}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -2,10 +2,13 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
|
|
||||||
export function Welcome({ className }: { className?: string }) {
|
export function Welcome({ className }: { className?: string }) {
|
||||||
|
const t = useTranslations("chat.welcome");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
className={cn("flex flex-col", className)}
|
className={cn("flex flex-col", className)}
|
||||||
@@ -13,21 +16,9 @@ export function Welcome({ className }: { className?: string }) {
|
|||||||
initial={{ opacity: 0, scale: 0.85 }}
|
initial={{ opacity: 0, scale: 0.85 }}
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
>
|
>
|
||||||
<h3 className="mb-2 text-center text-3xl font-medium">
|
<h3 className="mb-2 text-center text-3xl font-medium">{t("greeting")}</h3>
|
||||||
👋 Hello, there!
|
|
||||||
</h3>
|
|
||||||
<div className="text-muted-foreground px-4 text-center text-lg">
|
<div className="text-muted-foreground px-4 text-center text-lg">
|
||||||
Welcome to{" "}
|
{t("description")}
|
||||||
<a
|
|
||||||
href="https://github.com/bytedance/deer-flow"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="hover:underline"
|
|
||||||
>
|
|
||||||
🦌 DeerFlow
|
|
||||||
</a>
|
|
||||||
, a deep research assistant built on cutting-edge language models, helps
|
|
||||||
you search on web, browse information, and handle complex tasks.
|
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import { GithubOutlined } from "@ant-design/icons";
|
import { GithubOutlined } from "@ant-design/icons";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
|
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
@@ -25,12 +26,14 @@ const Main = dynamic(() => import("./main"), {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
|
const t = useTranslations("chat.page");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen w-screen justify-center overscroll-none">
|
<div className="flex h-screen w-screen justify-center overscroll-none">
|
||||||
<header className="fixed top-0 left-0 flex h-12 w-full items-center justify-between px-4">
|
<header className="fixed top-0 left-0 flex h-12 w-full items-center justify-between px-4">
|
||||||
<Logo />
|
<Logo />
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Tooltip title="Star DeerFlow on GitHub">
|
<Tooltip title={t("starOnGitHub")}>
|
||||||
<Button variant="ghost" size="icon" asChild>
|
<Button variant="ghost" size="icon" asChild>
|
||||||
<Link
|
<Link
|
||||||
href="https://github.com/bytedance/deer-flow"
|
href="https://github.com/bytedance/deer-flow"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import { GithubFilled } from "@ant-design/icons";
|
import { GithubFilled } from "@ant-design/icons";
|
||||||
import { ChevronRight } from "lucide-react";
|
import { ChevronRight } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
import { AuroraText } from "~/components/magicui/aurora-text";
|
import { AuroraText } from "~/components/magicui/aurora-text";
|
||||||
import { FlickeringGrid } from "~/components/magicui/flickering-grid";
|
import { FlickeringGrid } from "~/components/magicui/flickering-grid";
|
||||||
@@ -11,6 +12,9 @@ import { Button } from "~/components/ui/button";
|
|||||||
import { env } from "~/env";
|
import { env } from "~/env";
|
||||||
|
|
||||||
export function Jumbotron() {
|
export function Jumbotron() {
|
||||||
|
const t = useTranslations('hero');
|
||||||
|
const tCommon = useTranslations('common');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="flex h-[95vh] w-full flex-col items-center justify-center pb-15">
|
<section className="flex h-[95vh] w-full flex-col items-center justify-center pb-15">
|
||||||
<FlickeringGrid
|
<FlickeringGrid
|
||||||
@@ -34,15 +38,12 @@ export function Jumbotron() {
|
|||||||
<div className="relative z-10 flex flex-col items-center justify-center gap-12">
|
<div className="relative z-10 flex flex-col items-center justify-center gap-12">
|
||||||
<h1 className="text-center text-4xl font-bold md:text-6xl">
|
<h1 className="text-center text-4xl font-bold md:text-6xl">
|
||||||
<span className="bg-gradient-to-r from-white via-gray-200 to-gray-400 bg-clip-text text-transparent">
|
<span className="bg-gradient-to-r from-white via-gray-200 to-gray-400 bg-clip-text text-transparent">
|
||||||
Deep Research{" "}
|
{t('title')}{" "}
|
||||||
</span>
|
</span>
|
||||||
<AuroraText>at Your Fingertips</AuroraText>
|
<AuroraText>{t('subtitle')}</AuroraText>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="max-w-4xl p-2 text-center text-sm opacity-85 md:text-2xl">
|
<p className="max-w-4xl p-2 text-center text-sm opacity-85 md:text-2xl">
|
||||||
Meet DeerFlow, your personal Deep Research assistant. With powerful
|
{t('description')}
|
||||||
tools like search engines, web crawlers, Python and MCP services, it
|
|
||||||
delivers instant insights, comprehensive reports, and even captivating
|
|
||||||
podcasts.
|
|
||||||
</p>
|
</p>
|
||||||
<div className="flex gap-6">
|
<div className="flex gap-6">
|
||||||
<Button className="hidden text-lg md:flex md:w-42" size="lg" asChild>
|
<Button className="hidden text-lg md:flex md:w-42" size="lg" asChild>
|
||||||
@@ -56,7 +57,7 @@ export function Jumbotron() {
|
|||||||
: "/chat"
|
: "/chat"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Get Started <ChevronRight />
|
{tCommon('getStarted')} <ChevronRight />
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
{!env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY && (
|
{!env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY && (
|
||||||
@@ -71,14 +72,14 @@ export function Jumbotron() {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<GithubFilled />
|
<GithubFilled />
|
||||||
Learn More
|
{tCommon('learnMore')}
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute bottom-8 flex text-xs opacity-50">
|
<div className="absolute bottom-8 flex text-xs opacity-50">
|
||||||
<p>* DEER stands for Deep Exploration and Efficient Research.</p>
|
<p>{t('footnote')}</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
Minimize,
|
Minimize,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import "@xyflow/react/dist/style.css";
|
import "@xyflow/react/dist/style.css";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { useCallback, useRef, useState } from "react";
|
import { useCallback, useRef, useState } from "react";
|
||||||
|
|
||||||
import { Tooltip } from "~/components/deer-flow/tooltip";
|
import { Tooltip } from "~/components/deer-flow/tooltip";
|
||||||
@@ -47,6 +48,7 @@ const nodeTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function MultiAgentVisualization({ className }: { className?: string }) {
|
export function MultiAgentVisualization({ className }: { className?: string }) {
|
||||||
|
const t = useTranslations("chat.multiAgent");
|
||||||
const {
|
const {
|
||||||
graph: { nodes, edges },
|
graph: { nodes, edges },
|
||||||
activeStepIndex,
|
activeStepIndex,
|
||||||
@@ -120,12 +122,12 @@ export function MultiAgentVisualization({ className }: { className?: string }) {
|
|||||||
<div className="h-4 shrink-0"></div>
|
<div className="h-4 shrink-0"></div>
|
||||||
<div className="flex h-6 w-full shrink-0 items-center justify-center">
|
<div className="flex h-6 w-full shrink-0 items-center justify-center">
|
||||||
<div className="bg-muted/50 z-[200] flex rounded-3xl px-4 py-2">
|
<div className="bg-muted/50 z-[200] flex rounded-3xl px-4 py-2">
|
||||||
<Tooltip title="Move to the previous step">
|
<Tooltip title={t("moveToPrevious")}>
|
||||||
<Button variant="ghost" onClick={prevStep}>
|
<Button variant="ghost" onClick={prevStep}>
|
||||||
<ChevronLeft className="size-5" />
|
<ChevronLeft className="size-5" />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="Play / Pause">
|
<Tooltip title={t("playPause")}>
|
||||||
<Button variant="ghost" onClick={togglePlay}>
|
<Button variant="ghost" onClick={togglePlay}>
|
||||||
{playing ? (
|
{playing ? (
|
||||||
<Pause className="size-5" />
|
<Pause className="size-5" />
|
||||||
@@ -134,7 +136,7 @@ export function MultiAgentVisualization({ className }: { className?: string }) {
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="Move to the next step">
|
<Tooltip title={t("moveToNext")}>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -158,7 +160,7 @@ export function MultiAgentVisualization({ className }: { className?: string }) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Tooltip title="Toggle fullscreen">
|
<Tooltip title={t("toggleFullscreen")}>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
|
|||||||
@@ -3,93 +3,52 @@
|
|||||||
|
|
||||||
import { Bike, Building, Film, Github, Ham, Home, Pizza } from "lucide-react";
|
import { Bike, Building, Film, Github, Ham, Home, Pizza } from "lucide-react";
|
||||||
import { Bot } from "lucide-react";
|
import { Bot } from "lucide-react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
import { BentoCard } from "~/components/magicui/bento-grid";
|
import { BentoCard } from "~/components/magicui/bento-grid";
|
||||||
|
|
||||||
import { SectionHeader } from "../components/section-header";
|
import { SectionHeader } from "../components/section-header";
|
||||||
|
|
||||||
const caseStudies = [
|
const caseStudyIcons = [
|
||||||
{
|
{ id: "eiffel-tower-vs-tallest-building", icon: Building },
|
||||||
id: "eiffel-tower-vs-tallest-building",
|
{ id: "github-top-trending-repo", icon: Github },
|
||||||
icon: Building,
|
{ id: "nanjing-traditional-dishes", icon: Ham },
|
||||||
title: "How tall is Eiffel Tower compared to tallest building?",
|
{ id: "rental-apartment-decoration", icon: Home },
|
||||||
description:
|
{ id: "review-of-the-professional", icon: Film },
|
||||||
"The research compares the heights and global significance of the Eiffel Tower and Burj Khalifa, and uses Python code to calculate the multiples.",
|
{ id: "china-food-delivery", icon: Bike },
|
||||||
},
|
{ id: "ultra-processed-foods", icon: Pizza },
|
||||||
{
|
{ id: "ai-twin-insurance", icon: Bot },
|
||||||
id: "github-top-trending-repo",
|
|
||||||
icon: Github,
|
|
||||||
title: "What are the top trending repositories on GitHub?",
|
|
||||||
description:
|
|
||||||
"The research utilized MCP services to identify the most popular GitHub repositories and documented them in detail using search engines.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "nanjing-traditional-dishes",
|
|
||||||
icon: Ham,
|
|
||||||
title: "Write an article about Nanjing's traditional dishes",
|
|
||||||
description:
|
|
||||||
"The study vividly showcases Nanjing's famous dishes through rich content and imagery, uncovering their hidden histories and cultural significance.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "rental-apartment-decoration",
|
|
||||||
icon: Home,
|
|
||||||
title: "How to decorate a small rental apartment?",
|
|
||||||
description:
|
|
||||||
"The study provides readers with practical and straightforward methods for decorating apartments, accompanied by inspiring images.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "review-of-the-professional",
|
|
||||||
icon: Film,
|
|
||||||
title: "Introduce the movie 'Léon: The Professional'",
|
|
||||||
description:
|
|
||||||
"The research provides a comprehensive introduction to the movie 'Léon: The Professional', including its plot, characters, and themes.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "china-food-delivery",
|
|
||||||
icon: Bike,
|
|
||||||
title: "How do you view the takeaway war in China? (in Chinese)",
|
|
||||||
description:
|
|
||||||
"The research analyzes the intensifying competition between JD and Meituan, highlighting their strategies, technological innovations, and challenges.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "ultra-processed-foods",
|
|
||||||
icon: Pizza,
|
|
||||||
title: "Are ultra-processed foods linked to health?",
|
|
||||||
description:
|
|
||||||
"The research examines the health risks of rising ultra-processed food consumption, urging more research on long-term effects and individual differences.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "ai-twin-insurance",
|
|
||||||
icon: Bot,
|
|
||||||
title: 'Write an article on "Would you insure your AI twin?"',
|
|
||||||
description:
|
|
||||||
"The research explores the concept of insuring AI twins, highlighting their benefits, risks, ethical considerations, and the evolving regulatory.",
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export function CaseStudySection() {
|
export function CaseStudySection() {
|
||||||
|
const t = useTranslations("landing.caseStudies");
|
||||||
|
const cases = t.raw("cases") as Array<{ title: string; description: string }>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="relative container hidden flex-col items-center justify-center md:flex">
|
<section className="relative container hidden flex-col items-center justify-center md:flex">
|
||||||
<SectionHeader
|
<SectionHeader
|
||||||
anchor="case-studies"
|
anchor="case-studies"
|
||||||
title="Case Studies"
|
title={t("title")}
|
||||||
description="See DeerFlow in action through replays."
|
description={t("description")}
|
||||||
/>
|
/>
|
||||||
<div className="grid w-3/4 grid-cols-1 gap-2 sm:w-full sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
<div className="grid w-3/4 grid-cols-1 gap-2 sm:w-full sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||||
{caseStudies.map((caseStudy) => (
|
{cases.map((caseStudy, index) => {
|
||||||
|
const iconData = caseStudyIcons[index];
|
||||||
|
return (
|
||||||
<div key={caseStudy.title} className="w-full p-2">
|
<div key={caseStudy.title} className="w-full p-2">
|
||||||
<BentoCard
|
<BentoCard
|
||||||
{...{
|
{...{
|
||||||
Icon: caseStudy.icon,
|
Icon: iconData?.icon ?? Building,
|
||||||
name: caseStudy.title,
|
name: caseStudy.title,
|
||||||
description: caseStudy.description,
|
description: caseStudy.description,
|
||||||
href: `/chat?replay=${caseStudy.id}`,
|
href: `/chat?replay=${iconData?.id}`,
|
||||||
cta: "Click to watch replay",
|
cta: t("clickToWatch"),
|
||||||
className: "w-full h-full",
|
className: "w-full h-full",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,87 +1,90 @@
|
|||||||
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
import { Bird, Microscope, Podcast, Usb, User } from "lucide-react";
|
import {
|
||||||
|
Bird,
|
||||||
|
Microscope,
|
||||||
|
Podcast,
|
||||||
|
Usb,
|
||||||
|
User,
|
||||||
|
type LucideProps,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import type { ForwardRefExoticComponent, RefAttributes } from "react";
|
||||||
|
|
||||||
import { BentoCard, BentoGrid } from "~/components/magicui/bento-grid";
|
import { BentoCard, BentoGrid } from "~/components/magicui/bento-grid";
|
||||||
|
|
||||||
import { SectionHeader } from "../components/section-header";
|
import { SectionHeader } from "../components/section-header";
|
||||||
|
|
||||||
const features = [
|
type FeatureIcon = {
|
||||||
|
Icon: ForwardRefExoticComponent<
|
||||||
|
Omit<LucideProps, "ref"> & RefAttributes<SVGSVGElement>
|
||||||
|
>;
|
||||||
|
href: string;
|
||||||
|
className: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const featureIcons: Array<FeatureIcon> = [
|
||||||
{
|
{
|
||||||
Icon: Microscope,
|
Icon: Microscope,
|
||||||
name: "Dive Deeper and Reach Wider",
|
|
||||||
description:
|
|
||||||
"Unlock deeper insights with advanced tools. Our powerful search + crawling and Python tools gathers comprehensive data, delivering in-depth reports to enhance your study.",
|
|
||||||
href: "https://github.com/bytedance/deer-flow/blob/main/src/tools",
|
href: "https://github.com/bytedance/deer-flow/blob/main/src/tools",
|
||||||
cta: "Learn more",
|
|
||||||
background: (
|
|
||||||
<img alt="background" className="absolute -top-20 -right-20 opacity-60" />
|
|
||||||
),
|
|
||||||
className: "lg:col-start-1 lg:col-end-2 lg:row-start-1 lg:row-end-3",
|
className: "lg:col-start-1 lg:col-end-2 lg:row-start-1 lg:row-end-3",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Icon: User,
|
Icon: User,
|
||||||
name: "Human-in-the-loop",
|
|
||||||
description:
|
|
||||||
"Refine your research plan, or adjust focus areas all through simple natural language.",
|
|
||||||
href: "https://github.com/bytedance/deer-flow/blob/main/src/graph/nodes.py",
|
href: "https://github.com/bytedance/deer-flow/blob/main/src/graph/nodes.py",
|
||||||
cta: "Learn more",
|
|
||||||
background: (
|
|
||||||
<img alt="background" className="absolute -top-20 -right-20 opacity-60" />
|
|
||||||
),
|
|
||||||
className: "lg:col-start-1 lg:col-end-2 lg:row-start-3 lg:row-end-4",
|
className: "lg:col-start-1 lg:col-end-2 lg:row-start-3 lg:row-end-4",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Icon: Bird,
|
Icon: Bird,
|
||||||
name: "Lang Stack",
|
|
||||||
description:
|
|
||||||
"Build with confidence using the LangChain and LangGraph frameworks.",
|
|
||||||
href: "https://www.langchain.com/",
|
href: "https://www.langchain.com/",
|
||||||
cta: "Learn more",
|
|
||||||
background: (
|
|
||||||
<img alt="background" className="absolute -top-20 -right-20 opacity-60" />
|
|
||||||
),
|
|
||||||
className: "lg:col-start-2 lg:col-end-3 lg:row-start-1 lg:row-end-2",
|
className: "lg:col-start-2 lg:col-end-3 lg:row-start-1 lg:row-end-2",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Icon: Usb,
|
Icon: Usb,
|
||||||
name: "MCP Integrations",
|
|
||||||
description:
|
|
||||||
"Supercharge your research workflow and expand your toolkit with seamless MCP integrations.",
|
|
||||||
href: "https://github.com/bytedance/deer-flow/blob/main/src/graph/nodes.py",
|
href: "https://github.com/bytedance/deer-flow/blob/main/src/graph/nodes.py",
|
||||||
cta: "Learn more",
|
|
||||||
background: (
|
|
||||||
<img alt="background" className="absolute -top-20 -right-20 opacity-60" />
|
|
||||||
),
|
|
||||||
className: "lg:col-start-2 lg:col-end-3 lg:row-start-2 lg:row-end-3",
|
className: "lg:col-start-2 lg:col-end-3 lg:row-start-2 lg:row-end-3",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Icon: Podcast,
|
Icon: Podcast,
|
||||||
name: "Podcast Generation",
|
|
||||||
description:
|
|
||||||
"Instantly generate podcasts from reports. Perfect for on-the-go learning or sharing findings effortlessly. ",
|
|
||||||
href: "https://github.com/bytedance/deer-flow/blob/main/src/podcast",
|
href: "https://github.com/bytedance/deer-flow/blob/main/src/podcast",
|
||||||
cta: "Learn more",
|
|
||||||
background: (
|
|
||||||
<img alt="background" className="absolute -top-20 -right-20 opacity-60" />
|
|
||||||
),
|
|
||||||
className: "lg:col-start-2 lg:col-end-3 lg:row-start-3 lg:row-end-4",
|
className: "lg:col-start-2 lg:col-end-3 lg:row-start-3 lg:row-end-4",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function CoreFeatureSection() {
|
export function CoreFeatureSection() {
|
||||||
|
const t = useTranslations("landing.coreFeatures");
|
||||||
|
const tCommon = useTranslations("common");
|
||||||
|
const features = t.raw("features") as Array<{
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="relative flex w-full flex-col content-around items-center justify-center">
|
<section className="relative flex w-full flex-col content-around items-center justify-center">
|
||||||
<SectionHeader
|
<SectionHeader
|
||||||
anchor="core-features"
|
anchor="core-features"
|
||||||
title="Core Features"
|
title={t("title")}
|
||||||
description="Find out what makes DeerFlow effective."
|
description={t("description")}
|
||||||
/>
|
/>
|
||||||
<BentoGrid className="w-3/4 lg:grid-cols-2 lg:grid-rows-3">
|
<BentoGrid className="w-3/4 lg:grid-cols-2 lg:grid-rows-3">
|
||||||
{features.map((feature) => (
|
{features.map((feature, index) => {
|
||||||
<BentoCard key={feature.name} {...feature} />
|
const iconData = featureIcons[index];
|
||||||
))}
|
return iconData ? (
|
||||||
|
<BentoCard
|
||||||
|
key={feature.name}
|
||||||
|
{...iconData}
|
||||||
|
{...feature}
|
||||||
|
background={
|
||||||
|
<img
|
||||||
|
alt="background"
|
||||||
|
className="absolute -top-20 -right-20 opacity-60"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
cta={tCommon("learnMore")}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
})}
|
||||||
</BentoGrid>
|
</BentoGrid>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
import { GithubFilled } from "@ant-design/icons";
|
import { GithubFilled } from "@ant-design/icons";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
import { AuroraText } from "~/components/magicui/aurora-text";
|
import { AuroraText } from "~/components/magicui/aurora-text";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
@@ -10,21 +11,22 @@ import { Button } from "~/components/ui/button";
|
|||||||
import { SectionHeader } from "../components/section-header";
|
import { SectionHeader } from "../components/section-header";
|
||||||
|
|
||||||
export function JoinCommunitySection() {
|
export function JoinCommunitySection() {
|
||||||
|
const t = useTranslations("landing.joinCommunity");
|
||||||
return (
|
return (
|
||||||
<section className="flex w-full flex-col items-center justify-center pb-12">
|
<section className="flex w-full flex-col items-center justify-center pb-12">
|
||||||
<SectionHeader
|
<SectionHeader
|
||||||
anchor="join-community"
|
anchor="join-community"
|
||||||
title={
|
title={
|
||||||
<AuroraText colors={["#60A5FA", "#A5FA60", "#A560FA"]}>
|
<AuroraText colors={["#60A5FA", "#A5FA60", "#A560FA"]}>
|
||||||
Join the DeerFlow Community
|
{t("title")}
|
||||||
</AuroraText>
|
</AuroraText>
|
||||||
}
|
}
|
||||||
description="Contribute brilliant ideas to shape the future of DeerFlow. Collaborate, innovate, and make impacts."
|
description={t("description")}
|
||||||
/>
|
/>
|
||||||
<Button className="text-xl" size="lg" asChild>
|
<Button className="text-xl" size="lg" asChild>
|
||||||
<Link href="https://github.com/bytedance/deer-flow" target="_blank">
|
<Link href="https://github.com/bytedance/deer-flow" target="_blank">
|
||||||
<GithubFilled />
|
<GithubFilled />
|
||||||
Contribute Now
|
{t("contributeNow")}
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
import { MultiAgentVisualization } from "../components/multi-agent-visualization";
|
import { MultiAgentVisualization } from "../components/multi-agent-visualization";
|
||||||
import { SectionHeader } from "../components/section-header";
|
import { SectionHeader } from "../components/section-header";
|
||||||
|
|
||||||
export function MultiAgentSection() {
|
export function MultiAgentSection() {
|
||||||
|
const t = useTranslations("landing.multiAgent");
|
||||||
return (
|
return (
|
||||||
<section className="relative flex w-full flex-col items-center justify-center">
|
<section className="relative flex w-full flex-col items-center justify-center">
|
||||||
<SectionHeader
|
<SectionHeader
|
||||||
anchor="multi-agent-architecture"
|
anchor="multi-agent-architecture"
|
||||||
title="Multi-Agent Architecture"
|
title={t("title")}
|
||||||
description="Experience the agent teamwork with our Supervisor + Handoffs design pattern."
|
description={t("description")}
|
||||||
/>
|
/>
|
||||||
<div className="flex h-[70vh] w-full flex-col items-center justify-center">
|
<div className="flex h-[70vh] w-full flex-col items-center justify-center">
|
||||||
<div className="h-full w-full">
|
<div className="h-full w-full">
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import "~/styles/globals.css";
|
|||||||
import { type Metadata } from "next";
|
import { type Metadata } from "next";
|
||||||
import { Geist } from "next/font/google";
|
import { Geist } from "next/font/google";
|
||||||
import Script from "next/script";
|
import Script from "next/script";
|
||||||
|
import { NextIntlClientProvider } from 'next-intl';
|
||||||
|
import { getLocale, getMessages } from 'next-intl/server';
|
||||||
|
|
||||||
import { ThemeProviderWrapper } from "~/components/deer-flow/theme-provider-wrapper";
|
import { ThemeProviderWrapper } from "~/components/deer-flow/theme-provider-wrapper";
|
||||||
import { env } from "~/env";
|
import { env } from "~/env";
|
||||||
@@ -27,8 +29,11 @@ const geist = Geist({
|
|||||||
export default async function RootLayout({
|
export default async function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{ children: React.ReactNode }>) {
|
}: Readonly<{ children: React.ReactNode }>) {
|
||||||
|
const locale = await getLocale();
|
||||||
|
const messages = await getMessages();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="en" className={`${geist.variable}`} suppressHydrationWarning>
|
<html lang={locale} className={`${geist.variable}`} suppressHydrationWarning>
|
||||||
<head>
|
<head>
|
||||||
{/* Define isSpace function globally to fix markdown-it issues with Next.js + Turbopack
|
{/* Define isSpace function globally to fix markdown-it issues with Next.js + Turbopack
|
||||||
https://github.com/markdown-it/markdown-it/issues/1082#issuecomment-2749656365 */}
|
https://github.com/markdown-it/markdown-it/issues/1082#issuecomment-2749656365 */}
|
||||||
@@ -43,8 +48,10 @@ export default async function RootLayout({
|
|||||||
</Script>
|
</Script>
|
||||||
</head>
|
</head>
|
||||||
<body className="bg-app">
|
<body className="bg-app">
|
||||||
|
<NextIntlClientProvider messages={messages}>
|
||||||
<ThemeProviderWrapper>{children}</ThemeProviderWrapper>
|
<ThemeProviderWrapper>{children}</ThemeProviderWrapper>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
|
</NextIntlClientProvider>
|
||||||
{
|
{
|
||||||
// NO USER BEHAVIOR TRACKING OR PRIVATE DATA COLLECTION BY DEFAULT
|
// NO USER BEHAVIOR TRACKING OR PRIVATE DATA COLLECTION BY DEFAULT
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
import { SiteHeader } from "./chat/components/site-header";
|
import { SiteHeader } from "./chat/components/site-header";
|
||||||
@@ -27,20 +28,20 @@ export default function HomePage() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Footer() {
|
function Footer() {
|
||||||
|
const t = useTranslations('footer');
|
||||||
const year = useMemo(() => new Date().getFullYear(), []);
|
const year = useMemo(() => new Date().getFullYear(), []);
|
||||||
return (
|
return (
|
||||||
<footer className="container mt-32 flex flex-col items-center justify-center">
|
<footer className="container mt-32 flex flex-col items-center justify-center">
|
||||||
<hr className="from-border/0 via-border/70 to-border/0 m-0 h-px w-full border-none bg-gradient-to-r" />
|
<hr className="from-border/0 via-border/70 to-border/0 m-0 h-px w-full border-none bg-gradient-to-r" />
|
||||||
<div className="text-muted-foreground container flex h-20 flex-col items-center justify-center text-sm">
|
<div className="text-muted-foreground container flex h-20 flex-col items-center justify-center text-sm">
|
||||||
<p className="text-center font-serif text-lg md:text-xl">
|
<p className="text-center font-serif text-lg md:text-xl">
|
||||||
"Originated from Open Source, give back to Open Source."
|
"{t('quote')}"
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-muted-foreground container mb-8 flex flex-col items-center justify-center text-xs">
|
<div className="text-muted-foreground container mb-8 flex flex-col items-center justify-center text-xs">
|
||||||
<p>Licensed under MIT License</p>
|
<p>{t('license')}</p>
|
||||||
<p>© {year} DeerFlow</p>
|
<p>© {year} {t('copyright')}</p>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { useCallback, useRef, useState } from "react";
|
import { useCallback, useRef, useState } from "react";
|
||||||
|
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
@@ -29,6 +30,7 @@ export function AddMCPServerDialog({
|
|||||||
}: {
|
}: {
|
||||||
onAdd?: (servers: MCPServerMetadata[]) => void;
|
onAdd?: (servers: MCPServerMetadata[]) => void;
|
||||||
}) {
|
}) {
|
||||||
|
const t = useTranslations("settings");
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [input, setInput] = useState("");
|
const [input, setInput] = useState("");
|
||||||
const [validationError, setValidationError] = useState<string | null>("");
|
const [validationError, setValidationError] = useState<string | null>("");
|
||||||
@@ -50,7 +52,7 @@ export function AddMCPServerDialog({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
setValidationError("Invalid JSON");
|
setValidationError(t("invalidJson"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const result = MCPConfigSchema.safeParse(JSON.parse(value));
|
const result = MCPConfigSchema.safeParse(JSON.parse(value));
|
||||||
@@ -65,17 +67,17 @@ export function AddMCPServerDialog({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
result.error.errors[0]?.message ?? "Validation failed";
|
result.error.errors[0]?.message ?? t("validationFailed");
|
||||||
setValidationError(errorMessage);
|
setValidationError(errorMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const keys = Object.keys(result.data.mcpServers);
|
const keys = Object.keys(result.data.mcpServers);
|
||||||
if (keys.length === 0) {
|
if (keys.length === 0) {
|
||||||
setValidationError("Missing server name in `mcpServers`");
|
setValidationError(t("missingServerName"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}, []);
|
}, [t]);
|
||||||
|
|
||||||
const handleAdd = useCallback(async () => {
|
const handleAdd = useCallback(async () => {
|
||||||
abortControllerRef.current = new AbortController();
|
abortControllerRef.current = new AbortController();
|
||||||
@@ -139,21 +141,21 @@ export function AddMCPServerDialog({
|
|||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button size="sm">Add Servers</Button>
|
<Button size="sm">{t("addServers")}</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="sm:max-w-[560px]">
|
<DialogContent className="sm:max-w-[560px]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Add New MCP Servers</DialogTitle>
|
<DialogTitle>{t("addNewMCPServers")}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
DeerFlow uses the standard JSON MCP config to create a new server.
|
{t("mcpConfigDescription")}
|
||||||
<br />
|
<br />
|
||||||
Paste your config below and click "Add" to add new servers.
|
{t("pasteConfigBelow")}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<Textarea
|
<Textarea
|
||||||
className="h-[360px] sm:max-w-[510px] break-all"
|
className="h-[360px] break-all sm:max-w-[510px]"
|
||||||
placeholder={
|
placeholder={
|
||||||
'Example:\n\n{\n "mcpServers": {\n "My Server": {\n "command": "python",\n "args": [\n "-m", "mcp_server"\n ],\n "env": {\n "API_KEY": "YOUR_API_KEY"\n }\n }\n }\n}'
|
'Example:\n\n{\n "mcpServers": {\n "My Server": {\n "command": "python",\n "args": [\n "-m", "mcp_server"\n ],\n "env": {\n "API_KEY": "YOUR_API_KEY"\n }\n }\n }\n}'
|
||||||
}
|
}
|
||||||
@@ -169,7 +171,7 @@ export function AddMCPServerDialog({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button variant="outline" onClick={() => setOpen(false)}>
|
<Button variant="outline" onClick={() => setOpen(false)}>
|
||||||
Cancel
|
{t("cancel", { defaultValue: "Cancel" })}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="w-24"
|
className="w-24"
|
||||||
@@ -178,7 +180,7 @@ export function AddMCPServerDialog({
|
|||||||
onClick={handleAdd}
|
onClick={handleAdd}
|
||||||
>
|
>
|
||||||
{processing && <Loader2 className="animate-spin" />}
|
{processing && <Loader2 className="animate-spin" />}
|
||||||
Add
|
{t("add")}
|
||||||
</Button>
|
</Button>
|
||||||
{
|
{
|
||||||
processing && (
|
processing && (
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
import { Settings } from "lucide-react";
|
import { Settings } from "lucide-react";
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
import { Tooltip } from "~/components/deer-flow/tooltip";
|
import { Tooltip } from "~/components/deer-flow/tooltip";
|
||||||
@@ -29,6 +30,8 @@ import { cn } from "~/lib/utils";
|
|||||||
import { SETTINGS_TABS } from "../tabs";
|
import { SETTINGS_TABS } from "../tabs";
|
||||||
|
|
||||||
export function SettingsDialog() {
|
export function SettingsDialog() {
|
||||||
|
const t = useTranslations('settings');
|
||||||
|
const tCommon = useTranslations('common');
|
||||||
const { isReplay } = useReplay();
|
const { isReplay } = useReplay();
|
||||||
const [activeTabId, setActiveTabId] = useState(SETTINGS_TABS[0]!.id);
|
const [activeTabId, setActiveTabId] = useState(SETTINGS_TABS[0]!.id);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
@@ -92,7 +95,7 @@ export function SettingsDialog() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<Tooltip title="Settings">
|
<Tooltip title={tCommon('settings')}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="ghost" size="icon">
|
<Button variant="ghost" size="icon">
|
||||||
<Settings />
|
<Settings />
|
||||||
@@ -101,9 +104,9 @@ export function SettingsDialog() {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
<DialogContent className="sm:max-w-[850px]">
|
<DialogContent className="sm:max-w-[850px]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>DeerFlow Settings</DialogTitle>
|
<DialogTitle>{t('title')}</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Manage your DeerFlow settings here.
|
{t('description')}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<Tabs value={activeTabId}>
|
<Tabs value={activeTabId}>
|
||||||
@@ -157,10 +160,10 @@ export function SettingsDialog() {
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline" onClick={() => setOpen(false)}>
|
<Button variant="outline" onClick={() => setOpen(false)}>
|
||||||
Cancel
|
{tCommon('cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button className="w-24" type="submit" onClick={handleSave}>
|
<Button className="w-24" type="submit" onClick={handleSave}>
|
||||||
Save
|
{tCommon('save')}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { Settings } from "lucide-react";
|
import { Settings } from "lucide-react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -47,6 +48,7 @@ export const GeneralTab: Tab = ({
|
|||||||
settings: SettingsState;
|
settings: SettingsState;
|
||||||
onChange: (changes: Partial<SettingsState>) => void;
|
onChange: (changes: Partial<SettingsState>) => void;
|
||||||
}) => {
|
}) => {
|
||||||
|
const t = useTranslations("settings.general");
|
||||||
const generalSettings = useMemo(() => settings.general, [settings]);
|
const generalSettings = useMemo(() => settings.general, [settings]);
|
||||||
const form = useForm<z.infer<typeof generalFormSchema>>({
|
const form = useForm<z.infer<typeof generalFormSchema>>({
|
||||||
resolver: zodResolver(generalFormSchema, undefined, undefined),
|
resolver: zodResolver(generalFormSchema, undefined, undefined),
|
||||||
@@ -75,7 +77,7 @@ export const GeneralTab: Tab = ({
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<header>
|
<header>
|
||||||
<h1 className="text-lg font-medium">General</h1>
|
<h1 className="text-lg font-medium">{t("title")}</h1>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
@@ -93,7 +95,7 @@ export const GeneralTab: Tab = ({
|
|||||||
onCheckedChange={field.onChange}
|
onCheckedChange={field.onChange}
|
||||||
/>
|
/>
|
||||||
<Label className="text-sm" htmlFor="autoAcceptedPlan">
|
<Label className="text-sm" htmlFor="autoAcceptedPlan">
|
||||||
Allow automatic acceptance of plans
|
{t("autoAcceptPlan")}
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -105,7 +107,7 @@ export const GeneralTab: Tab = ({
|
|||||||
name="maxPlanIterations"
|
name="maxPlanIterations"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Max plan iterations</FormLabel>
|
<FormLabel>{t("maxPlanIterations")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
className="w-60"
|
className="w-60"
|
||||||
@@ -118,8 +120,7 @@ export const GeneralTab: Tab = ({
|
|||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
Set to 1 for single-step planning. Set to 2 or more to
|
{t("maxPlanIterationsDescription")}
|
||||||
enable re-planning.
|
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -130,7 +131,7 @@ export const GeneralTab: Tab = ({
|
|||||||
name="maxStepNum"
|
name="maxStepNum"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Max steps of a research plan</FormLabel>
|
<FormLabel>{t("maxStepsOfPlan")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
className="w-60"
|
className="w-60"
|
||||||
@@ -142,9 +143,7 @@ export const GeneralTab: Tab = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>{t("maxStepsDescription")}</FormDescription>
|
||||||
By default, each research plan has 3 steps.
|
|
||||||
</FormDescription>
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
@@ -154,7 +153,7 @@ export const GeneralTab: Tab = ({
|
|||||||
name="maxSearchResults"
|
name="maxSearchResults"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Max search results</FormLabel>
|
<FormLabel>{t("maxSearchResults")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
className="w-60"
|
className="w-60"
|
||||||
@@ -167,7 +166,7 @@ export const GeneralTab: Tab = ({
|
|||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
By default, each search step has 3 results.
|
{t("maxSearchResultsDescription")}
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
70
web/src/components/deer-flow/language-switcher.tsx
Normal file
70
web/src/components/deer-flow/language-switcher.tsx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useLocale } from "next-intl";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useTransition } from "react";
|
||||||
|
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "~/components/ui/dropdown-menu";
|
||||||
|
|
||||||
|
type LanguageOption = {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
flag: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const languages: Array<LanguageOption> = [
|
||||||
|
{ code: "en", name: "English", flag: "🇺🇸" },
|
||||||
|
{ code: "zh", name: "中文", flag: "🇨🇳" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function LanguageSwitcher() {
|
||||||
|
const locale = useLocale();
|
||||||
|
const router = useRouter();
|
||||||
|
const [isPending, startTransition] = useTransition();
|
||||||
|
|
||||||
|
const currentLanguage =
|
||||||
|
languages.find((lang) => lang.code === locale) ??
|
||||||
|
(languages[0] as LanguageOption);
|
||||||
|
|
||||||
|
const handleLanguageChange = (newLocale: string) => {
|
||||||
|
startTransition(() => {
|
||||||
|
console.log(`updateing locale to ${newLocale}`)
|
||||||
|
// Set locale in cookie
|
||||||
|
document.cookie = `NEXT_LOCALE=${newLocale}; path=/; max-age=31536000; SameSite=lax`;
|
||||||
|
// Reload the page to apply the new locale
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="ghost" size="sm" disabled={isPending}>
|
||||||
|
<span className="mr-2">{currentLanguage.flag}</span>
|
||||||
|
{currentLanguage.name}
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
{languages.map((language) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
key={language.code}
|
||||||
|
onClick={() => handleLanguageChange(language.code)}
|
||||||
|
className={locale === language.code ? "bg-accent" : ""}
|
||||||
|
>
|
||||||
|
<span className="mr-2">{language.flag}</span>
|
||||||
|
{language.name}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import { useMemo } from "react";
|
|||||||
import { useStore, useToolCalls } from "~/core/store";
|
import { useStore, useToolCalls } from "~/core/store";
|
||||||
import { Tooltip } from "./tooltip";
|
import { Tooltip } from "./tooltip";
|
||||||
import { WarningFilled } from "@ant-design/icons";
|
import { WarningFilled } from "@ant-design/icons";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
export const Link = ({
|
export const Link = ({
|
||||||
href,
|
href,
|
||||||
@@ -38,16 +39,14 @@ export const Link = ({
|
|||||||
: true;
|
: true;
|
||||||
}, [credibleLinks, href, responding, checkLinkCredibility]);
|
}, [credibleLinks, href, responding, checkLinkCredibility]);
|
||||||
|
|
||||||
|
const t = useTranslations("common");
|
||||||
return (
|
return (
|
||||||
<span className="inline-flex items-center gap-1.5">
|
<span className="inline-flex items-center gap-1.5">
|
||||||
<a href={href} target="_blank" rel="noopener noreferrer">
|
<a href={href} target="_blank" rel="noopener noreferrer">
|
||||||
{children}
|
{children}
|
||||||
</a>
|
</a>
|
||||||
{!isCredible && (
|
{!isCredible && (
|
||||||
<Tooltip
|
<Tooltip title={t("linkNotReliable")} delayDuration={300}>
|
||||||
title="This link might be a hallucination from AI model and may not be reliable."
|
|
||||||
delayDuration={300}
|
|
||||||
>
|
|
||||||
<WarningFilled className="text-sx transition-colors hover:!text-yellow-500" />
|
<WarningFilled className="text-sx transition-colors hover:!text-yellow-500" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
} from "novel";
|
} from "novel";
|
||||||
import { Markdown } from "tiptap-markdown";
|
import { Markdown } from "tiptap-markdown";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
import "~/styles/prosemirror.css";
|
import "~/styles/prosemirror.css";
|
||||||
import { resourceSuggestion } from "./resource-suggestion";
|
import { resourceSuggestion } from "./resource-suggestion";
|
||||||
@@ -82,6 +83,7 @@ const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
|||||||
{ className, loading, config, onChange, onEnter }: MessageInputProps,
|
{ className, loading, config, onChange, onEnter }: MessageInputProps,
|
||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
|
const t = useTranslations("messageInput");
|
||||||
const editorRef = useRef<Editor>(null);
|
const editorRef = useRef<Editor>(null);
|
||||||
const handleEnterRef = useRef<
|
const handleEnterRef = useRef<
|
||||||
((message: string, resources: Array<Resource>) => void) | undefined
|
((message: string, resources: Array<Resource>) => void) | undefined
|
||||||
@@ -136,9 +138,7 @@ const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
|||||||
}),
|
}),
|
||||||
Placeholder.configure({
|
Placeholder.configure({
|
||||||
showOnlyCurrent: false,
|
showOnlyCurrent: false,
|
||||||
placeholder: config?.rag.provider
|
placeholder: config?.rag.provider ? t("placeholderWithRag") : t("placeholder"),
|
||||||
? "What can I do for you? \nYou may refer to RAG resources by using @."
|
|
||||||
: "What can I do for you?",
|
|
||||||
emptyEditorClass: "placeholder",
|
emptyEditorClass: "placeholder",
|
||||||
}),
|
}),
|
||||||
Extension.create({
|
Extension.create({
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Check, FileText, Newspaper, Users, GraduationCap } from "lucide-react";
|
import { Check, FileText, Newspaper, Users, GraduationCap } from "lucide-react";
|
||||||
|
|
||||||
@@ -21,31 +22,32 @@ import { Tooltip } from "./tooltip";
|
|||||||
const REPORT_STYLES = [
|
const REPORT_STYLES = [
|
||||||
{
|
{
|
||||||
value: "academic" as const,
|
value: "academic" as const,
|
||||||
label: "Academic",
|
labelKey: "academic",
|
||||||
description: "Formal, objective, and analytical with precise terminology",
|
descriptionKey: "academicDesc",
|
||||||
icon: GraduationCap,
|
icon: GraduationCap,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "popular_science" as const,
|
value: "popular_science" as const,
|
||||||
label: "Popular Science",
|
labelKey: "popularScience",
|
||||||
description: "Engaging and accessible for general audience",
|
descriptionKey: "popularScienceDesc",
|
||||||
icon: FileText,
|
icon: FileText,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "news" as const,
|
value: "news" as const,
|
||||||
label: "News",
|
labelKey: "news",
|
||||||
description: "Factual, concise, and impartial journalistic style",
|
descriptionKey: "newsDesc",
|
||||||
icon: Newspaper,
|
icon: Newspaper,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "social_media" as const,
|
value: "social_media" as const,
|
||||||
label: "Social Media",
|
labelKey: "socialMedia",
|
||||||
description: "Concise, attention-grabbing, and shareable",
|
descriptionKey: "socialMediaDesc",
|
||||||
icon: Users,
|
icon: Users,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function ReportStyleDialog() {
|
export function ReportStyleDialog() {
|
||||||
|
const t = useTranslations("settings.reportStyle");
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const currentStyle = useSettingsStore((state) => state.general.reportStyle);
|
const currentStyle = useSettingsStore((state) => state.general.reportStyle);
|
||||||
|
|
||||||
@@ -68,12 +70,9 @@ export function ReportStyleDialog() {
|
|||||||
title={
|
title={
|
||||||
<div>
|
<div>
|
||||||
<h3 className="mb-2 font-bold">
|
<h3 className="mb-2 font-bold">
|
||||||
Writing Style: {currentStyleConfig.label}
|
{t("writingStyle")}: {t(currentStyleConfig.labelKey)}
|
||||||
</h3>
|
</h3>
|
||||||
<p>
|
<p>{t("chooseDesc")}</p>
|
||||||
Choose the writing style for your research reports. Different
|
|
||||||
styles are optimized for different audiences and purposes.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -82,17 +81,14 @@ export function ReportStyleDialog() {
|
|||||||
className="!border-brand !text-brand rounded-2xl"
|
className="!border-brand !text-brand rounded-2xl"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
>
|
>
|
||||||
<CurrentIcon className="h-4 w-4" /> {currentStyleConfig.label}
|
<CurrentIcon className="h-4 w-4" /> {t(currentStyleConfig.labelKey)}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<DialogContent className="sm:max-w-[500px]">
|
<DialogContent className="sm:max-w-[500px]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Choose Writing Style</DialogTitle>
|
<DialogTitle>{t("chooseTitle")}</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>{t("chooseDesc")}</DialogDescription>
|
||||||
Select the writing style for your research reports. Each style is
|
|
||||||
optimized for different audiences and purposes.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="grid gap-3 py-4">
|
<div className="grid gap-3 py-4">
|
||||||
{REPORT_STYLES.map((style) => {
|
{REPORT_STYLES.map((style) => {
|
||||||
@@ -111,11 +107,11 @@ export function ReportStyleDialog() {
|
|||||||
<Icon className="mt-0.5 h-5 w-5 shrink-0" />
|
<Icon className="mt-0.5 h-5 w-5 shrink-0" />
|
||||||
<div className="flex-1 space-y-1">
|
<div className="flex-1 space-y-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<h4 className="font-medium">{style.label}</h4>
|
<h4 className="font-medium">{t(style.labelKey)}</h4>
|
||||||
{isSelected && <Check className="text-primary h-4 w-4" />}
|
{isSelected && <Check className="text-primary h-4 w-4" />}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-muted-foreground text-sm">
|
<p className="text-muted-foreground text-sm">
|
||||||
{style.description}
|
{t(style.descriptionKey)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
||||||
import type { Resource } from "~/core/messages";
|
import type { Resource } from "~/core/messages";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
@@ -14,6 +15,7 @@ export const ResourceMentions = forwardRef<
|
|||||||
{ onKeyDown: (args: { event: KeyboardEvent }) => boolean },
|
{ onKeyDown: (args: { event: KeyboardEvent }) => boolean },
|
||||||
ResourceMentionsProps
|
ResourceMentionsProps
|
||||||
>((props, ref) => {
|
>((props, ref) => {
|
||||||
|
const t = useTranslations("common")
|
||||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||||
|
|
||||||
const selectItem = (index: number) => {
|
const selectItem = (index: number) => {
|
||||||
@@ -79,7 +81,7 @@ export const ResourceMentions = forwardRef<
|
|||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div className="items-center justify-center text-gray-500">
|
<div className="items-center justify-center text-gray-500">
|
||||||
No result
|
{t("noResult")}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
23
web/src/i18n.ts
Normal file
23
web/src/i18n.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import { cookies } from "next/headers";
|
||||||
|
import { getRequestConfig } from "next-intl/server";
|
||||||
|
|
||||||
|
// Can be imported from a shared config
|
||||||
|
const locales: Array<string> = ["zh", "en"];
|
||||||
|
|
||||||
|
export default getRequestConfig(async () => {
|
||||||
|
// Get locale from cookie
|
||||||
|
const cookieStore = await cookies();
|
||||||
|
const cookieLocale = cookieStore.get("NEXT_LOCALE")?.value;
|
||||||
|
|
||||||
|
// Validate that the incoming `locale` parameter is valid
|
||||||
|
const locale =
|
||||||
|
cookieLocale && locales.includes(cookieLocale) ? cookieLocale : "en";
|
||||||
|
|
||||||
|
return {
|
||||||
|
messages: (await import(`../messages/${locale}.json`)).default,
|
||||||
|
locale,
|
||||||
|
};
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user