📱

Get Our Mobile App

Take your business learning on the go!

Download on the App StoreGet it on Google Play

Build & Deploy a Full Stack Autonomous AI Agent SaaS (Like OpenClaw) - Next.js, React, Claude

Ankita Kulkarni2:36:40

Transcription

你好,我叫 Ankita,今天我们将构建并部署一个完整的 AI 业务,它代表了我们在 2026 年看到的巨大转变。你可能听说过 OpenClaw。它是一个重新定义自主代理实际工作方式的框架。所以在这个视频中,我们将使用 OpenClaw 使用的确切架构进行构建。我们将构建一个名为 exec OS 的高收入自主 AI SAS。我们正在超越从头开始构建一切的时代,转向使用 MCP 和技能。所以这正是我们今天要做的事情。成为一名高级开发人员就是关于架构和编排。所以 Exec OS 是一个自主的执行官,它以 15 分钟的心跳运行。它通过起草电子邮件草稿和会议跟进来分类你的收件箱,通过检测何时需要安排日历事件并在你提出要求之前起草下一步。要做到这一点,我们将使用 2026 年领先的文本黑客技术。我们将掌握明年的 16 岁以及 React 19,利用服务器和客户端组件、服务器操作、API 路由作为 exec OS 的大脑。我们将使用 anthropics 最新的云模型。我们使用 Versel AI SDK 来驱动代理的推理。我们将使用 Google API 作为我们的代理工具,直接与 Gmail 和 Google 日历交互,以便 AI 代理可以在你的系统中自主运行。对于 UI,我们将使用 Tilvin CSS 进行专业的响应式设计,并使用 Shatsian UI 组件库构建一个可视化代理实时推理的现代仪表板。我们将使用 clerk 进行强大的身份验证,并至关重要的是使用 clerk billing 来管理订阅、试用、基于使用量的门控。因此,将你的代理逻辑转化为经常性收入引擎。我们的整个数据层将由 Drizzle OM 和 Postgress 数据库提供支持,这是世界上最先进的关系数据库,使我们能够无限扩展代理的内存。我们将将其部署在 Versel 上,使我们能够更快地发布可扩展的应用程序。现在,无论你想建立一个副业还是一个全规模的 AI 机构,这个视频都是你 2026 年开发的蓝图。所有这些都将使你的技术栈免费,因为我们将使用的所有服务都是完全免费的。但是,要提供代理推理,你需要 clawed API 令牌。现在,如果你没有现有的 entropic 订阅,别担心。看看我频道上的这个视频,它会告诉你如何使用开源模型来驱动这个特定的应用程序。现在,为了帮助你跟上进度,你可以免费下载一个备忘单以及 GitHub 源代码,但评论是根据我们正在构建的功能进行划分的。所以,停止构建 AI 包装器,开始训练代理。那么,让我们开始吧。我们将构建 Exec OS,你的自主执行助理。你可能听说过 OpenClaw,AI 实际上在做事。这正是 Exec OS 将要做的事情。现在,请记住这一点。Open Claw 非常强大,它不仅集成了 Gmail 和 Google 日历,而且还能够集成许多不同的服务。但是,我们将在本视频中使用的架构、基本原理和基础知识,你将了解 openclaw 的实际工作原理。execos 所做的是,它确实是你的自主执行助理。它清空你的收件箱,发送电子邮件,管理你的日历,为你创建一些任务,所有这些都在你的仪表板中完成。你根本不必担心指导它。一旦你集成了服务,你就基本上准备好了。这是 Exec OS 的精美登陆页面。我们将使用 Clerk 进行订阅。我们有一个免费套餐,允许有限的代理和手动运行代理,这再次促使人们订阅专业套餐,这意味着我们将启动无限代理并自动运行它们。为此,我们将使用 versel 的 prawn 作业来完成。同时,如果用户订阅了,这意味着他们已登录。让我们继续登录。这是我的专业套餐账户。登录后,你已经可以看到我在这里是一个专业用户。然后我们引导他们完成这个基本的入职流程,在那里你可以通过 Gmail、Google 日历连接,他们可以订阅以激活代理。他们必须订阅专业套餐,否则他们将无法拥有我们都喜欢的这种自主行为。正如你所见,代理刚刚运行,状态是成功的,但没有必要处理的电子邮件,或者代理实际上没有什么可做的。但是正如你所见,我们也可以手动运行该代理,或者让它自主运行。当你转到监控时,你可以确切地看到代理处理了什么。例如,你可以看到待审查的行动项目。当你展开它时,你可以看到这是起草的回复,需要讨论的电话。这些是创建的不同行动项目,以及创建的不同日历项目,你可以看到到期日期。所有这些都由不同的代理为你自动处理。当我们转到设置页面时,你可以管理你的集成和偏好。你可以在这里简单地添加各种不同的工具。例如,Gmail、Google 日历等。所以这正是我们今天要做的事情。与 open claw 完全相同的架构,类似的架构,你可以了解它是如何构建的,并了解所有这些的基本原理。现在让我们来理解 exec OS 的架构,确切地说明我们将如何构建整个应用程序。所以首先,当用户注册时,他们将使用 NexJS 16 app router,这是构建特定应用程序的最新和最伟大的技术。NextJS 是一个非常强大的框架,这正是 exact OS 将使用它的原因。然后我们将使用 clerk 进行身份验证,用于中间件,用于计费等。所以当用户登录时,clerk 中间件将介入并基本上检查这个特定的用户是否可以登录到这个账户,注册等等,这个用户实际上可以访问哪些服务。所以显然登录和注册是公共页面,所以他们可以轻松访问它,但是当涉及到运行代理时,那就是代理运行在这里,我们也将构建一个 API,clerk 或中间件将负责保护它。所以不应该有访问权限的用户将无法访问它。这些是我们应用程序中的不同路由。例如,所有路由都是登录和注册。主路由是仪表板、监控和设置。我们转到仪表板。我们有仪表板、监控和设置。然后对于 API 路由,你将有一个代理运行的路由。我们将有一个 API/odd/google/callback 用于 Google API,我们可能还有一个 post API web hooks 调用,尽管这完全是可选的,我们稍后会讨论。然后你将有一个公共路由用于登陆页面以及登录和注册页面,因为这些是公共路由,应该可以被任何人访问。然后当涉及到核心库时,我们将将其分为几个不同的变体。一,核心库,即 lip 文件夹,将有单独的服务用于运行代理,用于处理电子邮件,用于 Gmail 和日历,我再次给你一个关于整个项目架构的想法,所以你应该能够思考为什么我们选择我们选择的方式,这是你们很多人提出的一个请求,我们可以更多地关注架构而不是仅仅编码,这样你们所有人都能很好地理解为什么我们这样做。然后我们有了 DB 文件夹,在那里我们将有 schema 查询和 drizzle OM DB 文件夹将托管所有与数据库相关的内容,即我们有 schema,任何我们需要的查询,以及我们将使用 drizzle OM。我们使用 drizzle OM 的原因是 OM 确实有助于我们与 SQL 查询进行交互,它负责找出它需要做的所有事情来编写最高效的查询。现在,如果你问我,我非常喜欢编写原始 SQL 查询。但是,有了 AI 的参与,你可以完全使用 OM 并让 OM 也编写 SQL 查询。特别是 Drizzle,它非常擅长编写高效的查询,这正是你将使用 Drizzle OM 的原因。你也可以看到 Drizzle 和 Prisma 之间的性能非常惊人。所以,这正是我们将要关注的。然后当涉及到 lib/encryption 时,任何时候我们与 Google API 交互,我们需要获取 Google 给我们的令牌,这样我们就无需一遍又一遍地重新连接我们的服务。例如,在这个特定的应用程序中,一旦用户连接了 Gmail 和 Google 日历,我们真的不想一直要求他们。所以这正是我们将要存储的,并且我们将把逻辑存储在 encryption.ds DS 中,我们将加密密钥并确保我们使用此算法来解密它,以用于任何类型的令牌安全。这样我们就不会泄露任何数据。然后是激动人心的部分,即 lib/AI。我们将使用 Versel AI SDK 和 Zord schema 来编写我们整个的代理流程。例如,这是 Versel AI SDK。它同样来自 Nex.js 的创建者,当涉及到编写 AI 代码或我应该说原始 AI 代码时,任何人都可以做到,但使用 SDK 确实为我们提供了构建强大 AI 产品所需的工具。我的意思是,AI SDK 为我们提供了一个非常方便的界面,我们可以简单地切换。例如,如果我们想生成文本,我们只需要改变这个小字符串,即模型和 progue,它会自动调整。它会自动切换模型。它使用 OpenAI 模型。你可以选择 anthropic 模型等等,以便我们构建 AI 应用程序。我们从 AI 获得的响应老实说最令人讨厌。它返回了许多不同的东西,需要进行解析才能在我们的 UI 上显示。AI SDK 也使我们更容易做到这一点。所以这正是我们将要使用的。然后我们在 utils 中将有许多不同的 utils 用于图标,用于 shad 和组件,我们将使用该包来合并不同的类名,称为 deal with merge。同样,一旦我们实际构建它,我们将讨论这一点。然后当涉及到 services 和 external layer 时,我们实际上依赖于许多不同的服务,例如 clerk,而 clerk 老实说是我构建应用程序的首选。你也可以在 next JS 课程中看到,我实际上也在使用 clerk 来为我的特定平台。原因如下。再次,我偷偷看了这段代码。如果你想使用它,我绝对会为我的课程打八八折。但回到它,但老实说,Clerk 非常棒,因为它不仅管理 O,而且它是一个完整的用户管理解决方案。我已经在行业工作了十多年。我从头开始构建了身份验证层。就像我尝试管理令牌一样。我编码解码令牌,处理刷新令牌。如果你想确保用户保持登录状态,请以安全的方式进行,管理所有用户数据,确保我遵守所有不同的合规性要求。所有这些都可能变得非常多,这就是为什么我非常欣赏 Clerk 这样的服务。Clerk 绝对赞助了这个视频,但老实说,我是一个巨大的粉丝,我不会在这个频道上推荐任何我个人不使用或不喜欢的赞助商。所以,总的来说,clerk 很棒,但除此之外,clerk 另一个我们都应该使用 clerk 的原因是,特别是当我们想销售东西并想构建一个 SAS 应用时,这正是我们在这里所做的,这个特定的应用是我们想要提供给他人并出售它,从中赚钱,在这种情况下,clerk 简直是不可思议的,原因如下,首先,现在 clerk 有许多不同的组件我们可以使用,所以你不必浪费时间构建注册模态框、登录、用户按钮、用户配置文件、等待列表。这是另一个对 SAS 很棒的。如果你想卖东西,你还没有准备好,你想看看是否有需求,你可以添加一个等待列表组件,然后开始收集电子邮件。但除此之外,真正酷的是计费。现在,Pler 也允许你向客户收费,再次通过提供这个定价表组件,你可以配置不同的功能是什么。但是,无论是试用、月度计费还是年度计费、计费取消、信用卡盗窃,它都会自动管理。web hook 设置。假设用户从个人套餐(免费套餐)订阅到专业套餐。即使是 clerk 也会自动管理。此外,我将向你展示它还具有方便的功能。此外,它还具有这些方便的功能,你可以使用测试信用卡来测试你的定价。所有这些都已内置,所以你真的不需要做任何事情。我构建了几个产品,我实际上正在后台构建一个产品,我正在使用 clerk,而 clerk 已经消除了 stripe 在我生活中造成的混乱和麻烦。Stripe 很棒,而 clerk billing 实际上建立在 stripe 之上,但他们增加了一个额外的层,我们不必处理那些 stripe API 和所有不同的 web hook,老实说,秘密和管理所有这些都变得非常复杂。Clerk 为我们处理所有这些,这简直太棒了。所以这正是我们将要使用的。此外,我们将使用 clerk skills,它基本上是一个包,为你的 AI 编码代理提供关于 clerk 的专业知识。只需进入 adb,然后使用它来告诉我们的代理,嘿,这就是我希望你如何设置它。所以你不需要从头开始学习一切。这些 skills 包将帮助我们做到这一点。同样,稍后将会有更多关于这方面的内容。所以回到项目架构,我们将使用 clerk 进行身份验证、计费、订阅管理和 web hooks。然后对于 postware SQL,我们将使用 railway。现在你也可以简单地将你的应用程序部署在 railway 上。你不一定需要只使用一个特定的服务,但我们将使用 railway 作为我们的进程数据库。所以 postgress 数据库在 railway 上是完全免费的。正如你在这里看到的,它已经部署了。这些是我们拥有的不同表,你可以深入其中查看确切的数据,数据是什么,代理的不同状态,摘要等等,你还可以创建惊人的备份等等。他们仍然会将其部署在 Versel 上,因为 Versel 是一个用于部署你的 next JS 应用程序的不可思议的平台,所以我们将坚持这一点,但请随时部署东西在 railways 上,老实说,很棒,但就 post SQL 而言,我们将存储关于我们的用户、我们的集成、我们的任务、我们的代理运行的信息,所有这些都将存储在 postgress SQL 数据库中。然后我们将使用 cloud 进行 AI。我们已经讨论了使用 AI SDK 来解析 AI 数据并使用它,并将其作为额外的层,但我们将使用 cloud AI 和 set for API 来完成。Sonet core for model for that as well。我们将使用它进行电子邮件分析、结构化输出以及使用 zord 解析数据。所以我们不会只是将乱七八糟的数据或恶意数据添加到我们的数据库中。然后当涉及到 API 时,你将使用 Google、Gmail、日历和 oath。所有这些基本上都是为了驱动我们的自主代理。你只需要一次授予访问权限,一旦完成,你就无需担心了。这正是我们将要使用的架构。然后当涉及到托管时,我们将使用 Versel,但我们也将使用 jobs,以便我们的代理能够自主运行,而无需每次手动运行。所以,是的,这就是整个架构。现在,如果你想知道,为什么我们要使用 CLA AI?为什么我们不能使用 OpenAI?你完全可以这样做。如果你有 OpenAI 的免费套餐,完全可以。如果你有 Claude 的免费套餐,也可以。事实上,我的 YouTube 频道上有一个视频,我谈论了如何开始使用开源模型并进行构建。我将在下面的描述中列出你可以使用的三个模型。如果你使用开源,你需要一台强大的机器来运行这些模型。所以也要记住这一点。废话不多说,让我们开始构建吧。那么,现在让我们进入。在这里我们可以称之为 exact OS。让我们就用推荐的默认设置吧。这将意味着它将安装我们项目中的所有依赖项以及开发依赖项。我们在这里使用了 bun,我们将使用它来创建 next 应用程序。然后让我们进入目录,并确保简单地在你的编辑器中打开它。所以我们可以这样做,打开并像这样打开它,或者简单地在光标中打开它。现在,当我们的项目在这里打开时,让我们关闭所有这些。现在我将使用 cursor 作为我们的编辑器。但是,你可以随意使用任何你喜欢的编辑器。现在,当我们在这里深入研究时,我们有 app router。所以这就是为什么我们在里面有一个 app 文件夹。如果你想深入了解 NextJS 应用程序如何工作以及 NextJS 通常如何工作,那么请查看我 YouTube 频道上的这个 Nex.js 16 崩溃课程。同时,如果你想更深入地了解并使用 NexJS 构建生产就绪的应用程序,那么也请查看 next.jscourse.dev。好的。所以现在我们有了 nextJS app router,我只提几件事。一,任何时候你想公开一个公共路由,你需要创建一个名为 page.tsx 的文件,因为 nextJS app router 使用基于文件的路由系统。同样,如果你转到 layout,这是一个包装你整个应用程序的文件。这意味着它有 HTML 和 body 元素。任何你想让你的整个应用程序访问的东西都应该存储在 layout 中。这就是为什么我们在 layout 中有字体,一些元数据信息等等。现在我们有了这个,让我们也关闭这个窗口,然后让我们安装。我们将转到 docs installation,你可以创建一个新的项目,使用 shots 和 UI,或者简单地选择框架并复制它。同样,我们在这里使用 over here。让我们粘贴它。然后让我们就用所有默认设置。好了。Chadian 只是简单地创建了我们需要的许多文件。所以如果我们回去,你会看到 globals.CSS 有许多不同的变量,Chad CNN 使用。它还为我们设置了一些颜色。Page.tsx 没有受到影响,但有 page components 或 JSON,chats 使用它来映射所有文件和文件夹。然后我们有一个 lib 目录,里面有 utils。这就是我在项目架构中谈到的。我们有一个名为 tails and merge 的包,shat 使用它。所以当你想要组合一堆类名并添加 is else 条件来设置样式时,你可以简单地使用 CN 函数,它基本上调用 TWW merge。所以你可以轻松访问类名。我们很快就会看到一个例子。好的。现在让我们确保我们的 next js 应用程序已完全设置好。让我们转到终端。现在在这里,让我们运行 pun dev。这将基本上选择可用的端口并运行我们的开发服务器。现在它运行在 localhost 3001。3000 端口已被占用,但也许对你来说是 3000 端口。我有一个东西在 3000 端口运行,这就是为什么它选择了 3001。去浏览器,只需 localhost 3001。瞧,我们的 next 应用程序已经准备就绪,这太棒了。让我们去 paste our DSX 并删除所有内容。让我们确保我们的 shad CN 工作正常。所以,让我们添加 hello world。删除未使用的导入。现在屏幕上出现了 hello world 文本。让我们去 shad CN UI 并安装我们需要的许多组件。所以,我们将需要 card 组件,为了添加它,我们只需要使用这个特定的行。让我们转到终端并停止服务器并粘贴这个特定的行。我们不仅需要 button,还需要 card、batch、progress、input、tabs、avatar、drop-down menu 等等。将所有这些组件安装到我们的项目中。现在,这些基本上都是文件,这就是 Shatsen UI 组件库的优点。让我们启动服务器。现在让我们去 components 文件夹。现在我们有一个新的 UI 文件夹,里面有所有这些不同的组件,随时可用。所以我们可以简单地访问它。完美。现在让我们在这里添加 button。这个 button 导入来自 shad CN UI。好了。让我们导入它。如果我们回到 localost 3001,我们确实得到了一个 button 组件。太棒了。所以现在 shaden UI 组件库工作正常。我还将给你将在整个项目中使用的确切样式。我们不需要过多关注样式,而是更多地关注 open claw 架构。我现在将继续。为了开始使用 clerk,首先,请确保你已登录,如果你已经有一个账户,或者简单地注册。确保使用下面的链接,或者让 Clerk 知道我推荐了你,这样我就可以继续在这个频道上制作免费内容,就像这个视频一样。现在,Clerk 的定价完全免费。所以,你可以使用他们的 Holly 套餐,并免费获得无限的应用程序和所有这些功能,他们甚至不向你要信用卡。所以,你可以免费开始构建,然后在这里注册。由于我已有账户,我们也可以简单地登录。现在,正如你所看到的,对于 app router 的快速入门,你可以创建一个新的 NexJS 应用程序,安装 clerk,设置 clerk 中间件,等等。现在,你可以手动执行所有这些步骤,或者我们可以简单地使用 clerk skills。现在,clerk skills 本质上是可安装的包,这些 skills 有助于 AI 编码代理更有效地与 Clerk 协同工作。skills 通常很棒,因为现在你不需要从头开始设置一切,并且确切地知道该做什么。库和不同的公司基本上提供了 AI 所需的知识。我们如何训练 AI?我们如何让 AI 对我们的服务有更专业的了解,以便我们开发者更容易使用它?我们将复制它。再次,它说 NBX,但我们也可以使用 bun 来做到这一点。所以让我们去这里,停止服务器,类似于我们有 npx,我们只需要 1 x。然后我们可以复制其余的命令。所以在这里我们可以说 pun x clerk skills。这将安装许多不同的 skills。我们可以选择我们希望它安装的 skills。所以我要说我们需要 clerk setup。我们不需要 clerk organizations,但我们绝对需要 next year's patterns。如果你想自定义 clerk 组件的外观,我们可以做到。所以,让我们这样做。然后,让我们也在这里添加 clerk。你也可以得到 cler pestl。好了。现在,这完全取决于你将要使用的应用程序或你将要使用的代理。在我的例子中,我有 claw code。所以,我将获得 claw code skills。但是,你可以简单地选择你正在使用的任何 AI 编码工具。例如,augment 或 kilo 或任何你喜欢的。Linser,任何你喜欢的,但我将简单地在这里选择 CL code。让我们继续安装它。这是项目。让我们添加一个组装。继续安装。现在让我们回到运行我们的开发服务器。现在如果你看到我们有一个名为 .claude 的文件夹,里面有 skills 文件夹,里面有所有已安装的 skills。这本质上是 skills.md 文件。这里也是一样,它包含了关于 clerk 或代理需要知道的所有信息。现在让我们打开 claw code。如果你没有它,打开任何你想要的代理。例如,如果你有 cursor agent,那么你只需要做 agent 或任何东西。假设你根本不使用 AI。我强烈建议你这样做,因为在 2026 年,老实说,如果你不使用 AI,你将被甩在后面。所以,请确保你至少订阅其中一项服务来使用它。但是,假设你没有,那么你也可以简单地手动完成,因为这正是我们将要做的。好的。所以现在我们可以简单地添加 clerk。所以我们可以简单地做 clerk setup,这将设置我们项目中的 clerk 身份验证。它将读取 package json 并为我们做所有事情。再次,这正是 clerk quick start 的指南。所以我将说允许 bun 用于此项目。但它将获取 CL NextJS SDK。它自动选择了我正在使用 bun。让我们做 all。现在它正在编写一个中间件文件。在 nextJS 16 中,中间件实际上已被弃用。所以我们将切换它以使其成为代理。但现在没关系,它只会给我们一个弃用的警告。现在它将更新根布局以包含 CL provider,这很棒。好了。所以现在它已经创建了中间件。它有布局等等,并且它添加了来自 cl 的这些组件,例如 signed out、sign in button、sign up button 等等。所以现在如果我们转到 localhost 3001,如果我们刷新它,你现在可以看到 hello world 和 sign in、sign up。再次,它将我们重定向到 clerk 上的这个 URL,目前这是一个虚拟 URL,因为我们还没有真正创建 clerk 账户。但正如你所见,它在这里说临时 API 密钥已启用,所以你可以立即开始,这正是我们正在做的。所以你可以简单地配置应用程序并开始使用。所以让我们称之为 exact OS。好了。然后设置你的环境变量。所以我将复制这个。让我们在这里的根目录中添加 l.lo 文件。粘贴这两个变量。完成。它自动检测到我们已成功设置。然后我们可以恢复开发。所以现在你的应用程序已完全配置。是不是很棒?现在看看我做 sign in 时会发生什么。它确实将我们带到一个新路由,这没关系。让我们实际上去登录并用我的账户登录。接受一切并验证我是一个人类。好了。我们现在有一个用户按钮。这就是我之前谈到的 clerk。所有这些都自动可用。我们不需要构建任何这些。它就在那里。如果我退出,我们就回到了登录和注册链接,这太棒了。所以,cler 已经自动为我们设置好了一切。现在,如果我只做 clerk,你就可以看到 clerk setup、clerk custom UI 等等。现在我们只做 custom UI,因为我们不想每次登录都去这个随机 URL。我们希望它去 / signin URL。所以,现在这正是它将要设置的。它正在安装 clerk themes 包。它正在获取 shad CNN CSS 导入。让我们关闭它。将 SHAT CN 主题应用于 clerk provider。好了。所以现在如果我们看一下,我们有 layout。它添加了一个 B base theme 作为 shaden。现在如果我们刷新页面并转到 sign in,它有这个漂亮的主题。它不再是我们之前那个黑色主题,但它仍然去不同的路由。好的。所以现在让我们创建 sign in 和 sign up 路由。滚动到底部,你可以看到 custom sign in 和 sign up。现在让我们复制这段代码。这就是我们需要的。我们需要这个特定的文件。所以让我们去这里,创建一个新文件并粘贴它。让我们复制这段代码,粘贴它。然后让我们去 signup page。让我们复制这个确切的东西。去 app 目录,新建文件,粘贴它。这是创建 signup page。复制这个。去这里并粘贴它。好了。所以现在我们有了 sign in 和 sign up 页面。所以现在如果我们去这里,去 sign in,然后让我们去 proxy 或 middleware,在我们的情况下,粘贴整个东西,这就是我们要创建路由匹配器的地方,并说它是公共路由,然后如果它不是公共路由,则保护它,否则这些是公共路由。接下来我们需要做的是更新它需要是什么路由。所以让我们复制这个。去 n local 文件并粘贴它。我总是喜欢添加一个注释,称为 clerk。这样我们就可以区分开。同样,对于 sign in,它将是 sign in,就像这样。现在如果我们去 localhost 3001,它会自动将我们重定向到 / signin,这正是我们的页面。所以这正如我们想要的那样工作。太棒了。同样,如果我将其更改为 sign up,它将带我们到 sign up。但是当我转到 localhost 3001 时,它仍然带我们到 sign in。原因如下。这是因为在我们的中间件中,我们仍然需要说 /。这样,/ 就成为一个公共路由。现在我们可以从这里去 sign in。它带我们到 sign in。Sign up。它不再是那个虚拟 URL。而且它实际上是我们的实际 URL。所以它看起来像是我们网站的一部分。现在我们需要做的下一件事是去 global.css CSS 并更新这些样式,因为我们不需要它。所以让我们复制整个东西并粘贴它。然后我们只需要另一行,就是这一行,其他所有东西都可以删除。好了。所以现在如果我们刷新页面,瞧,我们的应用程序现在默认是暗模式。如果我去 sign in 或 sign up,它实际上与我们的主题需求相匹配,这正是 clerk 自动从我们的 shadian 变量中采用的,这太棒了。所以我们的 clerk 模型实际上看起来像我们的设计,作为我们 SAS 的一部分,这太棒了。好的。现在如果我们去 home,我们看到 hello world 和 button click me here。现在,由于我们还将有其他路由,让我们在这里创建一个分组,并通过简单地在这些括号中添加 o 并移动这个文件夹,我们可以轻松地对路由进行分组。现在当你开始移动它们时,你会发现它有点分散。只需将其移到这里。好了。所以现在我们回到了起点。如果我们去 localhost 3001,去 sign in,它仍然按预期工作。现在这个特定的模型没有居中。所以让我们把它居中。所以让我们添加一个名为 appearance 的属性。复制这个确切的东西。去 signup page 并在这里也添加相同的。同样,让我们将其包装在一个 div 中,并给它一个名为 o container 的类名。就像这样。现在,我们可以在 sign in 中也这样做。O container。然后在这里包装它。好了。现在你可以看到,现在它稍微居中了。所以如果我去 sign in 或 sign up,它基本上是一样的。就像如果我回到 home,它仍然有效。现在其余的路由可以放在另一个分组中。我们称之为 main。好了。在这里我们可以创建另一个页面,而不是 home,而是 dashboard page。这个特定的 dashboard page 将包含仪表板的所有信息。我们只需要在这里创建一个新文件夹,然后在这里添加 dashboard,然后将 page 移到里面。现在我们有了 dashboard page。现在如果我们回到这里并去 / dashboard,它实际上会带我们去 sign in。所以我们的逻辑正如预期的那样工作,因为我们说 dashboard 或任何其他除了 sign in、sign up 和 / 之外的页面都将是私有页面。接下来我们需要做的是,让我们将这个更新为 proxy.ps PS,因为中间件实际上已被弃用,但其他一切都保持不变。它无关紧要。所以现在如果我刷新它,它仍然有效。如果我去 / dashboard,它仍然带我们去 sign in,这正是我们想要的。太棒了。现在让我们更新 layout 文件以选择我们想要的字体以及一些元数据详细信息。然后我们再添加一个页脚。让我们去 layout,就在 children 的正下方。我们这里不需要这些。我们需要将它移到我们这里有的 header 组件。让我们添加一个页脚。页脚将只是一个页脚,带有一个页脚包装器部分标题。居中文本,然后是版权图标和我们的名字。我们也可以说 new date get full year。这样我们就无需每年更新日期。现在如果我刷新,你可以看到我们的页脚在所有页面上都可用。接下来,我们不需要这个字体。我们实际上需要一个名为 Monsera 的字体。所以,让我们获取那个字体。为了设置字体,因为我们正在使用 next font,它们都是我们可以访问的 Google 字体,我们可以简单地创建一个新对象然后直接使用它。但只需 monetera.class name。这样,它就会自动应用到所有地方。然后,而不是称之为 create next app,让我们称之为 exec OS,并称之为 AI executive assistant text 将是这样的。显然这简化了它,但你可以随意调整这些元数据文本。现在如果我们回去,我们的字体已经改变了,如果我们去我们的浏览器开发者工具的 head 标签,我们现在有首先是标题,以及描述,这也是我们想要的。太棒了。接下来,如果你注意到我们的应用程序中的 header 有点不同。例如,这个没有 header。它有一个侧边栏和这个标志。但是如果我们去 home,我们有一个 header,有一个标志,一个 dashboard 链接和一个用户按钮。所以,让我们只更新我们的主页,使其具有我们想要的 header,因为那是我们唯一想要那个 header 的地方。首先,让我们添加一些类名。但在我们来回切换之前,让我们将窗口移到右边。所以现在让我们将这个 div 更新为具有一个类 tail of landing wrapper,这将给它必要的间距。然后让我们去 layout 并将这个 header 从这里移到 page,让我们删除所有这些,并且让我们也从 layout 中带入导入。我将这样做,删除 button 导入等等。现在对于 header,让我们就说这将是 landing header。将有另一个类。我们称之为 landing header inner,它将有另一个类名为 logo container,它将有一个 execos 的文本以及一个 next link 组件,它将重定向回 home。所以让我们给它一个 no text,带有 exacos。然后紧接着在链接下方,我们将有一个 sign in,然后显示用户按钮,我们还给它一个类名为 nav actions,而不是 user buttons,而是在 nav actions 中,让我们只做 make dashboard 和一个按钮,变体为 dashboard,但不是 outline,它将是一个 ghost button,正如你在这里看到的,让我们也将 user button 移到这里。现在当涉及到 sign out 时,让我们将所有这些都移到 sign in 的正下方。在 sign out 中,让我们也创建 nav actions,并将其包装在 signed out 中。让我们删除这个。现在它自动在右侧。现在,如果我们登录,它实际上将有一个 dashboard 和一个用户按钮,这正是我们在这里想要的。接下来,紧接着它下面,让我们给它一个 section。这就是其余内容将存在的地方。例如,我们将有这个文本和这两个按钮。我们将拥有强大的功能和定价表。让我们退一步。现在这个特定的页面有一个定价表。这个定价表实际上是来自 clerk 的定价表。所以我们可以很容易地添加它。所以在我们构建英雄部分和其他功能之前,让我们在这里完成 o 周期。当用户点击时,因为我已经登录了,它会自动给我一个切换到此计划的选项。让我们看看一旦我们这样做会发生什么。它会自动弹出结账模型,我只需要订阅。是不是很棒?但这都是由 clerk 定价表驱动的。所以我们可以把它加在这里。然后当我们去 dashboard 时,让我展开它。左下角,我们显示一个 pro 用户或不是。所以我们需要显示它,并且根据这个 pro 状态或免费套餐状态,我们需要配置很多事情。所以我们如何将其作为基础设施添加,然后我们可以在此基础上进行构建。我们的 section 将需要一个 section heading 和一个 H2。所以让我们称之为 simple transparent pricing。然后我们将添加来自 clerk 的定价表,它将只是这个。现在当计费被禁用时,定价表无法渲染。为此,我们只需要去 clerk dashboard。正如你所见,我在 clerk 上有各种应用程序。所以我绝对是一个重度用户。但是如果你点击这里,这是我们设置的应用程序。然后让我们去 billing 并启用 billing。Billing 已启用并点击保存。转到 subscription plans 并在这里添加各种不同的 subscription plans,它们将自动显示在卡片上。让我们不要操之过急。让我们刷新页面。现在你可以看到一个免费套餐已自动显示。让我们确保它与我们想要的外观匹配。所以让我们就做 appearance,然后让我们做 base theme 为 dark。但我想,由于 base theme 已经设置好了,你不需要担心它。让我们先看看 CL 提供了什么。所以现在如果我们回到这里,让我们添加两件事,让我们添加另一个名为 pro plan 的套餐。然后让我们就做 pro plan。你可以添加任何你想要的描述。我们会收取多少费用?这是一个 19 美元的套餐。所以让我们设置 19 美元。如果他们订阅它以获得年度折扣,那么他们确实会获得一定的免费金额。所以提供订阅者以这个折扣的月费支付年费的选项。所以月费可能是 16 美元,例如,如果你想给他们免费试用或不。同样,这就像切换它一样简单,然后按保存。所以现在如果我们去这里,你会看到我们的 probe plan 在这里。将其关闭。否则它是 19 美元,否则它是 16 美元。太棒了。好了。所以现在我们有了这两个套餐,我们也可以在这里添加不同的功能。所以如果你看到我们的功能是无限代理自动运行,有限代理手动运行代理。所以让我们就做无限代理创建。相同。让我们添加另一个功能自动运行创建。相同。这是专业套餐。现在如果我们去这里,去免费套餐,让我们也在这里添加功能。我们可以勾选这些,或者简单地创建一个新功能。它将是有限代理手动运行代理。有限代理。保存它。然后手动运行代理。再次保存。就是这样。这就是我们所需要的一切。回到这里,这个。让我们刷新页面。然后 boom。我们在这里有两个套餐。而我们所要做的就是更新它,这太棒了。但是这个套餐是活跃的。但是,如果我想订阅这个套餐,那么我们可以使用测试信用卡,然后支付年费,如果你愿意。正如你在这里看到的,然后点击继续。现在专业套餐已激活。但是如果我将其切换回,让我们就订阅。现在免费套餐已激活 2027 年,因为我已经订阅了专业套餐。现在很酷的是,如果你去 users,我已经在这里作为一个用户。如果我点击它,你将看到我的活动并设置一些元数据信息,禁用我的账户,我订阅了哪些订阅等等。同样,我的支付设置。此外,作为一个用户,因为我已经登录了,我也可以去管理账户,然后去 bill,它带有一个选项,我可以下载我的账单。我也可以自动切换套餐。正如你所见,相同的表格在这里可用。我可以去 payments 并查看更多关于我的支付信息,或者添加一个全新的支付方式,而无需联系,无需联系特定 SAS 的所有者,这太棒了。它也减少了大量的支持票。好了。所以现在我们有了这个,我们可以去 dashboard 并配置 dashboard 以显示用户是否是 pro。所以如果我们去 dashboard,我们现在有这个侧边栏,无论我们导航到 dashboard、monitoring 还是 settings,我们仍然显示这个特定的侧边栏。所以这个侧边栏需要存在于所有登录页面。所以我们可以简单地做的是,在 main 中,我们可以创建一个新的 layout tsx 文件,并且我们在 layout 中添加的任何内容都将自动显示。所以让我们刷新它。现在你这里什么都没有显示。但是如果我说 hi,它将在所有地方显示。所以即使我去 B,这个页面不存在。但是那里会有一些 hi。好了。所以现在首先我们需要做的是检查特定的用户是否已登录。如果已登录,则继续。否则重定向到 sign in。所以检查用户是否已通过身份验证。接下来我们需要检查,如果没有,则重定向到登录,否则显示 children。同样,无论我们是否需要从 DB 获取用户或创建用户,因为这是他们第一次尝试这样做,所以用户可能不存在于 DB 中。所以我们可能需要这样做,但这是我们可以做的一步,我们可以稍后做,因为那将是数据库连接发挥作用的地方。然后我们可以检查用户是否已付款,因为如果用户已付款,我们希望显示 pro,否则不显示,而且我们还想显示许多 nav 项。所以所有这些都可以在这里显示。让我们一步一步来。所以在这里我们可以说 user ID,然后另一个属性称为 has from O。Ora 来自 clerg O,这将使它成为异步的。如果没有 user ID,我们将使用 redirect 函数将用户重定向到 / signin。现在让我们检查一下 clerk 用户是否,让我们就做 current user 并从 clerk server 调用 current user。我们从 clerk 的 current user 函数中获取 cler user。然后在这里我们可以创建 get 或 create 用户,例如,然后获取他们的电子邮件地址等等。但是什么重要呢?用户是否

用户是否已付费?如果用户已付费,则已付费。它具有这个惊人的属性,我们可以检查用户是否拥有专业计划。这个特定的属性应该与我们在这里设置的匹配。所以免费用户计划非常重要。所以现在,如果用户是专业用户,则已付费。所以这里我们可以说,如果它是付费用户,它会说你是一个付费用户,否则你是一个免费用户。所以现在如果我回到主页,我是一个付费用户,因为我每年都为这一切付费,这太棒了。所以我是一个付费用户,我将可以访问该服务,即使我明年切换到免费计划。所以这正是我们在这里要做的事情。但在此之前,我们也可以在这里创建一个侧边栏。所以让我们给它一个类名 layout wrapper。然后让我们在这里构建一个侧边栏。我们将有一个 side sidebar container 一个 div sidebar inner。对于 logo,我们需要 logo container,它将包含 logo text 和一个返回主页的链接。从 next link 导入。好了。就在那下面,我们将有一个名为 sidebar 的 nav。现在应该有一堆选项。我们可以称它们为 nav items。应该是 dashboard。我们就叫它 home icon。而这个 home icon 来自一个名为 lucid react 的包。你可以直接导入它。然后我们将有 monitoring,它将是 mail icon。我将转到 monitoring。然后我们将有 settings,它将是 settings icon。好了。现在我们可以直接循环遍历它,就在这里。就是这样。让我们把这个 div 包在一个 button 里,它来自 button。好了。让我们把这个 div 移下去。好了。所以现在我们看到三个路由。现在,现在我们还将添加这段代码,它说这将是一个 zap icon,但它说如果它不是付费用户,则显示升级到 pro 选项。所以我们将在这里有一堆样式,并解锁自主 AI 代理,然后是一个重定向到定价页面的按钮。最后,就在那下面,我们将有一个带有用户配置文件和来自 clerk 的用户按钮的 sidebar section,因为这就是我们需要在这里底部显示的内容,并将这个 next.js 的东西移到右上角。现在你可以看到这个按钮已经出现了。就在那下面,我们可以说,如果它是付费用户,则显示姓名,或者如果它是付费用户,则只显示一个 badge class name BG primary 并成为 pro。好了。这里有一个 pro 用户。我相信我们可以删除这个,因为它默认是 primary。好了。现在我们可以完全删除这个文本。children 可以放在 main content class name 中。就在那下面,让我们给 main content inner 也添加更多样式。好了。所以现在你可以看到它已经看起来好多了。Dashboard 文本太大了。我们不需要那个。但除此之外,其他一切看起来都很好。稍微缩小一点,以便于查看。但是如果我们转到 dashboard page,dashboard page 应该有类似的逻辑,对吧?这里的布局逻辑,或者因为它可以自动检查,所以不需要再次检查。我们可以跳过它,因为布局已经自动为我们完成了。所以 inside the dashboard page 只有这一个。我们将有一个 page wrapper 而不是一个 h1。让我们删除这个。我们将有一个 div,它将有一个 h1,但带有 page title 和一个 page description,它将是 welcome back。你的 AI 正在发生什么。现在你可以看到这个撇号的东西。我们可以通过添加一个 boss 来修复它,或者我们可以直接去 ESL config 并说 unescape entities 为 off。我们不必转义它。我们也可以减小 sidebar 的大小。为此,他们将转到 globals.css。你可以看到我们有一堆 sidebar class names。large 可以是 64,但否则它将是 24。同样,large 将是 P4,否则它是 B2。就在那里它太小了。所以 40。所以回到 dashboard,我们已经准备好了这个页面。所以让我们快速回顾一下数据库设计。这样我们就能确切地理解为什么我们做出了构建这些表的决定。所以首先,我们的应用程序有四个表,这里是每个表的思考过程。我们将使用与 clerk 自动同步的用户。我们将使用 integrations,它将包含加密的 oat 令牌,就像我提到的,每当我们连接到 Gmail 或日历时。我们不希望用户反复重新认证或登录,因为这会影响代理的自主行为。然后我们有 tasks,它将包括 AI 生成的操作项。所以你可以把它看作是存储 AI 代理创建的不同操作项的一种方式。然后是 agent runs,这基本上是我们的执行历史。代理实际上做了什么?这是我推荐在所有事情中都做的事情,因为如果我们赋予代理自主行为,我们需要确切地知道它们在做什么。这就是为什么我们需要将它们存储在 agent runs 中。这里有几点需要注意,我们以结构化 JSON 的形式存储完整的操作日志,以便以后可以查询它,而无需创建另一个表访问令牌。它实际上是加密的。我们永远不想在我们的数据库中存储原始 o 令牌。然后我们还存储一个名为 created by agent 的字段,它区分 AI 创建的任务和用户创建的任务,以备将来使用。所以,当你设计你的 schema 时,你应该始终提前一步思考,并且对于所有这些数据库交互,我们将使用 drizzle OM。Brazma OM 非常棒,因为它提供了类型安全的数据库查询,运行时开销为零。与 Prisma 不同,它生成纯 SQL,并且不需要沉重的客户端。这就是为什么 Drizzle OM 真正区别于其竞争对手。所以,事不宜迟,现在我们对数据库结构有了更多的了解,我们可以开始创建这些表。现在请记住这一点。我们也可以在数据库中存储更多关于代理的信息。但我认为你只需要存储你真正需要的信息,而不必用不必要的信息充斥你的表。这就是为什么我们保持结构精简,并且可以根据需要随时添加。所以,让我们开始吧。现在让我们设置 drizzle 和 post SQL,以便我们可以开始存储信息并从数据库中收集信息。为此,我们将向下滚动并转到 postquestjs 并复制它。转到终端,停止服务器并粘贴。现在让我们重新启动服务器。现在在这里我们可以初始化驱动程序。所以让我们转到代码。关闭所有文件,然后让我们创建一个名为 db 的新目录。在这个目录中,我们想要一个 index.ds 文件,这个文件将包含一堆信息。让我们创建一个 schema 文件。这里将是我们的所有 schema,这里将是我们的数据库初始化,它看起来会是这样的。正如你所看到的,我们安装了这些包,让我们停止服务器。我们已经安装了 Postgress,但这就是我们初始化数据库的地方。所以,如果不存在数据库 URL,则抛出错误。我们创建一个 posgress 连接,然后再次创建一个 drizzle 实例,传入 schema。Schema 不存在。所以我们需要添加它。所以回到 schema,我们只需要粘贴它。这就是我们看到的 exact schema,但我将解释一些事情。首先,我们有 users 表 integrations tasks 以及一些类型和 agent runs。这是我们在这里所做的。我们这里还有一些 enums 可以使用,例如 subscription status 可以是 none, active, cancelled, past due。Integration provider 将是 Gmail 或 Google calendar,它们是我们代理可以访问的一堆工具。Task status 可以是 pending, completed, cancel 等等。所以,在 users 表中,我们有所有这些信息,对吧?更重要的是,代理是否启用了它?然后是 integrations 表,其中包含所有 o 令牌,例如。然后是 task 表,其中包含所有任务信息,例如 title, description, status, priority 等等。最后是 agent runs 表,其中将包含所有日志,例如处理了多少封电子邮件,创建了多少个任务,创建了多少个草稿,任何错误消息等等。所以我们可以随时在这里调查。然后 typescript 类型实际上是从 drizzle schema 推断出来的,这也是使用 drizzle 的一个优点,因为然后我们可以说 type of off.infers in for select,然后获取 user infer insert mean new user 等等,我总是喜欢遵循这种类型的约定 integration 或 new integration,而 infer select 意味着它将推断该表字段的任何选择。所以所有 integration 字段都将包含在 infer select 中。所以你不需要手动输入。你可以直接使用它。这里也是一样。好的。好的。所以,现在我们有了这个 schema 的外观,让我们继续下一个说明。所以,现在我们有了这个,你可以直接去 get started Postgress SQL。我们需要数据库 URL,因为我们还没有。你还没有创建 drizzle config 文件。所以我们需要创建它。所以让我们复制它,然后将其添加到根目录。所以 drizzle.config.ts。这个文件将在这里。这个位置将始终是 schema 的位置。所以它将是 db/s schema.ts。好的。现在我们只需要将更改应用到数据库,然后它将自动生成迁移,并生成我们的 schema 的描述,然后将其推送到我们的数据库。现在这里有一个问题。我们还没有数据库 URL。所以让我们去 Railway。Railway 再次非常棒,你可以在这里免费获得很多不同的服务。所以请务必注册 railway,然后在这里,让我们继续创建一个数据库。所以,正如你所看到的,我们可以从 GitHub 仓库数据库空项目等创建一些东西。我将继续创建一个数据库并选择 postress SQL。你也可以选择各种不同的数据库。好的,现在完成了。所以我们可以直接去 settings。它仍在部署。所以让我们给它一点时间。它正在尝试连接到数据库。好了,我们有了这个数据库 URL。让我们复制它。有一个 n.local 文件。让我们创建一个 post press 的。创建一个数据库 URL 并粘贴我们复制的内容。好了。现在我们已经创建了所有东西,让我们确保它已成功部署,这太棒了。所以现在我们可以直接去这里,而不是 npx,它应该是 bun x 并停止服务器,然后我们只需要做 bun x。这将是 this will get push。现在,对于 postcress 数据库方向,连接 URL 或 host database 都是必需的。所以现在如果我们回到这里,我们有 database URL。现在的问题是,我们需要在 Drizzle 中再调整一件事,让它选择 av 文件 local 文件而不是 env 文件,这是默认的。所以让我们替换它。它是一样的,除了我们需要更改 schema 将是 DB migrations。好了。现在这是从 n.local 文件加载环境变量的主要行。现在让我们再次启动它。好了,它已经从 n.local 文件中获取了,并且说我们无法访问它。所以让我们确保我们选择了正确的 URL。这是公共的,我们应该选择它。所以让我们去 n.lo 文件。将公共 URL 粘贴在这里。让我们再试一次。推送它。好了,它正在从数据库中拉取数据 schema 并很快就会推送。完美。所以现在如果我们去 deployments 或 database,让我们刷新它。让我们去这里。你现在可以看到 agent runs 是一个表。然后我们有 integrations,它也是一个表 tasks 等等。所以所有这些都对我们来说是现成的,这太棒了。所以这就是我们想要的。所以我们的数据库已经准备就绪。现在,继续在这里查看我们的信息完全没问题,但我个人非常喜欢 Drizzle kit studio。Drizzle Studio 非常棒。所以,我们可以直接在这里安装依赖项。然后我们只需要这个。使用 Drizzle Kit Studio 启动它。因为我们已经有了 Drizzle Kit Studio,因为我们已经有了 Drizzle Kit,我们可以直接复制并粘贴它。我们的 Drizzle Kit Studio 已经准备就绪。好了,我们有了 agent runs, integration, tasks, and users。这更容易看到。现在让我们在这里启动我们的调查。我们就做 bun。所以如果你刷新页面,我们应该可以开始。我们可以最小化它。所以如果你回到 dashboard,你会看到我们有这个 on boarding card。我们有这个 agent status card,last agent run,以及 unread emails。在这个特定的 SAS 应用程序中,我可以向你展示如何构建它的 UI 部分,当然,我将这样做。有趣的是这个 agent stuff,比如它如何自主运行,对吧?所以让我们先构建这个,然后我们可以构建其余的内容。所以让我们先理解 run agent 按钮是如何工作的,因为它非常强大,并且在理解了它之后,它基本上就是整个 SAS 应用了。所以,当用户在 dashboard page 上并点击 run agent 按钮时,我们会禁用该按钮并显示一个 spinner,然后我们在后台调用一个 post API 调用 run API agent。这个 post 请求主要负责确保我们收到的输入和输出正是我们想要的。这是实际执行函数的 post API,我们将看到下一个。所以那个特定的函数,那个 API 调用,它做了什么。首先,它检查 clerk session 是否有效。如果无效,它将取消授权。它会重定向到 sign it。这是我们以前见过的。这就是我们将在这里应用的 exact logic。然后我们将插入一个 agents run 表,状态为 running,因为记住我们要记录一切。然后我们要解密 o 令牌。现在这需要记住你已经登录了 Google,因为我们在连接 Gmail 和 Google calendar 之前就已经连接了,然后代理才能实际获取该信息并对其进行操作。为此,我们将存储令牌,我们将经历一个加密和解密过程。我之前已经提到过,但我会再解释一遍。原因如下。因为代理需要自主运行,它们不能一直给我们权限。我们需要存储令牌。但是,如果存储 Google 给我们的 o 令牌非常危险。所以我们需要加密它,然后也解密它。所以我们将这样做。然后我们将使用该令牌来获取用户的 Gmail 未读邮件以及过去 24 小历程的日历事件。现在你可以根据自己的喜好调整它,但这是我个人非常喜欢的设置。然后我们将调用 cloud API 并使用 Versa AI SDK 来调用这个特定的函数 generate object 并给它一些指令,一个 prompt。它将执行它,一旦执行完毕,它将插入数据库中的 tasks,创建任何 Gmail 草稿和日历事件,所有这些都在我们的系统中。然后,如果出现任何错误,它将把状态更新为 agent run status fail,但如果没有,它将说它已完成,包括任何摘要,然后它将返回 success is true 和摘要,然后 UI 将更新,例如按钮将被重新启用,列表中会出现一个新的 run,也许是一个 toast 说 agent run complete,这就是 rung function 所做的,为了完成这个,为了处理这个,我们首先需要确保我们已经连接了工具,即 Gmail 和 Google calendar,为了做到这一点,我们需要先实现 Google,我们需要配置 Google APIs,这样我们就可以设置它,所以首先,让我们来做吧,所以首先我们需要配置我们的 Google APIs,这是我们一次性的设置,为此我们需要创建一个 Google cloud project,所以我们称之为 exit OS main,选择一个组织,再次只需按照此步骤操作。所以一个项目正在创建。接下来,我们需要启用 APIs。现在我有两个项目。所以,让我们选择 exact OS main,然后启用 Gmail API,因为这是我们的代理需要的 API。然后我们去这里刷新页面。选择 XF OS main 或你给项目起的名字,然后启用 calendar API。好了。同时启用这两个 API,因为你的代理肯定需要它们。然后我们需要配置 oat 屏幕。为此,让我们开始并创建一个应用程序。我们称之为 exac OS。支持电子邮件将是此电子邮件。然后暂时,我们只说对任何有 Google 账户的测试用户可用,因为我们希望将来将应用程序提交到生产环境,然后他们会验证它。好的。回调信息。这只是我的电子邮件地址。好的。所以现在我将同意并继续。然后让我们创建。我们需要这样做才能配置我们的同意屏幕。现在让我们在这里创建一个 oat client,它将是一个 Web 应用程序。我们称之为 X oat。然后在这里你想给我们 localhost 3000。稍后我们将更改它。重定向 URI 将是,这同样将是 localhost 3000 并删除 https,然后让我们创建。现在 oat client 已经创建。让我们复制它,然后让我们去测试用户。has been limited to test users this on the oath consent screen。好的。这就是问题所在。复制 client ID。所以让我们去这里并添加 Google client ID 将是这个。我们有 Google client ID,但我们也需要 secret。所以让我们复制 secret 并将其添加到 Google client secret。这只是这个值。完美。所以让我们在这里添加它。还有一件事我们需要做,那就是当你点击这个并转到 API services 并转到 credentials,也就是数据访问,我们需要添加这些 scopes。我们需要添加 scopes 并字面上查找 googleis.com/gmail.readonate。这是我们需要添加的 scope。然后无论我们需要什么 scope,我们都需要将其添加到此。我想我们还没有添加。所以让我们这样做并更新。修改只读。我们还需要撰写新电子邮件的能力。所以让我们添加更新。然后对于 calendar 也是一样,我们需要 calendar read only,这将是这个。然后 calendar events 也是一样。所以让我们复制这个和 calendar events,就像这样。并更新。所以我们已经更新了所有这些,让我们按下保存。然后当测试时,我们可以随时发布应用程序。但对于测试用户,让我们添加你将用来登录的任何电子邮件。所以我将添加这个虚拟电子邮件地址。所以让我们按下保存并确保在这里使用你的电子邮件地址。所以一旦你完成了所有事情,测试,你就可以直接发布应用程序。我不会,但你以后可以这样做。所以现在我们的 Gmail Google 已经配置好了。所以现在让我们将其集成到我们的应用程序中。所以现在每当有人点击 dashboard 时,让我们登录。这样我们就有两个用户可以玩了。好的。所以现在如果我们转到 dashboard,你会看到这个 upgrade to prompt,对吧?这就是我们设置的。如果我点击 Gmail,它会带我去 settings。但如果我点击 connect,我点击 connect,然后它应该带我去另一个 URL,帮助我连接我的 Gmail 账户。他们会要求一些权限。这就是我们刚刚配置的同意屏幕。所以首先,我们需要一个名为 settings page 的新页面。所以我们称之为 settings。然后我们称之为 page.ds。然后我们在这里创建一个 setting page。再次,dashboard page 和 setting page 将非常相似。所以让我们复制并粘贴它,然后我们称之为 settings page。这将是 settings,然后这将是 manager account 或 manager integrations and preferences。好的。然后我们所做的是,你只需要循环遍历一堆 integrations 来在页面上显示它。所以我们有两个 providers,对吧?其中一个 provider 实际上是 Google,它的 key 是 Google,name 是 Gmail,而不是 Google,实际上是 Gmail。然后 description 是 connected,或者我认为 description 可以是 read and manage your emails。icon 将是 Sidway app 中的 mail icon,integration 将是 Gmail integration,但现在就这样保留。同样,我们有一个 Google calendar 的,带有 calendar icon,这个将是 view and manage a calendar event。所以现在这是我们第一次在项目中使用的 card component。card component 来自 SH CNUI,它将包含一堆信息。现在,我们不一定会构建整个 UI。我只会给你代码。让我们直接导入 card header, card title, card description, and so, and the badge icon button。我们还没有 disconnect button,但让我们就这样做。你不需要默认的 variant。让我们暂时将其设置为 true。好的。所以现在如果我们回到 settings,我们将有 settings page,settings text 带有两件事,Gmail 和 Google calendar。再次,我们只是渲染两个 cards 并循环遍历它们。这是 connected 的 badge。但如果我说 false,它将显示一个 connect button。所以,如果我们按下 connect Gmail,让我们说我用这个特定的账户登录,例如,你会得到 Google hasn't verified this app,这是因为我还没有公开提交以获得 Google 的批准。所以,让我们暂时继续。你可能会看到像一堆复选框,用于我们专门要求 Google 访问的服务。例如,查看我们的电子邮件消息,管理草稿和发送电子邮件,查看和编辑事件,查看和下载任何日历,等等。我们需要那个。这样,我们将能够访问我们用户的信息。所以,让我们按下 continue。然后现在,Gmail 已成功连接。现在,有人可以断开连接然后重新连接,等等。Calendar 也是一样。但这就是我们想要构建的流程。这个特定的按钮需要调用 / API/od/g Google,这就是我们需要构建的 API,我们需要说 okay provider key 实际上是 Gmail 或者是 Google calendar,基于此,我们将首先检查用户信息,获取 provider,然后生成令牌,等等。我们之前已经看过了,我们想要解密令牌,生成令牌,首先,然后将其存储在 cookie 中进行验证,然后创建另一个路由来处理回调。现在,如果你还记得,当我们在这里设置应用程序时,我们确实设置了这个回调 URL,这也是我们需要处理的回调 URL。所以它所做的是,当 Google 重定向回这里并带有一个 o code 时,我们将其交换为令牌并保存它们,然后我们可以验证状态,解析提供者,然后创建令牌。这样我们就可以一直访问,而集成创建的那一刻,所有令牌都会在进入数据库之前被加密,等等。所以这就是我们想要构建的。所以,让我们去 app,让我们创建一个新的 API 文件夹。这个 API 文件夹将有另一个名为 O 的文件夹,那个特定的文件夹将有一个名为 Google 的文件夹,它将有一个 route.ts。然后 O 里面也会有一个 callback 文件夹,但在 Google 中,你将有一个 callback 文件夹,它也将有一个另一个路由,整个东西都将轻松映射出来。所以,这里是我们想要做的。让我们创建一个简单的 API 请求,这是一个 get 请求,并且浏览器中的请求实际上是由 Nex.js 使用 next request 扩展的。第一步是验证 O。第二步是从请求中获取 provider。第三步是生成 CSRF 令牌。然后我们存储状态在 HTTP only cookie 中进行验证。我们只是重定向。这就是我们想要做的。另一方面,在 callback 中,哎呀,我们也将有一个 get 请求,它由 next server 扩展。获取 cookies。验证 O,从查询参数中获取 code。验证 CSRF 状态。从状态中解析 provider。将 code 交换为令牌。加密令牌。将令牌存储在数据库中。然后清除状态 cookie。然后重定向回 settings page。现在这只是非常重要的,这两个步骤,因为没有它们,就像我说的,我们无法真正拥有那种自主状态。所以,让我们先实现这个。所以,为了验证 O,我们将有 user ID,从 clerk server 获取 user ID。如果没有 user ID,我们将以不同的方式处理它。我们将返回一个 next response。而不是调用 JSON,我们需要调用 redirect。让我们从 next server 导入 next response。使用 redirect,让我们说我们想重定向到这个 sign-in page。如果没有 user ID,就没有意义。然后获取 provider,你想要获取 next oops,你想要获取 next request next URL.search params.get provider。让我们称之为 provider 而不是 search params。如果没有 provider 或者 provider 中没有 Gmail 或 Google calendar,那么我们想返回,我们想返回的不是重定向,而是 next response JSON 为 error invalid provider。现在下一步是生成令牌,我们可以通过使用 buffer.from JSON stringifier 来做到这一点。现在在 JSON stringify 中,你想说 nons random UID 并传入 provider 和 base 64。Crypto 是我们需要添加的东西。所以,让我们从 crypto 中导入 crypto。好的。所以,现在我们已经生成了令牌,我们需要将状态存储在 cookie 中。所以,让我们这样做 cookie store = array cookies。Cookies 可以来自 next headers,然后我们只需要设置它。所以你可以说 sets Google o state with state,然后传入 http cookie secure 和 same side as lax 等等,max age 将是 10 分钟,我们希望这个 cookie 存在 10 分钟,这就是 cookie store,最后我们只需要获取 o url 将是,我们可以调用一个 get o url 函数,传入 provider 和 state。这个函数不存在。然而,在 lib 中,让我们创建这个文件 google。现在,这个文件对你们所有人来说还不存在。所以,请确保复制它,我将带你了解它,但在此之前,让我们安装这个名为 Google APIs 的包。Bun install Google APIs。好了。重新启动服务器。为了获取 O URL,我们需要获取 oath client,它将调用 oat 2 的新实例。我们传入 Google ID secret,我们之前已经设置过了。Google client ID with secret for app。这很重要,我们说这将是 palwatch URL,然后我们生成它,我们在这里生成 o URL。如果是 Gmail,这些是不同的 scopes,我们设置了。如果是 Google calendar,这些是 scopes,因为这是我们需要的唯一访问权限,这里是 casted,然后返回 next response 或 redirect 或 URL。完美。下一件事是 callback。所以我们需要处理 callback,现在我们有了 get 请求。我们需要获取 cookies,我们可以直接运行 cookie storage cookies。让我们导入它。我们需要将整个事情包装在一个 try catch 块中。所以让我们添加一个 try 块和一个 catch 块。获取 next response。将其他所有内容移到 try 块中。首先,我们需要确保我们正在验证 O。同样,我们可以通过从 clerk server 获取 O 来做到这一点。然后我们要解析 code。你可以通过简单地这样做来做到这一点,那就是获取 search params 并从中获取 code,state 和 error,如果有 error 则返回它,如果没有 code 或 state,则将用户重定向到 settings page 并显示 error is has missing params。然后我们要验证 state。为此,我们只需要获取 cookie store value Google oad state,如果它不存在,则再次处理。error 将是 invalid state。下一件事是从 state 中解析 provider。所以我们可以简单地使用 buffer API 来做到这一点,并导入 Google provider type exchange the token create the oath client here。所以这次我们将获取令牌,如果它不存在,那么我们将重定向。接下来,我们还需要查找内部用户。所以这是我们需要做的一步,因为我们要确保这个特定的用户是有效的。所以我们需要调用一个名为 get user by clerk ID 的函数。但现在先等等。插入并加密用户。为此,我们可以调用 encrypt API。我们稍后会讲到。将令牌存储在数据库中。然后最后清除 cookie 并重定向。Voto 步骤。在 cookie 附近然后重定向。如果发生错误,在 catch 块中,我们只需返回它,我们可以删除这一行。所以我们正在 console log 它。我们正在删除 cookie,因为有错误,我们正在将用户重定向到 error as callback failed。好了。所以,现在我们已经做了很多事情。现在还剩一步,那就是我们需要查找内部用户,加密令牌,并实际将其存储在数据库中,因为没有它,我们就无法拥有那种自主状态。所以,让我们创建数据库调用,并获取用户 by click ID。现在让我们去 db 文件夹,让我们创建一个新文件。让我们称之为 queries。这里将存储我们所有的数据库查询。所以首先,我们想导入 DB,我们从 lib / DB 获得。我相信我们在同一个文件中就有 DB。所以这就是我们需要做的。所以,让我们去 queries,让我们直接输入 /。好了。现在我们想创建一个 get user by clerk ID,让我们在这里创建我们的查询。所以首先我们需要获取用户,这只是我们调用的 .select,让我们给它一个等于号,来自 users 表,users 表可以很容易地从 schema 中导入,我们也可以像这样调用 ver class,它也提供了方便的 like eq functions,设置得非常酷,然后我们可以将 limit 设置为 one return user or oh,例如。所以,这是我们获取用户 by cluck ID 的函数。但是,如果你还记得,我们从来没有处理过在数据库中创建用户的逻辑,对吧?这也是我们需要做的。所以,如果我们布局了,这就是我们添加注释的地方,对吧?我们想从 DB 获取用户或创建用户。所以,让我们调用 user = await create user,它将有一个 clerk user ID 和 email。这个还不存在。我们需要创建它。所以,我们将有一个 clerk user,它将是一个字符串。一个 email,它将是一个字符串,以及一个可选的 name。这就是我们可以检查它是否是现有用户的地方。只需调用我们创建的这个函数。Get user by clip ID。所以,如果 existing,那么很简单,对吧?返回 existing。但如果不存在,那么我们就想创建它。所以我们可以说 await db.insert users.values,然后我们可以 do clerk ID email name。如果它不存在,那么它是 null。我们还需要分配 name。然后第一个将是 clerk ID。Clerk ID。好了。然后简单地返回用户。完美。所以这正是我们在 layout ported 中需要调用的。我们只需要说 email 将是这个,然后 cler ID 将是这个 user ID。让我们在这里添加一个空字符串。同样,对于 name,它将是 full name,我们也可以在这里传递。完美。现在我们只是创建它。我们真的不关心用户的响应。我们只是创建它。好的。所以,我知道我们到处移动了很多,但是这个应用程序有很多移动部件,但现在一切都会说得通。所以,回到这里,我们想获取用户 a clerk ID。然后,让我们取消注释。如果没有用户,那么我们就重定向并显示错误 user not found。再次,所有这些都是我们可以在前端处理的。然后我们要调用另一个函数。让我们称之为 upsert integration 并将其存储在数据库中。所以 user ID 将是 user.ID。然后我们想传入 provider。Access token。这就是我们需要在这里加密 token 的地方。Refresh token 将是 refresh token。New date 将是 expiry date。现在加上这个。如果存在 expiry date,那么就没有 expiry date,那么这将是日期,我们有 token scope,我们将对其进行拆分,对于加密,我们只需要加密 token。现在这个特定的 encrypt 函数不存在,但我将给你一个文件,你可以从 GitHub 上获取,名为 encryption.ts,这个文件将允许我们加密 token,然后我们也可以解密它。所以,这里我们需要加密它。所以,让我们导入它。让我们实现 upsert integration。所以我们可以回到 queries,在这里我们只是想获取 integration,然后如果它不存在就插入它。所以我们可以说 function upsert,然后我们做 data user ID string provider is Google provider access token refresh token date and string array。然后在这里我们检查 existing = await get integration。这个函数还不存在,但我们需要传入 de user ID 和 data.provider。如果存在一个,那么我们就想更新它,并且 integrations 是我们想要发生的地方,然后返回它。但假设它不存在,我们想插入它。所以,让我们称之为 result DB.integrations.values with data 并返回它。但返回时,我们不想返回所有内容。让我们只返回 integrations id,然后让我们只做 result.ID。好了。现在获取 integration 也很相似,对吧?我们调用一个 select from integration where integration user ID is user ID and integration.provider is provider。我们将它限制为 one,然后返回 integration。所以,现在我们可以直接导入它。完美。我们也加密了 token 并将其存储在数据库中。好吧,我们做了很多。所以,让我们确保其他一切都正常工作。所以,如果我们回到 local host,让我们按下 connect。一旦我们这样做,它就会说访问被阻止。为什么呢?好吧,让我们看看。重定向 URI 是 undefined API 或 Google call back。所以,让我们在这里看看。你说它将是 30,000。它需要是 30,0001。所以,让我们保存它。让我们刷新页面。我想我们必须回去,让我们再次连接它。我们还需要注意的另一件事是,如果我们去 end local 文件 one,我们需要一个 encryption key。第二,我们还需要设置 next public app URL 为 localhost 3001。现在,如果你的应用程序运行在 localhost 3000 上,那么这个值需要是 3000 无论端口是什么。所以,这就是你需要的东西。所以,现在让我们连接它。好了。所以,如果我选择一个用户,你可以看到访问被拒绝,因为我们将用户限制为只有这个用户。所以,如果我们继续,Google 还没有验证应用程序。所以,让我们继续。我们知道这个消息。让我们给予所有权限。这就是我们应该看到的。所以,这正在起作用。砰。看起来它起作用了。所以现在如果我们去这里,去浏览器开发者控制台,去 application,去 cookies,我们可以通过去数据库 users 来验证这一点。我们确实有我的用户在这里。Integrations。这里也有一个 Gmail integration,这正是我们想要的。它连接到我的用户,这太棒了。如果你去 Google Oat State,因为这是我们创建的 cookie,它将不存在,因为它只持续几秒钟。记住这一点。所以,现在,让我们保持这个打开状态,并对 Google Calendar 做同样的事情。让我们选择我的用户,让我们继续。选择所有。继续。我相信它又断开了。但我们现在有数据库来验证。所以,让我们刷新它。现在我们有两个 integrations,即 Google calendar 和 access token 以及 refresh token,这正是我们想要的。所以一切都正常工作。这太棒了。你构建的这些类型的流程越多,你就会越了解你需要做什么。但此时只需跟随,以确保一切正常。你也可以暂停视频,并回顾我到目前为止所教的一切。但如果你回到这里,这就是我们所做的。就像我们现在已经加密了 token。我们仍然需要构建 run agent function 并构建 API 路由。检查 clerk session 是否有效。更新状态为 running。现在 token 已经创建,我们也可以解密 token。确保它是一个有效的 token,然后做其余的事情。但在我们开始之前,你可能会注意到这里它仍然显示 connect。它现在应该显示 connected 作为状态。所以,让我们现在来构建它,以进行 API 调用。我们需要基本上获取所有用户 integrations。所以,就在这上面,让我们做 user integrations = await user integrations。我们需要在这里传入 user.ID。User 是我们可以从 user ID 获取的东西。Clerk ID = await o。我们以前做过这个,因为这是一个 server component。你可以直接调用它。如果没有 clerk ID,我们可以重定向到 sign in。我们也以前做过。现在你也可以直接获取 clerk user = await current user。然后我们以前做过的事情,创建用户并获取 user integrations。我们可以传入 user ID,例如。现在让我们去 queries,在这里让我们添加 get user integration,例如。老实说,它只需要是所有东西。返回结果。所以,现在我们可以直接导入它。这里我们可以说 Gmail integration is user integration.find. Integration provider is Gmail。对于 Google calendar,它是 Google calendar。在这里,而不是 false,它应该是 provider.integration。这里将是 Gmail integration。这里将是 Gmail integration。这里将是 Google calendar integration,我们刚刚获得的。所以,现在你可以看到我们看到了这个图标,这实际上不是正确的图标,但你明白了,因为我相信我们使用的是 Lucid React 的 badge,但它应该是 Chad CN UI component library 的 badge。现在它显示了这两个都 connected,这太棒了。所以回到 dashboard,我们已经 connected 了 Gmail,我们已经 connected 了 Google calendar。我们仍然需要确保订阅以激活代理,但让我们专注于实际的 run agent 功能。所以,现在让我们在这里的页面上添加一个 run agent 按钮。所以,让我们关闭所有这些。回到我们的 dashboard page。这里我们只是说 welcome back。你的 AI 正在发生什么。但我们想添加一个 run agent 按钮。因为这是一个按钮,它将有一个 click handler,这就是为什么我们需要将它包装在一个 client component 中。所以,让我们称之为 run agent button。这个特定的按钮实际上可以放在 components 里面。我们可以创建一个名为 agents 的文件夹。在这里我们可以运行 run a button。NTSX。让我们添加它。让我们在那里做 run agent。好了。所以,现在让我们从 components agents 导入它,它是一个非常简单的按钮。所以,我们在这里屏幕上有一个按钮。好的。但现在这个按钮将做几件事,对吧?它将有一个 handle run agent function,它应该运行实际的代理。所以这是我们需要执行的事情。然后当有人按下按钮时,我们想显示某种加载器。让我们设置 class name 为 W4。并且需要一个 variant 为 outline。但我们也需要一个 loader。对吧?在设置了这里的 on click handler 之后,我们可以说 disable 需要是 if it's pending or something。现在实现这个的最佳方法是创建一个 start transition,并使用 is pending,然后将我们的 agent call 包装在里面。就像那样简单。所以这会自动连接我们的代理。当它 pending 时,它是 true。否则,它不是。当它 pending 时,我们只想显示 loader to icon from lucid react。老实说,我们希望它旋转。所以,让我们称之为 to spin our icon class thing like this like loader icon。我们可以说 running agent 否则就 run agent now button。好了。所以,现在当我们添加 start transition 时,我们也需要将它变成一个 client component,这就是它抱怨的原因。所以,现在这是 run agent button。如果我按下它,你可以看到有一点闪烁,对吧?这是因为我们在 button 里什么都没做。我们需要做一些事情。所以,首先,这将包装在一个 try catch 块中,对吧?像这样。我们可以只 console log 错误并简单地返回以自动更新。我们还需要使用 router use router from Nex.js,我们将自动刷新它。所以,这里我们可以说 router refresh。当数据自动更新后,代理就完成了。我不想使用 router。我们可以使用其他方法来做到这一点,但在这种情况下,它就像 router refresher 一样简单,因为再次,这将自主运行。我们不会真的按下这个按钮。它会自动运行。所以,我们现在不用太担心 UI。好的。所以,这里我们将有一个 response,我们将调用一个 fetch 请求,它将是一个 method post,然后根据 response,我们将 console log 数据。这里我们可以为 error 也说 agent run error to be error。如果 response 不 ok,我们也想这样做 console.agent run failed with results.n。我想我们可以

执行此操作,在出现点错误后,或者在此处说出结果,然后调用此结果。这要好得多。好了。所以现在这个 API 不存在。所以我们肯定需要调用它,例如。所以,让我们这样做。现在我这里不需要任何东西。因此,为了让我们创建 API,我们需要再次转到 API 文件夹,并创建一个名为 agent 的新文件夹。创建一个名为 run 的新文件夹。然后在这里,让我们创建一个名为 route.ts 的新文件。这实际上不是一个 post 请求。您需要牢记这一点。就是这样。现在这里有个问题。记住我们谈论过这个。我们正在构建这个特定的流程。我们已禁用按钮。已显示加载图标。我们将调用一个 post 请求。所以这就是我们需要做的。检查有效的 clerk 会话。在 agent runs 表中插入状态为 running。解密令牌。搜索 Gmail 和日历等 100 封电子邮件。获取日历事件。然后基于此调用 Claude。所以 Claude 可以响应更快,或者我猜是 anthropic API,对吧?在任务数据库中插入 AI 创建的任何任务,任何错误。将 agent runs 更新为状态为 complete,并附带摘要。返回 200 成功,并重新启用按钮。所以这正是我们现在需要做的。首先,我将给您一个文件。我们称该文件为 Google client。它包含大量信息。我们只需要修复它。这些函数允许我们获取所有 Gmail 客户端、Google 客户端等。我将此提供给您,以便我们可以更专注于调用 API 的实际功能,或者整个系统将如何自主运行。因此,在这个 post 请求中,像往常一样,让我们确切地写出我们将要做什么。首先是确保已进行身份验证,但此特定的 post 请求此时将有两个任务。任务一:手动运行,这意味着每当有人按下 run agent 时。但任务二:自动运行,这意味着某种 cron 作业。cron 作业部分是我们稍后将实现的。但现在,让我们只关注确保它不是 cron 作业时,我们将做什么。所以第一步是,如果它不是 cron 作业,我们可以说 is cron,然后说 process。我们将需要某种 cron 作业的 secret。所以,哎呀,不是那个。我们将添加 process.nc。以及我们的 secret,例如。但这显然不存在。所以它将始终为 false。而在这里,我们可以说,好的,如果它不是 cron 作业,那么我们在这里做什么?在那里,我们可以验证 o,然后我们按 ID 获取 clerk 用户。对吧?然后我们基本上执行 run agent 函数,然后返回 next response,它将从 next server.json JSON 中导入。对吧?这就是我们将要做的。如果它是 cron 作业,那么这是一个完全不同的过程。如果是 cron 作业,它将由 cron 触发,对吧?那么在这种情况下,我们可能想做一些其他事情,例如,我们可能想说,暂时 cron 作业 agent run successful 或类似的话。所以,让我们现在专注于这一点并执行它。所以,我们以前做过这个,所以让我们获取 userID,它是 alert ID await。o 来自 clerk next.js server。如果没有 clerk ID,那么我们就说 unauthorized。然后在这里,让我们获取 clerk 用户。让我们从我们的查询中导入它,因为我们已经有了那段代码。然后,让我们运行 run agent。现在 run agent 不存在。但我们在 lib 中可以创建 agent.ds。然后我们在那里执行它。所以我们可以说从这里导入它。好了。无论它产生什么结果,我们都可以将其作为 next response.json 返回。好了。所以这就是我们将在 post 请求中做的。所以我们可以实际尝试一下。如果我运行 agent running the agent,那么就会出现问题,因为实际上在此之后,如果用户未启用 agent,我们也需要返回 403。所以,现在让我们关闭这个并重新加载页面一次。运行 agent,然后我们得到一个问题,说 doc type 不是有效的 JSON。它是一个 get 请求,但我们实际上需要一个 post 请求。所以,如果我们回到 run agent 按钮,我们只需要确保 method 是 post。让我们运行 agent,然后我们得到这个错误。它说 404。好的,所以这需要是 agents。现在,如果我再次运行它,瞧,我们确实得到了一个响应,并且完成了。好了。所以现在我们的 agent 在这里成功运行。所以,现在我们可以开始构建函数了。好了。所以在 run agent 函数中,像往常一样,让我们添加我们将要执行的步骤。首先,所有这些都需要放在 try catch 块中。所以我们需要添加它。我们需要做的是可以更新 agent,对吧?所以首先按 userID 创建 agent run。所以这意味着我们需要更新表,登录表,说,好的,这个特定的 agent 实际上运行了。第二,我们需要获取 Gmail 客户端,只是看看是否有任何未读邮件。所以我们需要获取未读邮件,以便我们的 agent 可以开始执行其任务。然后我们需要获取日历事件(如果已连接)。对吧?这里一切都有一个 if。然后一旦 agent 完成了连接和获取日历事件,下一步就是处理每封电子邮件与 AI。这就是奇迹发生的地方。一旦 Claude 能够做到这一点,那么我们就可以创建一个 Gmail 草稿(如果需要回复),并且我们需要 AI 来告诉我们。然后我们也将再次创建日历事件(如果需要)。然后我们将把电子邮件标记为已读,因为它已完成处理。这样我们就会看到一个草稿,然后我们将汇总结果。最后,我们将记录完成的 agent run。这个完成的 agent run 并返回结果。这将是最后一步。好了。所以,第一步是按 userID 创建 agent run。让我们只做 agent run = await create agent run with a user ID。这里我们已经有了来自 run agent 函数的 user ID。所以,我们可以直接使用它。然后让我们去 DB queries。在这里,我们可以开始创建我们的查询。例如,我们只需要从表中导入 agent reference,然后插入它。在值方面,它将是 userID,然后 status 是 running。现在,让我们继续导入它。所以,这基本上就可以了。然后我们可以开始获取 Gmail 客户端。现在,正如我提到的,我已经为您提供了获取 Gmail 客户端的文件。所以它在这里获取 Google 客户端,也就是这个特定的函数。它实际上所做的是创建 oath 客户端,解密它,设置 oath 客户端的凭据,然后检查过期时间,更新它等等。如果令牌被删除,它还将删除集成。这对我们来说非常方便。所以我们将使用它。现在,如果没有 Gmail 客户端,那么我们想做一些其他事情,我们只想完成 agent run,对吧?我们将再次记录它,我们想每次都记录它,所以我们可以说 complete agent run with agent ID,然后是这个附加对象,它将具有 status 为 failed,summary 为 Gmail not connected,然后我们有 actions log 为空,emails processed 为零,task created 为零,drafts created 为零,error message 为 Gmail integration not found 或 token expire。我们不需要 started at,我们只需要 duration,而 duration 将只是 date.now。我们需要一个 start time for the agent,我们可以在整个过程中访问它。所以在这里我们可以说 start time 是 date.now。在查询中,我们将添加一个 complete complete agent run,它只是更新它。所以它将再次是 const run = updates always returning,然后在这里返回 run。现在我们正在更新 agents run 表。我们想在这里做的是简单地展开我们得到的数据,但也添加一个 completed ad,它将是 new date。好了。agent runs ID 应该匹配 agent run ID,因为这个特定的 agent 之前已经被创建了。我们只是在更新它。这里可以是可选的。现在,如果我们回到我们的 run agent 函数,我们只是用 complete agent 导入它。对吧?所以现在我们可以返回 real id 是 run.id status 是 success,我猜常量 status 将是 fail,因为它失败了,然后 summary 是 gmail not elected,就这样,完美。所以现在我们已经创建了 agent,基本上记录了它,创建了它,获取了 gmail 客户端,如果它不起作用,我们再次记录了它,所以我们想获取 fet unread emails。所以第三步就是这样。所以,让我们只做 emails = await fetch unread emails Gmail, head。这实际上将是 Gmail 客户端。现在 fetch unread emails 不存在,但我们需要创建它。现在 fetch under emails 或 fetching calendar events 都是 Google API,我将在这里为您提供几个文件,即 calendar 和 Gmail。让我们创建一个名为 agents 的新文件夹,然后将 Gmail 和 calendar 移到其中,然后从 agents 中导入它。这个文件所做的是,它使用 Google API 的 list 函数获取未读邮件。然后它解析消息并运行一个 promise.all 来解析每封电子邮件。类似地,要解析 Gmail 消息,它会检查标题。它获取标题。它解析 payload 数据,截断长电子邮件以保持在令牌限制内,然后返回消息。此 API 会将电子邮件标记为已读。这将创建一个新的草稿,依此类推。日历也是如此。它将获取即将发生的事件。它列出日历事件。然后这个会创建日历事件,依此类推。所以这正是我们在这里所做的。它获取未读邮件。然后,如果 email.length 为零,那么我们也想记录它。所以在这里我们将说 complete agent run agent id status 是 success。在这里我们将说 status 是 success。summary 是 no red emails to process。action logs 为空。email process 为零。所有这些都保持不变,返回的只是 run ID success。summary 是 no unread emails to process,就这样。类似地,对于获取日历事件(如果已连接),我们也想获取日历客户端,它将是这个。我可以从日历客户端获取。如果没有日历客户端,则想法相同,让我们只说,如果有一个日历客户端,那么做一些事情。所以我们可以添加另一个 try catch 块,在 try 块中,我们可以简单地获取即将发生的事件。所以 upcoming events = await fetch upcoming events。导入它,因为我们已经有了它。如果发生错误,我们不想中断整个事情。我们可以说 calendar fetch failed,并附带一个非致命消息和一个错误。好了。现在您想处理 Claude 的每封电子邮件,例如。所以首先,我们希望我们在这里获得的每封电子邮件,所有未读电子邮件,都逐一由 Claude 解析。所以,我们可以这样做。我们可以简单地创建一个 actions log,它将是一个 action log entry 数组。然后我们可以说 cost results = await 并使用 promise.all all settled 来循环遍历所有电子邮件,基本上就是这样。actions log 我们不需要另一个 actions log。好了。所以,现在当我们映射电子邮件时,让我们创建一个分析,我们将调用 analyze email,传递电子邮件以及即将发生的事件。现在这里有个问题,upcoming events 需要在外面声明,所以让我们这样做,并删除这里的 constant。所以 analyze email 不存在。现在所有这些都需要在这里发生。Ideate calendar events,将电子邮件标记为已读。所以,在我们继续之前,让我们专注于分析电子邮件。我们到底要做什么?所以首先,这将是一个单独的函数。我们称之为 process email.ds。我们称之为 analyze with AI。好了。所以首先我们需要一个模型,对吧?模型将是 anthropics model,它将是 plot set 4 2025 0514。如果您使用旧模型,它会更便宜,但如果您使用最新模型,它总是会更贵。在我们这样做之前,我们需要安装 AI 包。所以,让我们在这里安装 AI 包。所以,让我们只做 AI。让我们重新启动服务器。现在,让我们从这里获取 AI 包。所以你可以说 import。而且我相信我们还需要 AI SDK。所以,让我们去那里。让我们看看。我们需要 AI 和这个 AI,并且可以只使用模型。所以我们注释掉这个,然后做 generate text from AI,我们还需要 anthropic 的 AI SDK。或者,就像他们在这里展示的那样,我们需要 con result = await generate text。模型将是 cla,然后我们需要一个 system prompt,一个 prompt,以及一个 output。output 基本上是用于从 LLM 响应中解析结构化响应的规范。所以我们肯定需要它,并且我们需要要解析的 schema。schema 将是 email analysis schema。我们仍然需要创建这个。我们可以暂时注释掉它。所以对于 schema,实际上我们将使用 zord。所以,让我们也在这里安装 zord。让我们停止服务器并安装 zord。好了。我将给您确切的 schema,我们将使用它,它将是这个。您不需要导出这个,只需声明它。所以,Zord 将确保 schema 实际上匹配这个确切的结构。Zord 甚至不会允许或返回任何东西,或者 AI 如果不匹配这个特定的结构就不会返回任何东西。我们需要 summary。我们需要 priority。我们需要这些 action items。我们需要 needs reply 或 not a draft reply,一堆日历事件。所以我们正在向 AI 发出严格的指示,让它为我们创建这些。所以这正是 output 将要做的事情。在这里,我们可以将 output 设置为大写,而在这里,您可以只做 output。现在,关于 prompt 本身,我将把它交给您,因为它写起来相当长,但我们将深入探讨所有内容。prompt 将是这个。现在,如果您考虑一下,我们需要填写详细信息。但我们正在说要求 AI,您是一个分析电子邮件的 AI 助手。今天的日期是今天。分析以下电子邮件。从中提取 action items。对吧?我们需要所有这些信息。所以我们仍然需要填写其余的详细信息。所以,我们可以做的是,让我们删除这一行。现在,让我们只创建 get calendar context 为空,并检查是否有任何即将发生的事件需要我们担心。所以,让我们只获取任何类型的即将发生的事件并映射它。所以,如果我们说如果它大于零,那么我们可以调用这个 events list = upcoming events.lap event,它将是 e,然后我们只需要提取并给它一个特定的摘要。它看起来会很像这样,其中我们正在映射它,我们要求它提供 summary start 等等。然后 calendar context 将是 upcoming calendar event,并使用它进行分析。所以这是一个我们也在传递的 calendar context。然后,让我们设置今天的日期。所以,在这里我们可以说 today 将是今天的日期,并且有一个额外的逗号。所以,这就是 prompt。本质上,AI 包和 Zord 将为我们强制执行许多严格的约束。所以我们不必担心很多。现在,让我们在这里调用 analyze with AI,例如。一旦我们这样做,一旦我们用 AI 分析,我们就可以在下一步中输入分析,例如,正如我所说的,我们也可以仅从 action items 创建任务,这也是我们在这里错过的。所以我们有对每封电子邮件的分析,它也只是被创建了。所以,对于这个,我们可以说它将是 email tasks created 将为零。让我们保留一个计数器,然后让我们循环分析 action items,然后将其存储在数据库中。所以我们可以通过一个 for 循环说 con item of analysis action items 并简单地运行 create task,其中包含大量信息,如 userID。我们需要 title 是 item.title,description 是 description,priority 是 item priority,due date 将是 item due date 或 new date,item due date created by agent 是 true,class plus。现在,让我们确保这个类型是带有 email analysis 的 promise。在最后,我们只想返回 result.output,我们忘记返回了。所以,现在我们得到 analysis 或 action items。您还将看到,如果我检查了,我们有 calendar events category,而所有这些都在 zone 中定义了。所以它也是自动类型的。所以,现在如果我们去 queries,我们只需要在数据库中插入并创建任务,而这个需要是 analysis.priority。现在让我们去 queries。让我们只创建任务。从 schema 中导入它并插入它。Create task 基本上就是这个函数,其中我们将其插入到 task 表中,并带有所有这些不同的值。再次,我们以前做过。所以,现在我们可以简单地在这里导入 create task。好了。所以,现在我们已经用 AI 分析了。我们已经创建了任务,而且我们不需要分配它,因为我们只是,它是一个 action。我们不需要从它那里得到响应。现在我们可以创建 Gmail 草稿。再次,我们已经有了 API,对吧?所以我们可以简单地这样做,draft created 是 false。如果它需要回复并且需要草稿回复,那么让我们用 Gmail 客户端创建一个草稿。同样,对于日历事件,如果日历已连接,我们也需要做类似的事情。所以,类似这样,检查 AI 分析是否给了我们日历事件。如果是,则创建日历事件。完美。最后,我们只需要将电子邮件标记为已读。但我猜 marked as read 并将其导入。然后返回所有内容。我们想返回 email ID subject 和所有内容。所以,例如,subject from date success analysis 或 summary priority category needs reply draft reply 所有这些都需要插入。不需要 error 等等。好了。但如果不是这种情况,那么我们也需要返回一个 error。所以我们可以简单地返回所有这些,status 为 error,error 为类似这个。好了。所以,我们已经做了很多。我们已经采取了很多步骤,但很快我们就会看到所有结果。现在让我们汇总所有结果,因为我们再次需要将其存储在数据库中。例如,如果我们在这里查看 drizzle studio,在我们的 tasks 中,我们需要所有这些信息。但在 agent runs 中,我们也需要所有这些信息,就像我们以前做的一样。所以,根据表数据,您只需要插入它并汇总创建了多少任务,添加了多少东西等等。所以,现在关于聚合,让我们只做 four con result of results,然后如果 results 是 fulfilled,那么让我们做 entry = result.value action log push entry,如果 entry status 是 success,那么在那里我们进行聚合,例如 total task created 等等,并暂时删除这个。现在 total task created 和所有这些。我认为我们没有为它创建任何计数器。所以我们需要在它下面写,我们可以创建它。好了。让我们删除 total errors。这个我们可以说 one or zero。添加所有详细信息。完美。然后我们还需要检查有多少成功,有多少错误,我们需要 success count。再次,我们只是记录它。我们还需要 error count。我们不需要总是 error count。然后我们需要一个 overall status。所以,例如,如果 success count 大于零,那么 success,否则 failed。我们还需要一个 summary。而这个 summary 本身将是这样的。再次,我们正在记录它。所以,例如,process success count emails total task created。就像我们只是创建了一个很好的句子,让我们知道创建了多少事件,创建了多少草稿,错误计数是多少等等。在所有这些完成之后,我们可以简单地做 run 并记录事件。所以我们可以说 complete agent run agent run ID,然后 status 为 overall status。所以我们需要 action log 在这里。Success count。Task created 将是 total task created。Drafts created 将是 total tasks created。Error message 将是这样的。我们可以做得更聪明。这里我们可以说,如果大于零,那么 error count fail to process 或 undefiled。最后,只需返回结果,它将是 run ID status 将是 overall status,我猜,而 summary 将只是我们创建的 summary。完美。我们可以删除这个。我们不需要它。但在这里我们可以说 agent vendor ID,或者我们也需要记录它。所以,让我们这样做。然后在这里,让我们做 lard ID status 是 failed,而 summary 可以只是 error message。它将是这样的,要么是 error.message,要么是 unknown error。让我们看看 agent run 在哪里,这样每个人都可以访问它。好了。所以,我们已经做了很多,但我们一步一步地完成了,对吧?就像回顾一下一样,我们检查了有效的会话。我们将状态更新为 running。解密了令牌。获取了 API。使用了 cloud,但不是使用 generate object generate text,而是通过在数据库中插入来执行。记录了每一步。并返回了状态。这正是我们想要的,对吧?所以,让我们测试一下。让我们看看它到底给了我们什么。我们在 UI 上有一个按钮,让我们按下 run。它正在运行。很快我们就会发现一切是否如预期工作。它确实运行了,这很棒。Did run。所以,让我们打开 Drizzle Studio。并且它成功了。没有未读邮件需要处理。所以,这里我们可以做什么。从我的手机上,我将给自己发送一封电子邮件,然后我们可以看到它如何处理它。转到我最新的电子邮件。您将看到旧电子邮件,但您可以看到这封新收到的电子邮件,对吧?所以,现在让我们看看它将如何工作。让我们看看它是否真的处理了电子邮件。所以,如果我们回去运行 agent,如果一切看起来都符合预期,我们应该会看到一个新的草稿。它也应该将电子邮件标记为已读。Agent 已成功运行。然后我们去 Drizzle Studio。让我们看看会发生什么。有一个 action log。一封电子邮件处理失败。好的。所以,至少它在记录。所以我们知道 process zero emails one error。所以,让我们弄清楚是什么原因。Subject 是 high,这是电子邮件。所以,如果我们去终端,我们还没有真正设置我们的 AI 密钥。对吧?AI gateway API key 环境变量需要在这里设置。所以,我们可以回到 AI gateway API key,然后我们可以简单地获取 API key。现在,使用 Versell AI SDK,优点是它是一个提供商,可以将您从多个 AI 提供商连接到一个接口,我们可以简单地调用它。所以,我们在这里获取 API key,怎么样?所以,让我们只选择一个唯一的名称,然后说 exact OS API key。创建密钥,复制它,转到 N local 文件,然后对于 AI,让我们只说 AI gateway 并粘贴密钥。现在复制这个,回去粘贴。好了。现在,如果我们现在不想使用 AI gateway,我们也可以直接使用模型,这是我们的另一个选择,但我们暂时只使用 AI gateway。运行 agent,让我们看看发生了什么。发现一封未读邮件,仍在运行,这是一个好迹象。它创建了一堆日历事件,这很棒,然后停止了运行。所以一切都如预期工作。我们应该看到这封电子邮件被处理。看,我们确实有一个来自 AI 的草稿。感谢更新。我将在周三之前审查它。我有客户演示。 blah blah blah。如果您需要其他任何东西,请告诉我。这太棒了。它也正确地解决了这个问题。所以,它也成功地创建了草稿,这很棒。现在,这真的很棒。或者,如果我们只去日历,我们确实看到了这个特定的日历,但我们确实有这三个创建的事件,这很棒。所以,我们不必担心它。AI 正在做所有事情。所以,例如 Q1 预算。所以,例如,如果我们看到 Q1 预算在周三之前共享,那将在三月份。所以,因为根据我录制它的时间,这是星期五。所以,让我们也做一个同步通话。Q 预算。它实际上是休息日。如果您看到这是三月,它需要为星期三创建一个事件,而不是星期四。而且它也创建了这些事件,这很棒。所以,我们的自主 AI agent 已准备就绪,这很棒。现在,如果您看一下,当我们运行这个按钮时,无论它是成功还是不成功,我们实际上在页面上看不到它。我们真的需要看到更多信息,就像我们在这里一样。我们必须构建这个 agent status card。我们需要一个 last agent card,unread emails,所有这些。所以,让我们现在就开始构建它。所以,让我们只去 dashboard page。在这里,我们需要 agent status 的卡片。让我们只获取所有这些卡片和 agent status。这里将是 subscribe to run activate the autonomous agent。他们已经看到了它正在进展,对吧?然后 agent 按钮可能需要放在页脚。获取它。所以,让我们也添加另一个 div wrapper,它将是 stats grid 2,并包装整个东西。然后对于标题,让我们给它一个带有图标的 card title。就在它上面,让我们添加一个带有 sidebar icon 类名的 zap 图标。然后,在 description 中,subscribe to activate the autonomous agent。然后,我们还将有一个 card content,它将有一个 card content back 类名,在其下方将是 status row,然后是一个带有 status 的 span,带有 badge icon。实际上不是 badge icon,它将是一个 badge。让我们只使用一个 badge class name 或只是一个 badge Bitcoin ready。然后另一个 status row status label 是 Gmail,span 是 connected。我们可以将此按钮移到其下方,我们不需要单独的页脚。所以,这就是我们拥有的 agent,也就是 agent status。现在是 last agent run。非常相似,对吧?就像它具有相同的样式一样。所以,我们可以简单地复制它。复制它。粘贴它。在这种情况下,它将是 last agent run,而不是 agent status。删除这个 zap 图标。这个将完全没有类名。这将是类似 latest run。然后 most recent activity 或 no runs yet。我只是说这暂时为 true。然后,在 card content 方面,我们可以简单地删除里面的所有内容。然后在这里,我们将给它一个 card content stack SM 类名,带有一个 div。并添加一个数组 all label time value latest run.start.started at,否则 new date latest run 不存在。我们需要获取该数据。下一个值将是 status,然后是 summary。好了。然后这只是 item label stat row stat label stat value,就像我们之前的一样。完美。现在,让我们获取 latest run 的信息。所以,一切都会有意义。所以,在这里我们可以说 latest run = await get latest agent run 并传入 user per ID。我们的 dashboard 需要是一个 async 函数,我们还需要获取用户,以及用户是否是付费用户。所以,我们可以获取所有这些,将它移到这里,导入我们得到的所有内容。所以,现在我们有了用户信息,然后现在我们可以只写特定的查询。所以,如果我们回到 queries,让我们写 get the latest run,它将再次来自 agent runs,正如您所看到的,我们将所有内容都放入了我们的表中。所以,让我们导入它。好了。所以,现在其他一切都已修复。所以,如果我们回到这里,哎呀,忘了导入一些东西。从 polarities 获取它。漂亮。好了。所以,显然我们需要修复样式,但正如您所看到的,它正是我们想要的。它创建了四个任务,起草了一个回复,创建了三个日历事件,对吧?这正是我们想要的。所以,让我们修复样式。status row,如果我们在这里看到 latest run,意味着 most recent activity,否则不是。而 card header 需要在这里关闭。同样,cloud header 需要在这里关闭。就像这样。事实上,这个仍然感觉太近了。所以,让我们修复它。让我们给它一个 gap of two。也许四。只是为了给它一点空间。好了。所以,现在它看起来好多了。否则,它就像这样。完美。好了。现在它看起来非常漂亮。我们的 agent。显然,我们需要修复顶部部分,这将在我们添加 onboarding 后修复。空间看起来很好,但我们仍然拥有所有必要的数据。所以,怎么样?让我们现在就构建它。检查用户是否已完成 onboarding。或者像这样检查他们是否已连接他们的 Gmail,通过获取他们的 user integrations。对吧?所以,这就是我们现在要做的,其中我们获取 user integrations 并查看我们究竟需要向他们展示什么。所以,get integrations,我们已经有了查询。检查 Gmail 和 Google calendar,对吧?如果它实际上已连接。Gmail,我们称之为 connected,或者 Google calendar connected,对吧?然后我在这里创建一个 onboarding steps 数组,它将是 name 将是 connect Gmail。completed 将是 Gmail connected。我只是这样做。一旦有人点击它,它将是一个 settings page。这里也是一样。最后一步将是 subscribe to activate agent completed 将是这样的。现在,这将完全取决于用户是否是付费用户。我们以前做过,对吧?我们如何获取付费用户?我们可以简单地从 clerk 从 o 获取,它不需要是 await。所以,用户是否拥有 pro plan,根据这一点,我们向他们展示 onboarding。所以,completed count 将是 onboarding steps.filter step.comp completed.length。但我们也显示一个进度条,它将是一个基本的 map。所以,我们可以只做 math.round round 和 completed count 除以 this time 100。现在我们只需要在这里显示 onboarding completed。让我们不要花更多时间构建 UI。我将给您实际的代码,因为它就是同一个 card component,对吧?我们需要在这里做什么。所以,我们只是映射 onboarding steps 并显示它。所以,现在如果我们回到这里,progress 是我们需要添加的。Tron catnu。我们有这三个步骤都已连接,因为这实际上是一个 pro 用户。如果我注销,然后用另一个不是 pro 用户的用户登录。让我们看看会发生什么。让我们去 dashboard。如果您看一下,welcome back。这是正在发生的事情。所有这三个都没有连接。它说 zero of three steps completed,这太棒了。这正是我们需要的。所以,我们可以注销,回到 sign in,然后再次登录 pro 帐户,因为这正是我们现在需要的。好了。所以,这完成了。对于快速统计,我们将做类似的事情。我将把代码放在最底部。只需要一个 pending task count。例如。所以,如果我们回到 dashboard,这里有所有不同的项目,对吧?我们只需要填写它。所以,让我们在这里创建一个查询,用于 get unread emails 等等。所以,让我们只去 queries,然后去 schema agent runs。所以,让我们只获取这两个的 emails。让我们注释掉它。然后,如果您看一下我们的 schema,我们可以显示 task created,drafts created 等等。所以,我们可以稍微调整一下。您可以说 crafts,然后删除这个 link component。我们完全不需要它。但最终,如果您想让它链接到其他页面,您也可以这样做。好了。所以,现在如果我们回到我们的查询,您不仅可以选择 emails processed,我们还可以获取所有三个。Email process,draft created,task created,等等。所以,现在就这样做。然后,在这里我们可以简单地添加其余的。所以,现在我们有了更新的信息,这很棒。所以,我们的 dashboard 看起来非常漂亮。我们还没有构建 monitoring page。所以,让我们现在就做。所以,monitoring page 现在不存在。所以,让我们只去 main。创建一个新文件夹。转到 monitoring。创建一个新页面,并称之为 page.dsx。让我们创建一个 monitoring page。现在我们确实有 monitoring。请记住,当我们转到 monitoring page 时,会发生什么,对吧?我们想显示这些我们已经在 dashboard 上显示的东西,以及处理了多少电子邮件。我们在这里不显示它,因为我们从未运行过它。但在我们的情况下,我们确实需要显示它。让我们注销此用户,然后再次登录 pro 帐户。如果我转到 monitoring,您已经可以看到 process,high priority,drafts created,task extracted。当您点击它时,您可以看到所有 action items 来审查 action items with drafts reply。所以,您基本上得到了一个日志,对吧?关于 dashboard 中的所有内容。这正是我们现在要构建的。所以,就像我们有其他页面一样,比如 dashboard 或 settings,让我们也为 monitoring 创建一个。我们去 monitoring,文本将是这个。所以,我们的 monitoring page 已经看起来非常好了,因为它已经存在了。然后我们有这些,当您点击它时,它会展开,对吧?显然,我们正在使用 SHT CN UI 组件。而这些只是我们将要使用的 card components。现在,我将基本上给您标记。这样,我们可以专注于实际的后端。我们已经在 dashboard 中有了这个查询,对吧?基本上将整个东西复制到这里,然后删除我们不需要的代码。所以,获取它。我们不需要 onboarding。也不需要这个。没有 user integration。等等。让我们将它变成一个 async 函数。让我们导入我们需要的东西。好了,让我们导入 card 以及 card title。在这里,我们有 processed,然后是数字。所以,让我们只说 emails processed。而且我们不需要所有东西,email processed,带有邮件图标。我有点喜欢我们这里拥有的,比这个更好。所以,正如您所看到的,我们刚刚添加了这个并替换了代码,然后分配了一堆常量。所以,现在让我们编写代码。现在,我们可以通过将 action logs entries 展平到单个列表中来获取 literally process emails,然后获取有关处理了多少电子邮件,创建了多少草稿,总共创建了多少任务等信息。所以,让我们这样做。所以,首先我们需要为 runs 编写一个查询,它将是 await get agent runs。我们有吗?还没有。所以,让我们去 queries,然后创建一个 agent runs query。所以,我们从 agents run 表中获取它,只要 user ID 匹配,并添加一个 limit of 20,例如。在 monitoring page 中,让我们导入它。在这里,我们需要展平它。所以,我们可以说 processed emails = 一个数组,对于 cons runs of runs,cons log = runs,从 run 中获取 action log,或者将其设为一个空数组,通过另一个 for 循环,其中 entries 在 log 中,然后在这里我们可以说 if entry.id,然后 process emails push,在这里让我们只做 entry process stat 将是 run app。好了。所以,high priority 将是 processed emails.filter high。total task 将只是一个 reduce 函数,它为您进行计算。Total process 将是 process emails.length,就这样。所以,这正是我们想要的。所以,如果我们回到这里,这是数据。完美。所以,它已经创建了所有这些信息。现在,这些基本上是我们刚刚获得的已处理电子邮件。所以,我将把组件放在 components 中,可能是在 agents 中,称之为 email detail。现在这个不存在。没关系。让我们修复这里的导入。但我们暂时在这里这样做。但在这种情况下,本质上我们正在创建这个 card,这很棒,并且还在展开它,它打开了 action items 以及其他东西。我真的很想在这个特定的课程中专注于后端部分和架构部分,这就是为什么我试图确保我给您所有必需的标记。好的。所以,现在就在下面,我们可以做 stats space Y3,然后遍历 process emails 并在这里调用 email detail。所以,如果我们回到这里并向下滚动,您已经可以看到处理的两封电子邮件。例如,一封被标记为 high,另一封只是 high。您只需要确保 king 是正确的。我们可以做 less index。好了。类似这样。所以,现在,正如您所看到的,我们没有看到这封电子邮件的任何 action items 或 drafted replies,即使它们是草稿。这意味着我们显然错过了将其添加到数据库。所以,这是修复方法。如果我们去 agent,我们真的需要在一处,当我们完成 agent run 时,我们想在这里记录它。所以,我们肯定错过了 action items 的值,使其成为 action items。第二,我们还需要添加其余的,比如 entry.reply 等等,这就是为什么它实际上缺失了。所以,如果您再次运行它,我相信它会正常工作。基本上,这就是页面的摘要。然后第二件事是,我还删除了很多我们不需要的代码。这是我们不需要的。而 total drafts 需要来自 process emails。我们像这样修复了它。好了。所以,我们的页面看起来非常漂亮。我们只需要修复元素之间的间距,正如您所看到的。所以,如果我们去 dashboard,让我们只做 class name space by three。现在它们之间有一些间距。去 dashboard 这里。有更多间距。所以,让我们添加四。然后让我们也包装其中的一个 div。好了。所以,我们的间距终于修复了。完美。所以,正如您所看到的,run agent 现在按钮工作了,monitoring 也显示了所有最新的数据,这正是我们想要的。现在最后一部分是让我们的自主行为工作,那就是让 cron job 工作。这个特定 agent 的自主行为的酷之处在于,我们可以说我们希望它每分钟运行一次,每 15 分钟运行一次。我们也可以通过监听 webhook 的方式来做到这一点,然后只运行 agent,然后只运行 agent。但事实是,如果您想省钱,这也很有用,因为您不希望事情一直运行。这是一个很好的模型,因为每 15 分钟它就会检查一次。所以,为了实现它,我们将使用 Vercel 的 cron jobs,因为它有那个。还记得我们在这里设置了 cron secret 吗?现在我们还需要从 request.headers.get 中获取 cron secret。然后在这里我们可以称之为 authorization。然后 request 来自 next request。例如,我们设置了 cron secret。正如我们所说的,如果它不是 chron job,那么它将执行此操作。所以,这是手动运行。否则,它将是自动运行,对吧?所以,这是一个手动运行,而这是一个自动运行,对吧?这正是我们在这里想要做的。所以,对于 cron secret,我们需要在我们的环境变量中设置它。而这个 cron secret 可以是任何东西,对吧?就像创建您想要的任何值一样。确保它足够独特,没有人能猜到。实际上创建一个乱码值。一旦您设置了 run secret,现在这个 agent,当您设置该环境变量时,它将自主运行,一旦您有了这个值并设置了 authorization 的 header。所以,这里我们可以说,如果它是 chron,如果是 secret,而 cron secrets 等于,然后我们可以说最好是这个。现在,如果不是 chron,那么这样做,否则,如果它是一个 cron 触发的事件,那么我们可以做一些其他事情。就像这样。所以,这里我们可以获取符合条件的用户的资格,我们需要运行 prong job。所以,我们可以调用 get agent eligible users,然后循环遍历它们,然后在这里我们可以获取 eligible users 的 user ID,使用该 user ID 运行 agent,一旦完成,我们就可以将其推送到 results 中,我们可以将其存储在这个数组中。所以,我们可以说 push user ID status 是 success,然后是 result,然后就是这样。然后 response 将只是 results,然后 process 将是 results.length at length。这就是我们所需要的。现在,我们只需要编写这个特定的函数,它可以放在 queries 中。基本上,获取具有 agent enable 的用户,就像他们拥有那个一样,这就是我们所需要的。现在,让我们导入它。这就是我们运行它自主运行所需要的一切。就是这样。这个 run agent 函数将运行,但它只会在获得所有用户和 agent eligible users 时运行。所以,我们可以将其更改为这样,其中我们选择它,然后进行 inner join on integrations,因为我们不仅希望它为 true,还希望提供商是 Gmail。这是我们关心的另一件事。肯定需要在这里进行一些调整。这里有一个错误需要修复,它在 dashboard page 中,因为我们基本上正在完成我们的项目。它看起来非常漂亮。我们只需要部署它来测试 prawn 功能,以及其他一切都应该如我们所期望的那样工作。所以,tasks created 在这里,而我们从未给它一个 key。所以,让我们部署我们的应用程序。为此,让我们转到 GitHub,然后在这里创建一个全新的存储库,我们称之为 exact os open claw。将其设置为 public 并创建存储库。我们需要创建一个 GitHub 存储库并推送它。所以,让我们复制这些行,因为我们已经有一个 GitHub 存储库。当您使用 create next tab 创建一个新的 NexJS 项目时,它会自动创建一个 Git 存储库。

那么,让我们转到终端,停止服务器,我已经提交了所有更改。所以我们可以简单地这样做。我们的更改现在已经部署。所以如果我刷新页面,一切都会正常工作。现在让我们转到 Versel 并添加一个新项目。现在这个项目是我创建的,实际上我们可以导入它,就环境变量而言。我们想从这里获取所有环境变量并添加它。但有几件事我们需要调整。首先,Clerk ID,这两个需要是生产实例的,而不是开发实例的。其次,它现在指向 localhost 3001。情况并非如此。我们的域名很可能是 https://l.app。所以这是我们需要调整的。我们还需要做的另一件事是转到我们的项目,Google 项目,也就是 Exacos。在这里,让我们添加另一个 URI,基本上是 https://do.app。我们需要那个。同样,回调也将是类似的,诸如此类。现在让我们点击保存。这对你来说非常重要,因为没有它它就不会起作用。Gmail 将停止工作。所以我们已经实现了所有这些。我们还需要做的另一件事是更改此 cron 密钥值。这是你可以猜测的东西。让我们转到终端,然后执行 `o secret`。这将运行这个特定的包,并生成一个我们可以使用的密钥。所以它现在正在运行。好了。我们可以复制这个密钥,然后在这里设置它。这对我们来说非常重要。现在让我们继续部署我们的应用程序。现在,提供的 URL 是我们设置的 execos.openclaw.versel.app。希望它不错。它在生产环境中。我们还需要在这里更改加密密钥,但你可以用我向你展示的完全相同的方法做同样的事情。确保你这样做。好的。我们还要做的另一件事是,在构建运行时,我们也运行 `bun build` 或 `bun run build`。这将创建一个优化的生产构建。这正是 Versel 在我们部署应用程序时也将为我们运行的命令。所以如果有什么问题,我们可以捕获它并创建一个新的构建,创建一个新的提交。所以我们现在就来做。看起来一切都完美地工作了。所以我们应该不会有问题,但我们很快就会知道。好的。它仍在部署。让我们回去。部署。它正在构建。所以如果你看一下,不是日志,而是构建日志。这实际上是在运行 `next build`,它创建了一个优化的生产构建。完美。在发生这一切的同时,让我们进入设置中的 cron 作业。它们已被启用。我们需要为项目添加一个 cron 作业,我们已经有一个无服务器函数了。现在我们只需要添加这个。所以这将专门每 x 秒运行我们的 cron 作业。这正是我们所做的,即通过添加 `run secret` 环境变量来防止未经授权的访问,并检查传入的请求。所以任何时候,只要有一个带有这个特定 `run secret` 的请求,才会发生调用,否则就不会。所以我们需要创建一个 `vercel.json`,然后是 cron 作业。所以 `vercel.json`,设置它,我希望它每 15 分钟运行一次。所以这将是 `agent run`,这是每 15 分钟一次。所以现在让我们推送这个。让我们也修复我们之前遇到的这个密钥错误。所以我们只说 `jobs setup` 然后继续推送。好的。所以现在如果我们回去,我们的应用程序就可以使用了,这太棒了。由于我们还没有真正更改 Clerk 的主页,这里只有一个定价表。我们仍然需要构建登陆页面作为最后一步,但我们将在下一步进行。但正如你在这里看到的,我们有账单。所以让我们继续登录。因为我们还没有真正切换我们的开发凭据,我们可以简单地用我的 Gmail ID 登录,它应该仍然有效。事实上,这甚至有效是一件好事,因为我们能够简单地使用它。现在如果我转到连接 Gmail,它已经连接了,对吧?因为它是相同的凭据。现在让我们用另一个 ID 试试。让我们去登录,然后去我的另一个电子邮件地址,然后继续。现在如果我们去仪表板,这现在是免费计划。让我们连接 Gmail。Gmail 未连接。这将是一个很好的测试,看看一切是否正常。它确实正常,这太棒了。这就是我们想要的。如果你收到一个未经授权的错误,这意味着你需要像我在 Google 控制台项目中向你展示的那样更改回调 URL。现在,让我们只使用这个作为电子邮件地址。这仍然会显示出来,因为我们仍然需要将我们的应用程序提交到 Google 控制台。所以,就像我们需要提交它。我们需要提交它来完成。所以让我们继续,太棒了,它已成功连接。现在在此期间,我们也可以在这里查找 cron 作业,我们可以简单地每 15 分钟运行一次,或者现在手动运行一次。所以它还没有运行,但每 15 分钟它就会运行。所以现在如果我们去 Versel,去仪表板,因为 Gmail 已经连接了。让我们现在运行代理,只是为了确保它是一个手动运行并且有效。如果一切正常,我们应该在监控中看到一些东西,但没有电子邮件需要处理。它确实处理了一封电子邮件。所以让我们去监控,这是电子邮件。新设备登录到你的 Exacos 帐户,这是我收到的电子邮件。这些是行动项,太棒了。所以它能够创建 do 任务,处理电子邮件,一切都按预期工作。所以最后一步是检查 cron 作业。它们现在已经配置好了。看看它。它也刚刚运行了。所以它运行得非常好,这太棒了。所以这个项目就到此结束了。我真心希望你喜欢它并从中学习了很多。如果你想要更多这样的内容,那么可以看看我创建的这个 Next.js 16 速成课程,或者看看我们构建和部署生产就绪应用程序的这个项目。非常感谢你的观看。再见。