🔍 搜尋結果:C

🔍 搜尋結果:C

Supabase Auth:身分連結、Hooks 和 HaveIBeenPwned 集成

我們很高興地宣布 Supabase Auth 的四項新功能: 1. 身份連結 2. 會話控制 3. 密碼外洩保護 4. 帶有 Postgres 函數的 Auth Hooks {% 嵌入 https://youtu.be/LF8GABnAFyE %} ## 身份連結 當使用者登入時,系統會使用身份驗證方法和登入提供者建立身分。從歷史上看,如果身分與使用者共享相同的經過驗證的電子郵件,[Supabase Auth](https://supabase.com/docs/guides/auth) 會自動將身分連結到使用者。這可以方便地刪除重複的用戶帳戶。然而,一些開發人員還需要靈活地連結不共享相同電子郵件的帳戶。 今天,我們推出身份連結,開發人員可以使用它手動連結兩個單獨的身份。我們為開發人員新增了兩個新端點來管理身分連結流程: 使用者登入後,使用「linkIdentity()」[連結 OAuth 身分:](https://supabase.com/docs/reference/javascript/auth-linkidentity) ``` const { data, error } = await supabase.auth.linkIdentity({ provider: 'google', }) ``` 使用 `unlinkIdentity()` 來[取消連結身分](https://supabase.com/docs/reference/javascript/auth-unlinkidentity): ``` // retrieve all identities linked to a user const { data: { identities }, } = await supabase.auth.getUserIdentities() // find the google identity linked to the user const googleIdentity = identities.find(({ provider }) => provider === 'google') // unlink the google identity from the user const { data, error } = await supabase.auth.unlinkIdentity(googleIdentity) ``` 目前,這些方法支援連結 OAuth 身分。要將電子郵件或電話身分連結到用戶,您可以使用 [updateUser()](https://supabase.com/blog/supabase-auth-identity-linking-hooks#:~:text=can%20use% 20the -,updateUser(),-method.)方法。 預設情況下禁用手動連結。您可以在[儀表板驗證設定](https://supabase.com/dashboard/project/_/settings/auth) 中為您的專案啟用它。 ![如何啟用手動連結](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kmsrmiw0ue3q5rshji7v.jpg) > 有關更多訊息,請參閱[身份連結文件](https://supabase.com/docs/guides/auth/auth-identity-linking)。 ## 會話控制 Supabase Auth 從使用者登入應用程式那一刻起管理整個會話生命週期。這涉及以下步驟: 1. 為使用者建立會話。 2. 刷新會話以使其保持活動狀態。 3. 過期或登出時撤銷會話。 對於想要更好地控制使用者會話的開發人員,我們公開了 3 個新設定: - **時間盒使用者會話:** 強制使用者在一段時間間隔後再次登入。 - **不活動逾時:** 如果使用者在一段時間內不活動,則強制使用者重新登入。 - **每個使用者單一會話:** 將使用者限制為單一會話。保留最近的活動會話,並終止所有其他會話。 這些會話控制設定在專業版及以上版本中可用。 ![如何強制每個使用者單一會話](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aov0nw5xch0m4hw97vsg.jpg) > 有關更多訊息,請參閱[會話管理文件](https://supabase.com/docs/guides/auth/sessions)。 ## 密碼外洩保護 由於常見的使用者行為(例如選擇可猜測的密碼或在不同平台上重複使用密碼),密碼本質上可能是不安全的。 儘管 OAuth 和 magiclinks 更安全,但我們認識到密碼將繼續存在。我們希望讓用戶不易陷入潛在的陷阱。為了實現這一目標,我們在 Supabase Auth 中整合了 [HaveIBeenPwned.org](https://haveibeenpwned.com/) _Pwned Passwords API_,以防止使用者使用洩漏的密碼。 > **去圖書館** ℹ️ 我們開源了一個 Go 函式庫,用於與我們在身分驗證伺服器中使用的 [HaveIBeenPwned.org](http://haveibeenpwned.org/) Pwned 密碼 API 互動。查看 [存儲庫](https://github.com/supabase/hibp) 並隨時貢獻! 作為附加步驟,我們新增了為您的使用者指定密碼要求的功能。這可以透過[儀表板:](https://supabase.com/dashboard/project/_/settings/auth) 中專案的身份驗證設定進行配置 ![新增密碼要求](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3o9ax3tudp7v8tba67hd.jpg) > 請參閱[密碼文件](https://supabase.com/docs/guides/auth/passwords) 以了解更多資訊。 ## 驗證掛鉤 我們收到了大量回饋,詢問如何自訂 Auth,例如: - 將自訂聲明新增至存取權杖 JWT - 多次嘗試 MFA 驗證失敗後註銷用戶 - 對密碼驗證嘗試套用自訂規則 我們的目標是保持簡單、無縫的 Supabase Auth 體驗。對於大多數開發人員來說,它應該可以輕鬆工作,而無需自訂。但是,認識到應用程式的多樣性,您現在可以透過 Auth Hook 擴展標準 Auth 功能。 Auth Hooks 只是 Postgres 函數,它們在 Auth 生命週期的關鍵點同步執行,以更改操作的結果。 例如,要使用 Auth Hooks 自訂 JWT 聲明,您可以建立一個 Postgres 函數,該函數接受第一個參數中的 JWT 聲明並傳回您希望 Supabase Auth 使用的 JWT。 假設您正在建立一個遊戲化應用程式,並且希望將用戶的層級作為自訂聲明附加到 JWT: ``` create function custom_access_token_hook(event jsonb) returns jsonb language plpgsql as $$ declare user_level jsonb; begin -- fetch the current user's level select to_jsonb(level) into user_level from profiles where user_id = event->>'user_id'::uuid; -- change the event.claims.level return jsonb_set( event, '{claims,level}', user_level); end; $$ ``` 在資料庫中建立函數後,您只需使用 Supabase Auth 註冊它: ![Auth Hooks](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/unkgs56o62l8c4kfjzjg.jpg) 目前,您可以為流程中的以下點註冊 Auth Hook: - **自訂存取權杖:** 每次產生新的 JWT 時都會呼叫。 - **MFA 驗證嘗試:** 每次驗證 MFA 因素時都會呼叫,從而可以更好地控制檢測和阻止嘗試。 - **密碼驗證嘗試:** 每次使用密碼登入使用者時都會呼叫,從而可以更好地控制使用者帳戶的安全性。 如果編寫 PL/pgSQL 函數不是您的強項,您始終可以使用 [pg_net](https://supabase.com/docs/guides/database/extensions/pg_net) 向後端 API 發送請求,或使用[plv8]( https://supabase.com/docs/guides/database/extensions/plv8) 透過用JavaScript 編寫函數來更輕鬆地操作JSON。 Auth Hooks 今天可供自架,並將於下個月推出到該平台。如果您需要盡快存取,請透過[支援](https://supabase.help/)與我們聯繫! 那不是全部! Postgres 函數並不是寫鉤子的唯一方法。 Supabase 是 [Standard Webhooks](https://www.standardwebhooks.com/) 的創始貢獻者,這是一組關於輕鬆、安全、可靠地發送和接收 Webhook 的開源工具和指南。當然,Auth Hooks 將在 2024 年第一季支援 Webhooks。 ## 還有一件事… 如果您從一開始就關注我們(https://supabase.com/blog/supabase-auth),您就會知道Supabase Auth 是透過分叉[Netlify 的GoTrue 伺服器](https://github.com)開始的/netlify/gotrue)。從那時起,發生了很多變化,我們已經偏離了上游儲存庫。在這個階段,將專案重新命名為其他名稱是有意義的(提示鼓聲)-Auth。 這僅僅意味著儲存庫將從使用“gotrue”重新命名為“auth”。但別擔心! Docker 映像和庫(如“@supabase/gotrue-js”)將繼續發布,只要當前 v2 版本受支持,您就可以互換使用“@supabase/auth-js”。所有類別和方法都保持不變。這裡沒有重大變化! ## 結論 感謝您閱讀到最後!我們希望您喜歡第 X 週發布的 Supabase Auth 更新:身分連結、會話控制、洩露密碼保護和帶有 Postgres 功能的 Auth Hooks。 我們期待看到您使用這些新功能建立的內容,當然還有您的回饋意見,以使它們變得更好。 ## 更多發布第 X 週 - [第 1 天 - Supabase Studio 更新:AI 助理與使用者模擬](https://supabase.com/blog/studio-introducing-assistant) - [第 2 天 - Edge Functions:節點和本機 npm 相容性 ](https://supabase.com/blog/edge-functions-node-npm) -[第 3 天 - 介紹 Supabase Branching,這是一個針對每個拉取請求的 Postgres 資料庫](https://supabase.com/blog/supabase-branching) - [Postgres語言伺服器:實作解析器](https://supabase.com/blog/postgres-language-server-implementing-parser) - [Supabase 專輯](https://www.youtube.com/watch?v=r1POD-IdG-I) - [Supabase 啟動週 X 黑客松](https://supabase.com/blog/supabase-hackathon-lwx) - [啟動週 X 社群聚會](https://supabase.com/blog/community-meetups-lwx) --- 原文出處:https://dev.to/supabase/supabase-auth-identity-linking-hooks-and-haveibeenpwned-integration-19e1

使用 Prisma、Supabase 和 Shadcn 設定 Next.js 專案。

## 設定 Next.js 先執行以下指令,使用supabase、typescript和tailwind初始化下一個js專案:`npx create-next-app@latest`。選擇所有預設選項: ## 設定 Prisma 執行以下命令安裝 prisma: `npm install prisma --save-dev` 安裝 prisma 後,執行以下命令來初始化架構檔案和 .env 檔案: `npx 棱鏡熱` 現在應該有一個 .env 檔案。您應該加入您的database_url 將 prisma 連接到您的資料庫。應該看起來像這樣: ``` // .env DATABASE_URL=url ``` 在你的 schema.prisma 中你應該要加入你的模型,我現在只是使用一些隨機模型: ``` generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model Post { id String @default(cuid()) @id title String content String? published Boolean @default(false) author User? @relation(fields: [authorId], references: [id]) authorId String? } model User { id String @default(cuid()) @id name String? email String? @unique createdAt DateTime @default(now()) @map(name: "created_at") updatedAt DateTime @updatedAt @map(name: "updated_at") posts Post[] @@map(name: "users") } ``` 現在您可以執行以下命令將資料庫與架構同步: `npx prisma 資料庫推送` 為了在客戶端存取 prisma,您需要安裝 prisma 用戶端。您可以透過執行以下命令來執行此操作: `npm 安裝@prisma/client` 您的客戶端也必須與您的架構同步,您可以透過執行以下命令來做到這一點: `npx prisma 生成` 當您執行“npx prisma db push”時,會自動呼叫產生指令。 為了存取 prisma 用戶端,您需要建立它的一個實例,因此在 src 目錄中建立一個名為 lib 的新資料夾,並在其中新增一個名為 prisma.ts 的新檔案。 ``` // prisma.ts import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient(); export default prisma; ``` 現在您可以在任何檔案中匯入相同的 Prisma 實例。 ## 設定 Shadcn 首先執行以下命令開始設定 shadcn: `npx shadcn-ui@latest init` 我選擇了以下選項: 打字稿:是的 風格:預設 底色: 板岩色 全域 CSS:src/app/globals.css CSS 變數:是 順風配置:tailwind.config.ts 元件:@/元件(預設) utils:@/lib/utils(預設) 反應伺服器元件:是 寫入 Components.json:是 接下來執行以下命令來設定下一個主題: `npm 安裝下一個主題` 然後將一個名為 theme-provider.tsx 的檔案加入到您的元件庫中並新增以下程式碼: ``` // theme-provider.tsx "use client" import * as React from "react" import { ThemeProvider as NextThemesProvider } from "next-themes" import { type ThemeProviderProps } from "next-themes/dist/types" export function ThemeProvider({ children, ...props }: ThemeProviderProps) { return <NextThemesProvider {...props}>{children}</NextThemesProvider> } ``` 設定完提供者後,您需要將其新增至 layout.tsx 中,以便在整個應用程式上實現它。使用主題提供者包裝 {children},如下所示: ``` // layout.tsx return ( <html lang="en" suppressHydrationWarning> <body className={inter.className}> <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange > {children} </ThemeProvider> </body> </html> ); ``` 現在前往 shadcn [主題頁](https://ui.shadcn.com/themes)。然後選擇您要使用的主題並按複製程式碼。然後將複製的程式碼加入您的 globals.css 中,如下所示: ``` // globals.css @tailwind base; @tailwind components; @tailwind utilities; @layer base { :root { --background: 0 0% 100%; --foreground: 224 71.4% 4.1%; --card: 0 0% 100%; --card-foreground: 224 71.4% 4.1%; --popover: 0 0% 100%; --popover-foreground: 224 71.4% 4.1%; --primary: 262.1 83.3% 57.8%; --primary-foreground: 210 20% 98%; --secondary: 220 14.3% 95.9%; --secondary-foreground: 220.9 39.3% 11%; --muted: 220 14.3% 95.9%; --muted-foreground: 220 8.9% 46.1%; --accent: 220 14.3% 95.9%; --accent-foreground: 220.9 39.3% 11%; --destructive: 0 84.2% 60.2%; --destructive-foreground: 210 20% 98%; --border: 220 13% 91%; --input: 220 13% 91%; --ring: 262.1 83.3% 57.8%; --radius: 0.5rem; } .dark { --background: 224 71.4% 4.1%; --foreground: 210 20% 98%; --card: 224 71.4% 4.1%; --card-foreground: 210 20% 98%; --popover: 224 71.4% 4.1%; --popover-foreground: 210 20% 98%; --primary: 263.4 70% 50.4%; --primary-foreground: 210 20% 98%; --secondary: 215 27.9% 16.9%; --secondary-foreground: 210 20% 98%; --muted: 215 27.9% 16.9%; --muted-foreground: 217.9 10.6% 64.9%; --accent: 215 27.9% 16.9%; --accent-foreground: 210 20% 98%; --destructive: 0 62.8% 30.6%; --destructive-foreground: 210 20% 98%; --border: 215 27.9% 16.9%; --input: 215 27.9% 16.9%; --ring: 263.4 70% 50.4%; } } ``` 現在您應該能夠在專案中使用 shadcn 元件和主題。 ## 設定 Supabase 第一步是建立一個新的 SUPABASE 專案。接下來,安裝 next.js 驗證幫助程式庫: `npm install @supabase/auth-helpers-nextjs @supabase/supabase-js` 現在您必須將您的 supabase url 和您的匿名金鑰新增至您的 .env 檔案中。您的 .env 檔案現在應如下所示: ``` // .env DATABASE_URL=url NEXT_PUBLIC_SUPABASE_URL=your-supabase-url NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key ``` 我們將使用 supabase cli 根據我們的架構產生類型。使用以下命令安裝 cli: `npm install supabase --save-dev` 為了登入 supabase,請執行“npx supabase login”,它會自動讓您登入。 現在我們可以透過執行以下命令來產生我們的類型: `npx supabase gen types typescript --project-id YOUR_PROJECT_ID > src/lib/database.types.ts` 應該在您的 lib 資料夾中新增文件,其中包含基於您的架構的類型。 現在在專案的根目錄中建立一個 middleware.ts 檔案並新增以下程式碼: ``` import { createMiddlewareClient } from "@supabase/auth-helpers-nextjs"; import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; import type { Database } from "@/lib/database.types"; export async function middleware(req: NextRequest) { const res = NextResponse.next(); const supabase = createMiddlewareClient<Database>({ req, res }); await supabase.auth.getSession(); return res; } ``` 現在,在應用程式目錄中建立一個名為 auth 的新資料夾,然後在 auth 中建立另一個名為callback 的資料夾,最後建立一個名為route.ts 的檔案。在該文件中加入以下程式碼: ``` // app/auth/callback/route.ts import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs"; import { cookies } from "next/headers"; import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; import type { Database } from "@/lib/database.types"; export async function GET(request: NextRequest) { const requestUrl = new URL(request.url); const code = requestUrl.searchParams.get("code"); if (code) { const cookieStore = cookies(); const supabase = createRouteHandlerClient<Database>({ cookies: () => cookieStore, }); await supabase.auth.exchangeCodeForSession(code); } // URL to redirect to after sign in process completes return NextResponse.redirect(requestUrl.origin); } ``` 透過該設置,我們可以建立一個登入頁面。在應用程式目錄中建立一個名為「login with page.tsx」的新資料夾。 ``` // app/login/page.tsx "use client"; import { createClientComponentClient } from "@supabase/auth-helpers-nextjs"; import { useRouter } from "next/navigation"; import { useState } from "react"; import type { Database } from "@/lib/database.types"; export default function Login() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const router = useRouter(); const supabase = createClientComponentClient<Database>(); const handleSignUp = async () => { await supabase.auth.signUp({ email, password, options: { emailRedirectTo: `${location.origin}/auth/callback`, }, }); router.refresh(); }; const handleSignIn = async () => { await supabase.auth.signInWithPassword({ email, password, }); router.refresh(); }; const handleSignOut = async () => { await supabase.auth.signOut(); router.refresh(); }; return ( <> <input name="email" onChange={(e) => setEmail(e.target.value)} value={email} /> <input type="password" name="password" onChange={(e) => setPassword(e.target.value)} value={password} /> <button onClick={handleSignUp}>Sign up</button> <button onClick={handleSignIn}>Sign in</button> <button onClick={handleSignOut}>Sign out</button> </> ); } ``` 現在,在 auth 目錄中建立一個名為「sign-up」的新資料夾,並在該檔案中建立一個「route.ts」。新增以下程式碼: ``` // app/auth/sign-up/route.ts import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs"; import { cookies } from "next/headers"; import { NextResponse } from "next/server"; import type { Database } from "@/lib/database.types"; export async function POST(request: Request) { const requestUrl = new URL(request.url); const formData = await request.formData(); const email = String(formData.get("email")); const password = String(formData.get("password")); const cookieStore = cookies(); const supabase = createRouteHandlerClient<Database>({ cookies: () => cookieStore, }); await supabase.auth.signUp({ email, password, options: { emailRedirectTo: `${requestUrl.origin}/auth/callback`, }, }); return NextResponse.redirect(requestUrl.origin, { status: 301, }); } ``` 在同一位置建立另一個名為「登入」的資料夾。 ``` // app/auth/login/route.ts import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs"; import { cookies } from "next/headers"; import { NextResponse } from "next/server"; import type { Database } from "@/lib/database.types"; export async function POST(request: Request) { const requestUrl = new URL(request.url); const formData = await request.formData(); const email = String(formData.get("email")); const password = String(formData.get("password")); const cookieStore = cookies(); const supabase = createRouteHandlerClient<Database>({ cookies: () => cookieStore, }); await supabase.auth.signInWithPassword({ email, password, }); return NextResponse.redirect(requestUrl.origin, { status: 301, }); } ``` 最後在同一位置新增註銷路由。 ``` // app/auth/logout/route.ts import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs' import { cookies } from 'next/headers' import { NextResponse } from 'next/server' import type { Database } from '@/lib/database.types' export async function POST(request: Request) { const requestUrl = new URL(request.url) const cookieStore = cookies() const supabase = createRouteHandlerClient<Database>({ cookies: () => cookieStore }) await supabase.auth.signOut() return NextResponse.redirect(`${requestUrl.origin}/login`, { status: 301, }) } ``` 現在,當您導航至 localhost http://localhost:3000/login 時,應該有基本的登入登出註冊功能。 現在我們有了一些帶有 prisma shadcn 和 supabase auth 設定的下一個 js 應用程式的基本樣板。 --- 原文出處:https://dev.to/isaacdyor/setting-up-nextjs-project-with-prisma-200j

Edge Functions:Node 和本機 npm 相容性

我們很高興地宣布,[Edge Functions](https://supabase.com/docs/guides/functions) 現在原生支援 npm 模組和 Node 內建 API。您可以將數百萬個流行、常用的 npm 模組直接匯入 Edge Functions 中。 `從 'npm:drizzle-orm/node-postgres' 導入 { drizzle }` ## 將現有 Node 應用程式遷移到 Edge Functions 您可以透過最少的變更將現有的 Node 應用程式遷移到 Supabase Edge Functions。 我們建立了一個示範來展示如何遷移使用 Express、Node Postgres 和 Drizzle 的 Node 應用程式。有關在 Edge Functions 中使用 npm 模組和 Node 內建程式的更多訊息,請參閱[管理依賴項指南](https://supabase.com/docs/guides/functions/import-maps)。 {% 嵌入 https://youtu.be/eCbiywoDORw %} **npm 模組的底層運作原理** 我們執行一個開源 Deno 伺服器來託管 Edge Functions,稱為 [Supabase Edge Runtime](https://supabase.com/blog/edge-runtime-self-hosted-deno-functions)。此自訂版本可協助我們保持 Edge Functions 以相同的方式運作,無論部署在何處 - 在我們的託管平台上、在本地開發中還是在您的自託管環境中。 加入 npm 支援時最大的挑戰是找到適用於所有環境的方法。我們希望保持工作流程接近 Deno CLI 體驗。應該可以直接在原始程式碼中導入 npm 模組,而無需額外的建置步驟。 部署函數時,我們將其模組圖序列化為單一檔案格式([eszip](https://github.com/denoland/eszip))。在託管環境中,所有模組引用都會從 eszip 中載入。這可以防止獲取模組時出現任何額外的延遲以及模組依賴關係之間的潛在衝突。 我們也在本機和自架環境中使用了 eszip 模組載入器,因此我們只需要為所有環境實作一種模組載入策略。作為本地開發的另一個好處,此方法避免了與使用者係統中安裝的 npm 模組的潛在衝突,因為 Edge Function 的 npm 模組是獨立於 eszip 中的。 [重構模組載入器](https://github.com/supabase/edge-runtime/pull/223)修正了一些其他錯誤,例如[邊緣函數錯誤](https://github.com/supabase/cli /issues/1584#issuecomment-1848799355) 當專案中已存在`deno.lock` 檔案時。 ## 您要求的其他一些東西... **區域呼叫** 現在,您可以選擇在執行邊緣函數時指定區域(也許我們將來應該更改名稱)。通常,邊緣函數在最靠近呼叫函數的使用者的區域中執行。但是,有時您希望在靠近 Postgres 資料庫或其他第 3 方 API 的地方執行它,以獲得最佳效能。 功能仍然部署到所有區域。但是,在呼叫過程中,您可以提供“x-region”標頭以將執行限制在特定區域。 **捲曲** ``` # https://supabase.com/docs/guides/functions/deploy#invoking-remote-functions curl --request POST 'https://<project_ref>.supabase.co/functions/v1/hello-world' \ --header 'Authorization: Bearer ANON_KEY' \ --header 'Content-Type: application/json' \ --header 'x-region: eu-west-3' \ --data '{ "name":"Functions" }' ``` **JavaScript** ``` // https://supabase.com/docs/reference/javascript/installing import { createClient } from '@supabase/supabase-js' // Create a single supabase client for interacting with your database const supabase = createClient('https://xyzcompany.supabase.co', 'public-anon-key') // https://supabase.com/docs/reference/javascript/functions-invoke const { data, error } = await supabase.functions.invoke('hello-world', { body: { name: 'Functions' }, headers: { 'x-region': 'eu-west-3' }, }) ``` > ℹ️查看[區域呼叫指南](https://supabase.com/docs/guides/functions/regional-inspiration)以了解更多詳情。 **更好的指標** 我們在 [Supabase 儀表板](https://supabase.com/dashboard/project/_/functions) 的 Edge Functions 部分中加入了更多指標:它現在顯示 CPU 時間和使用的記憶體。我們也按 HTTP 狀態碼細分了呼叫。 這些變更可協助您發現邊緣功能的任何問題並採取行動。 > ℹ️ 請參閱 Edge Functions 的[日誌記錄和指標指南](https://supabase.com/docs/guides/functions/debugging) 以了解更多資訊。 ![使用視覺化範例](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9ki4pk0w0ykpa9i2c47q.jpg) **使用 Sentry 追蹤錯誤** 我們 Sentry 的朋友最近發布了官方的 [Sentry SDK for Deno](https://deno.land/x/[email protected])。有了這個,現在可以輕鬆追蹤 Sentry 邊緣函數中的錯誤和異常。 以下是一個簡單的範例,說明如何處理函數中的異常並將其傳送到 Sentry。 ``` import * as Sentry from 'https://deno.land/x/sentry/index.mjs' Sentry.init({ dsn: _DSN_, integrations: [], // Performance Monitoring tracesSampleRate: 1.0, // Set sampling rate for profiling - this is relative to tracesSampleRate profilesSampleRate: 1.0, }) // Set region and execution_id as custom tags Sentry.setTag('region', Deno.env.get('SB_REGION')) Sentry.setTag('execution_id', Deno.env.get('SB_EXECUTION_ID')) Deno.serve(async (req) => { try { const { name } = await req.json() const data = { message: `Hello ${name}!`, } return new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' } }) } catch (e) { Sentry.captureException(e) return new Response(JSON.stringify({ msg: 'error' }), { status: 500, headers: { 'Content-Type': 'application/json' }, }) } }) ``` ## 下一步是什麼 NPM 支援是 Edge Functions 最受歡迎的功能之一。如果您之前因缺乏支援而無法使用 Edge Functions,我們希望此更新能夠吸引您[再試一次](https://supabase.com/dashboard/project/_/functions)。如果您遇到任何問題,我們只需[一個支援請求](https://supabase.help/)。 對於現有的 Edge Functions 用戶來說,區域呼叫、更好的指標和錯誤處理只是接下來會發生的事情的一瞥。我們繼續迭代平台穩定性並對邊緣功能可以使用的資源設定自訂限制。請留意新的一年的另一篇文章。 ## 更多發布第 X 週 - [第 1 天 - Supabase Studio 更新:AI 助理與使用者模擬](https://supabase.com/blog/studio-introducing-assistant) - [pg_graphql:現在支援 Postgres 函式](https://supabase.com/blog/pg-graphql-postgres-functions) - [Postgres語言伺服器:實作解析器](https://supabase.com/blog/postgres-language-server-implementing-parser) - [Supabase 設計如何運作](https://supabase.com/blog/how-design-works-at-supabase) - [Supabase 專輯](https://www.youtube.com/watch?v=r1POD-IdG-I) - [Supabase 啟動週 X 黑客松](https://supabase.com/blog/supabase-hackathon-lwx) - [啟動週 X 社群聚會](https://supabase.com/blog/community-meetups-lwx) --- 原文出處:https://dev.to/supabase/edge-functions-node-and-native-npm-compatibility-77f

🚨 🚀 25 個原因(你必須知道!!)為什麼「Listicles」對 dev.to 不利 🤯 👿 🚨

是的,這篇文章的標題就是一個例子,類似於許多所謂的「Listicles」之一,這些「Listicles」一直在開發人員中亂扔。它們的標題充滿了表情符號和「你必須知道」或「對任何開發人員來說都是必不可少的」之類的詞語,它們吸引了我們人類天生的好奇心。 ## 重要的! ### 並非所有清單都不好! 請遵循以下準則來建立一個好的指南: - 確定目的:明確定義清單的目的和目標。您想提供讀者什麼資訊或價值? - 選擇相關表情符號:選擇與內容相符並有助於您想要傳達的訊息的表情符號。確保它們不會讓人不知所措或分散注意力。 - 提供有意義的內容:專注於建立內容豐富且寫得好的文本,為讀者提供價值。表情符號應該補充內容而不是掩蓋內容。 - 評估整體影響:在發布之前,請檢查清單文章,以確保標題中使用表情符號或潛在的標題誘餌文字可以增強讀者的體驗並支持文章的預期目的。 透過執行這些步驟,您可以建立一個清單文章,該文章可以根據需要有效地包含盡可能多的表情符號和「您必須知道」文本,同時保持內容的品質和實質內容。 ![dev.to 上的點擊誘餌](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a5tcsqtizqxzg10tvo1l.png) ![更多](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/90n3gpjrhz13i0w17z5o.png) 本質上,它們是點擊誘餌。這些帖子訴諸了人性中被稱為“FOMO”的東西。 ## 什麼是 FOMO? FOMO 代表「害怕錯過」。這是一種以焦慮或不安的感覺為特徵的現象,這種感覺來自於相信其他人可能擁有自己未參與的有益經驗或機會。 FOMO 通常與社交媒體以及其他人分享的持續更新和活動聯繫在一起。 ## 什麼是「表情符號誘餌」? 這些貼文利用的另一件事是「表情符號誘餌」。這個概念最近才被探索。它涉及使用 🤯、🚨、👿 和 🚀 等表情符號在社交媒體平台上操縱人們的情緒。人工智慧(AI)是這方面的專家。 **為什麼這些貼文不好?** 因為幾乎不可能找到任何好的內容! 作為內容創作者,我們有責任以各種可能的方式支持我們的開發者同行的成長。在下面的評論中,讓我們討論如何在 DEV 這樣的平台上實現這一目標。我很想聽聽您對此事的看法。如何才能提升DEV社群的價值,讓所有開發者不那麼難以承受? 請記住,成為一名出色的開發人員不僅僅需要了解所有可用的工具或資源。這是關於磨練解決問題的能力、培養創造力和擁抱終身學習。讓我們優先考慮這些基本技能,其餘的就自然而然了。 而且,正如所承諾的,這裡有 🚀 🚨 對於 dev.to 🤯 🚨 來說「Listicles」不好的 25 個原因。當您閱讀清單文章時,請考慮它如何反映其內容(畢竟,它本身就是清單文章) {% 可折疊 點選展開並查看清單 %} 1.內容淺薄:清單文章和點擊誘餌通常優先考慮簡潔性而不是深度,導致主題覆蓋膚淺。 2. 缺乏脈絡:它們提供的脈絡或背景資訊有限,使讀者的理解支離破碎。 3. 過度簡化:複雜的主題可能會過度簡化以適應清單格式,導致誤解和誤解。 4.標題誘餌:使用聳人聽聞或誤導性的標題來吸引點擊,損害內容的完整性。 5. 誤導性資訊:清單文章和點擊誘餌可能包含不準確或過時的訊息,導致讀者誤入歧途。 6. 缺乏可信度:有些缺乏可靠的來源或參考資料,導致難以驗證所提供資訊的準確性。 7. 內容重複:類似主題的清單文章經常有重複的內容,對尋求新見解的讀者沒有什麼價值。 8.缺乏深度:由於篇幅限制,清單文章很少深入研究某個主題的複雜或微妙的面向。 9. 阻礙批判性思考:清單文章和標題誘餌提供填鴨式訊息,阻礙讀者進行批判性思考和分析。 10. 忽略替代觀點:他們通常提出單一觀點,排除不同或互相衝突的意見。 11. 貨幣化高於品質:有些人優先考慮透過廣告創造收入,而不是製作高品質、資訊豐富的內容。 12. 不切實際的期望:清單文章和點擊誘餌可能會在沒有適當支持的情況下做出廣泛的主張或承諾,從而造成不切實際的期望。 13.內容研究不足:由於急於快速製作內容,一些內容缺乏徹底的研究和事實查核。 14. 缺乏原創性:許多人重複使用其他來源的內容,但沒有加入任何獨特的價值或見解。 15. 話題過度飽和:熱門話題經常被重複提及,導致社群充斥著冗餘內容。 16. 忽略長篇寫作:清單文章和標題誘餌掩蓋了長篇文章,削弱了深入分析的重要性。 17. 抑制學習:清單文章和點擊誘餌的小規模性質可能會阻礙讀者尋求全面的學習經驗。 18.注意力持續時間縮短:持續閱讀清單文章和點擊誘餌可能會導致注意力持續時間縮短,並減少對詳細內容的關注。 19. 浪費點擊:清單文章和點擊誘餌可能會用誘人的標題來吸引讀者,但結果卻是提供膚淺或無用的內容。 20. 失去細微差別:複雜的主題在簡化為編號列表時就失去了細微差別,從而過度簡化了重要細節。 21. 缺乏參與度:清單文章和點擊誘餌由於其膚淺的性質,經常無法產生有意義的討論或社區參與。 22. 創造力下降:它們可能會扼殺創意表達和原創性,因為作者覺得必須遵守清單格式。 23. 寫作標準下降:對快速清單文章和標題誘餌的需求可能會降低社群內寫作的整體品質和標準。 24. 延續線上瀏覽:清單文章和點擊誘餌助長了瀏覽而不是深度閱讀的文化,影響了整體理解。 25. 專業知識貶值:它們可能削弱主題專家的專業知識,因為他們的見解被濃縮或過度簡化。 {% 結束折疊 %} 我希望您覺得這篇文章很有趣!如果您對我做的其他事情感興趣,請查看我的網站: [https://the-best-codes.github.io/](https://the-best-codes.github.io/?ref=dev.to_post&click=https://dev.to/best_codes/25-原因-你-必須-知道-為什麼-listicles-are-bad-for-devto-1hok/&site=https://dev.to/&reason=best-codes-bad-listicles-post) 謝謝閱讀! _2023年12月13日:_ _更新說明:_ 您可能已經看過我的一些帖子,“面向 Web 開發人員的 11 個令人驚嘆的書籤🔖 🚀(它們是什麼?🤔)”、“6️⃣ 常見瀏覽器:它們到底有多安全? 🔒 😯」和「為無聊的程式設計師提供的 10 個有趣的 Web 開發專案創意 👍」。我相信這些不是“列表文章”或標題誘餌,因為它們並不淺薄,而且它們不僅僅是一個列表。如果您對其中任何一個有不同的意見,請告訴我如何改進它們! --- 原文出處:https://dev.to/best_codes/25-reasons-you-must-know-why-listicles-are-bad-for-devto-1hok

使用 Swirl 將 AI ✨ 加入到您的企業:搜尋更聰明、更好、更快 ⚡️

# 傳統搜尋的問題 傳統方法是將資料從一個容器提升並轉移到另一個容器。在很多情況下這是一個大問題。建立倒排索引廣泛應用於傳統搜尋引擎中,以實現快速資訊檢索。然而,這種方法的計算成本可能很高,特別是在辨識新資料並將其整合到這些索引中時。隨著業務的發展和資料變得更加複雜和龐大,這些傳統系統往往難以跟上。 此外,企業現在正以前所未有的速度產生新的資料類型,轉向分散式、基於雲端的資訊池的轉變加劇了這些困難。 傳統的企業資訊存取系統依賴定期更新的倒排索引,較不適合這種動態、異質的資料環境。它們無法輕鬆適應新資料類型的持續湧入或基於雲端的資訊系統的分散性。 這會導致資料檢索效率低下和延遲,從而阻礙組織內的決策和營運工作流程。 ![企業中的傳統搜尋](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0wwek4j7ubmh07b367zf.png) _Swirl 3.0 透過連接到各種資料來源並同時搜尋它們,為這個問題提供了一個簡單而優雅的解決方案。_ # 漩渦 3.0 功能 {% 嵌入 https://www.youtube.com/watch?v=nA8e0kMEDxs %} Swirl 建構在 Python Django 堆疊上,並提供了一個名為 Galaxy UI 的使用者友善介面。它可以在 Docker 中執行,也可以作為 Microsoft Azure 中的託管服務執行。 Swirl 使用戶能夠利用人工智慧驅動的重新排名功能,同時維護資料安全和隱私。 Swirl 的搜尋技術改變了企業跨應用程式和資料儲存存取資訊的方式。透過利用先進的大型語言模型,Swirl 可以快速篩選來自多個來源(例如 Salesforce 和 Microsoft365)的資料,為使用者提供最相關的結果和見解。 ![漩渦搜尋的工作原理](https://camo.githubusercontent.com/c2d20d9f469ed27110309dc8e4cd7d05c9f6019cd3f7622c8676563428a1c043/68747622c8676563428a1c043/68747622c8676563428a1c043/68747476267 e 746f6461792f696d616765732f416e696d6174696f6e5f322e676966) ## Swirl 方法的好處是顯而易見的: - 使用者收到根據其特定需求量身定制的微調搜尋結果。 - 無需移動資料或重新索引內容的麻煩。 ## 關鍵點: ![與 ChatGPT 漩渦](https://camo.githubusercontent.com/2e8a3a2d0345b29d2163569905a9d9a832e64bf0543f63e7691a7a3a2db01a99/bf0543f63e7691a7a3a2db01a99/60543f63e7691a7a3a2db01a99/687467267 72 6c2e746f6461792f696d616765732f416e696d6174696f6e5f312e676966) - Swirl 使用 LLM 技術對來自不同來源(如資料孤島、Salesforce、Microsoft 等)的搜尋結果進行分析和排名。 - 漩渦搜尋增強了近乎即時的相關性排名,並將目標查詢的結果置於上下文中。 - 該系統允許針對特定學科領域定制法學碩士,用戶回饋證實了 Swirl 相關性排名的有效性。 - Swirl 最大限度地減少了重新索引的需要,消除了搜尋基礎設施的內容移動,並有效地管理相關性排名和重複資料刪除。 ## 連接器: ![可用且不斷成長的連接器清單](https://res.cloudinary.com/practicaldev/image/fetch/s--jEv8D0Ca--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev -to -uploads.s3.amazonaws.com/uploads/articles/uy1qfukybrdbuogn8yh2.png) 您可以在我們的 GitHub 頁面上找到可用連接器清單的廣泛概述。如果您希望按需並優先建立任何內容,請透過「[email protected]」聯絡 Swirl 支援團隊。 # 內部工作和用例 Swirl 整合了先進的內容處理和分析。它使用 API(應用程式介面)來定位和排名多個來源的內容,並透過控制項來增強某些內容。 Swirl 的框架允許快速尋找資訊並將其串流傳輸到各種基於搜尋的應用程式的資料管道中,例如檢索增強生成 (RAG) 和微調大型語言模型。 它提供對組織資料孤島內的資訊的存取,解決與企業搜尋解決方案相關的傳統成本、複雜性和開發問題。 Swirl 採用 OAuth2 等基於標準的身份驗證機制來消除權限和安全性問題。 隨著組織的發展和數位資產的多樣化,像 Swirl 這樣的工具變得不可或缺。請繼續關注我們探索人工智慧驅動的解決方案如何塑造資訊存取和管理的未來。 # Swirl 是開源的 Swirl 是一個開源搜尋平台。這對您意味著什麼: {% 嵌入 https://github.com/swirlai/swirl-search %} - 它是一個自託管、非限制性軟體,具有寬鬆的 Apache 2.0 授權。 - 軟體開發人員可以為專案的開發做出貢獻,深入了解搜尋生態系統,同時深入了解 Swirl。 - 如果您想了解有關 Swirl 的更多訊息,請加入我們的 Slack 社區,進行更多討論。 {% cta https://join.slack.com/t/swirlmetasearch/shared_invite/zt-1qk7q02eo-kpqFAbiZJGOdqgYVvR1sfw %} 加入 Slack {% endcta %} --- 原文出處:https://dev.to/swirl/adding-ai-to-your-enterprise-with-swirl-search-smarter-better-and-faster-4f9b

為 2023 年準備好你自己的 DEV 🎁

隨著每個人和他們的貓為他們的應用程式建立一個“2023 Wrapped”,我無法阻止,不得不為這個很棒的 dev.to 社區建立一個小型開源應用程式 🥰 造訪[devto-wrapped.sliplane.app](https://devto-wrapped.sliplane.app/?username=code42cate),輸入您的用戶名,看看您作為dev.to 的作者在2023 年取得了什麼成就! **無需 API 金鑰或登入!** 這是我在 dev.to 的第一年的經驗: ![我的包裹](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c4zst6ibuahiq6wtk0e1.png) PS:在評論中分享你的截圖,我會隨機挑選一個人,給他們發送一些免費的開發者貼紙作為提前的聖誕禮物🎅🎁 不管怎樣,你來這裡是為了學習一些東西,所以讓我們深入研究程式碼吧! ## 教程 建立這個小應用程式的速度對我來說至關重要,因此我決定使用我最近使用的自己的[Hackathon Starter Template](https://dev.to/code42cate/how-to-win-any-hackathon -3i99)寫了關於。我剝離了一些我不需要的功能,從而產生了一個非常精簡的 monorepo: 1.Next.js + Tailwind 2. ShadcnUI 你可以在這個[Github儲存庫](https://github.com/Code42Cate/devto-wrapped)中看到所有內容 ### 設定 如果您想長期關注並親自嘗試一下,請按照以下步驟操作: ``` # Clone repository git clone https://github.com/Code42Cate/devto-wrapped.git # Install dependencies pnpm install # Start app pnpm run dev --filter web ``` 該應用程式現在應該從 http://localhost:3000 啟動。如果它不起作用,請在評論中告訴我! ### 存取 dev.to 資料 這個小應用程式最有趣的部分可能是我們如何存取 dev.to 資料。雖然有幾種方法可以解決這個問題,但我有一些要求幫助我決定前進的方向: 1. 不抓取 - 花費太長時間,我希望資料可用 <1 秒 2. 僅公開資料 - 我不想向使用者詢問 API 金鑰或使用我自己的 3.不需要資料庫-我很懶,想避免無用的複雜性 這為我們提供了兩種可能的獲取資料的方式: 1. [記錄和未經驗證的 API 呼叫](https://developers.forem.com/api/v1) 2. 即使您未登錄,dev.to 網站也會進行未記錄的公開 API 呼叫 考慮到這兩種獲取資料的方式,我們基本上可以獲得 3 類資料: 1.使用API公開使用者資訊:`dev.to/api/users/by_username` 2. 使用 `dev.to/search/feed_content` API 和 `class_name=Article` 發布帖子 3. 包含 `dev.to/search/feed_content` 和 `class_name=Comment&search_fields=xyz` 的搜尋查詢的評論 這些 API 呼叫都是在伺服器端進行的,以加快請求速度,可以在「/apps/web/actions/api.ts」中找到。由於這只是組合在一起,因此功能相當簡單,錯誤處理也非常少: ``` export async function getUserdata(username: string): Promise<User | undefined> { const res = await fetch( `https://dev.to/api/users/by_username?url=${username}`, ); if (!res.ok) { return undefined; } const data = await res.json(); return data as User; } ``` 對於這個用例來說,這很好,但如果您不希望用戶發生意外崩潰,請記住正確捕獲異常並驗證您的類型😵 ### 計算統計資料 計算統計資料出奇地容易,主要是因為我們的資料非常小。即使你每天發帖,我們只會瀏覽 365 個帖子。迭代 365 個專案的陣列幾乎不需要時間,這給了我們很大的空間來完成工作,而無需關心效能!您在頁面上看到的每個統計資料都是在單一函數中計算的。以「總反應」為例: ``` const reactionsCount = posts?.reduce( (acc: number, post: Article) => acc + post.public_reactions_count, 0, ); ``` 我們需要做的就是檢查帖子陣列並總結每個帖子的“public_reactions_count”數量。田田,完成! 即使對於更複雜的,它也只不過是一個嵌套循環: ``` const postsPerTag: Record<string, number> = posts?.reduce( (acc: Record<string, number>, post: Article) => { post.tag_list.forEach((tag) => { acc[tag] = acc[tag] ? acc[tag] + 1 : 1; }); return acc; }, {} as Record<string, number>, ); ``` ### 前端 由於這是使用 Next.js 建構的,因此所有內容都可以在「/apps/web/app/page.tsx」檔案中找到。 在元件的頂部,您可以先看到我們如何取得資料並檢查使用者是否存在或是否有足夠的資料來顯示任何內容: ``` const user = await getUserdata(username); if (!user) { return <EmptyUser message="This user could not be found 🫠" />; } const stats = await getStats(user.id.toString()); const mentionsCount = await getMentionedCommentCount(user.username); if (stats.postCount === 0) { return <EmptyUser message="This user has no posts 🫠" />; } ``` 不同的統計資料都是它們自己的元件,它們是 CSS 網格的一部分,看起來像這樣(縮短) ``` <div className="grid grid-cols-2 gap-2 w-full text-sm text-gray-800"> <PublishedPostsCard count={stats.postCount} /> <ReactionsCard count={stats.reactionsCount} /> <BusiestMonthCard busiestMonth={stats.busiestMonth} postsPerMonth={stats.postsPerMonth} /> <CommentsCard count={stats.commentsCount} /> <ReadingTimeCard readingTime={stats.readingTime} totalEstimatedReadingTime={stats.totalEstimatedReadingTime} /> </div> ``` 這些元件都是「啞」的,這意味著它們只負責顯示資料。他們不獲取或計算任何東西。其中大多數都非常簡單,就像這張「最佳貼文」卡: ``` import Image from "next/image"; import { Article } from "@/actions/api"; export default function BestPostCard({ post, coverImage, }: { post: Article; coverImage: string; }) { return ( <div className="flex w-full flex-col justify-between gap-2 rounded-xl border border-gray-300 bg-white p-4 shadow-md"> Your fans really loved this post: <br /> <Image src={coverImage} alt={post.title} width={500} height={500} className="rounded-md border border-gray-300" /> <a className="font-semibold underline-offset-2" href={`https://dev.to${post.path}`} > {post.title} </a> </div> ); } ``` ### 部署 為了部署我們的應用程式,我們將對其進行dockerize,然後使用Sliplane(稍微有偏見,我是聯合創始人!)將其託管在我們自己的[Hetzner Cloud](https://www.hetzner.com /cloud) 伺服器上。我在[上一篇部落格文章](https://dev.to/sliplane/understanding-nextjs-docker-images-2g08)中介紹瞭如何對Next.js 應用程式進行docker 化,這基本上是相同的,只是做了一些小的更改適應我的 Turborepo 設定:) ``` # src Dockerfile: https://github.com/vercel/turbo/blob/main/examples/with-docker/apps/web/Dockerfile FROM node:18-alpine AS alpine # setup pnpm on the alpine base FROM alpine as base ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable RUN pnpm install turbo --global FROM base AS builder # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. RUN apk add --no-cache libc6-compat RUN apk update # Set working directory WORKDIR /app COPY . . RUN turbo prune --scope=web --docker # Add lockfile and package.json's of isolated subworkspace FROM base AS installer RUN apk add --no-cache libc6-compat RUN apk update WORKDIR /app # First install the dependencies (as they change less often) COPY .gitignore .gitignore COPY --from=builder /app/out/json/ . COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml COPY --from=builder /app/out/pnpm-workspace.yaml ./pnpm-workspace.yaml RUN pnpm install # Build the project COPY --from=builder /app/out/full/ . COPY turbo.json turbo.json RUN turbo run build --filter=web # use alpine as the thinest image FROM alpine AS runner WORKDIR /app # Don't run production as root RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs USER nextjs COPY --from=installer /app/apps/web/next.config.js . COPY --from=installer /app/apps/web/package.json . # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/standalone ./ COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static COPY --from=installer --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public CMD node apps/web/server.js ``` 在 Docker 化並推送到 Github 儲存庫後,我們需要做的就是在 Sliplane 中建立一個新服務並選擇我們想要託管的伺服器。我已經有一台伺服器,在上面執行一些小型專案,所以我只使用該伺服器: ![Sliplane 建立服務](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2r1wfded0cy9vhw103dx.png) 點擊「部署」後,需要幾分鐘時間來建置並啟動我們的 Docker 映像。可以在日誌檢視器中監視進度: ![Sliplane 日誌檢視器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mpmxb1jlp540qvblxmoa.png) 第一次成功部署後,我們將獲得一個可以存取我們的應用程式的免費子網域,或者我們可以加入自己的自訂網域: ![網域](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tc7h2eu1ctw8o5xeq9xp.png) 就是這樣!我們的應用程式在線,世界上每個人都可以存取,並且不會產生令人驚訝的無伺服器帳單 🤑 感謝您到目前為止的閱讀,不要忘記用您的截圖進行評論,以_可能_贏得一些貼紙😊 乾杯,喬納斯 --- 原文出處:https://dev.to/code42cate/devto-wrapped-2023-13o

✨23 個開源函式庫,用於將您的作品集發射到月球🚀🚀

為優秀的開源庫做出貢獻是建立作品集的好方法。 我已經編譯了 23 個優秀的開源程式庫和一些很好的入門問題。 不要忘記加星號並支持這些🌟 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fi3dd111pv2948ya21w2.gif) --- #產品中的人工智慧: ### 1. [CopilotKit](https://github.com/CopilotKit/CopilotKit) - 應用內 AI 聊天機器人與 AI 文字區域 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ox3mv8nmqzot6m4kvkdh.png) 開源平台,用於使用兩個 React 元件將關鍵 AI 功能整合到 React 應用程式中。 CopilotPortal:應用程式內人工智慧聊天機器人,可以「查看」當前應用程式狀態並採取行動。 CopilotTextarea:AI 驅動的 <textarea /'> 替換。具有自動完成、插入和生成功能。 ###[好第一期:](https://github.com/CopilotKit/CopilotKit/issues/62) ``` Support bold and italicized text in CopilotTextarea Proposal: Add support for bold and italicized text in CopilotTextarea CopilotTextarea uses slate-js under the hood. Lots of examples for adding bold/italicized support Initially only add programatic support. UI support will be added separately in [TODO add issue] Implementation tips: changes will be made to render-element.tsx and base-copilot-textarea.tsx custom-editor.tsx structures may also require changes ``` {% cta https://github.com/CopilotKit/CopilotKit %} Star CopilotKit ⭐️ {% endcta %} --- ###2.[Tavily GPT 研究員](https://github.com/assafelovic/gpt-researcher){% embed https://github.com/assafelovic/gpt-researcher no-readme %} ###3.[Pezzo.ai](https://github.com/pezzolabs/pezzo){% 嵌入 https://github.com/pezzolabs/pezzo no-readme %} ###4.[Weaviate](https://github.com/weaviate/weaviate){% 嵌入 https://github.com/weaviate/weaviate no-readme %} ###5.[LangChain](https://github.com/langchain-ai/langchain){% 嵌入 https://github.com/langchain-ai/langchain no-readme %} --- &nbsp; #🛜網頁開發: ### 6. [Wasp](https://github.com/wasp-lang/wasp) - 使用 React 和 Node.js 開發全端 Web 應用程式 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/54jp6j6r8ils6we97i0f.png) 使用 React 和 Node.js 進行快速全端 Web 應用程式開發。 Wasp 提供了一種建立現代 Web 應用程式的簡化方法,將前端的 React 和後端的 Node.js 結合在一個緊密結合的框架中。 ###[好第一期:](https://github.com/wasp-lang/wasp/issues/874) ``` Add images (or link to the example app) of auth UI helpers Wasp provides At this point in docs (also in the tutorial if we're using it), it would be nice to add an image of UI helpers for Auth (login/signup form, Google/GitHub button, ...) so developers can immediately see what they are getting and how nice it looks. ``` {% cta https://github.com/wasp-lang/wasp %} 星黃蜂 ⭐️ {% endcta %} --- ###7.[ClickVote](https://github.com/clickvote/clickvote) {% 嵌入 https://github.com/clickvote/clickvote no-readme %} ###8.[ReactFlow](https://github.com/xyflow/xyflow) {% 嵌入 https://github.com/xyflow/xyflow no-readme %} ###9.[Trigger.dev](https://github.com/triggerdotdev/trigger.dev) {% 嵌入 https://github.com/triggerdotdev/trigger.dev no-readme %} ###10.[Novu](https://github.com/novuhq/novu) {% 嵌入 https://github.com/novuhq/novu no-readme %} --- &nbsp; #🧑‍💻DevOps: ### 11. [Logstash](https://github.com/elastic/logstash) - 由 elastic 傳輸和處理日誌和事件。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0nhya28nmwby9lurtrta.png) 輕鬆將點讚、按讚和評論加入到您的網路應用程式中。 用於加入這些元件的簡單反應程式碼。 ### [第一期好](https://github.com/elastic/logstash/issues/15561) ``` Allow comments in pipeline config between hash entries Currently it seems not allowed to make comments between hash entries, this is a feature request to allow it. ``` {% cta https://github.com/elastic/logstash %} 明星 Logstash ⭐️ {% endcta %} --- ###12.[Odigos](https://github.com/keyval-dev/odigos) {% 嵌入 https://github.com/keyval-dev/odigos no-readme %} ###13.[Glasskube](https://github.com/glasskube/operator) {% 嵌入 https://github.com/glasskube/operator no-readme %} ###14.[鏡像](https://github.com/metalbear-co/mirrord){% 嵌入 https://github.com/metalbear-co/mirrord no-readme %} ###15.[挖土機](https://github.com/diggerhq/digger) {% 嵌入 https://github.com/diggerhq/digger no-readme %} --- &nbsp; #💽資料庫: ### 16. [Supabase](https://github.com/supabase/supabase) - 開源 Rirebase 替代品 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j4xnzrefrjaaywu4b49p.png) 使用託管 Postgres、身份驗證和即時功能建立現代資料驅動應用程式 ###[第一期好:](https://github.com/supabase/supabase/issues/19396) ``` Horizontal Scroll for CodeBlocks Currently when reading the dcs, it's not possible to view all of the code for alot of the samples. Is this the Component rendered across all of the web properties, if so I'll be happy to throw on a horizontal scroll bar that matches supabase branding. ``` {% cta https://github.com/supabase/supabase %} 明星 Supabase ⭐️ {% endcta %} --- ###17.[Appwrite](https://github.com/appwrite/appwrite){% 嵌入 https://github.com/appwrite/appwrite no-readme %} ###18.[Superduperdb] (https://github.com/SuperDuperDB/superduperdb){% 嵌入 https://github.com/SuperDuperDB/superduperdb no-readme %} ###19.[Milvus](https://github.com/milvus-io/milvus) {% 嵌入 https://github.com/milvus-io/milvus no-readme %} --- &nbsp; #👾其他: ### 21. [Snapify](https://github.com/MarconLP/snapify) - 開源螢幕錄製 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/89h8mjriix6hdihcrfr8.png) 螢幕錄製,但免費、開源,您負責自己的資料。 ###[好第一期:](https://github.com/MarconLP/snapify/issues/18) ``` Ability to create GIFs and take screenshots to also store in S3 ``` {% cta https://github.com/MarconLP/snapify %} 明星 Snapify ⭐️ {% endcta %} --- ###22.[ReactAgent](https://github.com/eylonmiz/react-agent){% 嵌入 https://github.com/eylonmiz/react-agent no-readme %} ###23.[對初學者來說很棒](https://github.com/MunGell/awesome-for-beginners){% embed https://github.com/MunGell/awesome-for-beginners no -readme %} --- #就是這樣,夥計們! ## 別忘了按讚、留言和收藏🫡 --- 原文出處:https://dev.to/copilotkit/23-open-source-libraries-to-launch-your-portfolio-to-the-moon-fe

🧙‍♂️ 使用 ChatGPT 助理產生部落格 🪄 ✨

# 長話短說;博士 我們都已經看到了 ChatGPT 的功能(這對任何人來說都不陌生)。 很多文章都是使用 ChatGPT 一遍又一遍地寫的。 **實際上**,DEV 上的文章有一半是用 ChatGPT 寫的。 你可以使用一些[AI內容偵測器](https://copyleaks.com/ai-content- detector)來檢視。 問題是,ChatGPT 永遠不會產生一些非凡的內容,除了它內部已經有(經過訓練/微調)的內容。 但有一種方法可以超越目前使用 RAG(OpenAI 助理)訓練的內容。 [上一篇](https://dev.to/triggerdotdev/train-chatgpt-on-your-documentation-1a9g),我們討論了在您的文件上「訓練」ChatGPT;今天,讓我們看看如何從中製作出很多內容。我們將: - 使用 Docusaurus 建立新的部落格系統。 - 詢問 ChatGPT,為我們寫一篇與文件相關的部落格文章。 ![部落格](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ms26qb0uahpi898s0qun.gif) --- ## 你的後台工作平台🔌 [Trigger.dev](https://trigger.dev/) 是一個開源程式庫,可讓您使用 NextJS、Remix、Astro 等為您的應用程式建立和監控長時間執行的作業! &nbsp; [![GiveUsStars](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bm9mrmovmn26izyik95z.gif)](https://github.com/triggerdotdev/trigger.dev) 請幫我們一顆星🥹。 這將幫助我們建立更多這樣的文章💖 {% cta https://github.com/triggerdotdev/trigger.dev %} 為 Trigger.dev 儲存庫加註星標 ⭐️ {% endcta %} --- ## 上次回顧 ⏰ - 我們建立了一個作業來取得文件 XML 並提取所有 URL。 - 我們抓取了每個網站的 URL 並提取了標題和內容。 - 我們將所有內容儲存到文件中並將其發送給 ChatGPT 助手。 - 我們建立了一個 ChatBot 畫面來詢問 ChatGPT 有關文件的資訊。 您可以在此處找到上一個[教學]的完整原始程式碼(https://github.com/triggerdotdev/blog/tree/main/openai-assistant)。 --- ![工具](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i4adju83b5s1k0qozh3x.png) ## 稍作修改⚙️ 上次,我們建立了一個文件助理。我們寫: ``` You are a documentation assistant, loaded with documentation from ' + payload.url + ', return everything in an MD format. ``` 讓我們將其更改為部落格作者,請轉到“jobs/process.documentation.ts”第 92 行,並將其替換為以下內容: ``` You are a content writer assistant. You have been loaded with documentation from ${payload.url}, you write blog posts based on the documentation and return everything in the following MD format: --- slug: [post-slug] title: [post-title] --- [post-content] ``` 使用“slug”和“title”非常重要,因為這是 Docusaurus 的格式 - 我們的部落格系統可以接受(當然,我們也以 MD 格式發送所有輸出) --- ![Docusaurus](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gu8wlh7qk8e3rh6mz35v.png) ## 多庫龍🦖 您可以使用多種類型的部落格系統! 對於我們的用例,我們將使用 Docusaurus,它可以讀取基於 MD 的格式(我們從 ChatGPT 請求的輸出)。 **我們可以透過執行來安裝 Docusaurus:** ``` npx create-docusaurus@latest blog classic --typescript ``` 接下來,我們可以進入已建立的目錄並執行以下命令: ``` npm run start ``` 這將啟動 Docusaurus。你可以關註一下。還有一個名為“blog”的附加目錄,其中包含所有部落格文章;這是我們保存 ChatGPT 產生的部落格文章的地方。 ![範例](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pgo25rlkw85nfvbh0y4s.png) --- ![部落格](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v3oxjtli1dn9i9klnj5t.png) ## 產生部落格 📨 我們需要創造一個就業機會 - 取得部落格標題 - 使用 ChatGPT 產生完整的部落格文章 - 將其保存到我們部落格上的 MD 文件中 我們可以輕鬆地使用 ChatGPT 來實現這一點! 前往“jobs”資料夾並新增一個名為“process.blog.ts”的新檔案。新增以下程式碼: ``` import { eventTrigger } from "@trigger.dev/sdk"; import { client } from "@openai-assistant/trigger"; import {object, string} from "zod"; import {openai} from "@openai-assistant/helper/open.ai"; import {writeFileSync} from "fs"; import slugify from "slugify"; client.defineJob({ // This is the unique identifier for your Job, it must be unique across all Jobs in your project. id: "process-blog", name: "Process Blog", version: "0.0.1", // This is triggered by an event using eventTrigger. You can also trigger Jobs with webhooks, on schedules, and more: https://trigger.dev/docs/documentation/concepts/triggers/introduction trigger: eventTrigger({ name: "process.blog.event", schema: object({ title: string(), aId: string(), }) }), integrations: { openai }, run: async (payload, io, ctx) => { const {title, aId} = payload; const thread = await io.openai.beta.threads.create('create-thread'); await io.openai.beta.threads.messages.create('create-message', thread.id, { content: ` title: ${title} `, role: 'user', }); const run = await io.openai.beta.threads.runs.createAndWaitForCompletion('run-thread', thread.id, { model: 'gpt-4-1106-preview', assistant_id: payload.aId, }); if (run.status !== "completed") { console.log('not completed'); throw new Error(`Run finished with status ${run.status}: ${JSON.stringify(run.last_error)}`); } const messages = await io.openai.beta.threads.messages.list("list-messages", run.thread_id, { query: { limit: "1" } }); return io.runTask('save-blog', async () => { const content = messages[0].content[0]; if (content.type === 'text') { const fileName = slugify(title, {lower: true, strict: true, trim: true}); writeFileSync(`./blog/blog/${fileName}.md`, content.text.value) return {fileName}; } }); }, }); ``` - 我們加入了一些必要的變數: - `title` 部落格文章標題 - `aId` 上一篇文章中新增的助手 ID。 - 我們為助手建立了一個新線程(`io.openai.beta.threads.create`) - 我們無法在沒有任何線程的情況下質疑它。與之前的教程不同,在這裡,我們對每個請求建立一個新線程。我們不需要對話中最後一條訊息的上下文。 - 然後,我們使用部落格標題為線程(`io.openai.beta.threads.messages.create`)新增訊息。我們不需要提供額外的說明 - 我們已經在第一部分完成了該部分😀 - 我們執行 `io.openai.beta.threads.runs.createAndWaitForCompletion` 來啟動進程 - 通常,您需要某種每分鐘執行一次的遞歸來檢查作業是否完成,但是 [Trigger.dev]( http://Trigger .dev)已經加入了一種執行進程並同時等待它的方法🥳 - 我們在查詢正文中執行帶有“limit: 1”的“io.openai.beta.threads.messages.list”,以從對話中獲取第一則訊息(在ChatGPT 結果中,第一則訊息是最後一條訊息) 。 - 然後,我們使用「writeFileSync」從 ChatGPT 取得的值來儲存新建立的部落格 - 確保您擁有正確的部落格路徑。 轉到“jobs/index.ts”並加入以下行: ``` export * from "./process.blog"; ``` 現在,讓我們建立一個新的路由來觸發該作業。 前往“app/api”,建立一個名為“blog”的新資料夾,並在一個名為“route.tsx”的新檔案中 新增以下程式碼: ``` import {client} from "@openai-assistant/trigger"; export async function POST(request: Request) { const payload = await request.json(); if (!payload.title || !payload.aId) { return new Response(JSON.stringify({error: 'Missing parameters'}), {status: 400}); } // We send an event to the trigger to process the documentation const {id: eventId} = await client.sendEvent({ name: "process.blog.event", payload }); return new Response(JSON.stringify({eventId}), {status: 200}); } ``` - 我們檢查標題和助理 ID 是否存在。 - 我們在 [Trigger.dev](http://Trigger.dev) 中觸發事件並發送訊息。 - 我們將事件 ID 傳送回客戶端,以便我們可以追蹤作業的進度。 --- ![前端](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kgh52s7mxd20w91kr3c9.png) ## 前端🎩 沒什麼好做的! 在我們的「components」目錄中,建立一個名為「blog.component.tsx」的新檔案和以下程式碼: ``` "use client"; import {FC, useCallback, useEffect, useState} from "react"; import {ExtendedAssistant} from "@openai-assistant/components/main"; import {SubmitHandler, useForm} from "react-hook-form"; import {useEventRunDetails} from "@trigger.dev/react"; interface Blog { title: string, aId: string; } export const BlogComponent: FC<{list: ExtendedAssistant[]}> = (props) => { const {list} = props; const {register, formState, handleSubmit} = useForm<Blog>(); const [event, setEvent] = useState<string | undefined>(undefined); const addBlog: SubmitHandler<Blog> = useCallback(async (param) => { const {eventId} = await (await fetch('/api/blog', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(param) })).json(); setEvent(eventId); }, []); return ( <> <form className="flex flex-col gap-3 mt-5" onSubmit={handleSubmit(addBlog)}> <div className="flex flex-col gap-1"> <div className="font-bold">Assistant</div> <select className="border border-gray-200 rounded-xl py-2 px-3" {...register('aId', {required: true})}> {list.map(val => ( <option key={val.id} value={val.aId}>{val.url}</option> ))} </select> </div> <div className="flex flex-col gap-1"> <div className="font-bold">Title</div> <input className="border border-gray-200 rounded-xl py-2 px-3" placeholder="Blog title" {...register('title', {required: true})} /> </div> <button className="border border-gray-200 rounded-xl py-2 px-3 bg-gray-100 hover:bg-gray-200" disabled={formState.isSubmitting}>Create blog</button> </form> {!!event && ( <Blog eventId={event} /> )} </> ) } export const Blog: FC<{eventId: string}> = (props) => { const {eventId} = props; const { data, error } = useEventRunDetails(eventId); if (data?.status !== 'SUCCESS') { return <div className="pointer bg-yellow-300 border-yellow-500 p-1 px-3 text-yellow-950 border rounded-2xl">Loading</div> } return ( <div> <a href={`http://localhost:3000/blog/${data.output.fileName}`}>Check blog post</a> </div> ) }; ``` - 我們使用「react-hook-form」來輕鬆控制我們的輸入。 - 我們讓使用者選擇他們想要使用的助手。 - 我們建立一個包含文章標題的新輸入。 - 我們將所有內容傳送到先前建立的路由並傳回作業的「eventId」。 - 我們建立一個新的「<Blog />」元件,該元件顯示載入直到事件完成,並使用新建立的教程新增指向我們部落格的連結。 將元件加入我們的“components/main.tsx”檔案中: ``` {assistantState.filter(f => !f.pending).length > 0 && <BlogComponent list={assistantState} />} ``` 我們完成了! ![完成](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fkm37v5idrxexjje2u3o.png) 現在,讓我們新增部落格標題並點擊「生成」。 ![部落格](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gosm1f1ttz3q1m0atu7s.png) --- ![圖片](https://res.cloudinary.com/practicaldev/image/fetch/s--uTFwMeAp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3。 amazonaws.com/uploads/articles/0half2g6r5zfn7asq084.png) ## 讓我們聯絡吧! 🔌 作為開源開發者,您可以加入我們的[社群](https://discord.gg/nkqV9xBYWy) 做出貢獻並與維護者互動。請隨時造訪我們的 [GitHub 儲存庫](https://github.com/triggerdotdev/trigger.dev),貢獻並建立與 Trigger.dev 相關的問題。 本教學的源程式碼可在此處取得: https://github.com/triggerdotdev/blog/tree/main/openai-blog-writer 感謝您的閱讀! --- 原文出處:https://dev.to/triggerdotdev/generate-blogs-with-chatgpt-assistant-1894

keepHQ 如何獲得前 2,000 顆星!

我很高興與 [Keep](https://github.com/keephq/keep) 的執行長兼聯合創始人 Tal 交談。 它最初是一個 CLI 工具,隨著時間的推移,變成了一個警報聚合工具。 如今,他們擁有近 3,000 顆星。 **您可以在這裡觀看完整影片:** {% 嵌入 https://www.youtube.com/watch?v=eykb1zbDwQo %} <小時/> 開始 ------------ 他們製作了一個非常基本的警報 CLI 工具並將其發佈在 Hackernews 上 - **“顯示 HN:”** 他們乘坐飛機,當他們著陸時,**他們看到了 600 顆星星**。 [Hackernews](https://hackernews.com?utm_source=nevo.github20k.com&utm_medium=referral&utm_campaign=how-keephq-got-their-first-2-000-stars)是一個有趣的網站。它非常醜陋,牽引力很大,而且很難進入。 我在 Hackernews 上看到並推出了很多產品。雖然在 Hackernews 上發表一篇文章很困難,但 **「Show HN」** 的文章要容易得多。 這通常是一個秘密武器,因為你也許可以每年做一次 - 最好與更多管道合作,以獲得更好的機會在 GitHub 上流行。 他們創造了更多工具,例如[gnip](https://www.gnip.io/?utm_source=nevo.github20k.com&utm_medium=referral&utm_campaign=how-keephq-got-their-first-2-000-stars),以及雖然這個工具今天沒有帶來大量流量,但在發布之日就帶來了許多流量。 您會發現,對於您建立的每個副專案,您都可以在 Hackernews (Show HN) 和 Product Hunt 中啟動它。 我的建議? **目標是每月發布一次新專案。** **HACK:** 最近,一些隨機的人在 Hackernews 上發布了 **Novu** 組織頁面。我不知道它是如何被接受的,但它為我們帶來了 400 顆星。 [查看此處的貼文](https://news.ycombinator.com/item?id=38419513&utm_source=nevo.github20k.com&utm_medium=referral&utm_campaign=how-keephq-got-their-first-2-000-stars) <小時/> ![](https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/7eda5059-74b8-4b7a- 9468-d77cce002b2c/image.png?t=1701432843) 與社區一起尋找產品 ------------------------------------------- 雖然 Hackernews 對開源社群(許多早期採用者)非常友好,但 Product Hunt 感覺更像是獨立駭客/非開發人員的工具。 **目標是獲得盡可能多的讚成票並達到列表的頂部。** 您通常應該將 Product Hunt 與其他管道結合。 如果您剛開始並想要星星 - 將您的 GitHub 作為您的“存取”URL。 如果您是一家更知名的公司並且希望獲得更多潛在客戶並預訂會議,請加入您的**網站 URL。** **對於第一次啟動,我通常會瞄準 GitHub 存儲庫。** Keep 的社群很小,但仍然佔據了第一天的份額。 最好的創始人不會讓運氣引導他們。 **這是他們所做的:** * 他們在社交媒體上發布了有關其發布的訊息(像大多數人一樣) * 他們嘗試聯繫盡可能多的人來幫助他們。 * 他們建造了一個秘密武器工具! [Slackline](https://github.com/talboren/slackline?utm_source=nevo.github20k.com&utm_medium=referral&utm_campaign=how-keephq-got-their-first-2-000-stars) <小時/> ![](https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/1ff5c353-9447-45cb- 946c-2a4a3f3e29a6/image.png?t=1701433047) 鬆弛的線路 ------------------- 塔爾不久前向我展示了這個工具,從那時起我就一直使用它。 當您是一家小型新創公司時,您必須完成不可能的任務,從 0 到 1。**使用您擁有的所有可能的選項。** Slackline 正是為此而設計的。您可以加入任何 Slack 群組,並向頻道中的每個可能的人發送 DM。 有幾個選項: 1. 您可以在 Slack 頻道上使用它來推動對您的產品的回饋或從社群成員那裡獲得幫助來完成不同的事情。 2. 在 Product Hunt 上向人們尋求協助。 雖然數字 2 聽起來有點冒犯,但它確實有效 - 他們獲得了當天的第一名以及名譽和榮耀🎩 <小時/> ![](https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/2ac46256-fc15-4bfe- a2bb-724e305e20b9/image.png?t=1701433220) 使用賞金 -------------- 您可以對 [Algora](https://algora.io?utm_source=nevo.github20k.com&utm_medium=referral&utm_campaign=how-keephq-got-their-first-2-000-stars) 的問題進行懸賞,告訴您這會擴大你的社區——可能不會,但會給你帶來更多的可信度。 如果您剛開始,Algora 可以幫助您獲得更多貢獻者(在貢獻者清單中)並使您看起來更可信。 然而,請記住,為了錢而來的貢獻者通常不會免費做同樣的事情(並非總是如此)。 {% cta https://github.com/keephq/keep %}在 GitHub 上加星 Keep{% endcta %} --- ## 我邀請您註冊我的電子報。 若符合以下條件,本通訊對您有好處: - 您正在考慮開源您的產品(或建立新產品)。 - 您正在考慮開啟一個副產品並將其開源(以反映您的主要產品)。 - 您從事科技業,希望在沒有明星/沒有 GitHub 趨勢的情況下實現成長。 這是一份 100% 免費的時事通訊(並且永遠如此)。請隨時註冊: [https://gitroom.com](https://gitroom.com/) ![技術](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/86ywkzncq6erg44d6xy9.gif) --- 原文出處:https://dev.to/github20k/how-keephq-got-their-first-2000-stars-l7i

每個開發者都應該收藏的 19 個 GitHub 儲存庫 📚👍

在當今動態的軟體開發世界中,保持最新的工具、程式庫和框架對於開發人員來說至關重要。 GitHub 提供了寶貴的儲存庫寶庫,可顯著提高您的開發技能和專業知識。 我整理了每個開發人員都應該了解的 19 個 GitHub 儲存庫的列表,為學習、實踐和靈感提供了豐富的資源。 每個儲存庫都分為子類別,以便於導航。我還加入了直接連結和描述,以便立即獲得印象。 --- ## 1\. [集算器SPL](https://github.com/SPLWare/esProc)(贊助) 集算器SPL是新一代資料處理語言,整合SQL資料庫,支援進階分析與平行處理。 透過集算器SPL,您可以輕鬆轉換和分析大量資料集,發現隱藏的模式和趨勢,並從資料中獲得可操作的見解。一些主要功能包括: **⚡ 頂級效能:** 透過集算器SPL的最佳化演算法和高效率的記憶體管理體驗快速的處理速度。 **🚀 豐富的函數庫:** 存取全面的預先建構函數集合,滿足各種資料操作任務。 **✨ 直覺的語法:** 享受清晰簡潔的語法,提升程式碼的可讀性和可維護性。 **👨‍💻 Java整合:** 透過JDBC將集算器SPL腳本無縫整合到Java程式中,縮小資料分析和應用開發之間的差距。 **🧙‍♀️獨立執行:**獨立執行集算器SPL腳本,超越傳統限制,擴展您的資料處理能力。 ![esProc SPL](https://cdn.hashnode.com/res/hashnode/image/upload/v1679824673641/82f843e0-72a1-44a4-bd99-68616f322534.png?w=1600p=84cro格式&格式=webp&自動=壓縮,格式&格式=webp) ⭐ 支援他們的 GitHub 倉庫:[https://github.com/SPLWare/esProc](https://github.com/SPLWare/esProc) --- ## 🌱 學習編碼 ### 2\. [awesome-roadmaps](https://github.com/liuchong/awesome-roadmaps) >***⭐*** *GitHub 星數 3k+* 各種程式語言、框架和工具的路線圖集合。 ### 3\. [awesome-courses](https://github.com/prakhar1989/awesome-courses) >***⭐*** *GitHub 星數超過 50k* 用於學習程式設計、網路開發和其他技術技能的精選線上課程清單。 ### 4\. [free-certifications](https://github.com/cloudcommunity/Free-Certifications) >***⭐*** *GitHub 星數 12k+* 各種技術主題的認證和培訓課程的綜合清單。 ### 5\. [awesome-algorithms](https://github.com/tayllan/awesome-algorithms) >***⭐*** *GitHub 星數超過 15k* 用於學習和練習演算法和資料結構的資源集合。 ### 6\. [awesome-interview-questions](https://github.com/DopplerHQ/awesome-interview-questions) >***⭐*** *GitHub 星數 59k+* 軟體開發角色的常見面試問題彙編。 --- ## 🧑‍💻 建設專案 ### 7\. [awesome-for-beginners](https://github.com/MunGell/awesome-for-beginners) >***⭐*** *GitHub 星數 58k+* 適合初學者的專案想法和資源清單。 ### 8\. [應用程式想法](https://github.com/florinpop17/app-ideas) >***⭐*** *GitHub 星數 69k+* 適用於各種程式設計平台和技能水平的大量應用程式創意。 ### 9\. [邊玩邊學](https://github.com/lmammino/awesome-learn-by-playing) >***⭐*** *GitHub 星數 115+* 一系列遊戲和互動專案,可提高編碼技能。 ### 10\. [專案為基礎的學習](https://github.com/practical-tutorials/project-based-learning) >***⭐*** *GitHub 星數 123k+* 針對各個技術領域的基於專案的學習資源清單。 ### 11\. [build-your-own-x](https://github.com/codecrafters-io/build-your-own-x) >***⭐*** *GitHub 星數 229k+* 關於如何建立您自己的程式語言、工具和框架的指南集合。 --- ## 🚀 工具和資源 ### 12\. [免費開發](https://github.com/ripienaar/free-for-dev) >***⭐*** *GitHub 星數 76k+* 為開發人員提供的免費工具和資源的精選清單。 ### 13\. [awesome-selfhosted](https://github.com/awesome-selfhosted/awesome-selfhosted) >***⭐*** *GitHub 星數 157k+* 用於各種目的的自託管軟體應用程式的集合。 ### 14\. [棒設計工具](https://github.com/goabstract/Awesome-Design-Tools) >***⭐*** *GitHub 星數超過 30k* 用於各種設計目的的設計工具的綜合清單。 ### 15\. [awesome-stock-resources](https://github.com/neutraltone/awesome-stock-resources) >***⭐*** *GitHub 星數 11k+* 免費和付費庫存照片、圖標和其他設計資源的集合。 --- ## 💯 模式與最佳實踐 ### 16\. [awesome-sre](https://github.com/dastergon/awesome-sre) >***⭐*** *GitHub 星數超過 10k* 用於學習和實施站點可靠性工程實踐的資源集合。 ### 17\. [棒設計模式](https://github.com/DovAmir/awesome-design-patterns) >***⭐*** *GitHub 星數 33k+* 軟體設計模式及其應用的目錄。 ### 18\. [美麗文件](https://github.com/matheusfelipeog/beautiful-docs) >***⭐*** *GitHub 星數 8k+* 用於建立美觀且有效的文件的資源和最佳實踐的集合。 ### 19\. [很棒的可擴展性](https://github.com/binhnguyennus/awesome-scalability) >***⭐*** *GitHub 星數 49k+* 有關軟體系統可擴展性、效能和優化的精選資源清單。 --- 寫作一直是我的熱情,幫助和激勵人們讓我感到很高興。如果您有任何疑問,請隨時與我們聯繫! 透過訂閱[我的電子報](https://madzadev.substack.com/),確保獲得我發現的最好的資源、工具、生產力技巧和職業發展技巧! --- 原文出處:https://dev.to/madza/19-github-repositories-every-developer-should-bookmark-13bd

如何成為 10 倍速明星開發人員

如今,每個人都想成為我們所謂的「10 倍開發人員」。然而,這個術語經常被誤解和高估。 從本質上講,在我看來,高效或10 倍開發人員是指能夠利用所有可用工具來發揮其優勢的人,讓這些工具處理冗餘和重複性的任務,並使他能夠專注於複雜和創造性的工作。以下是成為 10 倍開發人員的一些提示和技巧: ## 使用腳本自動執行重複任務: 對於尋求優化工作流程的開發人員來說,透過腳本自動執行重複任務是一個遊戲規則改變者。 透過弄清楚哪些任務可以自動化,例如測試和部署,然後讓腳本處理它們,開發人員可以專注於工作中更具挑戰性的部分,並在過程中節省大量時間。 例如,此腳本建立一個新的專案資料夾,由使用者輸入命名,並在檔案總管中開啟它: ``` import os import subprocess def create_project_folder(project_name): # Create a new folder for the project os.makedirs(project_name) # Open the project folder in the file explorer subprocess.run(['explorer', project_name]) # Get the project name from the user project_name = input("Enter the name of your new project: ") # Call the function to create and open the project folder create_project_folder(project_name) ``` ## 鍵盤快速鍵掌握: 掌握程式碼編輯器或 IDE 中的鍵盤快速鍵對於加快編碼工作流程至關重要。 VS 程式碼的一些快捷方式範例: `Ctrl + P`:快速檔案導航,讓您可以按名稱開啟檔案。 `Ctrl + Shift + L`:選取目前單字的所有出現位置。 `Ctrl + /`:切換行註釋 `Ctrl + A`:選擇目前檔案中的所有行 `Ctrl + F`:尋找程式碼中的特定文本 `Ctrl + Shift + P`:開啟各種指令的指令面板。 `Alt + 向上/向下箭頭`:向上或向下移動目前行。 `Shift + 右箭頭 (→)`:選擇遊標右側的字元。 `Shift + 向左箭頭 (←)`:選擇遊標左側的字元。 「Alt + 點擊」:按住 Alt 鍵並點擊程式碼中的不同位置以建立多個遊標,從而允許您同時在這些位置進行編輯或鍵入。 ## 不要過度設計: 避免過度設計解決方案的誘惑。加入不必要的程式碼或架構複雜性是許多工程師和程式設計師遇到的常見陷阱。 然而,保持簡單不僅有利於您目前的工作流程,而且還可以讓其他人在將來更容易理解您的程式碼並就您的程式碼進行協作。 ## 掌握版本控制工作流程: 善於使用版本控制工作流程(例如使用 Git)將極大地提高您的工作效率,並幫助您的團隊在不妨礙彼此的情況下協同工作。 特別是使用 GitKraken 等工具或任何其他 GUI 替代品,提供直覺的介面,簡化分支、合併和追蹤變更等任務,使協作更加順暢。 ![GitKraken 網站圖片](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ggegydp0qnryv7cbszuk.png) 如果出現問題,您可以輕鬆恢復到先前的狀態。這就像有一個安全網,可以確保每個人的工作順利配合,從而使建立軟體的整個過程更快、壓力更小。 ## 利用現有元件和函式庫: 不要重新發明輪子,而是使用經過嘗試和測試的可用解決方案。這不僅節省時間,而且使您的程式碼更加可靠和有效率。 這種方法使您能夠更多地關注專案的獨特方面。這是一種明智的策略,可以提高生產力並建立強大的軟體,而無需從頭開始。 ## 採用 HTML Emmet 進行快速原型設計: Emmet 是一個針對 Web 開發人員的工具包,可透過縮寫快速且有效率地進行編碼。 如果您使用 HTML,Emmet 可以顯著加快建立 HTML 結構的過程。 例子: ``` div>(header>ul>li*2>a)+footer>p ``` 輸出: ``` <div> <header> <ul> <li><a href=""></a></li> <li><a href=""></a></li> </ul> </header> <footer> <p></p> </footer> </div> ``` ## 利用人工智慧協助: - **GitHub 副駕駛:** 是 GitHub 與 OpenAI 合作開發的人工智慧驅動的程式碼補全工具。它透過在開發人員鍵入時產生智慧建議和自動完成來改變開發人員編寫程式碼的方式。這是迄今為止我嘗試過的最好的人工智慧工具之一。 ![GitHub Copilot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/822ubh3qe2lyezubbva5.png) - **標籤九:** 是一個人工智慧驅動的程式碼編輯器自動完成擴充。它超越了傳統的自動完成功能,使用機器學習模型來預測和建議整行或程式碼區塊。 用戶可以選擇免費使用 TabNine,但有一些限制,也可以透過訂閱來選擇專業版以獲得高級功能。 [TabNine](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/68un8zingjmsvnuk5pyl.png) - **聊天gpt :** ChatGPT 可以真正改變您的工作效率。例如,它可以提供有用的範例,例如建議用於測試的陣列或幫助重建程式碼片段。 ![Chatgpt 範例](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hi8hskndin82w2vgx10l.png) 如果您在程式設計概念上遇到困難或需要澄清,ChatGPT 可以提供快速且易於理解的解釋。這就像擁有一位知識淵博的編碼夥伴,24/7 全天候幫助您應對編碼挑戰,使您的開發過程更加順暢和高效。 ## VS 程式碼中的擴充: VS Code 中的擴充功能可以透過加入功能、自動化任務和增強開發環境來顯著提高工作效率: - **更漂亮:** Prettier 是一個固執己見的程式碼格式化程序,它會自動格式化您的程式碼,使其看起來乾淨且一致,從而使您免於手動格式化的麻煩。有了 Prettier,您的程式碼變得更加賞心悅目,您可以更加專注於編寫邏輯,而不必擔心樣式。 ![更漂亮的擴充](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gurx061173zhqjd8lvvq.png) - **自動重新命名標籤:** 自動重命名標籤擴充就像 HTML 或 XML 的編碼助手。當您變更開始標記的名稱時,此擴充功能會自動更新結束標記以符合。 ![自動重新命名標籤擴充](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q31o7ljpjl3ciysch7b5.png) - **更好的評論:** Better Comments 擴充功能將幫助您在程式碼中建立更人性化的註解。透過此擴展,您將能夠對註釋進行分類。 ![更好的評論擴充](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t4f45e3cjl34mcs94rx6.png) - **智慧感知:** IntelliSense 是您的程式設計助手,可在您鍵入時提供智慧程式碼補全和建議。它預測您的需求並提供相關選項,使編碼更加有效率。一些範例: ![Tailwind CSS IntelliSense 擴充](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kcjdqgwg5n6dgn4naeuz.png) ![路徑智慧感知擴充](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9c1hvrb4l60mx6l2mp32.png) ![npm Intellisense 擴充](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yo40qrvwsplnbvzc2wn3.png) - **孔雀:** 當您處理大量專案並在 VSCode 視窗之間跳轉時,Peacock 非常有用。它允許您將顏色連結到每個專案,因此每當您打開它時,您都可以透過顏色快速查看您所在的視窗。 ![孔雀擴充](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gncbyqxup6iwa353q3vt.png) --- **總而言之**,結合這些策略和工具可以真正徹底改變您的編碼方法,將您轉變為更有效率、更有效率的開發人員。擁抱 10 倍思維不僅可以提高個人生產力,還可以為您的團隊做出積極貢獻。因此,繼續實施這些技巧,並觀察您的編碼之旅進入一個全新的水平。 --- 原文出處:https://dev.to/idboussadel/how-to-become-a-10x-dev-ake

您應該知道的 10 個高級 JavaScript 技巧!

歡迎來到 JavaScript 的奇妙世界,在這裡,程式設計與魔法相遇!如果您曾經感受到在網路上建立內容的快感,但想知道其背後隱藏的策略,那麼您來對地方了。✨🚀 在這篇部落格中,我們將穿越 JavaScript 領域,揭開複雜的神秘面紗,擁抱簡單。沒有令人困惑的術語——只有簡單的英語解釋和逐步說明。無論您是編碼新手還是經驗豐富的開發人員,請加入我們,我們會發現 10 個高級 JavaScript 技巧,助您一臂之力_“啊哈!”_ ## 1. DESTRUCTURING ASSIGNMENT 賦值解構是一種簡潔的擷取方式 來自陣列或物件的值並將它們分配給變數。 它簡化了您的程式碼並提高了可讀性。為了 陣列,可以使用括號表示法,並且可以使用 物件的大括號。 ![破壞](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/at1hxowbpsxzgi2gln6j.jpeg) --- ## 2. SPREAD SYNTAX 您可以使用擴充語法來擴充元素 一個陣列或一個物件的屬性到另一個物件中 這對於製作副本、合併物件以及 將多個參數傳遞給函數。 ![傳播語法](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uoqllndthqgr0n931thc.jpeg) --- ## 3. CURRYING 柯里化是一種函數式程式設計技術,其中 接受多個參數的函數被轉換 分成一系列函數,每個函數取一個參數 這允許更好地重複使用和組合 程式碼。 ![柯里化](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/isp1ssqnxde6uvodqr9j.jpeg) --- ## 4. MEMOIZATION 這是一種快取技術,用於儲存結果 昂貴的函數呼叫並避免不必要的 重新計算。 它會顯著降低長期性能 遞歸或消耗函數。 ![記憶](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xw1jdu75vkjev3kw8osy.jpeg) --- ## 5. PROMISES AND ASYNC/AWAIT Promise 和 Async/Await 對於處理至關重要 更優雅地非同步操作並使程式碼更優雅 更具可讀性和可維護性。 它們有助於避免地獄般的回調並改善錯誤處理。 ![Promises-Async/Await](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oq6276u1rilbozbfpo4u.jpeg) --- ## 6. CLOSURES 閉包是記住環境的函數 他們被創造出來,即使那個環境沒有 更容易存取。 它們對於建立私有變數和 行為封裝。 ![關閉](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iy1zkeqx9ta5gqormq0p.jpeg) --- ## 7. FUNCTION COMPOSITION 函陣列合是將兩個或兩個函陣列合起來的過程 更多功能建立新功能。 它鼓勵程式碼重複使用並幫助建立 轉變一步一步複雜。 ![函陣列合](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6g9kzxezkemutb1zvuvd.jpeg) --- ## 8. PROXY 代理物件允許您建立自訂行為 用於基本的物件操作。它允許你攔截 並修改物件操作。 '物件,例如 存取屬性、分配和呼叫方法。 ![代理](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/je32r9eu2y3wf5rrm50b.jpeg) --- ## 9. EVENT DELEGATION 事件委託是一種附加事件委託的技術 父級的單一事件偵聽器而不是多個 每個孩子的聽眾。記憶體使用情況並改善 效能,特別是對於大型列表或動態列表 產生的內容。 ![事件委託](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ar60xpbcri5931giv2ze.jpeg) --- ## 10. WEB WORKERS Web Workers 允許您在以下位置執行 JavaScript 程式碼 背景,與主線程一起。 它們對於卸載 CPU 密集型任務很有用, 避免 UI 掛起並提高效能回應能力。 ![Web-Workers](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c965v7g8kgh9y1rpp528.jpeg) --- ## 結論 好了,各位研究人員!😊 我們一起穿越了 JavaScript 星系,揭示了 10 種乍看之下很神奇的技術。但現在,有了一點理解和熱情,您就可以自信地應用這些編碼咒語了。 如果您喜歡這個神奇的旅程,請不要忘記點擊_關注按鈕_以了解更多編碼冒險。當然,您的評論是讓這個社區蓬勃發展的秘密武器。請在下面分享您的想法、問題或您自己的 JavaScript 技巧。讓我們繼續對話吧! 感謝您加入這次冒險。下次見,祝您編碼愉快! 🚀💻✨ --- 原文出處:https://dev.to/big_smoke/10-advanced-javascript-tricks-you-should-know--1ofj

在 GitHub 上發現 9️⃣ 個最佳自架 Open Source 💫

## 什麼是自架軟體? 自託管專案是指從使用者的伺服器或基礎架構安裝、管理和操作的軟體、應用程式或服務,而不是託管在外部或第三方伺服器(例如雲端服務供應商提供的伺服器)上。 這種模型可以更好地控制軟體和資料,並且通常在隱私、安全、客製化和成本效益方面受到青睞。 ### 自託管軟體對於新創公司的重要性🚀 - **資料控制和隱私🛡️**:完全控制您的資料。自託管意味著您新創公司的敏感資訊保留在內部,確保一流的隱私和安全。 - **客製化與靈活性 🔧**:客製化軟體以滿足您新創公司的獨特需求。與雲端託管服務不同,自架軟體允許進行廣泛的客製化。 - **成本效益💰**:從長遠來看更經濟實惠。自託管可以減少經常性的雲端服務費用,使其成為注重費用的新創公司的明智選擇。 - **可靠性和獨立性🌐**:不要受服務提供者的正常運作時間和政策的擺佈。自託管解決方案可確保一致的存取,這對於您的新創公司的順利運作至關重要。 - **合規性和安全性🔒**:輕鬆滿足特定的監管要求。透過管理您的伺服器,實施完全符合您新創公司需求的安全性和合規性措施。 ## 這些是您需要從 GitHub 取得的一些基本的自架開源儲存庫 👇 讓我們探索這些開源軟體,並了解它們如何徹底改變您的自架軟體解決方案方法。 ### [Swirl](https://github.com/swirlai/swirl-search):跨多個資料來源的人工智慧增強搜尋 [![Swirl](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ceyqeael4iamuvb97l26.jpg)](https://github.com/swirlai/swirl-search) [**Swirl**](https://github.com/swirlai/swirl-search) 是一款創新的開源軟體,利用人工智慧搜尋各種內容和資料來源,使用讀者法學碩士智慧找到最佳結果。然後,它利用生成式人工智慧提供客製化答案,整合用戶特定的資料以獲得更相關的結果。 **它解決了什麼問題,以及它如何提供優秀的開源解決方案?** - 🌐 **多重來源搜尋**:Swirl 熟練地跨資料庫、公共資料服務和企業來源進行搜尋,提供全面的搜尋解決方案。 - 🤖 **人工智慧驅動的見解**:利用人工智慧和 ChatGPT(及更多)等大型語言模型來分析和排名搜尋結果,確保高相關性和準確性。 - 🔄 **輕鬆整合**:設定和使用簡單;從 Docker 下載開始,然後根據需要擴展以合併更多來源。 **GitHub 儲存庫連結:** [GitHub 上的 Swirl](https://github.com/swirlai/swirl-search) --- ### Clickvote:將社交反應無縫整合到您的內容中 ![點擊投票](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nj42wirmciunulxyryqt.jpg) Clickvote 是一款開源工具,可輕鬆為任何線上內容加入點讚、按讚和評論,從而增強用戶在各種環境中的互動和參與。 **它解決的問題及其開源優勢:** - 🔄 **即時互動**:提供按讚、按讚和評論的即時更新,增強用戶參與度。 - 🔍 **深度分析**:透過詳細分析提供對使用者行為的洞察,幫助了解受眾偏好。 - 🚀 **可擴展性**:每秒處理無限次點擊,即使在大流量下也能確保穩健的效能。 **GitHub 儲存庫連結:** [GitHub 上的 Clickvote](https://github.com/clickvote/clickvote) --- ### Wasp:使用 React 和 Node.js 徹底改變全端 Web 開發 ![黃蜂](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pe0o1d6pys66eitva3if.jpg) Wasp 是一個尖端的開源框架,旨在簡化使用 React 和 Node.js 的全端 Web 應用程式的開發,只需一個 CLI 命令即可快速部署。 **它解決的問題及其開源優勢:** - 🚀 **快速開發**:只需幾行程式碼即可快速啟動,從而可以輕鬆建立和部署生產就緒的 Web 應用程式。 - 🛠️ **更少的樣板**:抽象複雜的全端功能,減少樣板並使維護和升級變得簡單 - 🔓 **無鎖定**:確保部署的靈活性,沒有特定的提供者鎖定和完整的程式碼控制。 **GitHub 儲存庫連結:** [GitHub 上的 Wasp](https://github.com/wasp-lang/wasp) --- ### Pezzo:利用雲端原生開源平台簡化 LLMOps ![Pezzo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uk3zt4fx8as8ngk6gmtg.jpg) Pezzo 是一個革命性的開源、開發人員優先的 LLMOps 平台,完全雲端原生,旨在增強 AI 操作的提示設計、版本管理、即時交付、協作、故障排除和可觀察性。 **它解決的問題及其開源優勢:** - 🤖 **AI 營運效率**:促進 AI 營運的無縫監控和故障排除。 - 💡 **降低成本和延遲**:輔助工具可將成本和延遲降低高達 90%,從而優化營運效率。 - 🌐 **統一提示管理**:提供單一平台來管理提示,確保簡化協作和即時 AI 變更交付。 **GitHub 儲存庫連結:** [GitHub 上的片段](https://github.com/pezzolabs/pezzo) --- ### Flagsmith:開源功能標記和遠端設定服務 ![Flagsmith](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r9d9fd0rvo4od1qbrr4h.jpg) Flagsmith 是一個開源平台,提供功能標記和遠端設定服務,允許靈活的本地託管選項或透過其託管版本。 **它解決的問題及其開源優勢:** - 🚀 **功能管理**:簡化跨 Web、行動和伺服器端應用程式的功能標記的建立和管理。 - 🔧 **可自訂部署**:可部署在私有雲或在本地執行,提供託管選項的多功能性。 - 🎛️ **使用者和環境特定控制**:允許針對不同的使用者群體或環境開啟或關閉功能,增強使用者體驗和測試靈活性。 **GitHub 儲存庫連結:** [GitHub 上的 Flagsmith](https://github.com/Flagsmith/flagsmith) --- ## Digger:用於 CI 管道的開源 IaC 編排工具 ![Digger](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l5e0ecvgkpuzs4agevaj.jpg) Digger 是一款用於基礎設施即程式碼 (IaC) 編排的創新開源工具,可與現有 CI 管道無縫集成,以提高部署 Terraform 配置的效率和安全性。 **它解決的問題及其開源優勢:** - 🛠️ **CI/CD 整合**:將 Terraform 直接整合到現有的 CI/CD 管道中,避免需要單獨的專用 CI 系統。 - 🔐 **增強的安全性**:確保安全操作,因為雲端存取機密不與第三方服務共用。 - 💡 **經濟有效且高效**:無需額外的運算資源,可在現有 CI 基礎設施中本機執行 Terraform。 - 🎚️ **高級功能**:提供諸如拉取請求評論中的 Terraform 計劃和應用程式、私有執行器、對 RBAC 的 OPA 支援、PR 級鎖和漂移檢測等功能。 **GitHub 儲存庫連結:** [GitHub 上的 Digger](https://github.com/diggerhq/digger) --- ## Keep:開源警報管理和自動化平台 ![保留](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i71cqjcdi5eto6qcz87f.jpg) Keep 是一個開源平台,旨在集中和自動化警報管理。它允許用戶將所有警報整合到一個介面中,並有效地自動化端到端流程。 **它解決的問題及其開源優勢:** - 🚨 **集中警報管理**:將所有警報整合到一處,簡化監控和回應流程。 - ⚙️ **工作流程自動化**:支援工作流程編排以自動化端到端流程,類似於 Datadog 工作流程自動化功能。 - 🔄 **廣泛的工具相容性**:支援多種可觀測工具、資料庫、通訊平台、事件管理工俱全面整合。 **GitHub 儲存庫連結:** [保留在 GitHub 上](https://github.com/keephq/keep) --- ## MeetFAQ:將您的支援管道轉變為人工智慧支援的公共常見問題解答 ![MeetFAQ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4m6a9gkjswcz17iiwxof.jpg) MeetFAQ 是一款創新的開源工具,可連接到您的支援管道(例如Discord),並採用人工智慧(特別是ChatGPT)將對話轉換為全面的公共常見問題解答,可透過URL 或直接在您的網站上存取。 **它解決的問題及其開源優勢:** - 🤖 **人工智慧驅動的常見問題解答產生**:使用 ChatGPT 將支援頻道對話轉換為常見問題解答,以實現更廣泛的可存取性。 - 🌍 **公共可存取性**:向更廣泛的受眾(而不僅僅是支援管道上的受眾)提供常見問題解答,從而增強客戶聯繫。 - 💡 **客戶保留**:透過提供易於存取的公共常見問題解答來幫助防止客戶流失,確保不會遺漏任何客戶問題。 **GitHub 儲存庫連結:** [GitHub 上的 MeetFAQ](https://github.com/github-20k/meetqa) --- ### Jackson:Web 應用程式的進階 SSO 和目錄同步 ![BoxyHQ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dx8wowakwnpa1wt2ehkf.jpg) Jackson 是一項開源單一登入 (SSO) 服務,可簡化 Web 應用程式驗證,支援 SAML 和 OpenID Connect 協定。它超越了 SSO,透過 SCIM 2.0 協定提供目錄同步,支援自動用戶和群組配置/取消配置。 **它解決的問題及其開源優勢:** - 🔒 **增強的身份驗證**:提供企業級 SSO 支持,簡化跨 Web 應用程式的身份驗證。 - 🔄 **目錄同步**:支援透過 SCIM 2.0 進行目錄同步,以實現高效的使用者和群組管理。 - 🌐 **協定支援**:促進 SAML 和 OpenID Connect 的集成,抽象化這些協定的複雜性以便於實施。 **GitHub 儲存庫連結:** [GitHub 上的傑克遜](https://github.com/boxyhq/jackson) --- ### 綜上所述 我們探索了九個出色的開源儲存庫。他們要不是一家新創公司,就是一個由獨立駭客變大的專案。 這些工具展示了自架的力量以及小型團隊和個人創作者蓬勃發展的創新。 感謝您與我一起經歷這些獨特專案的富有洞察力的旅程。一如既往,偉大即將到來! ![偉大即將到來](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xd1pazpm53d1kwoifb75.jpg) --- 原文出處:https://dev.to/srbhr/discover-the-9-best-self-hosted-open-source-repositories-on-github-23oc

🛠️6 個工具,利用 AI 做出你的全端應用程式 🤖

_「現在是2021 年了,我的飛行汽車在哪裡?」_ - Joel Spolsky(Stack Overflow 和Trello 的建立者)用這句話來表達他對Web 開發仍然與20 年前幾乎相同的感覺的幻滅。 但今天,有了 GPT,我們就可以再問這個問題了。我們看到了所有這些花哨的推文和演示,但是**當我需要啟動一個新的全端 Web 應用程式時**,這對我作為開發人員意味著什麼?我真的必須經歷“npm create vite my-new-app”,並再次從空白頁面開始嗎? 最後的答案是「否」——你可以使用很多很酷的東西來讓你的生活更輕鬆。它可能還不是超音速德羅寧,但它至少肯定是在地面上盤旋。 讓我們探討一下今天的 AI 場景為我們提供了什麼,以便更輕鬆地啟動和建立全端 Web 應用程式: ## 🐝 🤖 MAGE - 一分鐘內從單一提示到全端、React 和 Node.js 應用程式(免費使用!) ![MAGE 行動](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w9chayxjmuab1e85evc1.gif) [MAGE](https://usemage.ai/) (*Magic App GEnerator*) 可能是最容易使用的 AI 編碼代理 - 一切都透過 Web 介面進行,**您所要做的就是輸入您要建立的應用程式的簡短描述**。這樣,MAGE 將在由 [Wasp](https://wasp-lang.dev/) 提供支援的 React、Tailwind、Node.js 和 Prisma 中產生完整的全端程式碼庫,您可以免費下載。 MAGE 最好的部分是**它是完全開源且完全免費使用** - 您所需要做的就是[使用您的 GitHub 登入](https://usemage.ai/),然後您就可以開始建立應用程式! MAGE [於7 月在Product Hunt 上推出](https://www.producthunt.com/products/wasp-lang-alpha#gpt-webapp-generator-for-react-node-js),從那時起就被用來建立近 30,000 個應用程式。 ## 📟 Aider - 終端機中的 AI 配對程式設計師 ![Aider 示範](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1g8iir36pbnja90cldn1.gif) 在您使用 MAGE 建立應用程式的 v1 版並獲得基本功能後,您可能會想要加入更多功能。為什麼不使用人工智慧來實現這一點呢?這就是 Aider 發揮作用的地方! Aider 的超能力在於您可以將其插入任何現有專案並開始使用!這感覺就像與坐在您旁邊的開發人員同事聊天 - 只需描述您的下一個功能,Aider 將盡力實現它,同時提供流程的所有詳細訊息,並自動向您的存儲庫加入新的提交!多麼酷啊? 您可以了解更多有關 Aider 的資訊並在這裡嘗試一下:https://github.com/paul-gauthier/aider ## 🦀 🚀 Shuttle AI - 使用 GPT 在 Rust 中建立後端! ![穿梭示範](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2n2bw3i79f4ojhwdpky1.png) 當您聽到“網頁應用程式”這個詞時,我們大多數人都會立即想到 JavaScript。雖然對於前端來說這在很大程度上是正確的,但我們可以用我們喜歡的任何技術來建立後端! 除了 Python、Java 和 PHP 等常見的嫌疑犯之外,Rust 又如何呢?它是開發人員最喜愛的語言之一,它不應該只用於低階演算法。 Shuttle AI 讓這一切成為可能 - 他們強大的基於 Rust 的框架已經使建置和部署後端變得容易,而頂部的 AI 使其變得輕而易舉! 在這裡了解更多:https://www.shuttle.rs/ai ## ⚡️📦 Supabase AI - 再見,複雜的 SQL 查詢 ![Supabase 示範](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jzydppmhtizqx4poar5t.png) [Supabase](https://supabase.com/) 是為您的全端應用程式啟動和執行資料庫的最佳方法之一,除此之外您還可以獲得大量功能!由於它專門用於 Postgresql,這意味著您偶爾需要編寫一些 SQL。為什麼不從人工智慧得到一些幫助呢? Supabase 因其美觀且用戶友好的儀表板(帶有整合 SQL 編輯器)而聞名,現在他們透過加入自己的 AI 代理使其變得更好。要求它建立新的表和索引,甚至編寫資料庫函數! 在這裡了解更多:https://supabase.com/blog/supabase-studio-3-0 ## 👁️ 🧑‍✈️Visual Copilot - 將 Figma 設計編碼 ![figma 示範](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w1jhtqsqtj59wprziesa.png) 如果您曾經從設計師那裡獲得 Figma 設計講義,然後您的任務是用它來實現 UI,您是否想過是否有一種方法可以自動化此操作?這就是 Visual Copilot 所追求的! 只需點擊一下,並給出 Figma 設計,Visual Copilot 就會為其產生前端程式碼!它將盡最大努力使其具有響應性並保持程式碼整潔和可重複使用。 它目前可作為 [Figma 社群插件](https://www.figma.com/community/plugin/747985167520967365/builder-io-ai-powered-figma-to-code-react-vue-tailwind-more) 。 ## ✈️ 🤖 GPT Pilot - 使用協作 AI 啟動新應用程式 ![試辦示範](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/az5mkurpyu80dtvthxdy.png) GPT Pilot 是專門用於從頭開始建立新應用程式的編碼代理程式。它獨特的做法是它與開發者合作——每當遇到困難時,它都會尋求你的幫助! 在內部,它由多個代理組成,這些代理一起協作並經歷應用程式開發的不同階段 - 從產品所有者和架構師到 DevOps 和開發人員! ![試辦系統](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6vro6qo3khbskfxxfv0h.jpg) 這是另一個完全開源的解決方案,最近受到了開發人員的喜愛,並多次出現在 GitHub 趨勢排行榜上。 了解更多並在這裡嘗試一下:https://github.com/Pythagora-io/gpt-pilot ## 概括 ![換行](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/padyzsbgaec1ophqtqep.gif) 這就是一個包裝!還有更多的人工智慧工具,而且每天都有新的工具出現,但在本概述中,我們試圖專注於您今天可以用來啟動新的網路應用程式的工具。 希望您發現這很有幫助,並學到了一些可能派上用場的新東西!我也很想在評論中聽到您的意見 - 您最喜歡的 Web 開發人工智慧工具是什麼,無論是您每天使用的工具還是只是感到興奮的工具,接下來我們應該介紹什麼? --- 原文出處:https://dev.to/matijasos/6-tools-to-kickstart-your-full-stack-app-with-ai-4oh3

✨ 用您的文件訓練 ChatGPT 🪄 ✨

# 簡介 ChatGPT 訓練至 2022 年。 但是,如果您希望它專門為您提供有關您網站的資訊怎麼辦?最有可能的是,這是不可能的,**但不再是了!** OpenAI 推出了他們的新功能 - [助手](https://platform.openai.com/docs/assistants/how-it-works)。 現在您可以輕鬆地為您的網站建立索引,然後向 ChatGPT 詢問有關該網站的問題。在本教程中,我們將建立一個系統來索引您的網站並讓您查詢它。我們將: - 抓取文件網站地圖。 - 從網站上的所有頁面中提取資訊。 - 使用新資訊建立新助理。 - 建立一個簡單的ChatGPT前端介面並查詢助手。 ![助手](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ekre38der95twom33tqb.gif) --- ## 你的後台工作平台🔌 [Trigger.dev](https://trigger.dev/) 是一個開源程式庫,可讓您使用 NextJS、Remix、Astro 等為您的應用程式建立和監控長時間執行的作業!   [![GiveUsStars](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bm9mrmovmn26izyik95z.gif)](https://github.com/triggerdotdev/trigger.dev) 請幫我們一顆星🥹。 這將幫助我們建立更多這樣的文章💖 --- ## 讓我們開始吧🔥 讓我們建立一個新的 NextJS 專案。 ``` npx create-next-app@latest ``` >💡 我們使用 NextJS 新的應用程式路由器。安裝專案之前請確保您的節點版本為 18+ 讓我們建立一個新的資料庫來保存助手和抓取的頁面。 對於我們的範例,我們將使用 [Prisma](https://www.prisma.io/) 和 SQLite。 安裝非常簡單,只需執行: ``` npm install prisma @prisma/client --save ``` 然後加入架構和資料庫 ``` npx prisma init --datasource-provider sqlite ``` 轉到“prisma/schema.prisma”並將其替換為以下架構: ``` // This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" } datasource db { provider = "sqlite" url = env("DATABASE_URL") } model Docs { id Int @id @default(autoincrement()) content String url String @unique identifier String @@index([identifier]) } model Assistant { id Int @id @default(autoincrement()) aId String url String @unique } ``` 然後執行 ``` npx prisma db push ``` 這將建立一個新的 SQLite 資料庫(本機檔案),其中包含兩個主表:“Docs”和“Assistant” - 「Docs」包含所有抓取的頁面 - `Assistant` 包含文件的 URL 和內部 ChatGPT 助理 ID。 讓我們新增 Prisma 客戶端。 建立一個名為「helper」的新資料夾,並新增一個名為「prisma.ts」的新文件,並在其中新增以下程式碼: ``` import {PrismaClient} from '@prisma/client'; export const prisma = new PrismaClient(); ``` 我們稍後可以使用“prisma”變數來查詢我們的資料庫。 --- ![ScrapeAndIndex](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fc05wtlc4peosr62ydnx.png) ## 刮擦和索引 ### 建立 Trigger.dev 帳戶 抓取頁面並為其建立索引是一項長期執行的任務。 **我們需要:** - 抓取網站地圖的主網站元 URL。 - 擷取網站地圖內的所有頁面。 - 前往每個頁面並提取內容。 - 將所有內容儲存到 ChatGPT 助手中。 為此,我們使用 Trigger.dev! 註冊 [Trigger.dev 帳號](https://trigger.dev/)。 註冊後,建立一個組織並為您的工作選擇一個專案名稱。 ![pic1](https://res.cloudinary.com/practicaldev/image/fetch/s--B2jtIoA6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bdnxq8o7el7t4utvgf1u.jpeg) 選擇 Next.js 作為您的框架,並按照將 Trigger.dev 新增至現有 Next.js 專案的流程進行操作。 ![pic2](https://res.cloudinary.com/practicaldev/image/fetch/s--K4k6T6mi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e4kt7e5r1mwg60atqfka.jpeg) 否則,請點選專案儀表板側邊欄選單上的「環境和 API 金鑰」。 ![pic3](https://res.cloudinary.com/practicaldev/image/fetch/s--Ysm1Dd0r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ser7a2j5qft9vw8rfk0m.png) 複製您的 DEV 伺服器 API 金鑰並執行下面的程式碼片段來安裝 Trigger.dev。 仔細按照說明進行操作。 ``` npx @trigger.dev/cli@latest init ``` 在另一個終端中執行以下程式碼片段,在 Trigger.dev 和您的 Next.js 專案之間建立隧道。 ``` npx @trigger.dev/cli@latest dev ``` ### 安裝 ChatGPT (OpenAI) 我們將使用OpenAI助手,因此我們必須將其安裝到我們的專案中。 [建立新的 OpenAI 帳戶](https://platform.openai.com/) 並產生 API 金鑰。 ![pic4](https://res.cloudinary.com/practicaldev/image/fetch/s--uV1LwOH---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ashau6i2sxcpd0qcxuwq.png) 點擊下拉清單中的「檢視 API 金鑰」以建立 API 金鑰。 ![pic5](https://res.cloudinary.com/practicaldev/image/fetch/s--Tp8aLqSa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4bzc6e7f7avemeuuaygr.png) 接下來,透過執行下面的程式碼片段來安裝 OpenAI 套件。 ``` npm install @trigger.dev/openai ``` 將您的 OpenAI API 金鑰新增至「.env.local」檔案。 ``` OPENAI_API_KEY=<your_api_key> ``` 建立一個新目錄“helper”並新增一個新檔案“open.ai.tsx”,其中包含以下內容: ``` import {OpenAI} from "@trigger.dev/openai"; export const openai = new OpenAI({ id: "openai", apiKey: process.env.OPENAI_API_KEY!, }); ``` 這是我們透過 Trigger.dev 整合封裝的 OpenAI 用戶端。 ### 建立後台作業 讓我們繼續建立一個新的後台作業! 前往“jobs”並建立一個名為“process.documentation.ts”的新檔案。 **新增以下程式碼:** ``` import { eventTrigger } from "@trigger.dev/sdk"; import { client } from "@openai-assistant/trigger"; import {object, string} from "zod"; import {JSDOM} from "jsdom"; import {openai} from "@openai-assistant/helper/open.ai"; client.defineJob({ // This is the unique identifier for your Job; it must be unique across all Jobs in your project. id: "process-documentation", name: "Process Documentation", version: "0.0.1", // This is triggered by an event using eventTrigger. You can also trigger Jobs with webhooks, on schedules, and more: https://trigger.dev/docs/documentation/concepts/triggers/introduction trigger: eventTrigger({ name: "process.documentation.event", schema: object({ url: string(), }) }), integrations: { openai }, run: async (payload, io, ctx) => { } }); ``` 我們定義了一個名為「process.documentation.event」的新作業,並新增了一個名為 URL 的必要參數 - 這是我們稍後要傳送的文件 URL。 正如您所看到的,該作業是空的,所以讓我們向其中加入第一個任務。 我們需要獲取網站網站地圖並將其返回。 抓取網站將返回我們需要解析的 HTML。 為此,我們需要安裝 JSDOM。 ``` npm install jsdom --save ``` 並將其導入到我們文件的頂部: ``` import {JSDOM} from "jsdom"; ``` 現在,我們可以新增第一個任務。 用「runTask」包裝我們的程式碼很重要,這可以讓 Trigger.dev 將其與其他任務分開。觸發特殊架構將任務拆分為不同的進程,因此 Vercel 無伺服器逾時不會影響它們。 **這是第一個任務的程式碼:** ``` const getSiteMap = await io.runTask("grab-sitemap", async () => { const data = await (await fetch(payload.url)).text(); const dom = new JSDOM(data); const sitemap = dom.window.document.querySelector('[rel="sitemap"]')?.getAttribute('href'); return new URL(sitemap!, payload.url).toString(); }); ``` - 我們透過 HTTP 請求從 URL 取得整個 HTML。 - 我們將其轉換為 JS 物件。 - 我們找到網站地圖 URL。 - 我們解析它並返回它。 接下來,我們需要抓取網站地圖,提取所有 URL 並返回它們。 讓我們安裝“Lodash”——陣列結構的特殊函數。 ``` npm install lodash @types/lodash --save ``` 這是任務的程式碼: ``` export const makeId = (length: number) => { let text = ''; const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; for (let i = 0; i < length; i += 1) { text += possible.charAt(Math.floor(Math.random() * possible.length)); } return text; }; const {identifier, list} = await io.runTask("load-and-parse-sitemap", async () => { const urls = /(http|ftp|https):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])/g; const identifier = makeId(5); const data = await (await fetch(getSiteMap)).text(); // @ts-ignore return {identifier, list: chunk(([...new Set(data.match(urls))] as string[]).filter(f => f.includes(payload.url)).map(p => ({identifier, url: p})), 25)}; }); ``` - 我們建立一個名為 makeId 的新函數來為所有頁面產生隨機辨識碼。 - 我們建立一個新任務並加入正規表示式來提取每個可能的 URL - 我們發送一個 HTTP 請求來載入網站地圖並提取其所有 URL。 - 我們將 URL「分塊」為 25 個元素的陣列(如果有 100 個元素,則會有四個 25 個元素的陣列) 接下來,讓我們建立一個新作業來處理每個 URL。 **這是完整的程式碼:** ``` function getElementsBetween(startElement: Element, endElement: Element) { let currentElement = startElement; const elements = []; // Traverse the DOM until the endElement is reached while (currentElement && currentElement !== endElement) { currentElement = currentElement.nextElementSibling!; // If there's no next sibling, go up a level and continue if (!currentElement) { // @ts-ignore currentElement = startElement.parentNode!; startElement = currentElement; if (currentElement === endElement) break; continue; } // Add the current element to the list if (currentElement && currentElement !== endElement) { elements.push(currentElement); } } return elements; } const processContent = client.defineJob({ // This is the unique identifier for your Job; it must be unique across all Jobs in your project. id: "process-content", name: "Process Content", version: "0.0.1", // This is triggered by an event using eventTrigger. You can also trigger Jobs with webhooks, on schedules, and more: https://trigger.dev/docs/documentation/concepts/triggers/introduction trigger: eventTrigger({ name: "process.content.event", schema: object({ url: string(), identifier: string(), }) }), run: async (payload, io, ctx) => { return io.runTask('grab-content', async () => { // We first grab a raw html of the content from the website const data = await (await fetch(payload.url)).text(); // We load it with JSDOM so we can manipulate it const dom = new JSDOM(data); // We remove all the scripts and styles from the page dom.window.document.querySelectorAll('script, style').forEach((el) => el.remove()); // We grab all the titles from the page const content = Array.from(dom.window.document.querySelectorAll('h1, h2, h3, h4, h5, h6')); // We grab the last element so we can get the content between the last element and the next element const lastElement = content[content.length - 1]?.parentElement?.nextElementSibling!; const elements = []; // We loop through all the elements and grab the content between each title for (let i = 0; i < content.length; i++) { const element = content[i]; const nextElement = content?.[i + 1] || lastElement; const elementsBetween = getElementsBetween(element, nextElement); elements.push({ title: element.textContent, content: elementsBetween.map((el) => el.textContent).join('\n') }); } // We create a raw text format of all the content const page = ` ---------------------------------- url: ${payload.url}\n ${elements.map((el) => `${el.title}\n${el.content}`).join('\n')} ---------------------------------- `; // We save it to our database await prisma.docs.upsert({ where: { url: payload.url }, update: { content: page, identifier: payload.identifier }, create: { url: payload.url, content: page, identifier: payload.identifier } }); }); }, }); ``` - 我們從 URL 中獲取內容(之前從網站地圖中提取) - 我們用`JSDOM`解析它 - 我們刪除頁面上存在的所有可能的“<script>”或“<style>”。 - 我們抓取頁面上的所有標題(`h1`、`h2`、`h3`、`h4`、`h5`、`h6`) - 我們迭代標題並獲取它們之間的內容。我們不想取得整個頁面內容,因為它可能包含不相關的內容。 - 我們建立頁面原始文字的版本並將其保存到我們的資料庫中。 現在,讓我們為每個網站地圖 URL 執行此任務。 觸發器引入了名為“batchInvokeAndWaitForCompletion”的東西。 它允許我們批量發送 25 個專案進行處理,並且它將同時處理所有這些專案。下面是接下來的幾行程式碼: ``` let i = 0; for (const item of list) { await processContent.batchInvokeAndWaitForCompletion( 'process-list-' + i, item.map( payload => ({ payload, }), 86_400), ); i++; } ``` 我們以 25 個為一組[手動觸發](https://trigger.dev/docs/documentation/concepts/triggers/invoke)之前建立的作業。 完成後,讓我們將保存到資料庫的所有內容並連接它: ``` const data = await io.runTask("get-extracted-data", async () => { return (await prisma.docs.findMany({ where: { identifier }, select: { content: true } })).map((d) => d.content).join('\n\n'); }); ``` 我們使用之前指定的標識符。 現在,讓我們在 ChatGPT 中使用新資料建立一個新檔案: ``` const file = await io.openai.files.createAndWaitForProcessing("upload-file", { purpose: "assistants", file: data }); ``` `createAndWaitForProcessing` 是 Trigger.dev 建立的任務,用於將檔案上傳到助手。如果您在沒有整合的情況下手動使用“openai”,則必須串流傳輸檔案。 現在讓我們建立或更新我們的助手: ``` const assistant = await io.openai.runTask("create-or-update-assistant", async (openai) => { const currentAssistant = await prisma.assistant.findFirst({ where: { url: payload.url } }); if (currentAssistant) { return openai.beta.assistants.update(currentAssistant.aId, { file_ids: [file.id] }); } return openai.beta.assistants.create({ name: identifier, description: 'Documentation', instructions: 'You are a documentation assistant, you have been loaded with documentation from ' + payload.url + ', return everything in an MD format.', model: 'gpt-4-1106-preview', tools: [{ type: "code_interpreter" }, {type: 'retrieval'}], file_ids: [file.id], }); }); ``` - 我們首先檢查是否有針對該特定 URL 的助手。 - 如果我們有的話,讓我們用新文件更新助手。 - 如果沒有,讓我們建立一個新的助手。 - 我們傳遞「你是文件助理」的指令,需要注意的是,我們希望最終輸出為「MD」格式,以便稍後更好地顯示。 對於拼圖的最後一塊,讓我們將新助手儲存到我們的資料庫中。 **這是程式碼:** ``` await io.runTask("save-assistant", async () => { await prisma.assistant.upsert({ where: { url: payload.url }, update: { aId: assistant.id, }, create: { aId: assistant.id, url: payload.url, } }); }); ``` 如果該 URL 已經存在,我們可以嘗試使用新的助手 ID 來更新它。 這是該頁面的完整程式碼: ``` import { eventTrigger } from "@trigger.dev/sdk"; import { client } from "@openai-assistant/trigger"; import {object, string} from "zod"; import {JSDOM} from "jsdom"; import {chunk} from "lodash"; import {prisma} from "@openai-assistant/helper/prisma.client"; import {openai} from "@openai-assistant/helper/open.ai"; const makeId = (length: number) => { let text = ''; const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; for (let i = 0; i < length; i += 1) { text += possible.charAt(Math.floor(Math.random() * possible.length)); } return text; }; client.defineJob({ // This is the unique identifier for your Job; it must be unique across all Jobs in your project. id: "process-documentation", name: "Process Documentation", version: "0.0.1", // This is triggered by an event using eventTrigger. You can also trigger Jobs with webhooks, on schedules, and more: https://trigger.dev/docs/documentation/concepts/triggers/introduction trigger: eventTrigger({ name: "process.documentation.event", schema: object({ url: string(), }) }), integrations: { openai }, run: async (payload, io, ctx) => { // The first task to get the sitemap URL from the website const getSiteMap = await io.runTask("grab-sitemap", async () => { const data = await (await fetch(payload.url)).text(); const dom = new JSDOM(data); const sitemap = dom.window.document.querySelector('[rel="sitemap"]')?.getAttribute('href'); return new URL(sitemap!, payload.url).toString(); }); // We parse the sitemap; instead of using some XML parser, we just use regex to get the URLs and we return it in chunks of 25 const {identifier, list} = await io.runTask("load-and-parse-sitemap", async () => { const urls = /(http|ftp|https):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])/g; const identifier = makeId(5); const data = await (await fetch(getSiteMap)).text(); // @ts-ignore return {identifier, list: chunk(([...new Set(data.match(urls))] as string[]).filter(f => f.includes(payload.url)).map(p => ({identifier, url: p})), 25)}; }); // We go into each page and grab the content; we do this in batches of 25 and save it to the DB let i = 0; for (const item of list) { await processContent.batchInvokeAndWaitForCompletion( 'process-list-' + i, item.map( payload => ({ payload, }), 86_400), ); i++; } // We get the data that we saved in batches from the DB const data = await io.runTask("get-extracted-data", async () => { return (await prisma.docs.findMany({ where: { identifier }, select: { content: true } })).map((d) => d.content).join('\n\n'); }); // We upload the data to OpenAI with all the content const file = await io.openai.files.createAndWaitForProcessing("upload-file", { purpose: "assistants", file: data }); // We create a new assistant or update the old one with the new file const assistant = await io.openai.runTask("create-or-update-assistant", async (openai) => { const currentAssistant = await prisma.assistant.findFirst({ where: { url: payload.url } }); if (currentAssistant) { return openai.beta.assistants.update(currentAssistant.aId, { file_ids: [file.id] }); } return openai.beta.assistants.create({ name: identifier, description: 'Documentation', instructions: 'You are a documentation assistant, you have been loaded with documentation from ' + payload.url + ', return everything in an MD format.', model: 'gpt-4-1106-preview', tools: [{ type: "code_interpreter" }, {type: 'retrieval'}], file_ids: [file.id], }); }); // We update our internal database with the assistant await io.runTask("save-assistant", async () => { await prisma.assistant.upsert({ where: { url: payload.url }, update: { aId: assistant.id, }, create: { aId: assistant.id, url: payload.url, } }); }); }, }); export function getElementsBetween(startElement: Element, endElement: Element) { let currentElement = startElement; const elements = []; // Traverse the DOM until the endElement is reached while (currentElement && currentElement !== endElement) { currentElement = currentElement.nextElementSibling!; // If there's no next sibling, go up a level and continue if (!currentElement) { // @ts-ignore currentElement = startElement.parentNode!; startElement = currentElement; if (currentElement === endElement) break; continue; } // Add the current element to the list if (currentElement && currentElement !== endElement) { elements.push(currentElement); } } return elements; } // This job will grab the content from the website const processContent = client.defineJob({ // This is the unique identifier for your Job; it must be unique across all Jobs in your project. id: "process-content", name: "Process Content", version: "0.0.1", // This is triggered by an event using eventTrigger. You can also trigger Jobs with webhooks, on schedules, and more: https://trigger.dev/docs/documentation/concepts/triggers/introduction trigger: eventTrigger({ name: "process.content.event", schema: object({ url: string(), identifier: string(), }) }), run: async (payload, io, ctx) => { return io.runTask('grab-content', async () => { try { // We first grab a raw HTML of the content from the website const data = await (await fetch(payload.url)).text(); // We load it with JSDOM so we can manipulate it const dom = new JSDOM(data); // We remove all the scripts and styles from the page dom.window.document.querySelectorAll('script, style').forEach((el) => el.remove()); // We grab all the titles from the page const content = Array.from(dom.window.document.querySelectorAll('h1, h2, h3, h4, h5, h6')); // We grab the last element so we can get the content between the last element and the next element const lastElement = content[content.length - 1]?.parentElement?.nextElementSibling!; const elements = []; // We loop through all the elements and grab the content between each title for (let i = 0; i < content.length; i++) { const element = content[i]; const nextElement = content?.[i + 1] || lastElement; const elementsBetween = getElementsBetween(element, nextElement); elements.push({ title: element.textContent, content: elementsBetween.map((el) => el.textContent).join('\n') }); } // We create a raw text format of all the content const page = ` ---------------------------------- url: ${payload.url}\n ${elements.map((el) => `${el.title}\n${el.content}`).join('\n')} ---------------------------------- `; // We save it to our database await prisma.docs.upsert({ where: { url: payload.url }, update: { content: page, identifier: payload.identifier }, create: { url: payload.url, content: page, identifier: payload.identifier } }); } catch (e) { console.log(e); } }); }, }); ``` 我們已經完成建立後台作業來抓取和索引文件🎉 ### 詢問助理 現在,讓我們建立一個任務來詢問我們的助手。 前往“jobs”並建立一個新檔案“question.assistant.ts”。 **新增以下程式碼:** ``` import {eventTrigger} from "@trigger.dev/sdk"; import {client} from "@openai-assistant/trigger"; import {object, string} from "zod"; import {openai} from "@openai-assistant/helper/open.ai"; client.defineJob({ // This is the unique identifier for your Job; it must be unique across all Jobs in your project. id: "question-assistant", name: "Question Assistant", version: "0.0.1", // This is triggered by an event using eventTrigger. You can also trigger Jobs with webhooks, on schedules, and more: https://trigger.dev/docs/documentation/concepts/triggers/introduction trigger: eventTrigger({ name: "question.assistant.event", schema: object({ content: string(), aId: string(), threadId: string().optional(), }) }), integrations: { openai }, run: async (payload, io, ctx) => { // Create or use an existing thread const thread = payload.threadId ? await io.openai.beta.threads.retrieve('get-thread', payload.threadId) : await io.openai.beta.threads.create('create-thread'); // Create a message in the thread await io.openai.beta.threads.messages.create('create-message', thread.id, { content: payload.content, role: 'user', }); // Run the thread const run = await io.openai.beta.threads.runs.createAndWaitForCompletion('run-thread', thread.id, { model: 'gpt-4-1106-preview', assistant_id: payload.aId, }); // Check the status of the thread if (run.status !== "completed") { console.log('not completed'); throw new Error(`Run finished with status ${run.status}: ${JSON.stringify(run.last_error)}`); } // Get the messages from the thread const messages = await io.openai.beta.threads.messages.list("list-messages", run.thread_id, { query: { limit: "1" } }); const content = messages[0].content[0]; if (content.type === 'text') { return {content: content.text.value, threadId: thread.id}; } } }); ``` - 該事件需要三個參數 - `content` - 我們想要傳送給助理的訊息。 - `aId` - 我們先前建立的助手的內部 ID。 - `threadId` - 對話的執行緒 ID。正如您所看到的,這是一個可選參數,因為在第一個訊息中,我們還沒有線程 ID。 - 然後,我們建立或取得前一個執行緒的執行緒。 - 我們在助理提出的問題的線索中加入一條新訊息。 - 我們執行線程並等待它完成。 - 我們取得訊息清單(並將其限制為 1),因為第一則訊息是對話中的最後一則訊息。 - 我們返回訊息內容和我們剛剛建立的線程ID。 ### 新增路由 我們需要為我們的應用程式建立 3 個 API 路由: 1、派新助理進行處理。 2. 透過URL獲取特定助手。 3. 新增訊息給助手。 在「app/api」中建立一個名為assistant的新資料夾,並在其中建立一個名為「route.ts」的新檔案。裡面加入如下程式碼: ``` import {client} from "@openai-assistant/trigger"; import {prisma} from "@openai-assistant/helper/prisma.client"; export async function POST(request: Request) { const body = await request.json(); if (!body.url) { return new Response(JSON.stringify({error: 'URL is required'}), {status: 400}); } // We send an event to the trigger to process the documentation const {id: eventId} = await client.sendEvent({ name: "process.documentation.event", payload: {url: body.url}, }); return new Response(JSON.stringify({eventId}), {status: 200}); } export async function GET(request: Request) { const url = new URL(request.url).searchParams.get('url'); if (!url) { return new Response(JSON.stringify({error: 'URL is required'}), {status: 400}); } const assistant = await prisma.assistant.findFirst({ where: { url: url } }); return new Response(JSON.stringify(assistant), {status: 200}); } ``` 第一個「POST」方法取得一個 URL,並使用用戶端傳送的 URL 觸發「process.documentation.event」作業。 第二個「GET」方法從我們的資料庫中透過客戶端發送的 URL 取得助手。 現在,讓我們建立向助手新增訊息的路由。 在「app/api」內部建立一個新資料夾「message」並新增一個名為「route.ts」的新文件,然後新增以下程式碼: ``` import {prisma} from "@openai-assistant/helper/prisma.client"; import {client} from "@openai-assistant/trigger"; export async function POST(request: Request) { const body = await request.json(); // Check that we have the assistant id and the message if (!body.id || !body.message) { return new Response(JSON.stringify({error: 'Id and Message are required'}), {status: 400}); } // get the assistant id in OpenAI from the id in the database const assistant = await prisma.assistant.findUnique({ where: { id: +body.id } }); // We send an event to the trigger to process the documentation const {id: eventId} = await client.sendEvent({ name: "question.assistant.event", payload: { content: body.message, aId: assistant?.aId, threadId: body.threadId }, }); return new Response(JSON.stringify({eventId}), {status: 200}); } ``` 這是一個非常基本的程式碼。我們從客戶端獲取訊息、助手 ID 和線程 ID,並將其發送到我們之前建立的「question.assistant.event」。 最後要做的事情是建立一個函數來獲取我們所有的助手。 在「helpers」內部建立一個名為「get.list.ts」的新函數並新增以下程式碼: ``` import {prisma} from "@openai-assistant/helper/prisma.client"; // Get the list of all the available assistants export const getList = () => { return prisma.assistant.findMany({ }); } ``` 非常簡單的程式碼即可獲得所有助手。 我們已經完成了後端🥳 讓我們轉到前面。 --- ![前端](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k3s5gks1j0ojoz11b93i.png) ## 建立前端 我們將建立一個基本介面來新增 URL 並顯示已新增 URL 的清單: ![ss1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ihvx4yn6uee6gritr9nh.png) ### 首頁 將 `app/page.tsx` 的內容替換為以下程式碼: ``` import {getList} from "@openai-assistant/helper/get.list"; import Main from "@openai-assistant/components/main"; export default async function Home() { const list = await getList(); return ( <Main list={list} /> ) } ``` 這是一個簡單的程式碼,它從資料庫中取得清單並將其傳遞給我們的 Main 元件。 接下來,讓我們建立“Main”元件。 在「app」內建立一個新資料夾「components」並新增一個名為「main.tsx」的新檔案。 **新增以下程式碼:** ``` "use client"; import {Assistant} from '@prisma/client'; import {useCallback, useState} from "react"; import {FieldValues, SubmitHandler, useForm} from "react-hook-form"; import {ChatgptComponent} from "@openai-assistant/components/chatgpt.component"; import {AssistantList} from "@openai-assistant/components/assistant.list"; import {TriggerProvider} from "@trigger.dev/react"; export interface ExtendedAssistant extends Assistant { pending?: boolean; eventId?: string; } export default function Main({list}: {list: ExtendedAssistant[]}) { const [assistantState, setAssistantState] = useState(list); const {register, handleSubmit} = useForm(); const submit: SubmitHandler<FieldValues> = useCallback(async (data) => { const assistantResponse = await (await fetch('/api/assistant', { body: JSON.stringify({url: data.url}), method: 'POST', headers: { 'Content-Type': 'application/json' } })).json(); setAssistantState([...assistantState, {...assistantResponse, url: data.url, pending: true}]); }, [assistantState]) const changeStatus = useCallback((val: ExtendedAssistant) => async () => { const assistantResponse = await (await fetch(`/api/assistant?url=${val.url}`, { method: 'GET', headers: { 'Content-Type': 'application/json' } })).json(); setAssistantState([...assistantState.filter((v) => v.id), assistantResponse]); }, [assistantState]) return ( <TriggerProvider publicApiKey={process.env.NEXT_PUBLIC_TRIGGER_PUBLIC_API_KEY!}> <div className="w-full max-w-2xl mx-auto p-6 flex flex-col gap-4"> <form className="flex items-center space-x-4" onSubmit={handleSubmit(submit)}> <input className="flex-grow p-3 border border-black/20 rounded-xl" placeholder="Add documentation link" type="text" {...register('url', {required: 'true'})} /> <button className="flex-shrink p-3 border border-black/20 rounded-xl" type="submit"> Add </button> </form> <div className="divide-y-2 divide-gray-300 flex gap-2 flex-wrap"> {assistantState.map(val => ( <AssistantList key={val.url} val={val} onFinish={changeStatus(val)} /> ))} </div> {assistantState.filter(f => !f.pending).length > 0 && <ChatgptComponent list={assistantState} />} </div> </TriggerProvider> ) } ``` 讓我們看看這裡發生了什麼: - 我們建立了一個名為「ExtendedAssistant」的新接口,其中包含兩個參數「pending」和「eventId」。當我們建立一個新的助理時,我們沒有最終的值,我們將只儲存`eventId`並監聽作業處理直到完成。 - 我們從伺服器元件取得清單並將其設定為新狀態(以便我們稍後可以修改它) - 我們新增了「TriggerProvider」來幫助我們監聽事件完成並用資料更新它。 - 我們使用「react-hook-form」建立一個新表單來新增助手。 - 我們新增了一個帶有一個輸入「URL」的表單來提交新的助理進行處理。 - 我們迭代並顯示所有現有的助手。 - 在提交表單時,我們將資訊傳送到先前建立的「路由」以新增助理。 - 事件完成後,我們觸發「changeStatus」以從資料庫載入助手。 - 最後,我們有了 ChatGPT 元件,只有在沒有等待處理的助手時才會顯示(`!f.pending`) 讓我們建立 `AssistantList` 元件。 在「components」內,建立一個新檔案「assistant.list.tsx」並在其中加入以下內容: ``` "use client"; import {FC, useEffect} from "react"; import {ExtendedAssistant} from "@openai-assistant/components/main"; import {useEventRunDetails} from "@trigger.dev/react"; export const Loading: FC<{eventId: string, onFinish: () => void}> = (props) => { const {eventId} = props; const { data, error } = useEventRunDetails(eventId); useEffect(() => { if (!data || error) { return ; } if (data.status === 'SUCCESS') { props.onFinish(); } }, [data]); return <div className="pointer bg-yellow-300 border-yellow-500 p-1 px-3 text-yellow-950 border rounded-2xl">Loading</div> }; export const AssistantList: FC<{val: ExtendedAssistant, onFinish: () => void}> = (props) => { const {val, onFinish} = props; if (val.pending) { return <Loading eventId={val.eventId!} onFinish={onFinish} /> } return ( <div key={val.url} className="pointer relative bg-green-300 border-green-500 p-1 px-3 text-green-950 border rounded-2xl hover:bg-red-300 hover:border-red-500 hover:text-red-950 before:content-[attr(data-content)]" data-content={val.url} /> ) } ``` 我們迭代我們建立的所有助手。如果助手已經建立,我們只顯示名稱。如果沒有,我們渲染`<Loading />`元件。 載入元件在螢幕上顯示“正在載入”,並長時間輪詢伺服器直到事件完成。 我們使用 Trigger.dev 建立的 useEventRunDetails 函數來了解事件何時完成。 事件完成後,它會觸發「onFinish」函數,用新建立的助手更新我們的客戶端。 ### 聊天介面 ![聊天介面](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0u7db3qwz03d6jkk965a.png) 現在,讓我們加入 ChatGPT 元件並向我們的助手提問! - 選擇我們想要使用的助手 - 顯示訊息列表 - 新增我們要傳送的訊息的輸入和提交按鈕。 在「components」內部新增一個名為「chatgpt.component.tsx」的新文件 讓我們繪製 ChatGPT 聊天框: ``` "use client"; import {FC, useCallback, useEffect, useRef, useState} from "react"; import {ExtendedAssistant} from "@openai-assistant/components/main"; import Markdown from 'react-markdown' import {useEventRunDetails} from "@trigger.dev/react"; interface Messages { message?: string eventId?: string } export const ChatgptComponent = ({list}: {list: ExtendedAssistant[]}) => { const url = useRef<HTMLSelectElement>(null); const [message, setMessage] = useState(''); const [messagesList, setMessagesList] = useState([] as Messages[]); const [threadId, setThreadId] = useState<string>('' as string); const submitForm = useCallback(async (e: any) => { e.preventDefault(); setMessagesList((messages) => [...messages, {message: `**[ME]** ${message}`}]); setMessage(''); const messageResponse = await (await fetch('/api/message', { method: 'POST', body: JSON.stringify({message, id: url.current?.value, threadId}), })).json(); if (!threadId) { setThreadId(messageResponse.threadId); } setMessagesList((messages) => [...messages, {eventId: messageResponse.eventId}]); }, [message, messagesList, url, threadId]); return ( <div className="border border-black/50 rounded-2xl flex flex-col"> <div className="border-b border-b-black/50 h-[60px] gap-3 px-3 flex items-center"> <div>Assistant:</div> <div> <select ref={url} className="border border-black/20 rounded-xl p-2"> {list.filter(f => !f.pending).map(val => ( <option key={val.id} value={val.id}>{val.url}</option> ))} </select> </div> </div> <div className="flex-1 flex flex-col gap-3 py-3 w-full min-h-[500px] max-h-[1000px] overflow-y-auto overflow-x-hidden messages-list"> {messagesList.map((val, index) => ( <div key={index} className={`flex border-b border-b-black/20 pb-3 px-3`}> <div className="w-full"> {val.message ? <Markdown>{val.message}</Markdown> : <MessageComponent eventId={val.eventId!} onFinish={setThreadId} />} </div> </div> ))} </div> <form onSubmit={submitForm}> <div className="border-t border-t-black/50 h-[60px] gap-3 px-3 flex items-center"> <div className="flex-1"> <input value={message} onChange={(e) => setMessage(e.target.value)} className="read-only:opacity-20 outline-none border border-black/20 rounded-xl p-2 w-full" placeholder="Type your message here" /> </div> <div> <button className="border border-black/20 rounded-xl p-2 disabled:opacity-20" disabled={message.length < 3}>Send</button> </div> </div> </form> </div> ) } export const MessageComponent: FC<{eventId: string, onFinish: (threadId: string) => void}> = (props) => { const {eventId} = props; const { data, error } = useEventRunDetails(eventId); useEffect(() => { if (!data || error) { return ; } if (data.status === 'SUCCESS') { props.onFinish(data.output.threadId); } }, [data]); if (!data || error || data.status !== 'SUCCESS') { return ( <div className="flex justify-end items-center pb-3 px-3"> <div className="animate-spin rounded-full h-3 w-3 border-t-2 border-b-2 border-blue-500" /> </div> } return <Markdown>{data.output.content}</Markdown>; }; ``` 這裡正在發生一些令人興奮的事情: - 當我們建立新訊息時,我們會自動將其呈現在螢幕上作為「我們的」訊息,但是當我們將其發送到伺服器時,我們需要推送事件 ID,因為我們還沒有訊息。這就是我們使用 `{val.message ? <Markdown>{val.message}</Markdown> : <MessageComponent eventId={val.eventId!} onFinish={setThreadId} />}` - 我們用「Markdown」元件包裝訊息。如果您還記得,我們在前面的步驟中告訴 ChatGPT 以 MD 格式輸出所有內容,以便我們可以正確渲染它。 - 事件處理完成後,我們會更新線程 ID,以便我們從以下訊息中獲得相同對話的上下文。 我們就完成了🎉 --- ![完成](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0half2g6r5zfn7asq084.png) ## 讓我們聯絡吧! 🔌 作為開源開發者,您可以加入我們的[社群](https://discord.gg/nkqV9xBYWy) 做出貢獻並與維護者互動。請隨時造訪我們的 [GitHub 儲存庫](https://github.com/triggerdotdev/trigger.dev),貢獻並建立與 Trigger.dev 相關的問題。 本教學的源程式碼可在此處取得: [https://github.com/triggerdotdev/blog/tree/main/openai-assistant](https://github.com/triggerdotdev/blog/tree/main/openai-assistant) 感謝您的閱讀! --- 原文出處:https://dev.to/triggerdotdev/train-chatgpt-on-your-documentation-1a9g