🔍 搜尋結果:this

🔍 搜尋結果:this

使用 NextJS 建立電子商務商店

在本教程中,您將學習如何建立電子商務商店,客戶可以在其中透過 Stripe 購買產品並付款。成功付款後,將向客戶發送電子郵件通知,並向管理員用戶發送應用程式內通知。管理員用戶也可以在應用程式中建立和刪除產品。 為了建立這個應用程式,我們將使用以下工具: - [Appwrite](https://appwrite.io/) - 用於驗證使用者身份,以及保存和檢索產品詳細資訊。 - [Next.js](https://nextjs.org/) - 用於建立應用程式的使用者介面和後端。 - [Novu](https://docs.novu.co/getting-started/introduction) - 用於發送電子郵件和應用程式內通知。 - [React Email](https://react.email/docs/introduction) - 用於建立電子郵件範本。 - [Stripe](https://docs.stripe.com/) - 用於將付款結帳整合到應用程式中。 ![應用程式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t02iyysrqjfqw8imuqxn.png) --- 使用 Next.js 建立應用程式介面 ------------------- 應用程式頁面根據指派給使用者的角色分為兩部分。客戶可以在付款前存取主頁並登入應用程式。管理員使用者可以存取所有頁面,包括登入頁面和儀表板頁面,他們可以在其中新增和刪除產品。 現在,讓我們建立應用程式。 ![https://media1.giphy.com/media/iopxsZtW2QVRs4poEC/giphy.gif?cid=7941fdc6aot3qt7vvq4voh5c1iagyusdpuga713m8ljqcqmd&ep=v1_gifs_searchiagyusdpuga713m8ljqcqmd&ep=v1_gifs_searchiagyusdpugagif&ct](https://media1.giphy.com/media/iopxsZtW2QVRs4poEC/giphy.gif?cid=7941fdc6aot3qt7vvq4voh5c1iagyusdpuga713m8ljqcqmd&ep=v1_gifs_search&rid=giphy.gif&ct=g) 透過執行以下程式碼片段來建立一個新的 Next.js Typescript 專案: ``` npx create-next-app novu-store ``` 接下來,安裝[React Icons](https://react-icons.github.io/react-icons)和[Headless UI](https://headlessui.com/)包。 React Icons 允許我們在應用程式中使用各種圖標,而 Headless UI 則提供易於使用的現代 UI 元件。 ``` npm install react-icons @headlessui/react ``` 將此程式碼片段從[GitHub 儲存庫](https://github.com/dha-stix/ecom-store-with-nextjs-appwrite-novu-and-stripe/blob/main/src/app/page.tsx)複製到`app/page.tsx`檔案中。它在螢幕上呈現產品列表,並允許用戶選擇購物車中的商品,類似於下圖。 ![1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dj69givzhqfapgsg12rk.gif) 建立登入路由,使用戶能夠使用其 GitHub 帳戶進行簽署。將下面的程式碼片段複製到`app/login/page.tsx`檔案中。 ``` //👉🏻 create a login folder containing a page.tsx file export default function Home() { const handleGoogleSignIn = async () => {}; return ( <main className='w-full min-h-screen flex flex-col items-center justify-center'> <h2 className='font-semibold text-3xl mb-2'>Customer Sign in</h2> <p className='mb-4 text-sm text-red-500'> You need to sign in before you can make a purchase </p> <button className='p-4 border-[2px] border-gray-500 rounded-md hover:bg-black hover:text-white w-2/3' onClick={() => handleGoogleSignIn()} > Sign in with GitHub </button> </main> ); } ``` ![客戶登入](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3nh2rowpfg4hgksj5diy.png) 當使用者點擊「登入」按鈕時,會將他們重新導向到 GitHub 驗證頁面並提示他們登入應用程式。您很快就會了解如何使用[Appwrite](https://appwrite.io/)執行此操作。 接下來,讓我們建立管理頁面。在`app`資料夾中新增包含`login`和`dashboard`路由的`admin`資料夾。 ``` cd app mkdir admin && cd admin mkdir dashboard login ``` 在`dashboard`和`login`資料夾中新增`page.tsx`文件,並將下面的程式碼片段複製到`login/page.tsx`檔案中。 ``` "use client"; import Link from "next/link"; import { useState } from "react"; export default function Login() { const [email, setEmail] = useState<string>(""); const [password, setPassword] = useState<string>(""); const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); console.log({ email, password }); }; return ( <main className='w-full min-h-screen flex flex-col items-center justify-center'> <h2 className='font-semibold text-3xl mb-4'> Admin Sign in</h2> <form className='w-2/3' onSubmit={handleLogin}> <label htmlFor='email' className='block'> Email </label> <input type='email' id='email' className='w-full px-4 py-3 border border-gray-400 rounded-sm mb-4' required value={email} placeholder='[email protected]' onChange={(e) => setEmail(e.target.value)} /> <label htmlFor='password' className='block'> Password </label> <input type='password' id='password' className='w-full px-4 py-3 border border-gray-400 rounded-sm mb-4' required value={password} placeholder='admin123' onChange={(e) => setPassword(e.target.value)} /> <button className='p-4 text-lg mb-3 bg-blue-600 text-white w-full rounded-md'> Sign in </button> <p className='text-sm text-center'> Not an Admin?{" "} <Link href='/login' className='text-blue-500'> Sign in as a Customer </Link> </p> </form> </main> ); } ``` 上面的程式碼片段呈現一個表單,該表單接受管理員的電子郵件和密碼,驗證憑證,然後將使用者登入應用程式中。 ![管理員登入](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gjd9wsi63t96d5cls9om.png) 管理儀表板頁面呈現可用的產品,並允許管理員使用者在應用程式中新增和刪除產品。將此[程式碼片段複製](https://github.com/dha-stix/ecom-store-with-nextjs-appwrite-novu-and-stripe/blob/main/src/app/admin/dashboard/page.tsx)到`dashboard/page.tsx`檔案中以建立使用者介面。 ![2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p1gd1uq1eq6n76fesjxu.gif) 恭喜!您已經建立了應用程式介面。在接下來的部分中,您將了解如何將應用程式連接到 Appwrite 後端並在客戶端和伺服器之間發送資料。 --- 如何將 Appwrite 新增到 Next.js 應用程式 ----------------------------- Appwrite 是一項開源後端服務,可讓您建立安全且可擴展的軟體應用程式。它提供多種身份驗證方法、安全性資料庫、文件儲存、雲端訊息傳遞等功能,這些對於建立全端應用程式至關重要。 在本部分中,您將了解如何設定 Appwrite 專案,包括身份驗證、資料庫和檔案儲存等功能。 首先,請造訪[Appwrite Cloud](https://cloud.appwrite.io/register) ,並為您的專案建立一個帳戶和組織。 接下來,建立一個新專案並選擇您的首選區域來託管該專案。 ![應用程式寫入1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/as6302olk60oklfo70x5.png) 選擇`Web`作為應用程式的平台 SDK。 ![應用程式編寫2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bb5ae82i9fyoyrowsy96.png) 請依照螢幕上顯示的步驟進行操作。由於您目前正在開發模式下建置,因此您可以使用通配符 ( `*` ) 作為主機名,並在部署應用程式後將其變更為您的網域名稱。 ![應用程式寫入3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y5ccs0hzgs9ujf5lzh83.png) 在 Next.js 專案中安裝 Appwrite 用戶端 SDK。 ``` npm install appwrite ``` 最後,在 Next.js 應用程式資料夾中建立一個`appwrite.ts`文件,並將下面的程式碼片段複製到該文件中以初始化 Appwrite。 ``` import { Client, Account, Databases, Storage } from "appwrite"; const client = new Client(); client .setEndpoint("https://cloud.appwrite.io/v1") .setProject(<YOUR_PROJECT_ID>); export const account = new Account(client); export const db = new Databases(client); export const storage = new Storage(client); ``` ### 使用 Appwrite 設定 GitHub 身份驗證 在這裡,您將了解如何使用 Appwrite 設定 GitHub 和電子郵件/密碼驗證。預設已配置電子郵件/密碼身份驗證,因此我們專注於設定 GitHub 身份驗證。 在繼續之前,您需要使用您的 GitHub 帳戶建立[GitHub OAuth 應用程式](https://github.com/settings/developers)。 Appwrite 將需要客戶端 ID 和金鑰來設定 GitHub 身份驗證。 ![GitHub 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9znk1yr7tffus7soitq2.png) 透過從側邊欄選單中選擇`Auth`並導覽至`Settings`選項卡,啟用 Appwrite 的 GitHub 驗證方法。 ![應用程式編寫4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/43uo6nho1bz9su14zsno.png) 將您的 GitHub 用戶端 ID 和金鑰複製到 Appwrite 的 GitHub OAuth 設定中。 最後,確保將 Appwrite 產生的 URI 複製到 GitHub 應用程式設定中。 ![GitHub 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g75q5r5hc6l5pi09k88m.png) ### 設定 Appwrite 資料庫 從側邊欄選單中選擇資料庫並建立新資料庫。您可以將其命名為`novu store` 。 ![應用程式寫入5](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y7kn1llmu7olqirfcrpa.png) 接下來,建立`products`集合。它將包含應用程式中的產品清單。 ![應用程式寫入 6](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7p8laty6z37x0q1g6az4.png) 將名稱、價格和圖像屬性新增至集合。 ![應用程式寫入 7](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nzom3ptlz8t1rh9dtt1k.png) 在「設定」標籤下,更新權限以允許每個使用者執行 CRUD 操作。但是,您可以在部署應用程式後變更此設置,以確保只有經過身份驗證的使用者才能執行各種操作。 ![應用程式寫入 8](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/37cqr8s0crtcttocjagk.png) 最後,將專案、資料庫和集合 ID 複製到**`.env.local`**檔案中。這可以確保您的憑證安全,並允許您引用其環境變數中的每個值。 ``` NEXT_PUBLIC_PROJECT_ID=<YOUR_PROJECT_ID> NEXT_PUBLIC_DB_ID=<YOUR_DATABASE_ID> NEXT_PUBLIC_PRODUCTS_COLLECTION_ID=<YOUR_DB_COLLECTION_ID> ``` ### 設定應用程式寫入存儲 從側邊欄選單中選擇`Storage` ,然後建立新儲存桶來儲存所有產品影像。 ![應用程式編寫 9](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b84t9mk3k0wrkgiy4uca.png) 在`Settings`標籤下,更新「權限」以暫時允許任何使用者。 ![應用程式寫入 10](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zi3iozkaera7fohkwanm.png) 設定可接受的文件格式。由於我們上傳的是圖像,因此您可以選擇**`.jpg`**和**`.png`**檔案格式。 ![應用寫入 11](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zzxqpoq5dcpokkvdcsce.png) 最後,將您的儲存桶 ID 複製到`.env.local`檔案中。 ``` NEXT_PUBLIC_BUCKET_ID=<YOUR_BUCKET_ID> ``` 恭喜!您已成功配置 Appwrite。我們現在可以開始與其各種功能進行互動。 --- 如何使用Appwrite執行CRUD操作 -------------------- 在本部分中,您將了解如何從 Appwrite 建立、檢索和刪除產品。用戶需要能夠在購買前查看現有產品,而管理員用戶應有權在應用程式中新增和刪除產品。 首先,在 Next.js **`app`**資料夾中建立一個**`utils.ts`**檔案。該文件將包含所有 Appwrite 資料庫交互,然後您可以將其導入到必要的頁面中。 ``` cd app touch utils.ts ``` ### 將產品儲存到 Appwrite 回想一下, `products`集合有三個屬性:名稱、圖像和價格。因此,在將產品新增至資料庫時,您需要先上傳產品的圖像,從回應中檢索其 URL 和 ID,然後將 URL 作為產品的圖像屬性上傳,使用圖像的儲存 ID 作為產品資料。 這是解釋這一點的程式碼片段: ``` import { db, storage } from "@/app/appwrite"; import { ID } from "appwrite"; export const createProduct = async ( productTitle: string, productPrice: number, productImage: any ) => { try { //👇🏻 upload the image const response = await storage.createFile( process.env.NEXT_PUBLIC_BUCKET_ID!, ID.unique(), productImage ); //👇🏻 get the image's URL const file_url = `https://cloud.appwrite.io/v1/storage/buckets/${process.env.NEXT_PUBLIC_BUCKET_ID}/files/${response.$id}/view?project=${process.env.NEXT_PUBLIC_PROJECT_ID}&mode=admin`; //👇🏻 add the product to the database await db.createDocument( process.env.NEXT_PUBLIC_DB_ID!, process.env.NEXT_PUBLIC_PRODUCTS_COLLECTION_ID!, response.$id, //👉🏻 use the image's ID { name: productTitle, price: productPrice, image: file_url, } ); alert("Product created successfully"); } catch (err) { console.error(err); } }; ``` 上面的程式碼片段將圖像上傳到 Appwrite 的雲端存儲,並使用儲存桶 ID、圖像 ID 和專案 ID 檢索準確的圖像 URL。圖片成功上傳後,其 ID 將用於產品資料中,以便輕鬆檢索和參考。 ### 從 Appwrite 檢索產品 若要從 Appwrite 取得產品,您可以在頁面載入時在 React **`useEffect`**掛鉤中執行下列函數。 ``` export const fetchProducts = async () => { try { const products = await db.listDocuments( process.env.NEXT_PUBLIC_DB_ID!, process.env.NEXT_PUBLIC_PRODUCTS_COLLECTION_ID! ); if (products.documents) { return products.documents; } } catch (err) { console.error(err); } }; ``` `fetchProducts`函數傳回`products`集合中的所有資料。 ### 從 Appwrite 中刪除產品 管理員使用者也可以透過產品 ID 刪除產品。 **`deleteProduct`**函數接受產品的 ID 作為參數,並從資料庫中刪除所選產品(包括其圖像),因為它們使用相同的 ID 屬性。 ``` export const deleteProduct = async (id: string) => { try { await db.deleteDocument( process.env.NEXT_PUBLIC_DB_ID!, process.env.NEXT_PUBLIC_PRODUCTS_COLLECTION_ID!, id ); await storage.deleteFile(process.env.NEXT_PUBLIC_BUCKET_ID!, id); alert("Product deleted successfully"); } catch (err) { console.error(err); } }; ``` --- 如何使用 Appwrite 驗證使用者身份 --------------------- 在前面的部分中,我們已經設定了 GitHub 身份驗證方法。在這裡,您將了解如何處理使用者登入應用程式。 若要使客戶能夠使用其 GitHub 帳戶登入應用程式,請在按一下`Sign in`按鈕時執行以下功能。該函數將使用者重定向到 GitHub,在那裡他們可以向應用程式授權或授予權限,然後登入應用程式: ``` import { account } from "../appwrite"; import { OAuthProvider } from "appwrite"; const handleGoogleSignIn = async () => { try { account.createOAuth2Session( OAuthProvider.Github, "http://localhost:3000", "http://localhost:3000/login" ); } catch (err) { console.error(err); } }; ``` 管理員使用者可以使用電子郵件和密碼登入應用程式。 Appwrite 在授予對應用程式儀表板的存取權之前會驗證憑證。 ``` import { account } from "@/app/appwrite"; const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); try { await account.createEmailPasswordSession(email, password); alert(`Welcome back 🎉`); router.push("/admin/dashboard"); } catch (err) { console.error(err); alert("Invalid credentials ❌"); } }; ``` Appwrite 還允許您取得目前使用者的資料。例如,如果只有經過身份驗證的使用者才能付款,您可以透過執行下面的程式碼片段來完成此操作。它會檢索目前使用者的資料,如果使用者未登錄,則傳回 null。 ``` import { account } from "@/app/appwrite"; useEffect(() => { const checkAuthStatus = async () => { try { const request = await account.get(); setUser(request); } catch (err) { console.log(err); } }; checkAuthStatus(); }, []); ``` --- 如何將 Stripe 付款結帳新增至 Next.js -------------------------- 在本節中,您將了解如何在應用程式中實現 Stripe 付款結帳。 Stripe 是一種流行的線上支付處理平台,可讓您建立產品並將一次性和定期支付方式整合到您的應用程式中。 首先,您需要[建立一個 Stripe 帳戶](https://dashboard.stripe.com/login)。您可以在本教學中使用測試模式帳戶。 ![條紋1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nibs7bxb09i167mxm918.png) 點擊頂部選單中的`Developers` ,然後從 API 金鑰選單中複製您的金鑰。 ![條紋2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/up8757knbquc3k0577ps.png) 將您的 Stripe 金鑰貼到`.env.local`檔案中。 ``` STRIPE_SECRET_KEY=<your_secret_key> ``` 安裝[Stripe Node.js SDK](https://docs.stripe.com/libraries) 。 ``` npm install stripe ``` 接下來,在 Next.js `app`資料夾中建立一個`api`資料夾。 `api`資料夾將包含應用程式的所有 API 路由和端點。 ``` cd app mkdir api ``` 透過在`api`資料夾中新增`checkout`資料夾來建立`checkout`端點。 ``` cd api mkdir checkout && cd checkout touch route.ts ``` 將下面的程式碼片段複製到`route.ts`檔中。 ``` import { NextRequest, NextResponse } from "next/server"; import Stripe from "stripe"; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); export async function POST(req: NextRequest) { //👇🏻 accepts the customer's cart const cart = await req.json(); try { //👇🏻 creates a checkout session const session = await stripe.checkout.sessions.create({ payment_method_types: ["card"], line_items: cart.map((product: Product) => ({ price_data: { currency: "usd", product_data: { name: product.name, }, unit_amount: product.price * 100, }, quantity: 1, })), mode: "payment", cancel_url: `http://localhost:3000/?canceled=true`, success_url: `http://localhost:3000?success=true&session_id={CHECKOUT_SESSION_ID}`, }); //👇🏻 return the session URL return NextResponse.json({ session: session.url }, { status: 200 }); } catch (err) { return NextResponse.json({ err }, { status: 500 }); } } ``` 上面的程式碼片段建立了一個接受 POST 請求的結帳端點。它為客戶建立結帳會話並傳回會話 URL。 **`cancel_url`**和**`success_url`**確定完成或取消付款後將用戶重新導向到何處。 最後,當用戶決定為產品付款時,您可以透過執行以下程式碼片段將客戶的購物車發送到`/checkout`端點: ``` const processPayment = async (cart: Product[]) => { try { if (user !== null) { //👇🏻 saves cart to local storage localStorage.setItem("cart", JSON.stringify(cart)); //👇🏻 sends cart to /checkout route const request = await fetch("/api/checkout", { method: "POST", body: JSON.stringify(cart), headers: { "Content-Type": "application/json" }, }); //👇🏻 retrieves the session URL const { session } = await request.json(); //👇🏻 redirects the user to the checkout page window.location.assign(session); } else { //👇🏻 redirects unauthenticated users router.push("/login"); } } catch (err) { console.error(err); } }; ``` 上面的程式碼片段將購物車儲存到瀏覽器的本機儲存體並將其傳送到 API 端點,然後從後端伺服器檢索回應(會話 URL)並將使用者重新導向至 Stripe 結帳頁面。 ![條紋3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i5hokf2qyqyey3kwsg9x.gif) --- 使用 Novu 發送應用程式內通知和電子郵件通知 ------------------------ [Novu](https://github.com/novuhq/novu)是第一個提供統一 API 的通知基礎架構,用於透過多種管道(包括應用程式內、推播、電子郵件、簡訊和聊天)發送通知。 在本部分中,您將了解如何將 Novu 加入到您的應用程式,以便您能夠發送電子郵件和應用程式內訊息。 首先,安裝所需的 Novu 軟體包: ``` npm install @novu/node @novu/echo @novu/notification-center ``` 當用戶進行購買時,他們將收到一封付款確認電子郵件,管理員用戶也會收到一條應用程式內通知。 為此,您需要[在 Novu 上建立帳戶](https://web.novu.co/auth/login)並設定主要電子郵件提供者。在本教程中,我們將使用[“重新發送”](https://resend.com/docs/introduction) 。 在 Novu 上建立帳戶後,建立一個[重新傳送帳戶](https://resend.com/docs/introduction),然後從儀表板上的側邊欄選單中選擇`API Keys`來建立帳戶。 ![重新發送 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jhehx7s45x180zpir1ti.png) 接下來,回到 Novu 儀表板,從側邊欄選單中選擇`Integrations Store` ,然後新增 Resend 作為電子郵件提供者。您需要將重新傳送 API 金鑰和電子郵件地址貼到必填欄位中。 ![新 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f03vb6nftyi8g790vg7m.png) 從側邊欄選單中選擇**「設定」** ,然後將您的`Novu API`金鑰和`App ID`複製到**`.env.local`**檔案中,如下所示。另外,將您的`subscriber ID`複製到其欄位中 - 您可以從`Subscribers`部分獲取此資訊。 ``` NOVU_API_KEY=<YOUR_API_FOR_NEXT_SERVER> NEXT_PUBLIC_NOVU_API_KEY=<YOUR_API_FOR_NEXT_CLIENT> NEXT_PUBLIC_NOVU_APP_ID=<YOUR_API_ID> NOVU_SUBSCRIBER_ID=<YOUR_API_FOR_NEXT_SERVER> NEXT_PUBLIC_NOVU_SUBSCRIBER_ID=<YOUR_API_FOR_CLIENT> ``` ![新2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/voeofvvtv88pex9rpr1s.png) 最後,將 Novu 通知鈴新增至管理儀表板,以使管理員使用者能夠在應用程式內接收通知。 ``` import { NovuProvider, PopoverNotificationCenter, NotificationBell, } from "@novu/notification-center"; export default function AdminNav() { return ( <NovuProvider subscriberId={process.env.NEXT_PUBLIC_NOVU_SUBSCRIBER_ID!} applicationIdentifier={process.env.NEXT_PUBLIC_NOVU_APP_ID!} > <PopoverNotificationCenter colorScheme='light'> {({ unseenCount }) => <NotificationBell unseenCount={unseenCount} />} </PopoverNotificationCenter> </NovuProvider> ); } ``` ![儀表板](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m62ft87ue9orse2yww9z.png) --- 如何使用 Novu Echo 建立通知工作流程 ----------------------- [Novu](https://docs.novu.co/echo/quickstart)提供程式碼優先的工作流程引擎,讓您能夠在程式碼庫中建立通知工作流程。它允許您將電子郵件、簡訊、聊天範本和內容產生器(例如[React Email](https://react.email/docs/introduction)和[MJML](https://mjml.io/) )整合到 Novu 中,以建立高級且強大的通知。 在本部分中,您將了解如何在應用程式中建立通知工作流程、如何使用 Novu 的電子郵件通知範本以及如何使用 Novu 發送應用程式內通知和電子郵件通知。 透過執行以下命令安裝[React Email](https://react.email/docs/introduction) : ``` npm install react-email @react-email/components -E ``` 將以下腳本包含在您的 package.json 檔案中。 `--dir`標誌使 React Email 能夠存取位於專案內的電子郵件範本。在本例中,電子郵件範本位於`src/emails`資料夾中。 ``` { "scripts": { "email": "email dev --dir src/emails" } } ``` 接下來,在 Next.js `app`資料夾中建立一個包含`email.tsx`的`emails`資料夾,並將以下程式碼片段複製到該檔案中: ``` import { Body, Column, Container, Head, Heading, Hr, Html, Link, Preview, Section, Text, Row, render, } from "@react-email/components"; import * as React from "react"; const EmailTemplate = ({ message, subject, name, }: { message: string; subject: string; name: string; }) => ( <Html> <Head /> <Preview>{subject}</Preview> <Body style={main}> <Container style={container}> <Section style={header}> <Row> <Column style={headerContent}> <Heading style={headerContentTitle}>{subject}</Heading> </Column> </Row> </Section> <Section style={content}> <Text style={paragraph}>Hey {name},</Text> <Text style={paragraph}>{message}</Text> </Section> </Container> <Section style={footer}> <Text style={footerText}> You&apos;re receiving this email because your subscribed to Newsletter App </Text> <Hr style={footerDivider} /> <Text style={footerAddress}> <strong>Novu Store</strong>, &copy;{" "} <Link href='https://novu.co'>Novu</Link> </Text> </Section> </Body> </Html> ); export function renderEmail(inputs: { message: string; subject: string; name: string; }) { return render(<EmailTemplate {...inputs} />); } const main = { backgroundColor: "#f3f3f5", fontFamily: "HelveticaNeue,Helvetica,Arial,sans-serif", }; const headerContent = { padding: "20px 30px 15px" }; const headerContentTitle = { color: "#fff", fontSize: "27px", fontWeight: "bold", lineHeight: "27px", }; const paragraph = { fontSize: "15px", lineHeight: "21px", color: "#3c3f44", }; const divider = { margin: "30px 0", }; const container = { width: "680px", maxWidth: "100%", margin: "0 auto", backgroundColor: "#ffffff", }; const footer = { width: "680px", maxWidth: "100%", margin: "32px auto 0 auto", padding: "0 30px", }; const content = { padding: "30px 30px 40px 30px", }; const header = { borderRadius: "5px 5px 0 0", display: "flex", flexDireciont: "column", backgroundColor: "#2b2d6e", }; const footerDivider = { ...divider, borderColor: "#d6d8db", }; const footerText = { fontSize: "12px", lineHeight: "15px", color: "#9199a1", margin: "0", }; const footerLink = { display: "inline-block", color: "#9199a1", textDecoration: "underline", fontSize: "12px", marginRight: "10px", marginBottom: "0", marginTop: "8px", }; const footerAddress = { margin: "4px 0", fontSize: "12px", lineHeight: "15px", color: "#9199a1", }; ``` 上面的程式碼片段使用 React Email 建立了一個可自訂的電子郵件範本。您可以找到更多[易於編輯的靈感或模板](https://demo.react.email/preview/notifications/vercel-invite-user)。該元件還接受訊息、主題和名稱作為屬性,並將它們填入元素中。 最後,您可以在終端機中執行`npm run email`來預覽範本。 接下來,讓我們將電子郵件範本整合到 Novu Echo 中。首先,關閉 React Email 伺服器,然後執行下面的程式碼片段。它會在瀏覽器中開啟[Novu Dev Studio](https://docs.novu.co/echo/concepts/studio) 。 ``` npx novu-labs@latest echo ``` 在 Next.js 應用程式資料夾中建立一個包含`client.ts`檔案的`echo`資料夾,並將此程式碼片段複製到該檔案中。 ``` import { Echo } from "@novu/echo"; import { renderEmail } from "@/app/emails/email"; interface EchoProps { step: any; payload: { subject: string; message: string; name: string; totalAmount: string; }; } export const echo = new Echo({ apiKey: process.env.NEXT_PUBLIC_NOVU_API_KEY!, devModeBypassAuthentication: process.env.NODE_ENV === "development", }); echo.workflow( "novu-store", async ({ step, payload }: EchoProps) => { //👇🏻 in-app notification step await step.inApp("notify-admin", async () => { return { body: `${payload.name} just made a new purchase of ${payload.totalAmount} 🎉`, }; }); //👇🏻 email notification step await step.email( "email-customer", async () => { return { subject: `${payload ? payload?.subject : "No Subject"}`, body: renderEmail(payload), }; }, { inputSchema: { type: "object", properties: {}, }, } ); }, { payloadSchema: { type: "object", properties: { message: { type: "string", default: "Congratulations! Your purchase was successful! 🎉", }, subject: { type: "string", default: "Message from Novu Store" }, name: { type: "string", default: "User" }, totalAmount: { type: "string", default: "0" }, }, required: ["message", "subject", "name", "totalAmount"], additionalProperties: false, }, } ); ``` 此程式碼片段定義了一個名為`novu-store` Novu 通知工作流程,該工作流程接受包含電子郵件主題、訊息、客戶姓名和總金額的有效負載。 此工作流程有兩個步驟:應用程式內通知和電子郵件通知。應用程式內通知使用通知鈴聲向管理員發送訊息,而電子郵件則向客戶的電子郵件發送訊息。 接下來,您需要為 Novu Echo 建立 API 路由。在`api`資料夾中,建立一個包含`route.ts`檔案的`email`資料夾,並將下面提供的程式碼片段複製到該檔案中。 ``` import { serve } from "@novu/echo/next"; import { echo } from "@/app/echo/client"; export const { GET, POST, PUT } = serve({ client: echo }); ``` 在終端機中執行`npx novu-labs@latest echo` 。它將自動開啟 Novu Dev Studio,您可以在其中預覽工作流程並將其與雲端同步。 ![新3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ed2sl38m7zrlgjoj4a6y.gif) `Sync to Cloud`按鈕會觸發一個彈出窗口,其中提供有關如何將工作流程推送到 Novu 雲端的說明。 ![新4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8ch8ba7y9klyudmmv9jz.png) 若要繼續,請在終端機中執行以下程式碼片段。這將產生一個唯一的 URL,表示您的開發環境和雲端環境之間的本機隧道。 ``` npx localtunnel --port 3000 ``` 將產生的連結與 Echo API 端點一起複製到 Echo Endpoint 欄位中,按一下`Create Diff`按鈕,然後部署變更。 ``` https://<LOCAL_TUNNEL_URL>/<ECHO_API_ENDPOINT (/api/email)> ``` 恭喜!您剛剛從程式碼庫建立了 Novu 工作流程。 ![新5](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6bdugs6g15y1e7xeixux.png) 最後,讓我們建立在用戶付款時發送電子郵件和應用程式內通知的端點。建立一個`api/send`路由並將下面的程式碼片段複製到檔案中: ``` import { NextRequest, NextResponse } from "next/server"; import { Novu } from "@novu/node"; const novu = new Novu(process.env.NOVU_API_KEY!); export async function POST(req: NextRequest) { const { email, name, totalAmount } = await req.json(); const { data } = await novu.trigger("novu-store", { to: { subscriberId: process.env.NOVU_SUBSCRIBER_ID!, email, firstName: name, }, payload: { name, totalAmount, subject: `Purchase Notification from Novu Store`, message: `Your purchase of ${totalAmount} was successful! 🎉`, }, }); console.log(data.data); return NextResponse.json( { message: "Purchase Completed!", data: { novu: data.data }, success: true, }, { status: 200 } ); } ``` 端點接受客戶的電子郵件、姓名和支付總額,並在付款成功後觸發 Novu 通知工作流程發送所需的通知。 --- 結論 -- 到目前為止,您已經學會如何執行以下操作: - 實施多種身份驗證方法,從 Appwrite 儲存和檢索資料和檔案。 - 使用 React Email 建立電子郵件模板,並使用 Novu 發送應用程式內和電子郵件通知。 如果您希望在應用程式中發送通知,Novu 是您的最佳選擇。使用 Novu,您可以為應用程式加入多個通知管道,包括聊天、簡訊、電子郵件、推播和應用程式內通知。 本教學的源程式碼可在此處取得: <https://github.com/novuhq/ecom-store-with-nextjs-appwrite-novu-and-stripe> 感謝您的閱讀! --- 原文出處:https://dev.to/novu/building-an-e-commerce-store-with-nextjs-49m

使用 Langchain 為您的文件建立 QA 機器人 😻

--- 標題:使用 Langchain 為您的文件建立 QA 機器人 😻 描述:使用 Wing Framework、NextJS 和 Langchain 建立的 ChatGPT 用戶端應用程式 canonical\_url:https://www.winglang.io/blog/2024/05/29/qa-bot-for-your-docs-with-langchain 發表:真實 --- 長話短說 ---- 在本教學中,我們將為您的網站文件建立一個人工智慧驅動的問答機器人。 - 🌐 建立一個用戶友好的 Next.js 應用程式來接受問題和 URL - 🔧 設定一個 Wing 後端來處理所有請求 - 💡 透過使用 RAG 抓取和分析文件,將 @langchain 納入 AI 驅動的答案 - 🔄 前端輸入和人工智慧處理的回應之間的完整連接。 ![問題](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ykw5f2sos4fdhj8akowt.gif) 什麼是翼? ----- [Wing](https://wing.cloud/redirect?utm_source=qa-bot-reddit&redirect=https%3A%2F%2Fwww.winglang.io%2Fblog%2F2024%2F05%2F29%2Fqa-bot-for-your-docs-with-langchain)是一個雲端開源框架。 它允許您將應用程式的基礎架構和程式碼組合為一個單元,並將它們安全地部署到您首選的雲端提供者。 Wing 讓您可以完全控制應用程式基礎架構的配置方式。除了其易於學習的[程式語言](https://www.winglang.io/docs/language-reference)之外,Wing 還支援 Typescript。 在本教學中,我們將使用 TypeScript。所以,別擔心,您的 JavaScript 和 React 知識足以理解本教學。 ![翼登陸頁面](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u366v255drbwqmcoagrz.png) {% cta https://wingla.ng/github %} 看 Wing ⭐️ {% endcta %} --- 使用 Next.js 建立前端 --------------- 在這裡,您將建立一個簡單的表單,它接受文件 URL 和使用者的問題,然後根據網站的資料回傳回應。 首先,建立一個包含兩個子資料夾的資料夾 - `frontend`和`backend` 。 `frontend`資料夾包含 Next.js 應用程式, `backend`資料夾用於 Wing。 ``` mkdir qa-bot && cd qa-bot mkdir frontend backend ``` 在**`frontend`**資料夾中,透過執行以下程式碼片段來建立 Next.js 專案: ``` cd frontend npx create-next-app ./ ``` ![下一個應用程式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pyq8dnmmmplvzl7g41ir.png) 將下面的程式碼片段複製到`app/page.tsx`檔案中,以建立接受使用者問題和文件 URL 的表單: ``` "use client"; import { useState } from "react"; export default function Home() { const [documentationURL, setDocumentationURL] = useState<string>(""); const [question, setQuestion] = useState<string>(""); const [disable, setDisable] = useState<boolean>(false); const [response, setResponse] = useState<string | null>(null); const handleUserQuery = async (e: React.FormEvent) => { e.preventDefault(); setDisable(true); console.log({ question, documentationURL }); }; return ( <main className='w-full md:px-8 px-3 py-8'> <h2 className='font-bold text-2xl mb-8 text-center text-blue-600'> Documentation Bot with Wing & LangChain </h2> <form onSubmit={handleUserQuery} className='mb-8'> <label className='block mb-2 text-sm text-gray-500'>Webpage URL</label> <input type='url' className='w-full mb-4 p-4 rounded-md border text-sm border-gray-300' placeholder='https://www.winglang.io/docs/concepts/why-wing' required value={documentationURL} onChange={(e) => setDocumentationURL(e.target.value)} /> <label className='block mb-2 text-sm text-gray-500'> Ask any questions related to the page URL above </label> <textarea rows={5} className='w-full mb-4 p-4 text-sm rounded-md border border-gray-300' placeholder='What is Winglang? OR Why should I use Winglang? OR How does Winglang work?' required value={question} onChange={(e) => setQuestion(e.target.value)} /> <button type='submit' disabled={disable} className='bg-blue-500 text-white px-8 py-3 rounded' > {disable ? "Loading..." : "Ask Question"} </button> </form> {response && ( <div className='bg-gray-100 w-full p-8 rounded-sm shadow-md'> <p className='text-gray-600'>{response}</p> </div> )} </main> ); } ``` 上面的程式碼片段顯示了一個表單,該表單接受使用者的問題和文件 URL 並將它們暫時記錄到控制台。 ![QA 機器人表單](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7b4w3tq0srua93gnk73n.png) 完美的! 🎉您已經完成了應用程式的使用者介面。接下來,讓我們設定 Wing 後端。 --- 如何在電腦上設定 Wing ------------- Wing 提供了一個 CLI,使您能夠在專案中執行各種 Wing 操作。 它還提供[VSCode](https://marketplace.visualstudio.com/items?itemName=Monada.vscode-wing)和[IntelliJ](https://plugins.jetbrains.com/plugin/22353-wing)擴展,透過語法突出顯示、編譯器診斷、程式碼完成和片段等功能增強開發人員體驗。 在繼續之前,請停止 Next.js 開發伺服器並透過在終端機中執行下面的程式碼片段來安裝 Wing CLI。 ``` npm install -g winglang@latest ``` 執行以下程式碼片段以確保 Winglang CLI 已安裝並按預期工作: ``` wing -V ``` 接下來,導航到`backend`資料夾並建立一個空的 Wing Typescript 專案。確保選擇`empty`模板並選擇 Typescript 作為語言。 ``` wing new ``` ![永新](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ezy04zqvz9lura0d25dj.png) 將下面的程式碼片段複製到`backend/main.ts`檔案中。 ``` import { cloud, inflight, lift, main } from "@wingcloud/framework"; main((root, test) => { const fn = new cloud.Function( root, "Function", inflight(async () => { return "hello, world"; }) ); }); ``` **`main()`**函數充當 Wing 的入口點。 它建立一個雲端函數並在編譯時執行。另一方面, **`inflight`**函數在執行時執行並返回`Hello, world!`文字. 透過執行下面的程式碼片段啟動 Wing 開發伺服器。它會自動在瀏覽器中開啟 Wing 控制台,網址為`http://localhost:3000` 。 ``` wing it ``` ![Wing TS 最小控制台](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z1ejobkm0dq5akhut732.png) 您已在電腦上成功安裝 Wing。 --- 如何將 Wing 連接到 Next.js 應用程式 ------------------------- 在前面的部分中,您已在`frontend`資料夾中建立了 Next.js 前端應用程式,並在`backend`資料夾中建立了 Wing 後端。 在本部分中,您將了解如何在 Next.js 應用程式和 Wing 後端之間通訊和發送資料。 首先,透過執行以下程式碼在後端資料夾中安裝[Wing React](https://github.com/winglang/winglibs/tree/main/react)函式庫: ``` npm install @winglibs/react ``` 接下來,更新`main.ts`文件,如下所示: ``` import { main, cloud, inflight, lift } from "@wingcloud/framework"; import React from "@winglibs/react"; main((root, test) => { const api = new cloud.Api(root, "api", { cors: true }) ; //👇🏻 create an API route api.get( "/test", inflight(async () => { return { status: 200, body: "Hello world", }; }) ); //👉🏻 placeholder for the POST request endpoint //👇🏻 connects to the Next.js project const react = new React.App(root, "react", { projectPath: "../frontend" }); //👇🏻 an environment variable react.addEnvironment("api_url", api.url); }); ``` 上面的程式碼片段建立了一個 API 端點 ( `/test` ),它接受 GET 請求並傳回`Hello world`文字。 `main`函數也連接到 Next.js 專案並將`api_url`新增為環境變數。 環境變數中包含的 API URL 使我們能夠將請求傳送到 Wing API 路由。我們如何在 Next.js 應用程式中檢索 API URL 並發出這些請求? 更新 Next.js `app/layout.tsx`檔案中的`RootLayout`元件,如下所示: ``` export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <html lang='en'> <head> {/** ---👇🏻 Adds this script tag 👇🏻 ---*/} <script src='./wing.js' defer /> </head> <body className={inter.className}>{children}</body> </html> ); } ``` 透過執行`npm run build`重新建置 Next.js 專案。 最後,啟動Wing開發伺服器。它會自動啟動 Next.js 伺服器,可以在瀏覽器中透過**`http://localhost:3001`**存取該伺服器。 ![控制台到 URL](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t7rkxw9bds97a0qzg5vh.gif) 您已成功將 Next.js 連接到 Wing。您也可以使用`window.wingEnv.<attribute_name>`存取環境變數中的資料。 ![視窗.wingEnv](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0up5430jmxufmyeb9e4h.gif) 使用LangChain和Wing處理用戶請求 ---------------------- 在本節中,您將學習如何向 Wing 發送請求,使用[LangChain 和 OpenA](https://js.langchain.com/docs/get_started/quickstart#llm-chain) I 處理這些請求,並在 Next.js 前端顯示結果。 首先,我們更新 Next.js **`app/page.tsx`**檔案以檢索 API URL 並將使用者資料傳送到 Wing API 端點。 為此,請透過在**`page.tsx`**檔案頂部新增以下程式碼片段來擴充 JavaScript **`window`**物件。 ``` "use client"; import { useState } from "react"; interface WingEnv { api_url: string; } declare global { interface Window { wingEnv: WingEnv; } } ``` 接下來,更新`handleUserQuery`函數以將包含使用者問題和網站URL 的POST 請求傳送到Wing API 端點。 ``` //👇🏻 sends data to the api url const [response, setResponse] = useState<string | null>(null); const handleUserQuery = async (e: React.FormEvent) => { e.preventDefault(); setDisable(true); try { const request = await fetch(`${window.wingEnv.api_url}/api`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ question, pageURL: documentationURL }), }); const response = await request.text(); setResponse(response); setDisable(false); } catch (err) { console.error(err); setDisable(false); } }; ``` 在建立接受 POST 請求的 Wing 端點之前,請在`backend`資料夾中安裝下列套件: ``` npm install @langchain/community @langchain/openai langchain cheerio ``` [Cheerio](https://js.langchain.com/v0.2/docs/integrations/document_loaders/web_loaders/web_cheerio/)使我們能夠抓取軟體文件網頁,而[LangChain 軟體包](https://js.langchain.com/v0.1/docs/get_started/quickstart/)使我們能夠存取其各種功能。 LangChain OpenAI整合包使用OpenAI語言模型;因此,您需要一個有效的 API 金鑰。您可以從[OpenAI 開發者平台](https://platform.openai.com/api-keys)取得。 ![朗查恩](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/omg4o524oklrssso5rqc.png) 接下來,讓我們建立處理傳入請求的`/api`端點。 端點將: - 接受來自 Next.js 應用程式的問題和文件 URL, - 使用[LangChain 文件載入器](https://js.langchain.com/v0.1/docs/modules/data_connection/document_loaders/)載入文件頁面, - 將檢索到的文件分成區塊, - 轉換分塊文件並將它們保存在[LangChain 向量儲存](https://js.langchain.com/v0.1/docs/modules/data_connection/vectorstores/)中, - 並建立一個[檢索器函數](https://js.langchain.com/v0.1/docs/modules/data_connection/),從向量儲存中檢索文件。 首先,將以下內容匯入`main.ts`檔案: ``` import { main, cloud, inflight, lift } from "@wingcloud/framework"; import { ChatOpenAI, OpenAIEmbeddings } from "@langchain/openai"; import { ChatPromptTemplate } from "@langchain/core/prompts"; import { createStuffDocumentsChain } from "langchain/chains/combine_documents"; import { CheerioWebBaseLoader } from "@langchain/community/document_loaders/web/cheerio"; import { RecursiveCharacterTextSplitter } from "langchain/text_splitter"; import { MemoryVectorStore } from "langchain/vectorstores/memory"; import { createRetrievalChain } from "langchain/chains/retrieval"; import React from "@winglibs/react"; ``` 在`main()`函數中加入以下程式碼片段以建立`/api`端點: ``` api.post( "/api", inflight(async (ctx, request) => { //👇🏻 accept user inputs from Next.js const { question, pageURL } = JSON.parse(request.body!); //👇🏻 initialize OpenAI Chat for LLM interactions const chatModel = new ChatOpenAI({ apiKey: "<YOUR_OPENAI_API_KEY>", model: "gpt-3.5-turbo-1106", }); //👇🏻 initialize OpenAI Embeddings for Vector Store data transformation const embeddings = new OpenAIEmbeddings({ apiKey: "<YOUR_OPENAI_API_KEY>", }); //👇🏻 creates a text splitter function that splits the OpenAI result chunk size const splitter = new RecursiveCharacterTextSplitter({ chunkSize: 200, //👉🏻 characters per chunk chunkOverlap: 20, }); //👇🏻 creates a document loader, loads, and scraps the page const loader = new CheerioWebBaseLoader(pageURL); const docs = await loader.load(); //👇🏻 splits the document into chunks const splitDocs = await splitter.splitDocuments(docs); //👇🏻 creates a Vector store containing the split documents const vectorStore = await MemoryVectorStore.fromDocuments( splitDocs, embeddings //👉🏻 transforms the data to the Vector Store format ); //👇🏻 creates a document retriever that retrieves results that answers the user's questions const retriever = vectorStore.asRetriever({ k: 1, //👉🏻 number of documents to retrieve (default is 2) }); //👇🏻 creates a prompt template for the request const prompt = ChatPromptTemplate.fromTemplate(` Answer this question. Context: {context} Question: {input} `); //👇🏻 creates a chain containing the OpenAI chatModel and prompt const chain = await createStuffDocumentsChain({ llm: chatModel, prompt: prompt, }); //👇🏻 creates a retrieval chain that combines the documents and the retriever function const retrievalChain = await createRetrievalChain({ combineDocsChain: chain, retriever, }); //👇🏻 invokes the retrieval Chain and returns the user's answer const response = await retrievalChain.invoke({ input: `${question}`, }); if (response) { return { status: 200, body: response.answer, }; } return undefined; }) ); ``` API 端點接受使用者的問題和來自 Next.js 應用程式的頁面 URL,初始化[`ChatOpenAI`](https://js.langchain.com/v0.2/docs/integrations/chat/openai/)和[`OpenAIEmbeddings`](https://js.langchain.com/v0.2/docs/integrations/text_embedding/openai/) ,載入文件頁面,並以文件的形式檢索使用者查詢的答案。 然後,將文件分割成區塊,將區塊保存在`MemoryVectorStore`中,並使我們能夠使用[LangChain 檢索器](https://js.langchain.com/v0.1/docs/modules/data_connection/)來取得問題的答案。 從上面的程式碼片段來看,OpenAI API金鑰直接輸入到程式碼中;這可能會導致安全漏洞,使 API 金鑰可供攻擊者存取。為了防止這種資料洩露,Wing 允許您將私鑰和憑證保存在名為`secrets`的變數中。 當您建立機密時,Wing 會將此資料保存在`.env`檔案中,確保其安全且可存取。 更新`main()`函數以從 Wing Secret 取得 OpenAI API 金鑰。 ``` main((root, test) => { const api = new cloud.Api(root, "api", { cors: true }); //👇🏻 creates the secret variable const secret = new cloud.Secret(root, "OpenAPISecret", { name: "open-ai-key", }); api.post( "/api", lift({ secret }) .grant({ secret: ["value"] }) .inflight(async (ctx, request) => { const apiKey = await ctx.secret.value(); const chatModel = new ChatOpenAI({ apiKey, model: "gpt-3.5-turbo-1106", }); const embeddings = new OpenAIEmbeddings({ apiKey, }); //👉🏻 other code snippets & configurations ); const react = new React.App(root, "react", { projectPath: "../frontend" }); react.addEnvironment("api_url", api.url); }); ``` - 從上面的程式碼片段來看, ``` - The `secret` variable declares a name for the secret (OpenAI API key). ``` ``` - The [`lift().grant()`](https://www.winglang.io/docs/typescript/inflights#permissions) grants the API endpoint access to the secret value stored in the Wing Secret. ``` ``` - The [`inflight()`](https://www.winglang.io/docs/typescript/inflights) function accepts the context and request object as parameters, makes a request to LangChain, and returns the result. ``` ``` - Then, you can access the `apiKey` using the `ctx.secret.value()` function. ``` 最後,透過在終端機中執行此命令將 OpenAI API 金鑰儲存為機密。 ![翅膀的秘密](https://www.winglang.io/assets/images/qa-bot-wing-secrets-883db5e81515894ae280d77b7f72bb25.gif) 恭喜!您已成功完成本教學的專案。 以下是該應用程式的簡短演示: ![QA 機器人演示](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ropklqge2xzpibl29vye.gif) --- 讓我們更深入地研究 Wing 文件,看看我們的 AI 機器人可以提取哪些資料。 ![QA 機器人演示](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hnmf6n6mszc6go0uiw1v.gif) --- 總結一下 ---- 到目前為止,我們已經討論了以下內容: - 什麼是翼? - 如何使用Wing並使用Langchain查詢資料, - 如何將 Wing 連接到 Next.js 應用程式, - 如何在 Next.js 前端和 Wing 後端之間發送資料。 > [Wing](https://github.com/winglang/wing)旨在恢復您的創意流並縮小想像力與創造之間的差距。 Wing 的另一個巨大優勢是它是開源的。因此,如果您希望建立利用雲端服務的分散式系統或為雲端開發的未來做出貢獻, [Wing](https://github.com/winglang/wing)是您的最佳選擇。 請隨意為[GitHub 儲存庫做出貢獻,](https://github.com/winglang/wing)並與團隊和大型開發人員社群[分享您的想法](https://t.winglang.io/discord)。 本教學的源程式碼可[在此處](https://github.com/NathanTarbert/wing-langchain-nextjs)取得。 感謝您的閱讀! 🎉 --- 原文出處:https://dev.to/winglang/build-a-qa-bot-for-your-documentation-with-langchain-27i4

DevOps 的 Git 分支策略:協作最佳實踐

介紹 == 先決條件 ---- 本文面向已經熟悉 GitHub 並了解其功能的讀者。如果您是 GitHub 新手,請考慮 **[在此處](https://dev.to/angelotheman/master-git-commands-with-this-handy-cheat-sheet-4412)**探索一些介紹資源 DevOps 中的 Git 分支簡介 ------------------ 在快節奏的軟體工程世界中,高效且有效的程式碼管理至關重要。 Git 分支策略在維持流暢的工作流程和確保團隊成員之間的無縫協作方面發揮著至關重要的作用。 本文旨在探索各種 Git 分支策略,提供見解和最佳實踐,以協助您和您的團隊加強協作並簡化開發流程。 了解 Git 分支 ========= 在 Git 中,**分支**代表單一開發線。分支允許多個開發人員同時處理不同的功能、錯誤修復或實驗,而不會幹擾主程式碼庫。這種靈活性在 DevOps 環境中至關重要,在這種環境中,持續整合和持續交付實踐需要頻繁的更新和部署。 ### 關鍵概念: - **分支:**與主專案不同的平行開發線。 - **提交:**對程式碼庫進行的單獨更改或更新。 - **合併:**將一個分支的變更整合到另一個分支的過程。 Git 分支策略 ======== 基於主幹的開發 ------- 基於主幹的開發著重於擁有一個主分支,通常稱為***「主幹」***或***「主幹」。***開發人員經常提交此分支,確保程式碼庫始終處於可部署狀態。如果使用樹枝,它們的壽命很短,很快就會合併回主幹。 ### 優點 - 簡化 CI 流程 - 降低管理分公司的複雜性 - 促進快速交付和快速回饋週期。 ### 缺點 - 頻繁的整合可能會導致衝突 - 需要遵守維護建置和測試穩定性的紀律 GitHub 流程 --------- GitHub Flow 是一種簡單且有效的分支策略,圍繞著單一生產就緒分支(通常名為 main 或 master)。開發工作是在短期功能分支上完成的,並且更改透過拉取請求合併到主分支中,這有助於協作和程式碼審查。 ### 優點 - 保持分支模型簡單 - Pull 請求鼓勵協作和程式碼審查 - 與 CI/CD 管道良好整合以實現持續部署 ### 缺點 - 管理大型團隊可能會變得困難 - 依靠徹底的程式碼審查來保持品質 Git 流程 ------ 除了短期功能分支之外,Git Flow 使用多個長期分支,包括`main` 、 `develop` 、 `release`和`hotfix` 。此策略提供了用於管理不同類型變更的結構化流程,使其成為大型團隊和複雜專案的理想選擇。 ### 優點 - 有組織和結構化的流程 - 生產和開發程式碼之間的明確分離 - 能夠很好地適應大型團隊和複雜專案 ### 缺點 - 對於較小的團隊或專案來說可能過於複雜 - 頻繁的合併和分支管理可能非常耗時 功能分支(基於功能的開發) ------------- 功能分支涉及為每個功能建立專用分支,該分支可能是長期的,也可能是短期的。這種策略允許並行開發多個功能而不影響主分支。一旦功能完成並經過測試,就會將其合併回主分支 ### 優點 - 每個功能都是隔離的,減少衝突的風險 - 促進平行開發 - 為每個功能維護清晰的提交歷史記錄 ### 缺點 - 功能可能會在單獨的分支中保留太長時間,從而導致整合挑戰 - 合併多個功能分支可能很複雜且容易出錯 總計表 --- &lt; 表&gt; #### 策略 #### 關鍵特點 #### 主要優勢 #### 主要缺點 **基於主幹的** 單一主幹 簡化 CI 和快速交付 頻繁的整合衝突 **GitHub 流程** 功能分支 + PR 鼓勵協作和程式碼審查 不適合大型團隊 **Git 流程** 多個長壽分支 流程結構化,分離清晰 可能很複雜且耗時 **特徵分支** 專用功能分支 隔離和並行開發 整合延遲和複雜的合併 這是一個範例程式碼片段,總結了分支的建立和合併 ``` # Create and switch to a develop branch git checkout -b develop # Create a feature branch from develop git checkout -b feature-branch develop # Make changes and commit git add . git commit -m "Implement new feature" # Merge feature branch back to develop git checkout develop git merge feature-branch # When ready for release, merge develop to master git checkout master git merge develop # Tag the release git tag -a v1.0 -m "Release version 1.0" ``` 選擇正確的策略 ======= 選擇適當的分支策略取決於多個因素,包括團隊規模和部署頻率。讓我們仔細看看這些因素如何影響分支策略的選擇。 團隊規模 ---- 開發團隊的規模會顯著影響分支策略的選擇。較小的團隊通常受益於更簡單、更精簡的方法,而較大的團隊可能需要更結構化的策略來有效管理其工作流程。下表概述了根據團隊規模推薦的分支策略。 &lt; 表&gt; #### 團隊規模 #### 推薦策略 #### 推理 **小團隊(1 - 5 名成員)** 基於主幹的開發,GitHub Flow 更簡單的策略可以最大限度地減少開銷並促進快速整合。 **中型團隊(6 - 20 名成員)** Github 流程、功能分支 結構化分支可同時處理多個功能和任務。 **大型團隊(20+)** 功能分支、Git 流程 管理多個並行開發和發布的結構化方法。 部署頻率 ---- 團隊部署程式碼的頻率是選擇正確分支策略的另一個關鍵因素。高部署頻率需要支援快速整合和部署的方法,而較不頻繁的部署可以適應更複雜和結構化的分支模型。下表總結了根據部署頻率所建議的策略。 &lt; 表&gt; #### 部署頻率 #### 推薦策略 #### 推理 **高頻** 基於主幹的開發,GitHub Flow 支援持續整合和快速部署。 **中頻** GitHub Flow,功能分支 平衡定期更新的結構和靈活性。 **低頻** 功能分支、Git 流程 管理較長的開發週期並確保穩定性。 協作最佳實踐 ====== 無論選擇何種分支策略,有效的協作對於任何開發團隊的成功至關重要。以下是一些確保順利協作並最大限度地提高生產力的最佳策略。 有效溝通 ---- 清晰一致的溝通對於避免誤解和確保每個人都達成共識至關重要。增強團隊內部溝通的一些方法如下 - 舉行每日站立會議或每週簽到,討論進度、挑戰和後續步驟 - 利用 Slack、Microsoft Teams 或 Discord 等通訊工具進行即時通訊和更新。 - 維護流程、指南和專案詳細資訊的全面文件。 Confluence 或 Notion 等工具可以幫助解決這個問題 程式碼審查和拉取請求 ---------- 程式碼審查對於維護程式碼品質和團隊內的知識共享至關重要。進行程式碼審查的最佳實踐是 - 定義清晰的程式碼審查指南,包括要尋找的內容,並提供建設性的回饋。 - 在審查過程之前,使用自動化工具檢查程式碼品質、樣式和安全問題。我在**[上](https://dev.to/angelotheman/how-to-set-up-github-actions-for-continuous-integration-2054)**一篇文章中對此進行了介紹。 - 鼓勵開發人員建立小型、集中的拉取請求,以便更容易審查和合併。 處理衝突和合併 ------- 合併衝突在協作開發中是不可避免的,但可以透過正確的方法進行有效管理。以下是如何處理衝突並順利合併: - 定期將主分支的變更合併到功能分支中以最大程度地減少衝突。這可以讓您的分行及時了解最新的變更。 - 衝突一出現就立即解決,以防止其隨著時間的推移變得更加複雜。 - 讓團隊成員參與解決衝突,特別是當變更影響程式碼庫的多個區域時。結對程式設計或群體程式設計對於解決棘手的衝突很有用。 與 CI/CD 集成 ---------- 將分支策略與持續整合和持續交付實踐保持一致對於高效開發和部署至關重要。以下是將分支策略與 CI/CD 整合的方法: - 為每個分支設定自動化建置和測試,以儘早發現問題。 Jenkins、Travis CI 和 GitHub Actions 等工具可以提供協助。 - 配置 CI/CD 管道以自動從主分支或指定的發布分支部署變更。這可確保您的程式碼始終處於可部署狀態。 - 使用功能標誌安全地部署不完整的功能。這允許您將程式碼發佈到生產環境,同時隱藏新功能直到它們準備就緒。 有用的網址 ===== - [Git 分支策略 - GitKraken](https://www.gitkraken.com/learn/git/best-practices/git-branch-strategy) - [Git 分支](https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging) - [分支策略 - Atlassian](https://www.atlassian.com/agile/software-development/branching) 結論 == 在快節奏的軟體開發世界中,選擇正確的 Git 分支策略對於維持團隊內部的效率和協作至關重要。透過了解團隊規模和部署頻率的需求,您可以選擇平衡簡單性和結構的策略,確保開發工作流程順利進行。 我相信這篇文章為您節省了大量的時間來整合和決定使用哪些分支策略。 不要忘記在**[LinkedIn](https://linkedin.com/in/angelotheman)**和**[X](https://x.com/_angelotheman)上與我聯絡。** 快樂學習🚀 --- 原文出處:https://dev.to/angelotheman/git-branching-strategies-for-devops-best-practices-for-collaboration-35l8

Node.js 中的高階錯誤處理

錯誤處理是軟體開發的重要方面,可確保應用程式的行為可預測,並在出現問題時提供有意義的回饋。在 Node.js 中,由於其非同步特性,有效的錯誤處理可能特別具有挑戰性。本文深入探討了管理 Node.js 應用程式中的錯誤的先進技術和最佳實踐。 了解錯誤類型 ------ 在深入研究錯誤處理策略之前,了解可能遇到的錯誤類型非常重要: 1. 同步錯誤: 同步程式碼執行期間發生的錯誤可以使用 try-catch 區塊擷取。 2. 非同步錯誤: 錯誤發生在非同步程式碼執行期間,例如回呼、promise 和 async/await 函數。 3. 操作錯誤: 表示程式需要處理的執行時間問題(例如,無法連線到資料庫)的錯誤。 4. 程式設計師錯誤: 程式中的錯誤(例如,型別錯誤、斷言失敗)。通常不應以與操作錯誤相同的方式捕獲和處理這些錯誤。 **同步錯誤處理** ---------- 對於同步程式碼,錯誤處理使用 try-catch 區塊: ``` try { // Synchronous code that might throw an error let result = dafaultFunction(); } catch (error) { console.error('An error occurred:', error.message); // Handle the error appropriately } ``` **非同步錯誤處理** ----------- - *callback* 在基於回調的非同步程式碼中,錯誤通常是回呼函數中的第一個參數: ``` const fs = require('fs'); fs.readFile('/path/to/file', (err, data) => { if (err) { console.error('An error occurred:', err.message); // Handle the error return; } // Process the data }); ``` - *promise* Promise 提供了一種更簡潔的方法來使用 .catch() 處理非同步錯誤: ``` const fs = require('fs').promises; fs.readFile('/path/to/file') .then(data => { // Process the data }) .catch(err => { console.error('An error occurred:', err.message); // Handle the error }); ``` - *async/await* Async/await 語法允許在非同步程式碼中進行更同步的錯誤處理: ``` const fs = require('fs').promises; async function readFile() { try { const data = await fs.readFile('/path/to/file'); // Process the data } catch (err) { console.error('An error occurred:', err.message); // Handle the error } } readFile(); ``` 集中錯誤處理 ------ 對於較大的應用程式,集中錯誤處理可以幫助更有效地管理錯誤。這通常涉及 Express.js 應用程式中的中間件。 - *Express.js 中介軟體* Express.js 提供了一種透過中間件處理錯誤的機制。這個中間件應該是堆疊中的最後一個: ``` const express = require('express'); const app = express(); // Define routes and other middleware app.get('/', (req, res) => { throw new Error('Something went wrong!'); }); // Error-handling middleware app.use((err, req, res, next) => { console.error(err.stack); res.status(500).json({ message: 'Internal Server Error' }); }); app.listen(3000, () => { console.log('Server is running on port 3000'); }); ``` 先進技術 ---- - *自訂錯誤類別* 建立自訂錯誤類別可以幫助區分不同類型的錯誤並使錯誤處理更加精細: ``` class AppError extends Error { constructor(message, statusCode) { super(message); this.statusCode = statusCode; Error.captureStackTrace(this, this.constructor); } } // Usage try { throw new AppError('Custom error message', 400); } catch (error) { if (error instanceof AppError) { console.error(`AppError: ${error.message} (status: ${error.statusCode})`); } else { console.error('An unexpected error occurred:', error); } } ``` - *錯誤記錄* 實施強大的錯誤日誌記錄以監視和診斷問題。 Winston 或 Bunyan 等工具可以幫助記錄日誌: ``` const winston = require('winston'); const logger = winston.createLogger({ level: 'error', format: winston.format.json(), transports: [ new winston.transports.File({ filename: 'error.log' }) ] }); // Usage try { // Code that might throw an error throw new Error('Something went wrong'); } catch (error) { logger.error(error.message, { stack: error.stack }); } ``` - *全域錯誤處理* 處理未捕獲的異常和未處理的承諾拒絕可確保不會漏掉任何錯誤: ``` process.on('uncaughtException', (error) => { console.error('Uncaught Exception:', error); // Perform cleanup and exit process if necessary }); process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled Rejection at:', promise, 'reason:', reason); // Perform cleanup and exit process if necessary }); ``` 最佳實踐 ---- - 快速失敗:儘早偵測並處理錯誤。 - 正常關閉:確保您的應用程式在發生嚴重錯誤時可以正常關閉。 - 有意義的錯誤訊息:提供清晰且可操作的錯誤訊息。 - 避免靜默故障:始終記錄或處理錯誤以避免靜默故障。 - 測試錯誤場景:編寫測試來覆蓋潛在的錯誤場景並確保錯誤處理按預期工作。 結論 -- 為了有效地處理 Node.js 中的錯誤,您需要結合使用同步和非同步技術、集中管理以及進階策略,例如自訂錯誤類別和強大的日誌記錄。透過結合這些最佳實踐和先進技術,您可以建立強大的 Node.js 應用程式,優雅地處理錯誤並為用戶提供改進的體驗。 --- 原文出處:https://dev.to/amritak27/advanced-error-handling-in-nodejs-1ep8

JavaScript 中除了 console.log() 之外的除錯函數

大多數 JavaScript 開發人員都熟悉基本的`console.log()` 。然而,控制台 API 提供了許多其他方法,這些方法在開發和偵錯工作流程中都非常有用。 ### 基本`console.log()` 讓我們從基本的`console.log()`開始,它用於將訊息列印到控制台。 ``` console.log("Hello, World!"); ``` ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717602776372/yR9V84n9r.png?auto=format) 這是除錯和記錄訊息最常用的方法。 ### `console.error()` `console.error()`用於輸出錯誤訊息。它有助於區分控制台中的錯誤和常規日誌訊息。 ``` console.error("This is an error message"); ``` ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717602826130/csjO0YUGj.png?auto=format) 這將以紅色列印該訊息,使其脫穎而出。 ### `console.warn()` `console.warn()`輸出警告訊息。這些問題沒有錯誤那麼嚴重,但仍然需要注意。 ``` console.warn("This is a warning message"); ``` ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717602863488/xaOE9ZLjI.png?auto=format) 警告訊息以黃色顯示,這有助於將它們與常規日誌和錯誤區分開來。 ### `console.info()` `console.info()`用於資訊性訊息。 ``` console.info("This is an informational message"); ``` ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717602908638/wZIyzHzQa.png?auto=format) 它的行為與`console.log()`類似,但對於對日誌訊息進行分類非常有用。 ### `console.debug()` `console.debug()`用於偵錯目的,並且通常在許多瀏覽器的控制台設定中預設為隱藏。 ``` console.debug("This is a debug message"); ``` ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717603092431/GMNga7Wge.png?auto=format) 要啟用它,請確保在頂部欄中選取`Verbose` 。 ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717603225265/UVsWL5bA0.png?auto=format) ### `console.table()` `console.table()`可讓您將資料顯示為表格。這在處理陣列或物件時非常有用。 ``` const users = [ { name: "Alice", age: 25 }, { name: "Bob", age: 30 }, { name: "Charlie", age: 35 }, ]; console.table(users); ``` ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717603316367/bDPVHX7dX.png?auto=format) 這會將`users`陣列列印為表格,使其更易於閱讀。 ### `console.time()`和`console.timeEnd()` 這些方法用於測量一段程式碼執行所需的時間。 ``` function processData() { console.time("processData"); // Simulating a time-consuming process for (let i = 0; i < 1000000; i++) { // Process } console.timeEnd("processData"); } processData(); ``` ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717603951155/Ss_aFfxVf.png?auto=format) `console.time("processData")`啟動計時器, `console.timeEnd("processData")`停止計時器,列印經過的時間(以毫秒為單位)。這可以幫助辨識程式碼中的瓶頸並提高效能。 ### `console.count()` `console.count()`用於統計程式碼區塊的執行次數。 ``` for (let i = 0; i < 5; i++) { console.count("Counter"); } ``` ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717603544889/KOsdLFTNl.png?auto=format) 這將在每次循環執行時列印計數。 ### `console.group()`和`console.groupEnd()` 這些方法可讓您將多個日誌訊息分組在一起。 ``` console.group("User Details"); console.log("Name: Alice"); console.log("Age: 25"); console.groupEnd(); ``` ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717603600402/MlxNun_v8.png?auto=format) 這將在控制台中建立一個可擴展組。您也可以建立嵌套群組以更好地組織。 ### `console.assert()` 只有當斷言為 false 時`console.assert()`才會將訊息寫入控制台。 ``` const x = 10; console.assert(x > 10, "x is not greater than 10"); ``` ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717603763457/j99LQUfCM.png?auto=format) 由於`x`不大於 10,因此將列印該訊息。 ### 設定控制台日誌的樣式 您可以使用 CSS 設定控制台日誌訊息的樣式。 ``` console.log("%cThis is a styled message", "color: blue; font-size: 20px;"); ``` ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1717603814168/VUOV4fH4T.png?auto=format) `%c`是後面的 CSS 樣式字串的佔位符。 ### 最佳實踐 1. **使用適當的方法:**使用`console.error()`來處理錯誤,使用`console.warn()`來處理警告,等等。這有助於過濾控制台中的訊息。 2. **刪除生產中的日誌:**確保刪除或停用生產程式碼中的控制台日誌,以避免混亂和潛在的效能問題。 3. **將相關日誌分組:**使用`console.group()`和`console.groupEnd()`將相關日誌保存在一起。 ### 結論 下次當您深入程式碼並需要偵錯時,請嘗試這些方法。它們可以讓您的生活更輕鬆,除錯過程更有效率。因此,繼續在您的下一個專案中嘗試這些,看看它們可以產生什麼變化。 更詳細的訊息,您可以參考[控制台上的 MDN Web 文件](https://developer.mozilla.org/en-US/docs/Web/API/Console)。 --- 原文出處:https://dev.to/codeparrot/debugging-beyond-consolelog-in-javascript-32g6

資深工程師程式碼審查指南

程式碼審查。 ----- 你知道它們有多重要。 它們是獲得可靠程式碼的支柱之一。 然而,這是您在超級忙碌的日子裡需要**擠出**時間做的事情之一。 如果您不審查程式碼,那麼您可能會向用戶發送地雷,因為您永遠不知道它什麼時候會爆炸。 🤷 顯然,你知道這一點。你來這裡不是為了被告知「嘿!你應該進行程式碼審查!這是一件至關重要的事情! 我的團隊已經做了評論。我為什麼要在乎? ------------------- 如果不小心和勤勉地處理程式碼審查流程,可能會產生嚴重後果。 在我之前的一個組織中,程式碼審查通常沒有徹底完成,因此需要多次審查。它們也是由地球兩端的審查者完成的! 🌏 因此,處理任何評論幾乎花了一整天的時間。再說一次,因為評論通常不全面,所以 PR 的返工時間可能會因為一些瑣碎的事情而花費幾天的時間。 ![團隊週期時間分解](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uvws8nxa2kjdvx6034n1.png) “如果不衡量,就無法改進” *通常被認為是彼得·德魯克(Peter Drucker)的作品,但我還沒有找到真正的證據。* 但我發現這句話對我的經驗來說意義深遠。 我過去曾向我的領導階層提出必要的組織變革,使所有團隊能夠減少跨時區的相互依賴,從而使人們能夠更快地協作。 我知道這樣做有多麼困難,但如果沒有可靠的資料支持理由來解釋為什麼需要改變,就更難實現任何改變。 *PS:這就是 Dhruv 和我創辦[Middleware 的](https://www.middlewarehq.com/)部分原因。 🚀* {% 嵌入 https://github.com/middlewarehq/middleware %} 好的,我聽到了。我有什麼選擇? --------------- 您理想的情況是進行徹底的程式碼審查,也就是說,明顯的危險訊號、效能或安全缺陷或其他難以閱讀的程式碼不應被忽視。 但您也希望所有這一切都在合理的時間內發生。 在合理的時間內合併經過嚴格審查的程式碼,這意味著您的團隊的交付可預測且具有高可靠性。 除非有一種經過充分研究、結構化的方法來掌握這一點。 🤔 …… 您聽過…DORA 指標嗎? 好吧,這不是另一部《DORA GOOD!文章。 這些是我過去專注於四鍵(正如出色的[Nathen Harvey](https://www.linkedin.com/in/nathen/)所[解釋的](https://dora.dev/guides/dora-metrics-four-keys/))如何幫助我改善我自己和我的團隊的程式碼交付體驗的經驗。 ![朵拉指標](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b49lb17w68bs5fojske7.png) *在中介軟體開源中看到的 DORA 指標* 使用 DORA 探索程式碼審查 --------------- ### 評論會延長交貨時間多久 審核週期長直接影響變更的前置時間。 交貨時間基本上由 5 個部分組成。 1. 從第一次提交到 PR 開放的時間 2. 然後 PR 收到第一次審核(可能是評論、更改、批准) 3. 更改 PR 直至最終獲得批准所花費的時間 4. 批准和 PR 合併之間的時間 5. PR最終部署的時間 當然,任何需要花費時間的部分都會增加您團隊的時間。但有兩個部分是造成延誤的特別嚴重的因素。這是#2 和#3。 **\#2. PR 收到第一次審核的時間(首次回應時間)** PR 公開後,開發人員實際上無法對其做太多事情。公關可能完全可以順利進行!它可能需要切實的改變。在這一點上,只有評論才能告訴我們答案。這也是開發人員可能無法承擔更多任務的時候,因為從技術上講,審查可能隨時發生,並且他們會遭受上下文切換的困擾。 **上下文切換是開發人員最大的生產力殺手之一。** ### 對「每次審核時間」的誤導性關注 這討論了提前期指標的第三個子部分。 **#3。更改 PR 所花費的時間(返工時間)** 這裡真正的問題不在於在這裡花了多少時間,而是來回發生了多少次。我們稱之為「返工週期」。 因為如果因為PR被批准而只有1個返工週期,那麼在批准之前可能仍然需要很長時間,但這是實際的實施時間,而不是空閒時間。可以透過更好的培訓、程式碼庫入門、上下文共享等來減輕這種返工。 但是…如果您要來回很多次,那麼每個週期都有一些與之相關的空閒時間,就像第一次回應時間一樣。 在此期間,開發人員無法接受新的工作,因為這將不可避免地導致快速的上下文切換。 這種情況很可能發生在PR太大而無法一次審完,或是審查者因為其他原因沒有審透徹底的情況下。當作者和審稿人位於相距甚遠的時區時,情況尤其嚴重。由於每次審核和返工都可能發生在各自時區的工作時間內,因此返工更改之前的時間可能會被誇大到很多小時。 **這就是滾雪球效應** 像這樣被屏蔽的 PR 越多,團隊交付的速度就越慢。通常新工作不會停止出現,這使得開發人員準確管理和評估他們的工作變得更具挑戰性。 如果這種情況持續發生,也會對團隊士氣造成打擊。 **博士** 僅僅專注於減少「每次審查的時間」可能會適得其反。 目標應該是在不犧牲徹底性的情況下優化審核流程,確保每次審核都能增加真正的價值。 ### 低於標準的審查和變更失敗率 團隊始終在壓力和緊迫的期限下運作。期望這種情況會神奇地改變是不合理的。但認為不會偷工減料以確保貨物不能按時發貨也是不切實際的。 由於我們談論的是程式碼審查,因此經常被削減的角落之一是: 1. 建立的大型 PR 包含某個功能的所有程式碼,而不是包含良好的較小且更易於審查的 PR。 2. PR 的審核只需略讀即可,因為審核者目前可能沒有足夠的心理能力或時間來正確處理它。 這兩件事時常發生。開發人員也是人。你不能僅僅把責任歸咎於他們或強迫他們「正確地」複習來解決這個問題。 最重要的是你首先要知道它正在發生。因為這樣你就可以做點什麼。你問,你怎麼知道這件事? 1. 您的交貨時間應該會縮短。因為審核速度更快(通常比應有的速度快) 2. 您的變更失敗率可能會上升。當然,如果評論低於標準,您可能會帶來更多錯誤。 ``` 1. But, even if your CFR isn’t going noticeably down, your team might still be shipping low performance or quality code that would bite you back later, and will likely show up as higher Lead Time down the line. But by then it’ll be too difficult to correlate with the reviews of today. ``` **現在是時候提一下《朵拉》是一位很棒的嚮導,但它並不完美。** 不要將其視為明確的規則手冊。不要用它來衡量個人。 為您的團隊全面使用它,但也要參與其中,以確保它真正幫助您的團隊。畢竟這就是目標,不是嗎? 偉大的!如何? 👉 更快、更有效的審核策略 --------------------- ### 這是一個快速**預審清單** 1. 測試:確保所有相關測試都已編寫並通過✅。 ``` 1. This can be done by a CI bot (or Github Actions) ``` 2. 文件:更新相關文件,包括內嵌註解和自述文件。 3. 清晰的提交訊息:編寫描述性提交訊息,解釋更改背後的「原因」。 ``` 2. This could also be enforced via [commit-lint](https://commitlint.js.org/) ``` ``` 3. You could also use [aicommit](https://github.com/Nutlope/aicommits) to help write good and detailed commit messages! ``` ``` 4. My team often uses GH Copilot to create commit messages that actually end up being totally satisfactory to me! ``` 提交訊息範例: ``` feat: add user authentication - Implemented OAuth2 for secure login - Added unit tests for authentication flows - Updated API documentation with new endpoints ``` ### 正確的審稿人,正確的時間 將審閱者與其專業知識和當前工作量相匹配,以避免超負荷。複雜的變更交給高階開發人員,簡單的變更交給同業。 但您還需要了解開發人員對特定程式碼庫有多少上下文。 這裡有一些挑戰: - 如果您的開發人員在單一特定儲存庫中高度專業化,那麼由於需要時間來加入和共享上下文,因此在單獨的程式碼庫上使用他們的技能將非常困難。 - 如果您的開發人員對所有程式碼庫過於籠統,那麼由於缺乏特定程式碼庫的深層上下文,他們可能很難更快地解決某些問題。 - 如果團隊中的一位開發人員對事物有很多背景知識,那麼很容易讓他們負擔過重。您需要儘早騰出時間來分發上下文,這樣您的工作就不會在最關鍵的時候受到阻礙。 您希望確保將兩者結合起來,並且只需與您合作的 2-3 名開發人員即可實現這一目標。 ![視覺化團隊中的瓶頸](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/va7uc3ajyb17kiamhq0h.png) *了解誰被阻止對誰進行程式碼審查至關重要。您不希望您的團隊因為有人需要休假而根本無法交付。* ### 貿易工具 使用靜態分析、程式碼檢查和自動檢查在人工審查之前捕獲簡單的問題。這讓審閱者可以專注於更複雜的回饋。 範例工具: - [ESLint](https://eslint.org/) :JavaScript linting。 - [Husky](https://typicode.github.io/husky/) :用於執行預提交檢查和靜態分析。 - CI/CD 管道:自動化測試和建置流程。 **超重要提示:** 爭論空格和製表符、分號與否、尾隨換行符很容易浪費大量時間。 但這一切並不重要。 決定並同意團隊最終確定的任何程式碼風格,並將它們作為 linter 規則的一部分強制執行。 這些東西不值得你花時間。 👍 ### 回饋的藝術 給予可操作的、具體的評論,重點是改進,而不是吹毛求疵。避免模糊的陳述並提供明確的指導。 分享如何將一個文件重組為多個文件,以及為什麼這樣做是個好主意。 分享為什麼在循環中多次呼叫資料庫可能是一個壞主意,因為我確信我不需要在這裡解釋。 😆 如果挑剔的問題主要是可以由 linter 處理的事情,那麼就使用其中之一。 人們討厭那些大多只有蝨子的評論。但同樣,糟糕的變數名稱、拼字錯誤等不能只是去生產! 😁 例子: ``` # Ineffective comment "Fix this." # Effective comment "Consider using a map here to improve lookup efficiency. This will reduce the time complexity from O(n) to O(1)." ``` 使用中介軟體簡化流程 ---------- ### 中介軟體如何提供協助 我能夠具體了解我的團隊在哪裡陷入困境、原因以及如何解除困境。 這就是我工作的一半,現在我能夠比以前更快地完成這些工作! ![團隊流程概述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yl0k380fegaussrrckyd.png) ### 我重點關注以下幾點: - 審核指標:追蹤審核所需的時間並確定發生延遲的位置。 - 流程洞察:了解整個審核流程並找到需要最佳化的領域。 我不會對此進行太多討論,因為那樣聽起來就像是推銷! 😂 超越技術細節:人的因素 ----------- ### 培養建設性回饋文化 倡導一種將回饋視為成長機會的文化。建設性的、相互尊重的溝通有助於提高程式碼品質和團隊士氣💬。 ### 平衡速度與徹底性 平衡速度與徹底性。快速審查不應影響審查,徹底審查不應拖延。 ### 心理安全 確保審稿人和作者的心理安全。鼓勵公開討論不加指責地解決錯誤,營造持續改進的環境🌱。 請記住,當您分享改進回饋時,人們常常會保持警惕。考慮周到,思路清晰。 結論 -- 有效的程式碼審查對於保持程式碼品質和交付速度至關重要。透過與 DORA 指標保持一致、使用正確的工具並培養建設性的回饋文化,團隊可以簡化其審核流程。採用這些實踐可以使您的程式碼審查既高效又具有影響力。 *嘗試使用[中間件](https://www.middlewarehq.com/)來更深入地了解您的程式碼審查流程並確定需要進一步改進的領域。 🚀* 同樣,這些只是指導方針以及我們如何看待程式碼審查。請分享您的組織中如何進行程式碼審查! 程式碼審查對於整體產品可靠性起著至關重要的作用。有些情況下,糟糕的程式碼審查(不是糟糕的程式碼!)會造成負面的品牌影響。總而言之,更好的程式碼審查流程有助於降低故障率。 像[DORA](https://github.com/middlewarehq/middleware)這樣的框架被設計為輕量級的,可以幫助工程團隊提高工作效率,而無需工程師甚至領導者付出太多的努力。 Middleware 的使命是幫助工程團隊交付高效率的程式碼。請查看我們的[開源中間件](https://github.com/middlewarehq/middleware),我們的開源 DORA 指標解決方案,它是本地託管的。如果您喜歡,請考慮給我們一顆星! --- 原文出處:https://dev.to/middleware/the-senior-engineers-guide-to-the-code-reviews-1p3b

不是💩,這裡是如何編寫真正好的提交訊息(提示:這不僅僅是加入 commit-lint)

- **更新建置文件** - **修復依賴陣列** - **重構** - **修復測試** - *最有名的:***更新README.md** (感謝Github🤦) 這些是......我在上個月實際看到的一些令人驚嘆的提交訊息。 👀 而且......其中一些也存在於我們的程式碼庫中。 有點尷尬。我知道。 😅 但這是不行的🙅,理想情況下我想讓世界擺脫這些。 > ### “當然,無論如何。即使讀完這篇博客,我也不認為任何事情都會改變。” 為了改變一切,我需要讓你**相信**: - 這帶來了您可以感覺到或測量到的真正差異 - 這對其他人產生影響(稍後可能會歸因於您) > ### “好吧,繼續吧。我該怎麼做,我能從中得到什麼?” 寫得好的提交訊息有一些明顯的好處: ### **1. 🧑‍💻更容易除錯** `git blame`使我們非常容易理解為什麼要引入更改,並且可以更輕鬆地向作者詢問相關問題。 **`git blame`**並不是除錯過程中使用的第一個工具,但對於與業務邏輯相關的**複雜問題**,它是一個特別在大中型公司中**發揮作用的工具**。有些程式碼只是引發了這樣一個問題:“為什麼要這樣寫?”或「這最初是為了解決什麼目的?」。有些問題的答案不在於工程,而在於產品決策。寫這篇文章的作者最有可能知道其中涉及哪些決定。 ![無上下文偵錯](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w3q6uby1ilrqoiixsds5.png) 當然,即使不需要花俏的訊息`git blame`也會告訴你提交的作者。那為什麼是這篇文章的這一部分呢? 因為身為作者,您多久會記得**您 6 個月前寫的投稿正在做什麼**?特別是當它有像“重構”這樣的提交訊息時?**運氣不好吧?** 我見過更有經驗的開發人員編寫了非常有用的提交訊息。我認為他們中的一些人做得有點過頭了,但這裡有一個粗略的例子: ``` Fix crash on login screen due to null pointer exception. - Checks for null values before accessing user profile data - Adds unit test to cover this scenario - Issue linked: BUG-5678 ``` 也許有點太多了,但如果不是很明顯的自動對焦就太糟糕了。 現在,當我需要除錯該領域的問題時,能夠為作者和上下文`git blame`可能是我的救星。 *我這麼說是充滿信心的,因為我曾經在 Uber 既是給予者,也是接受者。* **這有一個嚴重的額外好處,當您閱讀此部落格時,您可能會高估這一好處:** - 為您和您的團隊節省時間,減少挫折感 **在沒有上下文的情況下進行除錯是痛苦的。**甚至不是那種你可以說「殺不死你的,會讓你更強大」的情況。因為它只會讓你感到沮喪。 你知道的。你的團隊知道這一點。這就是為什麼你試圖了解是誰引入了「那個錯誤」並與他們交談,卻發現他們兩年前離開了公司。祝你好運。 🤝 這樣,您就留下了一份遺產,**讓人們深情地記住您,**並希望他們能再次與您合作。 > **“如果我使用合併或壓縮提交怎麼辦?”** > *有些人對他們有強烈的支持或反對意見。* > *我要說的是,合併提交有助於在一定程度上包含提交中包含的 PR 上下文,但如果寫得不好,單一提交本身可能沒有太多與之關聯的上下文。* > *而且由於 PR 可能涉及各種不同的更改,這些更改一起工作以發布功能,並且構成更改可能有自己的上下文以這種方式編寫,因此壓制它們可能會有效地混淆所有上下文,從而很難從提交歷史記錄。* > ***但無論哪種方式都可以寫出功能完善的軟體。**只要你和你的團隊都同意你的合併 PR 的方式對你來說是可行的,並且問題最少,我是誰,我會干涉嗎?* ### **2. 🔍 更輕鬆的程式碼審查** 如果您的組織不太關心讓程式碼審查變得輕鬆,也不讓它感覺像是一件苦差事,**那麼您需要與您的員工分享這一點**: {% 嵌入 https://dev.to/middleware/the-senior-engineers-guide-to-the-code-reviews-1p3b %} 現在,更好的提交訊息如何幫助程式碼審查? 根據[Middleware 的](https://github.com/middlewarehq/middleware)分析資料,成熟程式碼庫(活躍的倉庫,經過其設定階段)中的平均 PR 可以**約為 300 行**。 有很多行。大多數這些更改都不會發生在同一個文件中。他們也不應該。 **300 行位於可讀文件的上限**(儘管這不是一成不變的規則)。 這幾乎可以保證這些 PR 中會提供不同類型的更改,這些更改需要組合在一起才能提供所需的功能。 如果您因任何原因無法建立較小的 PR,**則可以在同一 PR 中進行較小的提交**。使每個提交都包含最小的邏輯隔離更改,以便提交訊息可以充分解釋其包含的內容。因為您也不想編寫一段長的提交訊息,所以您需要建立一個足夠小的提交,大約 50-60 個字元可以解釋其內容。 現在,審閱者可以**透過提交來審查你的 PR 提交,**而不必跟進你的所有事情,或者想知道為什麼某些東西是這樣寫的(如果他們這樣做,他們將不得不問你\[作者\],你知道嗎? ![有意義的提交訊息](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mhchgf489srffox2x8nd.png) ### **3. 🚀 更好的發行說明!** 或者,如果您沒有準確產生“發行說明”,那麼它仍然是專案歷史的**更好文件**! 具體來說,Github 還允許您**根據提交**自動**產生專案的發行說明**。很多人會查看發行說明來了解新版本包含哪些修復或功能。 請參閱[React 程式碼庫](https://github.com/facebook/react/releases)的發布部分,並查看每個發布說明有多少反應! ![反應發行說明](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bbsdve9uyj84h3ajwevw.png) 明確**發布說明對人們很重要**。透過編寫良好的提交,您可以節省編寫發行說明的精力。 最後... ### **4. 👥 鼓勵整個團隊更好的實踐** ![使用路克的力量](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5y102iefuj5xzm2pdu2s.png) 當團隊成員看到編寫良好的提交訊息的好處時,他們很可能會效仿。這可以導致整個團隊採用更嚴格的程式碼提交方法,從而鼓勵清晰和精確的文化。這就是讓**SDE1 的思考和行為像 SDE2 一樣**的東西。 這是此類提交的另一個範例: ``` Update permissions routing layer to handle subroutes independently. - TKT-1234 - The permissions routing logic was previously coupled with its nested routes - This change will allow you to move subroutes to any other parent route without also having to make any changes to how the permissions for that subroute are defined ``` 在多人處理同一個專案的環境中,一致且詳細的提交訊息可以協調每個人的理解和期望,**減少潛在的衝突**和誤解,而不需要太多時間來阻止上下文共享。 當然,如果您能夠成為團隊或組織中更好的承諾訊息的擁護者,您會很樂意獲得由此帶來的任何好處,不是嗎? 👀 **能夠異步工作的團隊,才是能夠有效率地工作的團隊。**沒有人希望你的團隊在這樣的事情上遇到瓶頸。 👌 總結一下 ---- 好的提交訊息會帶來巨大的收益。 💪 為您和您所屬的團隊帶來收益。 更少的除錯時間、更少的挫折時刻、更好的文件、自動發行說明等。 作為所有這些改進的副作用,您甚至可能會注意到更快的程式碼交付、更少的審核等待時間、更少的返工週期! 繼續!您所帶來的所有效率改進都值得稱讚! *並且確切了解使用[中介軟體](https://github.com/middlewarehq/middleware)等生產力智慧工具為您的團隊帶來了多少收益!* 🚀 {% 嵌入 https://github.com/middlewarehq/middleware %} --- 原文出處:https://dev.to/middleware/not-heres-how-to-write-actually-good-commit-messages-hint-its-not-just-adding-commit-lint-j2i

使用 Gemini API 和 ToolJet 在 10 分鐘內建立 AI 內容產生器 🛠️

在本快速教學中,我們將使用 Gemini API 和 ToolJet 建立一個由 AI 驅動的內容產生器,這一切只需 10 分鐘即可完成。該應用程式將根據上傳的圖像、選定的內容類型以及用戶輸入的附加資訊來產生內容。無論您需要標題、簡短描述、詳細描述、創意故事、部落格文章大綱、完整部落格文章、社交媒體標題還是廣告文案,此應用程式都能滿足您的需求。請跟隨使用 ToolJet 的快速開發流程和 Gemini 的先進 AI 功能將內容建立無縫整合到您的工作流程中。 這是我們最終應用程式的預覽: ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4jktjjr8hox6c4y38ndq.png) --- 先決條件 ---- - ToolJet (https://github.com/ToolJet/ToolJet):一個開源、低程式碼的商業應用程式建構器。[註冊](https://www.tooljet.com/signup)免費的 ToolJet 雲端帳號或使用 Docker[在本機上執行 ToolJet](https://docs.tooljet.com/docs/setup/try-tooljet/) 。 - Gemini API Key:Gemini API 是[Google AI Studio](https://aistudio.google.com/app/apikey)提供的進階人工智慧服務。它使開發人員能夠將強大的內容生成功能整合到他們的應用程式中。 --- 首先建立一個名為*AI Content Generator*的應用程式。 第一步 - 設計 UI 🎨 ------------- 建立應用程式後,我們就可以開始使用 ToolJet 的預先建置元件設計 UI。 - 從右側[元件庫](https://docs.tooljet.com/docs/tooljet-concepts/what-are-components)中拖曳一個**Container**元件,並調整其大小,使其覆蓋大部分畫布。 - 將**圖示**元件和**文字**元件放在容器上。然後,將 Icon 元件重新命名為*logo* ,將 Text 元件重新命名為*logoText* 。 - 選擇 Icon 元件以查看右側的屬性面板。選擇**IconListSearch**作為圖示。 - 對於文字元件,在其**文字**屬性下輸入*AI Content Generator* ,並調整其字體粗細和文字大小。 - 將圖示和文字元件的顏色變更為深藍色(十六進位程式碼 - #354094)。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kf3k3orw0yckdn786unl.png) *本教學使用深藍色(#354094)作為主色。在接下來的步驟中相應地更新元件的顏色。隨意使用不同的配色。* - 在我們剛剛建立的標題下方新增一個**圖像**元件和一個**文字**元件。分別將它們重新命名為*imagePreview*和*output* 。 *imagePreview*將顯示上傳圖像的預覽,*輸出*將顯示基於圖像和所選選項生成的文字。 - 在映像下方新增一個**File Picker**元件並將其重新命名為*imageUploader* 。 - 在其旁邊放置一個**下拉**元件和**文字輸入**元件。分別將它們重新命名為*typeOfContentInput*和*additionalInfoInput* 。 - 對於文字輸入元件,在**佔位符**屬性下輸入下列值: `Enter additional information` - 對於下拉元件,使用雙花括號將以下陣列貼到**選項值**和**選項標籤**屬性下: ``` {{["Title", "Short Description (1-2 sentences)", "Long Description (paragraph)", "Creative Story", "Blog Post Outline", "Blog Post", "Social Media Caption", "Advertisement Copy"]}} ``` - 在下拉元件的 Placeholder 屬性下輸入下列值: `Select type of content` ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mqnp25tphss8np3to3fc.png) *當應用程式有大量元件並且我們需要引用應用程式內與元件相關的值時,重新命名元件會很有用。* - 在底部新增一個**Button**元件作為 UI 建置過程的最後一步。將元件重新命名為*generateContentButton* 。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u25jhdxw86aeqkr5yx2k.png) 我們為此應用程式設計了一個簡單的 UI,您可以完全自訂它以滿足您的特定要求。 ToolJet 提供了廣泛的靈活性,讓您可以完全按照您的設想定義和排列元件。 第二步 - 整合 AI 能力🛠️ ---------------- UI 完成後,我們可以使用 ToolJet 的[查詢](https://docs.tooljet.com/docs/tooljet-concepts/what-are-queries)與 Gemini API 連接,並根據上傳的圖像、內容類型和我們在元件中輸入的其他資訊來獲得回應。 為了保護您的 Gemini API 金鑰,我們將利用 ToolJet 的[工作空間常數](https://docs.tooljet.com/docs/tooljet-concepts/workspace-constants)。這樣,您的金鑰就可以保持隱藏且安全。 - 點擊左上角的 ToolJet 標誌。從下拉清單中選擇工作空間常數。 - 點選**建立新常數**按鈕。將名稱設為*GEMINI\_API\_KEY*並在值輸入中輸入您的 Gemini API 金鑰。 點選**新增常數**按鈕。現在,該常數將在我們的工作區中可用,並且可以使用`{{constants.GEMINI_API_KEY}}`進行存取。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/og7gvo56vva0t2ziu25j.png) *您可以使用現有的 Google 憑證登入[Google AI Studio](https://aistudio.google.com/app/apikey) 。在 AI Studio 介面中,您將能夠找到並複製您的 API 金鑰。* - 導航回您的應用程式並展開底部的**查詢面板**。 - 點擊**+ 新增**按鈕並選擇**REST API**選項。將查詢重新命名為*getContent* 。 - 將請求方法變更為**POST**並將以下 URL 貼到 URL 輸入下: `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro:generateContent?key={{constants.GEMINI_API_KEY}}` - 導航到*getContent*查詢的**正文**部分。切換到**原始 JSON**並輸入以下程式碼: ``` {{ `{ "contents": [{ "parts": [{ "text": "Generate the following content for this image in markdown format: content type: ${components.typeOfContentInput.value}, additional info: ${components.additionalInfoInput.value}" }, { "inline_data": { "mime_type":"image/jpeg", "data": "${components.imageUploader.file[0].base64Data}" } },], },], }` }} ``` ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qnelq2jt9zmbjxa4k3tw.png) *在上面的配置中,我們建立了一個結合了使用者輸入文字和影像資料的結構化 JSON 有效負載。然後,JSON 物件被傳送到 Gemini API 端點以處理提供的內容和圖像。* 第三步 - 將資料綁定到元件🔗 --------------- 準備好查詢後,我們可以設定每次點擊 Button 元件時觸發它的方法。 - 選擇 Button 元件,然後導覽其右側的屬性面板。 - 在**「事件」**下,按一下**[「新事件處理程序」](https://docs.tooljet.com/docs/tooljet-concepts/what-are-events)**以建立新事件。 - 對於新事件,選擇**“單擊時”**作為“事件”,並**選擇“執行查詢”**作為“操作”。 - 選擇*getContent*作為查詢(在上一個步驟中建立的查詢)。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ouhohqt8tartis014yv3.png) 現在,每次單擊 Button 元件時,都會觸發*getContent*查詢,它將根據上傳的圖像和使用者輸入返回 AI 生成的內容。 接下來,我們將使用以下步驟使用*getContent*查詢傳回的值填入 Text(*輸出*) 元件: - 選擇為查詢輸出建立的文字(*輸出*)元件。 - 在其**Data**屬性下,輸入以下程式碼: `{{queries.getContent.data.candidates[0].content.parts[0].text}}` 同樣,使用從文件選擇器元件上傳的圖像填充圖像元件: - 選擇影像元件。 - 在其**URL**屬性下,輸入以下程式碼: `{{'data:image;base64,' + components.imageUploader.file[0].base64Data}}` 我們的應用程式現已準備就緒。讓我們嘗試一下並查看結果。選擇圖像,選擇內容類型,輸入一些附加訊息,然後按一下生成按鈕。我們現在應該能夠看到圖像的預覽和人工智慧生成的文字輸出。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b85w892qss30h3smueij.png) 結論 -- 現在,我們使用 Gemini API 和 ToolJet 在短短 10 分鐘內建立了一個功能齊全的 AI 驅動的內容產生器。該應用程式演示了 ToolJet 的快速開發環境如何與 Gemini 的高級 AI 功能無縫集成,以根據用戶輸入自動建立內容。 要探索更多訊息,請查看[ToolJet 文件](https://docs.tooljet.com/docs/)或加入我們的[slack](https://join.slack.com/t/tooljet/shared_invite/zt-2ij7t3rzo-qV7WTUTyDVQkwVxTlpxQqw) 。快樂編碼! --- 原文出處:https://dev.to/tooljet/build-an-ai-content-generator-using-gemini-api-and-tooljet-in-10-minutes-2d0m

TypeScript 專案的自訂實用程式類型

在我們探索 TypeScript 開發的第二部分中,我們引入了另外十種自訂實用程式類型,這些類型可以擴展程式碼的功能,提供更多工具來更有效地管理類型。這些實用程式類型有助於保持您的程式碼庫乾淨、高效和健壯。 第一部分: [TypeScript 專案的 1-10 個自訂實用程式類型](https://dev.to/antonzo/10-sustom-utility-types-for-typescript-projects-48pe) 總有機碳 ---- - [不可空深](#NonNullableDeep) - [合併](#Merge) - [元組到物件](#TupleToObject) - [獨佔元組](#ExclusiveTuple) - [Promise類型](#PromiseType) - [省略方法](#OmitMethods) - [函數參數](#FunctionArguments) - [承諾](#Promisify) - [約束函數](#ConstrainedFunction) - [聯合解析器](#UnionResolver) <a name="NonNullableDeep"></a> `NonNullableDeep` ----------------- `NonNullableDeep`類型是一個實用程序,可從給定類型`T`的所有屬性中深度刪除`null`和`undefined` 。這意味著不僅物件的頂級屬性不可為空,而且所有嵌套屬性也遞歸地標記為不可為空。在必須確保物件的屬性(包括深度嵌套的屬性)不為`null`或`undefined`情況下(例如在處理必須完全填充的資料時),此類型特別有用。 ``` type NonNullableDeep<T> = { [P in keyof T]: NonNullable<T[P]> extends object ? NonNullableDeep<NonNullable<T[P]>> : NonNullable<T[P]>; }; ``` **例子** 以下範例示範如何套用`NonNullableDeep`類型來確保`Person`物件本身及其任何巢狀屬性都無法為`null`或`undefined` ,從而確保整個物件已完全填入。 ``` interface Address { street: string | null; city: string | null; } interface Person { name: string | null; age: number | null; address: Address | null; } const person: NonNullableDeep<Person> = { name: "Anton Zamay", age: 26, address: { street: "Secret Street 123", city: "Berlin", }, }; // Error: Type 'null' is not assignable to type 'string'. person.name = null; // Error: Type 'undefined' is not assignable to type 'number'. person.age = undefined; // Error: Type 'null' is not assignable to type 'Address'. person.address = null; // Error: Type 'null' is not assignable to type 'string'. person.address.city = null; ``` <a name="Merge"></a> `Merge` ------- `Merge<O1, O2>`類型對於透過組合兩個物件類型`O1`和`O2`的屬性來建立新類型非常有用。當屬性重疊時, `O2`中的屬性將覆寫`O1`中的屬性。當您需要擴展或自訂現有類型以確保特定屬性優先時,這特別有用。 ``` type Merge<O1, O2> = O2 & Omit<O1, keyof O2>; ``` **例子** 在此範例中,我們定義了兩種物件類型,分別表示預設設定和使用者設定。使用`Merge`類型,我們組合這些設定來建立最終配置,其中`userSettings`會覆蓋`defaultSettings` 。 ``` type DefaultSettings = { theme: string; notifications: boolean; autoSave: boolean; }; type UserSettings = { theme: string; notifications: string[]; debugMode?: boolean; }; const defaultSettings: DefaultSettings = { theme: "light", notifications: true, autoSave: true, }; const userSettings: UserSettings = { theme: "dark", notifications: ["Warning 1", "Error 1", "Warning 2"], debugMode: true, }; type FinalSettings = Merge<DefaultSettings, UserSettings>; const finalSettings: FinalSettings = { ...defaultSettings, ...userSettings }; ``` <a name="TupleToObject"></a> `TupleToObject` --------------- `TupleToObject`類型是將元組類型轉換為物件類型的實用程序,其中元組的元素成為物件的鍵,並根據這些元素在元組中的位置提取關聯的值。這種類型在需要將元組轉換為更結構化的物件形式的情況下特別有用,允許透過元素的名稱而不是位置更直接地存取元素。 ``` type TupleToObject<T extends [string, any][]> = { [P in T[number][0]]: Extract<T[number], [P, any]>[1]; }; ``` **例子** 考慮這樣一個場景,您正在使用將表架構資訊儲存為元組的資料庫。每個元組包含一個欄位名稱及其對應的資料類型。這種格式通常用於資料庫元資料 API 或架構遷移工具。元組格式緊湊且易於處理,但對於應用程式開發來說,使用物件更方便。 ``` type SchemaTuple = [ ['id', 'number'], ['name', 'string'], ['email', 'string'], ['isActive', 'boolean'] ]; const tableSchema: SchemaTuple = [ ['id', 'number'], ['name', 'string'], ['email', 'string'], ['isActive', 'boolean'], ]; // Define the type of the transformed schema object type TupleToObject<T extends [string, string | number | boolean][]> = { [P in T[number][0]]: Extract< T[number], [P, any] >[1]; }; type SchemaObject = TupleToObject<SchemaTuple>; const schema: SchemaObject = tableSchema.reduce( (obj, [key, value]) => { obj[key] = value; return obj; }, {} as SchemaObject ); // Now you can use the schema object console.log(schema.id); // Output: number console.log(schema.name); // Output: string console.log(schema.email); // Output: string console.log(schema.isActive); // Output: boolean ``` <a name="ExclusiveTuple"></a> `ExclusiveTuple` ---------------- `ExclusiveTuple`類型是一個實用程序,它產生包含來自給定聯合類型`T`的唯一元素的元組。此類型確保聯合的每個元素僅在結果元組中包含一次,從而有效地將聯合類型轉換為具有聯合元素的所有可能的唯一排列的元組類型。這在您需要枚舉聯合成員的所有唯一組合的情況下特別有用。 ``` type ExclusiveTuple<T, U extends any[] = []> = T extends any ? Exclude<T, U[number]> extends infer V ? [V, ...ExclusiveTuple<Exclude<T, V>, [V, ...U]>] : [] : []; ``` **例子** 考慮這樣一個場景:您正在開發一個旅行應用程式的功能,該功能可以為遊覽某個城市的遊客產生獨特的行程。該市有三個主要景點:博物館、公園和劇院。 ``` type Attraction = 'Museum' | 'Park' | 'Theater'; type Itineraries = ExclusiveTuple<Attraction>; // The Itineraries type will be equivalent to: // type Itineraries = // ['Museum', 'Park', 'Theater'] | // ['Museum', 'Theater', 'Park'] | // ['Park', 'Museum', 'Theater'] | // ['Park', 'Theater', 'Museum'] | // ['Theater', 'Museum', 'Park'] | // ['Theater', 'Park', 'Museum']; ``` <a name="PromiseType"></a> `PromiseType` ------------- `PromiseType`類型是一個實用程序,用於提取給定 Promise 解析為的值的類型。這在使用非同步程式碼時非常有用,因為它允許開發人員輕鬆推斷結果的類型,而無需明確指定它。 ``` type PromiseType<T> = T extends Promise<infer U> ? U : never; ``` 此類型使用 TypeScript 的條件類型和`infer`關鍵字來決定`Promise`的解析類型。如果`T`擴展`Promise<U>` ,則表示`T`是解析為類型`U` `Promise` ,而`U`是推斷的類型。如果`T`不是`Promise` ,則型別解析為`never` 。 **例子** 以下範例示範如何使用 PromiseType 類型從 Promise 中提取已解析的類型。透過使用此實用程式類型,您可以推斷 Promise 將解析為的值的類型,這有助於在處理非同步操作時進行類型檢查並避免錯誤。 ``` type PromiseType<T> = T extends Promise<infer U> ? U : never; interface User { id: number; name: string; } interface Post { id: number; title: string; content: string; userId: number; } async function fetchUser(userId: number): Promise<User> { return { id: userId, name: "Anton Zamay" }; } async function fetchPostsByUser(userId: number): Promise<Post[]> { return [ { id: 1, title: "Using the Singleton Pattern in React", content: "Content 1", userId }, { id: 2, title: "Hoisting of Variables, Functions, Classes, Types, " + "Interfaces in JavaScript/TypeScript", content: "Content 2", userId }, ]; } async function getUserWithPosts( userId: number ): Promise<{ user: User; posts: Post[] }> { const user = await fetchUser(userId); const posts = await fetchPostsByUser(userId); return { user, posts }; } // Using PromiseType to infer the resolved types type UserType = PromiseType<ReturnType<typeof fetchUser>>; type PostsType = PromiseType<ReturnType<typeof fetchPostsByUser>>; type UserWithPostsType = PromiseType<ReturnType<typeof getUserWithPosts>>; async function exampleUsage() { const userWithPosts: UserWithPostsType = await getUserWithPosts(1); // The following will be type-checked to ensure correctness const userName: UserType["name"] = userWithPosts.user.name; const firstPostTitle: PostsType[0]["title"] = userWithPosts.posts[0].title; console.log(userName); // Anton Zamay console.log(firstPostTitle); // Using the Singleton Pattern in React } exampleUsage(); ``` **為什麼我們需要`UserType`而不僅僅是使用`User` ?** 這是個好問題!使用`UserType`而不是直接使用`User`主要原因是為了確保從非同步函數的回傳類型準確推斷出類型。這種方法有幾個優點: 1. **類型一致性:**透過使用`UserType` ,您可以確保類型始終與`fetchUser`函數的實際回傳類型一致。如果`fetchUser`的回傳類型發生更改, `UserType`將自動反映該更改,而無需手動更新。 2. **自動類型推斷**:在處理複雜類型和巢狀承諾時,手動確定和追蹤解析的類型可能具有挑戰性。使用 PromiseType 允許 TypeScript 為您推斷這些類型,從而降低錯誤風險。 <a name="OmitMethods"></a> `OmitMethods` ------------- `OmitMethods`型別是個實用程序,可從給定型別`T`中刪除所有方法屬性。這意味著作為函數的類型`T`的任何屬性都將被省略,從而產生僅包含非函數屬性的新類型。 ``` type OmitMethods<T> = Pick<T, { [K in keyof T]: T[K] extends Function ? never : K }[keyof T]>; ``` **例子** 此類型在您想要從物件類型中排除方法的情況下特別有用,例如將物件序列化為 JSON 或透過 API 發送物件時,其中方法不相關且不應包含在內。以下範例示範如何將`OmitMethods`套用至物件類型以刪除所有方法,確保產生的類型僅包含非函數的屬性。 ``` interface User { id: number; name: string; age: number; greet(): void; updateAge(newAge: number): void; } const user: OmitMethods<User> = { id: 1, name: "Alice", age: 30, // greet and updateAge methods are omitted from this type }; function sendUserData(userData: OmitMethods<User>) { // API call to send user data console.log("Sending user data:", JSON.stringify(userData)); } sendUserData(user); ``` <a name="FunctionArguments"></a> `FunctionArguments` ------------------- `FunctionArguments`類型是一個實用程序,用於提取給定函數類型`T`的參數類型。這意味著對於傳遞給它的任何函數類型,該類型將傳回一個表示函數參數類型的元組。此類型在需要捕獲或操作函數的參數類型的情況下特別有用,例如在高階函數中或建立類型安全的事件處理程序時。 ``` type FunctionArguments<T> = T extends (...args: infer A) => any ? A : never; ``` **例子** 假設您有一個高階函數包裝,它接受一個函數及其參數,然後使用這些參數來呼叫該函數。使用 FunctionArguments,您可以確保包裝函數參數的類型安全。 ``` function wrap<T extends (...args: any[]) => any>(fn: T, ...args: FunctionArguments<T>): ReturnType<T> { return fn(...args); } function add(a: number, b: number): number { return a + b; } type AddArgs = FunctionArguments<typeof add>; // AddArgs will be of type [number, number] const result = wrap(add, 5, 10); // result is 15, and types are checked ``` <a name="Promisify"></a> `Promisify` ----------- `Promisify`類型是一個實用程序,它將給定類型`T`的所有屬性轉換為各自類型的 Promise。這意味著結果類型中的每個屬性都將是該屬性的原始類型的`Promise` 。這種類型在處理非同步操作時特別有用,您希望確保整個結構符合基於`Promise`的方法,從而更輕鬆地處理和管理非同步資料。 ``` type Promisify<T> = { [P in keyof T]: Promise<T[P]> }; ``` **例子** 考慮一個顯示使用者個人資料、最近活動和設定的儀表板。這些資訊可能是從不同的服務獲取的。透過承諾單獨的屬性,我們確保使用者資料的每個部分都可以獨立取得、解析和處理,從而在處理非同步操作時提供靈活性和效率。 ``` interface Profile { name: string; age: number; email: string; } interface Activity { lastLogin: Date; recentActions: string[]; } interface Settings { theme: string; notifications: boolean; } interface UserData { profile: Profile; activity: Activity; settings: Settings; } // Promisify Utility Type type Promisify<T> = { [P in keyof T]: Promise<T[P]>; }; // Simulated Fetch Functions const fetchProfile = (): Promise<Profile> => Promise.resolve({ name: "Anton Zamay", age: 26, email: "[email protected]" }); const fetchActivity = (): Promise<Activity> => Promise.resolve({ lastLogin: new Date(), recentActions: ["logged in", "viewed dashboard"], }); const fetchSettings = (): Promise<Settings> => Promise.resolve({ theme: "dark", notifications: true }); // Fetching User Data const fetchUserData = async (): Promise<Promisify<UserData>> => { return { profile: fetchProfile(), activity: fetchActivity(), settings: fetchSettings(), }; }; // Using Promisified User Data const displayUserData = async () => { const user = await fetchUserData(); // Handling promises for each property (might be in different places) const profile = await user.profile; const activity = await user.activity; const settings = await user.settings; console.log(`Name: ${profile.name}`); console.log(`Last Login: ${activity.lastLogin}`); console.log(`Theme: ${settings.theme}`); }; displayUserData(); ``` <a name="ConstrainedFunction"></a> `ConstrainedFunction` --------------------- `ConstrainedFunction`類型是一個實用程序,它約束給定的函數類型 T 以確保保留其參數和傳回類型。它本質上捕獲函數的參數類型和返回類型,並強制結果函數類型必須遵守這些推斷類型。當您需要對高階函數實施嚴格的類型約束或建立必須符合原始函數簽署的包裝函數時,此類型非常有用。 ``` type ConstrainedFunction<T extends (...args: any) => any> = T extends (...args: infer A) => infer R ? (args: A extends any[] ? A : never) => R : never; ``` **例子** 在事先未知函數簽署且必須動態推斷的情況下, `ConstrainedFunction`可確保根據推斷的類型正確應用約束。想像一個實用程序,它包裝任何函數以記憶其結果: ``` function memoize<T extends (...args: any) => any>(fn: T): ConstrainedFunction<T> { const cache = new Map<string, ReturnType<T>>(); return ((...args: Parameters<T>) => { const key = JSON.stringify(args); if (!cache.has(key)) { cache.set(key, fn(...args)); } return cache.get(key)!; }) as ConstrainedFunction<T>; } const greet: Greet = (name, age) => { return `Hello, my name is ${name} and I am ${age} years old.`; }; const memoizedGreet = memoize(greet); const message1 = memoizedGreet("Anton Zamay", 26); // Calculates and caches const message2 = memoizedGreet("Anton Zamay", 26); // Retrieves from cache ``` 在這裡, `memoize`使用`ConstrainedFunction`來確保記憶函數保持與原始函數`fn`相同的簽名,而不需要明確定義函數類型。 <a name="UnionResolver"></a> `UnionResolver` --------------- `UnionResolver`類型是將聯合型別轉換為可區分聯合的實用程式。具體來說,對於給定的聯合類型`T` ,它會產生一個物件陣列,其中每個物件都包含一個屬性類型,該屬性類型保存聯合中的類型之一。在需要明確處理聯合的每個成員的情況下使用聯合類型時,此類型特別有用,例如在類型安全的 Redux 操作或 TypeScript 中的可區分聯合模式中。 ``` type UnionResolver<T> = T extends infer U ? { type: U }[] : never; ``` **例子** 以下範例示範如何應用`UnionResolver`類型將聯合類型轉換為物件陣列,每個物件都具有`type`屬性。這允許對聯合內的每個操作進行類型安全處理,確保考慮到所有情況並降低使用聯合類型時發生錯誤的風險。 ``` type ActionType = "ADD_TODO" | "REMOVE_TODO" | "UPDATE_TODO"; type ResolvedActions = UnionResolver<ActionType>; // The resulting type will be: // { // type: "ADD_TODO"; // }[] | { // type: "REMOVE_TODO"; // }[] | { // type: "UPDATE_TODO"; // }[] const actions: ResolvedActions = [ { type: "ADD_TODO" }, { type: "REMOVE_TODO" }, { type: "UPDATE_TODO" }, ]; // Now you can handle each action type distinctly actions.forEach(action => { switch (action.type) { case "ADD_TODO": console.log("Adding a todo"); break; case "REMOVE_TODO": console.log("Removing a todo"); break; case "UPDATE_TODO": console.log("Updating a todo"); break; } }); ``` --- 原文出處:https://dev.to/antonzo/11-20-sustom-utility-types-for-typescript-projects-2bg5

REST API 設計規則

為什麼編寫乾淨的 REST-API 設計很重要 ----------------------- 在當今互聯的世界中,精心設計的 REST API 是高效且可擴展的應用程式的支柱。 編寫乾淨的 REST API 設計至關重要,原因如下: - **增強的可用性:**精心設計的 API 直覺且易於使用,適合各種技能水平的開發人員使用。這簡化了整合並縮短了學習曲線。 - **提高可維護性:**乾淨的程式碼可以更輕鬆地辨識和修復錯誤、加入功能和擴展 API,從而提高可維護性。這確保了長期穩定性並降低了開發成本。 - **提高安全性:**結構良好的 API 具有適當的身份驗證和授權機制,有助於防止未經授權的存取、資料外洩和其他安全漏洞。 - **增強的效能:**簡潔的設計透過使用高效的資料結構、避免不必要的呼叫並最大限度地減少延遲來優化效能。這提供了無縫的用戶體驗並提高了整體應用程式效能。 - **縮短開發時間:**明確定義的 API 規格和清晰的文件可消除猜測並減少大量測試的需要,從而加快開發速度。這節省了寶貴的開發時間和資源。 - **改進的可擴展性:**簡潔的設計透過提供模組化架構來實現輕鬆的可擴展性,該架構可以輕鬆擴展以處理增加的流量或新功能。這確保了 API 可以隨著應用程式的需求而成長。 - **提高可重複使用性:**設計良好的 API 可以在多個應用程式中重複使用,從而減少重複並提高一致性。這簡化了開發並節省了時間和精力。 - **改進的文件:**簡潔的設計更容易記錄,讓開發人員清楚 API 的工作原理以及如何有效地使用它。這可以減少混亂並提高採用率。 URI規則 ----- **一個url的結構如下** **`scheme :// authority / path [?query][#fragment]`** 例如 `https://soccer.api.org/teams/dortmund/players?name=Rona#2` 有兩種類型的資源 1. 集合資源:包含資源的集合。它可以比喻為資料庫關係 2. 單例資源:包含單一資源。它可以比喻為資料庫記錄。 --- 設計 Rest-Api 時 1 集合資源應該是複數 ``` + soccer.api.org/teams/dortmond - soccer.api.org/team/dortmond ``` 2 Singleton資源應該是單一的,可以用api後面資料庫系統中代表資源的唯一id來代替 ``` +soccer.api.org/teams/dortmond/players/58c1aaae-205a-11ef-aeea-a64c74618950 ``` 3 URI 中沒有**尾隨正斜杠** ``` +soccer.api.org/teams/dortmond/players -soccer.api.org/teams/dortmond/players/ ``` 4 使用**連字號**取代**底線**以提高 API 的可讀性 ``` + api.blog.com/blogs/this-is-my-blog - api.blog.com/blogs/this_is_my_blog ``` 5 URI 路徑**中小寫字母**優先於**大寫字母** ``` + api.example.com/my-api/my-resource - api.example.com/My-Api/My-Resource ``` 6 URI 中沒有**檔案副檔名** ``` + api.example.com/api/resource - api.example.com/api/resource.json ``` 7 CRUD 函數名稱**不應**在 URI 中使用 ``` + DELETE api.example.com/api/resource - GET api.example.com/api.resource/delete ``` 8 URI 的查詢元件只能在集合資源中使用 ``` + GET /users?role=admin ``` 9 URI 的查詢元件應用於對集合結果分頁 ``` + GET /users?pageSize=25&pageStartIndex=50 ``` HTTP 方法規則 --------- | HTTP 方法 |用途 | | ----------- | -------------------------------------------------- -------------------------------------------------- ------------- | |POST |建立新資源。類似於建立 | |GET |獲取資源的表示。類似閱讀 | |PUT|透過替換**整個**資源來更新資源 | |DELETE |刪除資源 | |PATCH|透過更改所需資源的一部分來更新資源,而不取代整個資源。 | |HEAD|僅獲取響應頭而不是響應體 | |OPTION |獲取特定資源的所有可用選項 | > PUT 可用於建立和更新資源。但是,根據最佳實踐,通常建議使用 POST 來建立新資源,並使用 PUT 來完全取代現有資源。 --- 版本控制 ---- 對 api 進行版本控制對於以下方面可能很重要: **保持向後相容性:**版本控制可讓您引入新功能,而不會破壞依賴舊 API 版本的現有整合。使用者可以繼續使用熟悉的端點,而尋求新功能的使用者可以採用版本化的 API。 **確保一致且設計良好的 API:**跨版本的一致命名約定有助於提供使用者友善的體驗。更改端點會破壞這種體驗,而版本控制有助於避免這種情況。 --- 結論 == 現在您已經掌握了這些 REST API 設計規則,是時候將它們付諸實現了!在下面的評論中分享您的 API 創作,讓我們一起建立一個設計良好且對開發人員友好的 API 世界。 --- 原文出處:https://dev.to/ezekiel_77/rest-api-design-rules-2c4j

什麼是 Webpack

Webpack 似乎令人難以承受,並且是您在建立 React 應用程式時可能會避免學習的東西。但為您的 React 應用程式自行設定和建立非常簡單。這將是 Webpack 及其*實際*功能的初學者指南。讓我們來解答您可能對 Webpack 有的任何疑問…哈哈… 我們將回顧: - **什麼是 Webpack?** - **如何配置 Webpack** - **Webpack 中的快取清除** #### Webpack 是什麼,它是如何運作的,它有什麼作用? 如果您熟悉 Create React App,那麼 Webpack 負責 Create React App 中的主要功能,以及 Babel(JS 編譯器)等其他一些功能。 Create React App 使用 Webpack 將檔案捆綁在一起,這就是為什麼使用 create React app 開發 React 應用程式非常簡單的原因。 Webpack 是一個模組捆綁器。但是,這是什麼意思?這意味著 Webpack 將 JS 文件編譯為一個主文件,或者您想要將程式碼捆綁到其中的任意多個文件,但通常它是一個捆綁文件。 Webpack 具有許多功能,例如模組捆綁、文件縮小(透過刪除空格、註解、不必要的程式碼以及最小化/縮短程式碼來最小化程式碼的過程。)、SASS 編譯等。應用程式轉換為瀏覽器可以理解的內容。 Webpack 管理依賴關係並載入需要先載入的程式碼。 Webpack 會追蹤哪些文件依賴哪些文件並相應地載入它們。 Webpack 解決的問題是,在開發大型應用程式時,您的依賴項可能會堆積並交叉不同的文件,這使其變得複雜且難以管理。 Webpack 為您管理所有這些依賴項和文件,將其捆綁到瀏覽器可以理解的純 JS 中。 #### 如何配置 Webpack 如果您想將載入程式和外掛程式新增至 Webpack 捆綁包中,則必須在 Webpack 設定檔中執行此操作。 這是 webpack 設定檔的樣子。 ``` //Webpack.config.js file: const path = require("path"); module.exports = { mode: "development", entry: "./src/index.js", output: { filename: "main.js", path: path.resolve(__dirname, "dist") } }; ``` 讓我們解開這個文件中發生的事情。 首先,我們將`mode`設定為`development` ,這告訴Webpack不要縮小我們的程式碼,這在開發時非常有幫助。 然後,如果我們想使用它,就必須確保該文件中的所有內容都作為模組導出。 在使用`npm init`初始化應用程式時建立的`package.json`檔案中,您可以告訴 Webpack 在`scripts`中執行,如下所示: ``` "scripts": { "start": "webpack --config webpack.config.js" }, //The webpack config file can be named anything, //In this case, it is webpack.config.js ``` 接下來, `entry`屬性接受整個應用程式執行位置的 src。預設情況下,Webpack 將在`src`資料夾中查找`index.js`文件,但您可以在此處指定應用程式啟動的文件以及需要捆綁的程式碼。 `output`屬性是一個物件,您希望在其中輸出程式碼。 `filename`屬性可以命名為您想要的任何名稱,通常是`main.js` 。 `path`屬性指定程式碼的去向。在本例中,我們正在解析`dist`目錄的絕對路徑,您可以將此資料夾稱為任何名稱。本質上,這是 Webpack 將捆綁您的應用程式的資料夾,傳統上該資料夾是`dist` 。 這是配置 Webpack 檔案以及如何讓 Webpack 執行您的應用程式的基礎知識。當然,您可以向此設定檔加入更多內容,例如載入程式和外掛程式。如果你對此有興趣,可以參考[Webpack文件](https://webpack.js.org/configuration/)。 #### Webpack 中的快取清除 快取清除一開始可能是一個令人困惑的話題,但它對於應用程式的功能以及可以在 Webpack 中輕鬆完成的事情可能很重要。 當您使用 Webpack 捆綁應用程式時,Webpack 會將所有內容捆綁在可部署的`/dist`目錄中。 將應用程式部署到伺服器後,並且用戶正在存取您的應用程式,客戶端(通常是瀏覽器)必須再次存取伺服器以檢索應用程式的所有資產。 這就是瀏覽器快取應用程式資源的原因。本質上,它們將文件或模組保存在`/dist`目錄中,以便用戶下次刷新或存取瀏覽器時不必檢索它已經記住的任何資產。 如何防止瀏覽器快取我們更改過的 CSS 或檔案包? 瀏覽器快取檔案可能會導致問題,因為如果我們更改已快取的文件,瀏覽器可能不會更新它們,並假設文件是相同的,因為檔案名稱沒有更改。 快取清除背後的想法是,我們希望每次對文件進行更改時都建立一個新的文件名,如果沒有進行更改,則保持文件名相同。這樣,當您對文件進行更改時,當客戶端向伺服器發出請求以檢索應用程式資產時,您更改的文件將更新,因為瀏覽器無法辨識文件名。 幸運的是,Webpack 在 Webpack 設定檔的`output.filename`中附帶了一個名為`[contenthash]`的內建替換功能。 `[contenthash]`替換將根據資產內容是否已變更建立唯一的雜湊值,僅在變更時更新。 當您將`[contenthash]`新增至設定檔中的輸出物件時,它將如下所示: ``` //the output object in module.exports from the webpack.config.js file: output: { filename: "main.[contentHash].js", path: path.resolve(__dirname, "dist") }, ``` **因此**,我希望 Webpack 的介紹易於理解,並回答了您可能對 Webpack 存在的一些問題。 Webpack 正在做很多幕後工作以使您的 React 應用程式正常工作,您所需要做的就是確保為您的應用程式正確配置它。下次您開發 React 應用程式時,請考慮自行設定 Webpack! --- 原文出處:https://dev.to/cs_carms/what-is-webpack-43h6

提升你的技能

介紹 -- 學習如何成為更好的開發人員需要不斷提升自己的技能。一個人如何學習成長並成為更好的開發人員?讓我們探討幾個總體上適用於大多數開發人員的想法。程式碼範例全部採用 C# 語言,之所以選擇它們是因為它們對於大多數開發人員來說並不常見,並且是在內部完成的。 腳步 -- - [Pluralsight](https://www.pluralsight.com/)是一個付費網站,提供數百門 C# 課程。首先使用他們的人工智慧評估,這將引導您走上正確的道路。許多課程也有自己的評估。 Pluralsight 讓您輕鬆地向高評價作者學習,並透過任何裝置(例如筆記型電腦、手機或平板電腦)存取課程。 Pluralsite 提供免費試用,有時還會在購買訂閱時提供折扣。 - 使用[微軟學習](https://learn.microsoft.com/en-us/training/)。無論您是剛開始職業生涯,還是經驗豐富的專業人士,我們的自我導向方法都可以幫助您更快、更有信心地按照自己的步調實現目標。透過互動式模組和路徑培養技能或向講師學習。以您的方式學習和成長。 - 花時間閱讀 Microsoft 文件,例如,閱讀[C# 程式的一般結構、類型運算子和表達式語句、](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/program-structure/)各種[類別](https://learn.microsoft.com/en-us/dotnet/api/system.string?view=net-6.0)[、物件導向程式設計](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/tutorials/oop)等等。 - 在學習過程中,嘗試使用控制台或單元測試專案使事情變得簡單,換句話說,將後端學習與前端使用者介面學習分開。 - 在您感覺舒服的某個時間點,確定一個簡單的專案,在編碼之前寫出任務,然後編寫程式碼,而不是同時思考和編碼。新手等級的思考和編碼簡直就是一場即將發生的災難。 - 在網路上尋找資訊並找到解決方案時,不要簡單地複製和貼上,檢查程式碼,在使用所述程式碼之前先嘗試弄清楚它在做什麼。 - 了解如何在 Visual Studio 中使用 GitHub 來備份和版本程式碼。假設您編寫了程式碼並破壞了它,透過 GitHub 儲存庫中的正確版本控制,您可以還原變更並還原程式碼。 - 使用 .NET Framework Core 6 或 .NET Core Framework 8 而不是 .NET Framework classic,因為使用 .NET Core 有更多好處 - 如果學習使用資料,請從 SQL-Server Express 開始並安裝 SSMS (SQL-Server Management Studio),同時學習使用 Entity Framework Core。 - 充分了解學習任何語言時慢速學習比快速學習好,而且沒有人知道這一切。 ![了解如何使用除錯器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xp3uj4nuhzx7sple8tad.png) 加速學習的工具 ------- Microsoft Visual Studio 絕對是最好的 IDE(整合開發環境),它具有以下專案可以增加學習並節省編碼時間。 - 適用於 Visual Studio 和 SSMS 的 Red Gate [SQL 提示符](https://www.red-gate.com/products/sql-prompt/) - 高級 IntelliSense 風格的程式碼完成 - 重構 SQL 程式碼 - SSMS SQL 歷史記錄 - 以及更多 - Jetbrains [ReSharper](https://www.jetbrains.com/resharper/)這是一個非常寶貴的 Visual Studio 擴充功能。 - [EF Power Tools](https://marketplace.visualstudio.com/items?itemName=ErikEJ.EFCorePowerTools)可輕鬆對 EF Core 的 SQL-Server 資料庫進行逆向工程 深入了解程式碼基礎知識 ----------- 掌握基礎知識後,尋找有助於成長為更好的開發人員的程式碼範例。 一個可能的途徑是使用[Microsoft Entity Framework Core](https://learn.microsoft.com/en-us/ef/core/) (EF Core) 或使用[Dapper](https://www.learndapper.com/)等資料提供者來處理資料庫。 還有其他處理資料的方法,但 EF Core 和 Dapper 在效能和易於學習方面是最好的。 在 Web 上尋找程式碼範例時,請確保它們適用於您的專案的 .NET Framework,因為 .NET Framework 4.8 程式碼範例與 .NET Core 8 Framework 有很大不同。 Microsoft 每年都會為 EF Core 建立程式碼範例,但在許多情況下,其結構可能不適合缺乏經驗的開發人員學習,因此 Karen Payne 採用 EF Core 8 程式碼範例並建立了以下[文章](https://dev.to/karenpayneoregon/microsoft-entity-framework-core-8-samples-3dj8)/[儲存庫,在大多數情況下,這些文章/儲存庫](https://github.com/karenpayneoregon/ef-code-8-samples)很容易學習。 第 1 課 - SQL-Server 計算列 ---------------------- ### EF 核心版本 {% cta https://github.com/karenpayneoregon/sql-basics/tree/master/EF\_CoreBirthdaysCompulatedColumns %} 範例專案 {% endcta %} [計算列](https://learn.microsoft.com/en-us/sql/relational-databases/tables/specify-computed-columns-in-a-table?view=sql-server-ver16)是虛擬列,除非該列被標記為 PERSISTED,否則不會實際儲存在表中。計算列表達式可以使用其他欄位中的資料來計算其所屬列的值。您可以使用 SQL Server Management Studio (SSMS) 或 Transact-SQL (T-SQL) 為 SQL Server 中的計算列指定運算式。 完整文章,請參閱[SQL-Server:使用 Ef Core 計算列](https://dev.to/karenpayneoregon/sql-server-computed-columns-with-ef-core-3h8d) 但在這裡,我們將使用 EF Core 和 Dapper 從開始和演練使用情況建立一個計算列。 原文來自以下 Stackoverflow貼[文](https://stackoverflow.com/questions/9/how-do-i-calculate-someones-age-based-on-a-datetime-type-birthday?page=2&tab=modifieddesc#tab-top)。取得出生日期和目前日期,用出生日期減去目前日期,然後除以 10,000。 在 SSMS(SQL Server Management Studio)中 請注意,在程式碼範例中,完整資料庫存在於腳本資料夾下的專案 EF\_CoreBirthdaysCompulatedColumns 中。在執行腳本之前,請在 SSMS 中建立資料庫,然後執行腳本來建立表格並填入資料。 另請注意,在程式碼範例中,連接字串使用 NuGet 套件[ConsoleConfigurationLibrary](https://www.nuget.org/packages/ConsoleConfigurationLibrary/)駐留在 appsettings.json 中。 **表結構** ![表結構](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/06sfbbb9i4ru5203l1nx.png) **SQL** ![選擇語句](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8bqiabe7j0a4nvez1yqa.png) 將聲明分開。 - 使用日期分隔符號格式化兩個日期並將每個日期轉換為整數。 - 從目前日期減去出生日期,括號很重要。 - 將以上除以 10,000 即可得到年齡。 **結果** ![SELECT 的結果](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cvzc25dc1ojr4arurnqy.png) 現在為名為 YearsOld 的表建立一個 nvarchar 類型的新欄位,並將此語句放入計算列屬性中,然後儲存變更。 ``` (CAST(FORMAT(GETDATE(), 'yyyyMMdd') AS INTEGER) - CAST(FORMAT(BirthDate, 'yyyyMMdd') AS INTEGER)) / 10000 ``` ![ssms中的表設計](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tvwq73hel66uumzbixh5.png) - 建立一個新的 C# 控制台專案。 - 新增[Microsoft.EntityFrameworkCore.SqlServer](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.SqlServer/8.0.0?_src=template)的依賴項 - 安裝 Visual Studio 擴充[EF Power Tools](https://marketplace.visualstudio.com/items?itemName=ErikEJ.EFCorePowerTools) 。若要了解如何使用 EF Power Tools,請觀看作者提供的以下[影片](https://www.youtube.com/watch?v=uph-AGyOd8c)。新增[完整文件](https://github.com/ErikEJ/EFCorePowerTools/wiki/Reverse-Engineering)。 使用 EF Power Tools 後,將產生以下類別。 代表 SQL-Server 資料庫表的模型。 ``` public partial class BirthDays { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateOnly? BirthDate { get; set; } public int? YearsOld { get; set; } } ``` 所謂的[DbContext](https://learn.microsoft.com/en-us/dotnet/api/system.data.entity.dbcontext?view=entity-framework-6.2.0)和與資料庫互動的配置。 注意 YearsOld 上的[HasCompulatedColumnSql](https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.relationalpropertybuilderextensions.hascomputedcolumnsql?view=efcore-8.0) ,這是我們的計算列。 ``` public partial class Context : DbContext { public Context() { } public Context(DbContextOptions<Context> options) : base(options) { } public virtual DbSet<BirthDays> BirthDays { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) #warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263. => optionsBuilder.UseSqlServer(DataConnections.Instance.MainConnection); protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<BirthDays>(entity => { entity.Property(e => e.YearsOld).HasComputedColumnSql("((CONVERT([int],format(getdate(),'yyyyMMdd'))-CONVERT([int],format([BirthDate],'yyyyMMdd')))/(10000))", false); }); OnModelCreatingPartial(modelBuilder); } partial void OnModelCreatingPartial(ModelBuilder modelBuilder); } ``` > **筆記** > 執行上述工作有兩個陣營:資料庫優先或程式碼優先。對於剛開始使用 EF Core 的人來說,資料庫優先是最好的路徑。 要查看資料, [Spectre.Console](https://spectreconsole.net/)用於建立一個漂亮的表格。 ``` internal partial class Program { static async Task Main(string[] args) { await Setup(); var table = CreateTable(); await using (var context = new Context()) { var list = await context.BirthDays.ToListAsync(); foreach (var bd in list) { table.AddRow( bd.Id.ToString(), bd.FirstName, bd.LastName, bd.BirthDate.ToString(), bd.YearsOld.ToString()); } AnsiConsole.Write(table); } ExitPrompt(); } public static Table CreateTable() { var table = new Table() .AddColumn("[b]Id[/]") .AddColumn("[b]First[/]") .AddColumn("[b]Last[/]") .AddColumn("[b]Birth date[/]") .AddColumn("[b]Age[/]") .Alignment(Justify.Left) .BorderColor(Color.LightSlateGrey); return table; } } ``` ![上述程式碼的截圖](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i60dye7au7fve08y1wfu.png) 為了獲取我們的資料,一行程式碼用於實例化 EF Core,一行程式碼用於讀取資料。 EF Core 也非常適合關聯式資料庫,請參閱以下[儲存庫](https://github.com/karenpayneoregon/ef-code-8-samples)。 有關記錄 EF Core 產生的 SQL,請參閱下列[專案](https://github.com/karenpayneoregon/ef-code-8-samples/tree/master/DualContextsApp),該專案也展示如何使用兩個不同的 SQL-Server 實例。 ### 短小精悍的版本 {% cta https://github.com/karenpayneoregon/sql-basics/tree/master/DapperBirthdaysCompulatedColumns %} 範例專案 {% endcta %} 與 EF Core 不同,使用 Dapper,開發人員在 SSMS 中編寫 SQL 語句並將有效語句新增到程式碼中。有關 Dapper 的更多訊息,請參閱我的[系列](https://dev.to/karenpayneoregon/series/25270)。 這裡 SQL 儲存在唯讀字串中,替代方法是將 SQL(或任何語句)儲存在預存程序中。 ![學習編寫正確的 SQL](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uyn1awyunv0pvbhtgzy1.png) ``` internal class SqlStatements { public static string GetBirthdays => """ SELECT Id ,FirstName ,LastName ,BirthDate ,YearsOld FROM BirthDaysDatabase.dbo.BirthDays """; } ``` 讀取資料的程式碼。 ``` internal class DapperOperations { private IDbConnection _cn; public DapperOperations() { _cn = new SqlConnection(DataConnections.Instance.MainConnection); SqlMapper.AddTypeHandler(new SqlDateOnlyTypeHandler()); SqlMapper.AddTypeHandler(new SqlTimeOnlyTypeHandler()); } public async Task<List<BirthDays>> GetBirthdaysAsync() { return (await _cn.QueryAsync<BirthDays>(SqlStatements.GetBirthdays)).AsList(); } } ``` 在類別構造函數中 1. 使用[Microsoft.Data.SqlClient](https://www.nuget.org/packages/Microsoft.Data.SqlClient/5.2.1?_src=template) NuGet 套件建立連線。 1. 使用[kp.Dapper.Handlers](https://www.nuget.org/packages/kp.Dapper.Handlers/1.0.0?_src=template) NuGet 套件為 Dapper 新增理解 DateOnly 類型的功能。 讀取資料是一個單行資料,表示我們需要非同步生日列表。 ``` public async Task<List<BirthDays>> GetBirthdaysAsync() { return (await _cn.QueryAsync<BirthDays>(SqlStatements.GetBirthdays)).AsList(); } ``` 回到 Program.cs,除了建立 Dapper 類別的實例並呼叫方法之外,程式碼與 EF Core 相同。 ``` internal partial class Program { static async Task Main(string[] args) { await Setup(); var table = CreateTable(); var operations = new DapperOperations(); var list = await operations.GetBirthdaysAsync(); foreach (var bd in list) { table.AddRow( bd.Id.ToString(), bd.FirstName, bd.LastName, bd.BirthDate.ToString(), bd.YearsOld.ToString()); } AnsiConsole.Write(table); ExitPrompt(); } public static Table CreateTable() { var table = new Table() .AddColumn("[b]Id[/]") .AddColumn("[b]First[/]") .AddColumn("[b]Last[/]") .AddColumn("[b]Birth date[/]") .AddColumn("[b]Age[/]") .Alignment(Justify.Left) .BorderColor(Color.LightSlateGrey); return table; } } ``` ### 計算列的摘要 並未詳細介紹程式碼的每個方面,這意味著在專案中採用技術之前需要花時間剖析程式碼以及使用了哪些 NuGet 套件。也可以考慮透過[Visual Studio 偵錯器](https://learn.microsoft.com/en-us/visualstudio/get-started/csharp/tutorial-debugger?view=vs-2022)執行程式碼。 偵錯是許多新手開發人員忽略的事情,也是 Visual Studio 的最佳功能之一。學習如何除錯並不需要花費大量時間。 第 2 課 - 重構程式碼 ------------- 許多人認為編碼的主要任務是讓程式碼正常工作,然後返回並重構程式碼。從個人經驗來看,這種情況一般不會發生。這就是開發人員需要在工作專案之外磨練技能的原因。 ![從未停止學習](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ju4889uewhkpfrfmp0w5.png) ### 實施例1 開發人員被要求將字串拆分為大寫字符,並將字串放在前面。 例如,給定 ThisIsATest,輸出將是 This Is A Test。開發者在網路上搜尋並找到以下內容。 ``` public static class StringExtensions { private static readonly Regex CamelCaseRegex = new(@"([A-Z][a-z]+)"); /// <summary> /// KarenPayne => Karen Payne /// </summary> [DebuggerStepThrough] public static string SplitCamelCase(this string sender) => string.Join(" ", CamelCaseRegex.Matches(sender) .Select(m => m.Value)); } ``` 這是可行的,但有一個更好的版本,在下面的範例中是由GitHub Copilot 編寫的,並且是第二次迭代,這意味著第一次copilot 被問到時,它提供了一個未經優化的解決方案,因為問題是如何提出的。 ``` [DebuggerStepThrough] public static string SplitCamelCase(this string input) { if (string.IsNullOrEmpty(input)) { return input; } Span<char> result = stackalloc char[input.Length * 2]; var resultIndex = 0; for (var index = 0; index < input.Length; index++) { var currentChar = input[index]; if (index > 0 && char.IsUpper(currentChar)) { result[resultIndex++] = ' '; } result[resultIndex++] = currentChar; } return result[..resultIndex].ToString(); } ``` ![更少的程式碼並不總是最好的](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i8u0zjrr2qjgot6tmav8.png) 等一下,第二個版本的程式碼多了很多,這個版本怎麼會更好呢?新手和經驗豐富的開發人員都有一種心態,就是程式碼行數越少越好,也許是為了提高可讀性。當然,開發人員應該始終努力編寫可讀的程式碼,但多行程式碼也可以輕鬆閱讀。 如何編寫可讀的程式碼。 - 使用有意義的變數名稱,例如在 for 語句中使用索引而不是 i 或使用firstName 而不是fName。 - 折疊程式碼而不是一行,如下所示 ``` public static class CheckedListBoxExtensions { public static List<T> CheckedList<T>(this CheckedListBox sender) => sender.Items.Cast<T>() .Where((_, index) => sender.GetItemChecked(index)) .Select(item => item) .ToList(); } ``` 而不是 ``` public static class CheckedListBoxExtensions { public static List<T> CheckedList<T>(this CheckedListBox sender) => sender.Items.Cast<T>().Where((_, index) => sender.GetItemChecked(index)).Select(item => item).ToList(); } ``` 下一步 --- 這裡有一些想法,即使是許多經驗豐富的開發人員也會避免,而不是你! - [泛型](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/generics) - [介面](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/interface) - 建立公共庫 - [JSON 序列化與反序列化](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/overview) 概括 -- 這些是成為更好的開發人員的更多技巧中的一些。實現這一目標的唯一方法是在專案之外不斷學習。 如果您的老闆或團隊領導者沒有提供時間來學習新技能,您可以每週花一兩個小時來學習和成長。 --- 原文出處:https://dev.to/karenpayneoregon/push-your-skills-2pho

編寫 SOLID React Hooks

SOLID 是較常用的設計模式之一。它在許多語言和框架中都很常用,也有一些文章介紹如何在 React 中使用它。 每篇關於 SOLID 的 React 文章都以稍微不同的方式介紹該模型,有些將其應用於元件,有些將其應用於 TypeScript,但很少有人將這些原理應用於鉤子。 由於 hooks 是 React 基礎的一部分,我們將在這裡看看 SOLID 原則如何應用於這些。 單一職責原則(SRP) ----------- Solid 中的第一個字母 S 是最容易理解的。本質上,它的意思是,讓一個鉤子/元件做一件事。 ``` // Single Responsibility Principle ``` ``` A module should be responsible to one, and only one, actor ``` 例如,看看下面的 useUser 鉤子,它會取得使用者和待辦事項,並將任務合併到使用者物件中。 ``` import { useState } from 'react' import { getUser, getTodoTasks } from 'somewhere' const useUser = () => { const [user, setUser] = useState() const [todoTasks, setTodoTasks] = useState() useEffect(() => { const userInfo = getUser() setUser(userInfo) }, []) useEffect(() => { const tasks = getTodoTasks() setTodoTasks(tasks) }, []) return { ...user, todoTasks } } ``` 這個鉤子並不牢固,它不遵守單一責任原則。這是因為它有責任獲取用戶資料和待辦任務,這是兩件事。 相反,上面的程式碼應該分為兩個不同的鉤子,一個用於獲取有關用戶的資料,另一個用於獲取任務。 ``` import { useState } from 'react' import { getUser, getTodoTasks } from 'somewhere' // useUser hook is no longer responsible for the todo tasks. const useUser = () => { const [user, setUser] = useState() useEffect(() => { const userInfo = getUser() setUser(userInfo) }, []) return { user } } // Todo tasks do now have their own hook. // The hook should actually be in its own file as well. Only one hook per file! const useTodoTasks = () => { const [todoTasks, setTodoTasks] = useState() useEffect(() => { const tasks = getTodoTasks() setTodoTasks(tasks) }, []) return { todoTasks } } ``` 這個原則適用於所有的鉤子和元件,它們都應該只做一件事。要問自己的事情是: 1. 這是一個應該顯示 UI(演示性)或處理資料(邏輯性)的元件嗎? 1. 這個鉤子應該處理什麼單一類型的資料? 1. 這個鉤子/元件屬於哪一層?它是處理資料儲存還是 UI 的一部分? 如果您發現自己建造的鉤子對上述每個問題都沒有單一答案,那麼您就違反了單一責任原則。 這裡值得注意的一件有趣的事情是第一個問題。這實際上意味著渲染 UI 的元件不應該處理資料。這意味著,要真正嚴格遵循這項原則,每個顯示資料的 React 元件都應該有一個鉤子來處理其邏輯和資料。換句話說,不應在顯示資料的相同元件中取得資料。 ### 為什麼在 React 中使用 SRP? 這種單一責任原則其實非常適合 React。 React 遵循基於元件的架構,這意味著它由組合在一起的小元件組成,因此它們一起可以建構並形成一個應用程式。元件越小,可重複使用的可能性就越大。這適用於元件和鉤子。 因此,React 或多或少是建立在單一職責原則上的。如果你不遵循它,你會發現自己總是在編寫新的鉤子和元件,並且很少重複使用它們中的任何一個。 違反單一責任原則將使您的程式碼難以測試。如果不遵循這個原則,您經常會發現您的測試文件有數百行,甚至可能多達 1000 行程式碼。 {% 嵌入 https://dev.to/perssondennis/how-to-use-mvvm-in-react-using-hooks-and-typescript-3o4m %} 開閉原理 (OCP) ---------- 讓我們繼續遵循開閉原則,畢竟這是 SOLID 中的下一個字母。 OCP 和 SRP 一樣是較容易理解的原則之一,至少其定義是如此。 ``` // Open/Closed Principle ``` ``` Software entities (classes, modules, functions, etc.) should ``` ``` be open for extension, but closed for modification ``` 對於最近開始使用 React 的傻瓜來說,這句話可以翻譯為: ``` Write hooks/component which you never will have a reason to ``` ``` touch again, only re-use them in other hooks/components ``` 回想一下本文前面所說的單一責任原則;在 React 中,您需要編寫小元件並將它們組合在一起。讓我們看看為什麼這有幫助。 ``` import { useState } from 'react' import { getUser, updateUser } from 'somewhere' const useUser = ({ userType }) => { const [user, setUser] = useState() useEffect(() => { const userInfo = getUser() setUser(userInfo) }, []) const updateEmail = (newEmail) => { if (user && userType === 'admin') { updateUser({ ...user, email: newEmail }) } else { console.error('Cannot update email') } } return { user, updateEmail } } ``` 上面的鉤子獲取用戶並返回它。如果使用者的類型是管理員,則允許該使用者更新其電子郵件。普通使用者不允許更新其電子郵件。 上面的程式碼絕對不會讓你被解僱。但這會惹惱你團隊中的後端人員,他會為他的孩子閱讀設計模式書籍作為睡前故事。我們就叫他皮特吧。 皮特會抱怨什麼?他會要求你重寫該元件,如下所示。將管理功能提升到它自己的 useAdmin 掛鉤,並讓 useUser 掛鉤除了那些應該可供普通用戶使用的功能之外沒有其他功能。 ``` import { useState } from 'react' import { getUser, updateUser } from 'somewhere' // useUser does now only return the user, // without any function to update its email. const useUser = () => { const [user, setUser] = useState() useEffect(() => { const userInfo = getUser() setUser(userInfo) }, []) return { user } } // A new hook, useAdmin, extends useUser hook, // with the additional feature to update its email. const useAdmin = () => { const { user } = useUser() const updateEmail = (newEmail) => { if (user) { updateUser({ ...user, email: newEmail }) } else { console.error('Cannot update email') } } return { user, updateEmail } } ``` 皮特為什麼要求更新?因為那個無禮挑剔的混蛋皮特寧願希望你現在花時間重寫那個鉤子,然後明天回來進行新的程式碼審查,而不是將來可能需要用一個微小的新 if 語句更新程式碼,如果有的話成為另一種類型的使用者。 好吧,這是消極的說法...樂觀的說法是,使用這個新的useAdmin 掛鉤,當您打算實現僅影響管理員用戶的功能時,或者當您打算實現僅影響管理員用戶的功能時,您不必更改useUser 掛鉤中的任何內容。 當新增新的使用者類型或更新 useAdmin 掛鉤時,無需弄亂 useUser 掛鉤或更新其任何測試。這意味著,當您新增的使用者類型(例如假使用者)時,您不必意外地將錯誤傳送給普通使用者。相反,您只需加入一個新的 userFakeUser 鉤子,您的老闆就不會在周五晚上 9 點給您打電話,因為客戶在發薪週末會遇到銀行帳戶顯示虛假資料的問題。 ![床下的前端開發人員](https://www.perssondennis.com/images/articles/write-solid-react-hooks/frontend-developer-under-the-bed.webp) *皮特的兒子知道要小心義大利麵式程式碼開發人員* ### 為什麼在 React 中使用 OCP? 一個 React 專案應該有多少個 hooks 和元件是有爭議的。每一個都需要渲染效果圖的代價。 React 不是 Java,其中 22 種設計模式導致 422 個類別用於簡單的 TODO 清單實作。這就是狂野西部網絡 (www) 的魅力所在。 然而,開放/封閉原則顯然也是在 React 中使用的少數模式。上面的鉤子範例是最小的,鉤子沒有做太多事情。隨著更多實質的掛鉤和更大的專案,這項原則變得非常重要。 這可能會花費您一些額外的鉤子,並且需要稍長的時間來實現,但是您的鉤子將變得更加可擴展,這意味著您可以更頻繁地重複使用它們。您將不必經常重寫測試,從而使掛鉤更加牢固。最重要的是,如果您從不接觸舊程式碼,則不會在舊程式碼中產生錯誤。 ![沒有破損的東西不要碰](https://www.perssondennis.com/images/articles/write-solid-react-hooks/dont-touch-what-is-not-broken.webp) *天知道不要碰沒有破損的東西* {% 嵌入 https://dev.to/perssondennis/react-anti-patterns-and-best-practices-dos-and-donts-3c2g %} 里氏替換原理 (LSP) ------------ 啊啊,這個名字……誰是利斯科夫?而誰來代替她呢?而這個定義,難道就沒有意義嗎? ``` If S subtypes T, what holds for T holds for S ``` 這個原則顯然是關於繼承的,在 React 或 JavaScript 中,繼承的實踐並不像大多數後端語言中那麼多。 JavaScript 在 ES6 之前甚至沒有類,ES6 是[在 2015/2016 年左右引入的,](https://caniuse.com/?search=class)作為基於原型的繼承的語法糖。 考慮到這一點,該原則的用例實際上取決於您的程式碼的外觀。類似 Liskov 的原則在 React 中有意義,可能是: ``` If a hook/component accepts some props, all hooks and components ``` ``` which extends that hook/component must accept all the props the ``` ``` hook/component it extends accepts. The same goes for return values. ``` 為了說明這一點,我們可以看一下兩個儲存鉤子:useLocalStorage 和 useLocalAndRemoteStorage。 ``` import { useState } from 'react' import { getFromLocalStorage, saveToLocalStorage, getFromRemoteStorage } from 'somewhere' // useLocalStorage gets data from local storage. // When new data is stored, it calls saveToStorage callback. const useLocalStorage = ({ onDataSaved }) => { const [data, setData] = useState() useEffect(() => { const storageData = getFromLocalStorage() setData(storageData) }, []) const saveToStorage = (newData) => { saveToLocalStorage(newData) onDataSaved(newData) } return { data, saveToStorage } } // useLocalAndRemoteStorage gets data from local and remote storage. // I doesn't have callback to trigger when data is stored. const useLocalAndRemoteStorage = () => { const [localData, setLocalData] = useState() const [remoteData, setRemoteData] = useState() useEffect(() => { const storageData = getFromLocalStorage() setLocalData(storageData) }, []) useEffect(() => { const storageData = getFromRemoteStorage() setRemoteData(storageData) }, []) const saveToStorage = (newData) => { saveToLocalStorage(newData) } return { localData, remoteData, saveToStorage } } ``` 透過上面的鉤子,useLocalAndRemoteStorage 可以被視為 useLocalStorage 的子類型,因為它與 useLocalStorage 執行相同的操作(保存到本地存儲),而且還通過將資料保存到其他位置來擴展 useLocalStorage 的功能。 這兩個鉤子有一些共享的屬性和回傳值,但是 useLocalAndRemoteStorage 缺少 useLocalStorage 接受的 onDataSaved 回呼屬性。傳回屬性的名稱也有不同的命名,本地資料在useLocalStorage中命名為data,而在useLocalAndRemoteStorage中命名為localData。 如果你問利斯科夫,這就違背了她的原則。實際上,當她嘗試更新Web 應用程式以在伺服器端保留資料時,她會非常憤怒,只是意識到她不能簡單地用useLocalAndRemoteStorage 鉤子替換useLocalStorage,只是因為一些懶惰的開發人員從未為useLocalAndRemoteStorage 鉤子實現onDataSaved回調。 利斯科夫會痛苦地更新鉤子來支持這一點。同時,她也會更新 useLocalStorage 掛鉤中的本地資料名稱,以符合 useLocalAndRemoteStorage 中的本地資料名稱。 ``` import { useState } from 'react' import { getFromLocalStorage, saveToLocalStorage, getFromRemoteStorage } from 'somewhere' // Liskov has renamed data state variable to localData // to match the interface (variable name) of useLocalAndRemoteStorage. const useLocalStorage = ({ onDataSaved }) => { const [localData, setLocalData] = useState() useEffect(() => { const storageData = getFromLocalStorage() setLocalData(storageData) }, []) const saveToStorage = (newData) => { saveToLocalStorage(newData) onDataSaved(newData) } // This hook does now return "localData" instead of "data". return { localData, saveToStorage } } // Liskov also added onDataSaved callback to this hook, // to match the props interface of useLocalStorage. const useLocalAndRemoteStorage = ({ onDataSaved }) => { const [localData, setLocalData] = useState() const [remoteData, setRemoteData] = useState() useEffect(() => { const storageData = getFromLocalStorage() setLocalData(storageData) }, []) useEffect(() => { const storageData = getFromRemoteStorage() setRemoteData(storageData) }, []) const saveToStorage = (newData) => { saveToLocalStorage(newData) onDataSaved(newData) } return { localData, remoteData, saveToStorage } } ``` 透過為鉤子提供通用介面(傳入的 props、傳出的返回值),它們可以變得非常容易交換。如果我們遵循里氏替換原則,繼承另一個鉤子/元件的鉤子和元件應該可以用它繼承的鉤子或元件替換。 ![擔心的利斯科夫](https://www.perssondennis.com/images/articles/write-solid-react-hooks/worried-liskov.webp) *當開發人員不遵循她的原則時,利斯科夫感到失望* ### 為什麼在 React 中使用 LSP? 儘管繼承在 React 中並不是很突出,但它肯定在幕後使用。 Web 應用程式通常可以有幾個外觀相似的元件。文字、標題、連結、圖示連結等都是類似類型的元件,可以從繼承中受益。 IconLink 元件可能會也可能不會包裝 Link 元件。無論哪種方式,它們都會受益於使用相同的介面(使用相同的 props)實作。這樣,您可以隨時在應用程式中的任何位置將 Link 元件替換為 IconLink 元件,而無需編輯任何其他程式碼。 鉤子也是如此。 Web 應用程式從伺服器取得資料。他們也可能使用本地儲存或狀態管理系統。這些最好可以共享道具以使它們可以互換。 應用程式可能會從後端伺服器取得使用者、任務、產品或任何其他資料。類似的函數也可以共享接口,從而更容易重複使用程式碼和測試。 {% 嵌入 https://dev.to/perssondennis/the-20-most-common-use-cases-for-javascript-arrays-2j8j %} 介面隔離原則(ISP) ----------- 另一個更明確的原則是介面隔離原則。定義很短。 ``` No code should be forced to depend on methods it does not use ``` 顧名思義,它與介面有關,基本上意味著函數和類別應該只實現它明確使用的介面。最容易實現這一點的方法是保持介面整潔,讓類別選擇其中的一些來實現,而不是被迫用它不關心的幾種方法來實現一個大介面。 例如,代表擁有網站的人的類別應該實現兩個接口,一個稱為 Person 的接口,描述有關此人的詳細訊息,另一個用於網站的接口,其中包含有關其擁有的網站的元資料。 ``` interface Person { firstname: string familyName: string age: number } interface Website { domain: string type: string } ``` 如果相反,建立一個單一介面網站,包括有關所有者和網站的訊息,則將違反介面隔離原則。 ``` interface Website { ownerFirstname: string ownerFamilyName: number domain: string type: string } ``` 你可能會想,上面的介面有什麼問題嗎?它的問題是它使介面不太可用。想想看,如果公司不是人,而是公司,你會怎麼做?公司其實沒有姓氏。然後您會修改介面以使其對人類和公司都可用嗎?或者您會建立一個新介面 CompanyOwnedWebsite 嗎? 然後,您最終會得到一個具有許多可選屬性的接口,或分別稱為 PersonWebsite 和 CompanyWebsite 的兩個接口。這些解決方案都不是最佳的。 ``` // Alternative 1 // This interface has the problem that it includes // optional attributes, even though the attributes // are mandatory for some consumers of the interface. interface Website { companyName?: string ownerFirstname?: string ownerFamilyName?: number domain: string type: string } // Alternative 2 // This is the original Website interface renamed for a person. // Which means, we had to update old code and tests and // potentially introduce some bugs. interface PersonWebsite { ownerFirstname: string ownerFamilyName: number domain: string type: string } // This is a new interface to work for a company. interface CompanyOwnedWebsite { companyName: string domain: string type: string } ``` ISP 遵循的解決方案如下所示。 ``` interface Person { firstname: string familyName: string age: number } interface Company { companyName: string } interface Website { domain: string type: string } ``` 透過上述適當的接口,代表公司網站的類別可以實現接口 Company 和 Website,但不需要考慮 Person 接口中的 firstname 和 familyName 屬性。 ### React 中使用 ISP 嗎? 所以,這個原則顯然適用於接口,這意味著它只應該在您使用 TypeScript 編寫 React 程式碼時才有意義,不是嗎? 當然不是!不輸入介面並不意味著它們不存在。到處都有,只是你沒有明確地輸入它們。 在 React 中,每個元件和鉤子都有兩個主要接口,輸入和輸出。 ``` // The input interface to a hook is its props. const useMyHook = ({ prop1, prop2 }) => { // ... // The output interface of a hook is its return values. return { value1, value2, callback1 } } ``` 使用 TypeScript,您通常會鍵入輸入接口,但輸出接口通常會被跳過,因為它是可選的。 ``` // Input interface. interface MyHookProps { prop1: string prop2: number } // Output interface. interface MyHookOutput { value1: string value2: number callback1: () => void } const useMyHook = ({ prop1, prop2 }: MyHookProps): MyHookOutput => { // ... return { value1, value2, callback1 } } ``` 如果鉤子不會將 prop2 用於任何用途,那麼它不應該成為其 props 的一部分。對於單一道具,可以輕鬆地將其從道具清單和介面中刪除。但是,如果 prop2 是物件類型,例如上一章不正確的 Website 介面範例,該怎麼辦? ``` interface Website { companyName?: string ownerFirstname?: string ownerFamilyName?: number domain: string type: string } interface MyHookProps { prop1: string website: Website } const useMyCompanyWebsite = ({ prop1, website }: MyHookProps) => { // This hook uses domain, type and companyName, // but not ownerFirstname or ownerFamilyName. return { value1, value2, callback1 } } ``` 現在我們有一個 useMyCompanyWebsite 鉤子,它有一個 website 屬性。如果鉤子中使用了網站介面的部分內容,我們不能簡單地刪除整個網站道具。我們必須保留 website 屬性,因此也保留ownerFirstname 和ownerFamiliyName 的介面屬性。這也意味著,該針對公司的掛鉤可以由人類擁有的網站所有者使用,即使該掛鉤可能不適用於該用途。 ### 為什麼在 React 中使用 ISP? 我們現在已經了解了 ISP 的含義,以及它如何應用於 React,即使不使用 TypeScript。透過查看上面的小例子,我們也看到了一些不遵循 ISP 的問題。 在更複雜的專案中,可讀性是最重要的。介面隔離原則的目的之一是避免混亂,避免不必要的程式碼的存在,這些程式碼只會破壞可讀性。不要忘記可測試性。您是否應該關心您實際未使用的道具的測試覆蓋率? 實現大型介面也迫使您將 props 設定為可選。導致更多的 if 語句來檢查函數的存在和潛在的誤用,因為在介面上顯示該函數將處理此類屬性。 {% 嵌入 https://dev.to/perssondennis/answers-to-common-nextjs-questions-1oki %} 依賴倒置原則(DIP) ----------- 最後一個原則,即 DIP,包括一些被廣泛誤解的術語。令人困惑的地方在於依賴反轉、依賴注入和控制反轉之間的差異。所以我們先聲明一下。 **依賴倒置** 依賴倒置原則(DIP)表示高階模組不應該從低階模組導入任何內容,兩者都應該依賴抽象。這意味著任何高階模組自然可能依賴它所使用的模組的實作細節,但不應該具有這種依賴性。 高級模組和低階模組的編寫方式應使它們都可以在不了解其他模組內部實現的任何細節的情況下使用。只要介面保持不變,每個模組都應該可以用它的替代實作來替換。 **控制反轉** 控制反轉(IoC)是用來解決依賴反轉問題的原理。它指出模組的依賴關係應由外部實體或框架提供。這樣,模組本身只需使用依賴項,而不必建立依賴項或以任何方式管理它。 **依賴注入** 依賴注入(DI)是實現 IoC 的常見方法。它透過建構函數或 setter 方法注入模組來提供對模組的依賴關係。這樣,模組就可以使用依賴項而無需負責建立它,這符合 IoC 原則。值得一提的是,依賴注入並不是實現控制反轉的唯一方法。 ### React 中使用 DIP 嗎? 澄清了這些術語,並知道 DIP 原則是關於依賴倒置的,我們可以再次看看這個定義是怎樣的。 ``` High-level modules should not import anything from low-level modules. ``` ``` Both should depend on abstractions ``` 這如何適用於 React? React 不是一個通常與依賴注入相關的函式庫,那我們該如何解決依賴倒置的問題呢? 這個問題最常見的解決方案是鉤子。鉤子不能算作依賴注入,因為它們被硬編碼到元件中,並且不可能在不更改元件實現的情況下用另一個鉤子替換鉤子。相同的鉤子將在那裡,使用相同的鉤子實例,直到開發人員更新程式碼。 但請記住,依賴注入並不是實現依賴倒置的唯一方法。 Hooks 可以被視為 React 元件的外部依賴,它有一個介面(它的 props),可以抽像出 hook 中的程式碼。這樣,鉤子就實現了依賴倒置的原則,因為元件依賴抽象接口,而不需要知道有關鉤子的任何細節。 React 中 DIP 的另一個更直觀的實作(實際上使用依賴注入)是 HOC 和上下文的使用。請參閱下面的 withAuth HOC。 ``` const withAuth = (Component) => { return (props) => { const { user } = useContext(AuthContext) if (!user) { return <LoginComponent> } return <Component {...props} user={user} /> } } const Profile = () => { // Profile component... } // Use the withAuth HOC to inject user to Profile component. const ProfileWithAuth = withAuth(Profile) ``` 上面顯示的 withAuth HOC 使用依賴項注入為使用者提供 Profile 元件。這個範例的有趣之處在於,它不僅顯示了依賴注入的一種用法,而且實際上包含了兩個依賴注入。 將使用者註入到設定檔元件並不是此範例中的唯一注入。 withAuth 鉤子實際上也透過 useContext 鉤子透過依賴注入來獲取使用者。在程式碼中的某個地方,有人聲明了一個將使用者註入上下文的提供者。該用戶實例甚至可以在執行時透過更新上下文中的用戶來更改。 ### 為什麼在 React 中使用 DIP? 儘管依賴注入不是與 React 相關的常見模式,但它實際上與 HOC 和上下文相關。鉤子從 HOC 和上下文中佔據了大量市場份額,也很好地證實了依賴倒置原則。 因此,DIP 已經內建到 React 庫本身中,當然應該使用。它既易於使用,又具有模組之間的鬆散耦合、鉤子和元件的可重複使用性和可測試性等優點。它也使得實現其他設計模式(例如單一職責原則)變得更加容易。 我不鼓勵的是,當確實有更簡單的解決方案可用時,請嘗試實施智慧解決方案並過度使用該模式。我在網路和書籍中看到了使用 React 上下文的建議,其唯一目的是實現依賴注入。像下面這樣的東西。 ``` const User = () => { const { role } = useContext(RoleContext) return <div>{`User has role ${role}`}</div> } const AdminUser = ({ children }) => { return ( <RoleContext.Provider value={{ role: 'admin' }}> {children} </RoleContext.Provider> ) } const NormalUser = ({ children }) => { return ( <RoleContext.Provider value={{ role: 'normal' }}> {children} </RoleContext.Provider> ) } ``` 儘管上面的範例確實將角色注入到 User 元件中,但為其使用上下文純粹是矯枉過正。當上下文本身有其用途時,應該在適當的時候使用 React 上下文。在這種情況下,一個簡單的道具可能是更好的解決方案。 ``` const User = ({ role }) => { return <div>{`User has role ${role}`}</div> } const AdminUser = () => <User role='admin' /> const NormalUser = () => <User role='normal' /> ``` {% cta https://2e015922.sibforms.com/serve/MUIFAGF3ypa0p6D6nTWI0MHVOIAC7q4TIJd0yXAhiBC9CswkNPnOlQBzeqSbR2XFM95gUn2G1IxWwCpDpDjkjk aaG9tz9UYhn\_O\_dWg1PPGS8kRM5ROREaJsslnGD8WEHszzZr0geJ9-g7lGsbn\_hTT-wZSKWa1C8ay4Ok85ozro %}訂閱我的文章{% endcta %} {% 嵌入 https://dev.to/perssondennis %} --- 原文出處:https://dev.to/perssondennis/write-solid-react-hooks-436o

如何建置:具有嵌入式 AI copilot 的待辦事項清單應用程式(Next.js、GPT4 和 CopilotKit)

**長話短說** -------- 待辦事項清單是每個開發人員的經典專案。在當今世界,學習如何使用人工智慧進行建構並在你的投資組合中加入一些人工智慧專案是很棒的。 今天,我將逐步介紹如何使用嵌入式 AI 副駕駛來建立待辦事項列表,以實現一些 AI 魔法🪄。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nw0jituk3y5tosz6f34u.gif) 我們將介紹如何: - 使用 Next.js、TypeScript 和 Tailwind CSS 建立待辦事項清單產生器 Web 應用。 - 使用 CopilotKit 將 AI 功能整合到待辦事項清單產生器中。 - 使用 AI 聊天機器人新增清單、將清單分配給某人、將清單標記為已完成以及刪除清單。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/guf0l2fiq1g1jv86o7bg.png) --- CopilotKit:建構應用內人工智慧副駕駛的框架 -------------------------- CopilotKit是一個[開源的AI副駕駛框架](https://github.com/CopilotKit/CopilotKit)。我們可以輕鬆地將強大的人工智慧整合到您的 React 應用程式中。 建造: - ChatBot:上下文感知的應用內聊天機器人,可以在應用程式內執行操作 💬 - CopilotTextArea:人工智慧驅動的文字字段,具有上下文感知自動完成和插入功能📝 - 聯合代理:應用程式內人工智慧代理,可以與您的應用程式和使用者互動🤖 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/myp7zashy99m33ya8kaf.gif) {% cta https://go.copilotkit.ai/bonnie %} 明星 CopilotKit ⭐️ {% endcta %} --- 先決條件 ---- 要完全理解本教程,您需要對 React 或 Next.js 有基本的了解。 以下是建立人工智慧驅動的待辦事項清單產生器所需的工具: - [Nanoid](https://github.com/ai/nanoid) - 一個小型、安全、URL 友善、唯一的 JavaScript 字串 ID 產生器。 - [OpenAI API](https://platform.openai.com/api-keys) - 提供 API 金鑰,讓您能夠使用 ChatGPT 模型執行各種任務。 - [CopilotKit](https://github.com/CopilotKit) - 一個開源副駕駛框架,用於建立自訂 AI 聊天機器人、應用程式內 AI 代理程式和文字區域。 專案設定和套件安裝 --------- 首先,透過在終端機中執行以下程式碼片段來建立 Next.js 應用程式: ``` npx create-next-app@latest todolistgenerator ``` 選擇您首選的配置設定。在本教學中,我們將使用 TypeScript 和 Next.js App Router。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0tc6he9eivkt3hhxnj3p.png) 接下來,安裝 Nanoid 套件及其相依性。 ``` npm i nanoid ``` 最後,安裝 CopilotKit 軟體套件。這些套件使我們能夠從 React 狀態檢索資料並將 AI copilot 新增至應用程式。 ``` npm install @copilotkit/react-ui @copilotkit/react-textarea @copilotkit/react-core @copilotkit/backend @copilotkit/shared ``` 恭喜!您現在已準備好建立由人工智慧驅動的待辦事項清單產生器。 **建立待辦事項清單產生器前端** ----------------- 在本節中,我將引導您完成使用靜態內容建立待辦事項清單產生器前端的過程,以定義生成器的使用者介面。 首先,請在程式碼編輯器中前往`/[root]/src/app`並建立一個名為`types`資料夾。在 types 資料夾中,建立一個名為`todo.ts`的文件,並新增以下程式碼來定義名為**`Todo`的 TypeScript 介面。** **`Todo`**介面定義了一個物件結構,其中每個待辦事項都必須具有**`id`** 、 **`text`**和**`isCompleted`**狀態,同時也可以選擇具有**`assignedTo`**屬性。 ``` export interface Todo { id: string; text: string; isCompleted: boolean; assignedTo?: string; } ``` 然後轉到程式碼編輯器中的`/[root]/src/app`並建立一個名為`components`的資料夾。在 Components 資料夾中,建立三個名為`Header.tsx` 、 `TodoList.tsx`和`TodoItem.tsx`的檔案。 在`Header.tsx`檔案中,新增以下程式碼,定義一個名為`Header`的功能元件,該元件將呈現生成器的導覽列。 ``` import Link from "next/link"; export default function Header() { return ( <> <header className="flex flex-wrap sm:justify-start sm:flex-nowrap z-50 w-full bg-gray-800 border-b border-gray-200 text-sm py-3 sm:py-0 "> <nav className="relative max-w-7xl w-full mx-auto px-4 sm:flex sm:items-center sm:justify-between sm:px-6 lg:px-8" aria-label="Global"> <div className="flex items-center justify-between"> <Link className="w-full flex-none text-xl text-white font-semibold p-6" href="/" aria-label="Brand"> To-Do List Generator </Link> </div> </nav> </header> </> ); } ``` 在`TodoItem.tsx`檔案中,新增以下程式碼來定義名為**`TodoItem`**的 React 功能元件。它使用 TypeScript 來確保類型安全性並定義元件接受的 props。 ``` import { Todo } from "../types/todo"; // Importing the Todo type from a types file // Defining the interface for the props that the TodoItem component will receive interface TodoItemProps { todo: Todo; // A single todo item toggleComplete: (id: string) => void; // Function to toggle the completion status of a todo deleteTodo: (id: string) => void; // Function to delete a todo assignPerson: (id: string, person: string | null) => void; // Function to assign a person to a todo hasBorder?: boolean; // Optional prop to determine if the item should have a border } // Defining the TodoItem component as a functional component with the specified props export const TodoItem: React.FC<TodoItemProps> = ({ todo, toggleComplete, deleteTodo, assignPerson, hasBorder, }) => { return ( <div className={ "flex items-center justify-between px-4 py-2 group" + (hasBorder ? " border-b" : "") // Conditionally adding a border class if hasBorder is true }> <div className="flex items-center"> <input className="h-5 w-5 text-blue-500" type="checkbox" checked={todo.isCompleted} // Checkbox is checked if the todo is completed onChange={() => toggleComplete(todo.id)} // Toggle completion status on change /> <span className={`ml-2 text-sm text-white ${ todo.isCompleted ? "text-gray-500 line-through" : "text-gray-900" // Apply different styles if the todo is completed }`}> {todo.assignedTo && ( <span className="border rounded-md text-xs py-[2px] px-1 mr-2 border-purple-700 uppercase bg-purple-400 text-black font-medium"> {todo.assignedTo} {/* Display the assigned person's name if available */} </span> )} {todo.text} {/* Display the todo text */} </span> </div> <div> <button onClick={() => deleteTodo(todo.id)} // Delete the todo on button click className="text-red-500 opacity-0 group-hover:opacity-100 transition-opacity duration-200"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-5 h-5"> <path strokeLinecap="round" strokeLinejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" /> </svg> </button> <button onClick={() => { const name = prompt("Assign person to this task:"); assignPerson(todo.id, name); }} className="ml-2 text-blue-500 opacity-0 group-hover:opacity-100 transition-opacity duration-200"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-5 h-5"> <path strokeLinecap="round" strokeLinejoin="round" d="M18 7.5v3m0 0v3m0-3h3m-3 0h-3m-2.25-4.125a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0ZM3 19.235v-.11a6.375 6.375 0 0 1 12.75 0v.109A12.318 12.318 0 0 1 9.374 21c-2.331 0-4.512-.645-6.374-1.766Z" /> </svg> </button> </div> </div> ); }; ``` 在`TodoList.tsx`檔案中,加入以下程式碼來定義名為**`TodoList`**的 React 功能元件。此元件用於管理和顯示待辦事項清單。 ``` "use client"; import { TodoItem } from "./TodoItem"; // Importing the TodoItem component import { nanoid } from "nanoid"; // Importing the nanoid library for generating unique IDs import { useState } from "react"; // Importing the useState hook from React import { Todo } from "../types/todo"; // Importing the Todo type // Defining the TodoList component as a functional component export const TodoList: React.FC = () => { // State to hold the list of todos const [todos, setTodos] = useState<Todo[]>([]); // State to hold the current input value const [input, setInput] = useState(""); // Function to add a new todo const addTodo = () => { if (input.trim() !== "") { // Check if the input is not empty const newTodo: Todo = { id: nanoid(), // Generate a unique ID for the new todo text: input.trim(), // Trim the input text isCompleted: false, // Set the initial completion status to false }; setTodos([...todos, newTodo]); // Add the new todo to the list setInput(""); // Clear the input field } }; // Function to handle key press events const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === "Enter") { // Check if the Enter key was pressed addTodo(); // Add the todo } }; // Function to toggle the completion status of a todo const toggleComplete = (id: string) => { setTodos( todos.map((todo) => todo.id === id ? { ...todo, isCompleted: !todo.isCompleted } : todo ) ); }; // Function to delete a todo const deleteTodo = (id: string) => { setTodos(todos.filter((todo) => todo.id !== id)); }; // Function to assign a person to a todo const assignPerson = (id: string, person: string | null) => { setTodos( todos.map((todo) => todo.id === id ? { ...todo, assignedTo: person ? person : undefined } : todo ) ); }; return ( <div> <div className="flex mb-4"> <input className="border rounded-md p-2 flex-1 mr-2" value={input} onChange={(e) => setInput(e.target.value)} onKeyDown={handleKeyPress} // Add this to handle the Enter key press /> <button className="bg-blue-500 rounded-md p-2 text-white" onClick={addTodo}> Add Todo </button> </div> {todos.length > 0 && ( // Check if there are any todos <div className="border rounded-lg"> {todos.map((todo, index) => ( <TodoItem key={todo.id} // Unique key for each todo item todo={todo} // Pass the todo object as a prop toggleComplete={toggleComplete} // Pass the toggleComplete function as a prop deleteTodo={deleteTodo} // Pass the deleteTodo function as a prop assignPerson={assignPerson} // Pass the assignPerson function as a prop hasBorder={index !== todos.length - 1} // Conditionally add a border to all but the last item /> ))} </div> )} </div> ); }; ``` 接下來,前往`/[root]/src/page.tsx`文件,新增以下程式碼,匯入`TodoList`和`Header`元件並定義名為`Home`的功能元件。 ``` import Header from "./components/Header"; import { TodoList } from "./components/TodoList"; export default function Home() { return ( <> <Header /> <div className="border rounded-md max-w-2xl mx-auto p-4 mt-4"> <h1 className="text-2xl text-white font-bold mb-4"> Create a to-do list </h1> <TodoList /> </div> </> ); } ``` 接下來,刪除`globals.css`檔案中的 CSS 程式碼並新增以下 CSS 程式碼。 ``` @tailwind base; @tailwind components; @tailwind utilities; body { height: 100vh; background-color: rgb(16, 23, 42); } ``` 最後,在命令列上執行命令`npm run dev` ,然後導航到 http://localhost:3000/。 現在您應該在瀏覽器上查看待辦事項清單產生器前端,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wmd8j3brrtiqackalul1.png) **使用 CopilotKit 將 AI 功能整合到待辦事項清單產生器** ------------------------------------- 在本節中,您將學習如何將 AI 副駕駛新增至待辦事項清單產生器,以使用 CopilotKit 產生清單。 CopilotKit 提供前端和[後端](https://docs.copilotkit.ai/getting-started/quickstart-backend)套件。它們使您能夠插入 React 狀態並使用 AI 代理在後端處理應用程式資料。 首先,我們將 CopilotKit React 元件新增至待辦事項清單產生器前端。 ### **將 CopilotKit 新增至待辦事項清單產生器前端** 在這裡,我將引導您完成將待辦事項清單產生器與 CopilotKit 前端整合以促進清單產生的過程。 首先,使用下面的程式碼片段匯入`/[root]/src/app/components/TodoList.tsx`檔案頂部的`useCopilotReadable`和`useCopilotAction`自訂掛鉤。 ``` import { useCopilotAction, useCopilotReadable } from "@copilotkit/react-core"; ``` 在`TodoList`函數內的狀態變數下方,新增以下程式碼,該程式碼使用`useCopilotReadable`掛鉤來新增將作為應用程式內聊天機器人的上下文產生的待辦事項清單。該鉤子使副駕駛可以讀取待辦事項清單。 ``` useCopilotReadable({ description: "The user's todo list.", value: todos, }); ``` 在上面的程式碼下方,新增以下程式碼,該程式碼使用`useCopilotAction`掛鉤來設定名為`updateTodoList`的操作,該操作將啟用待辦事項清單的產生。 此操作採用一個名為 items 的參數,該參數可以產生待辦事項列表,並包含一個根據給定提示產生待辦事項列表的處理程序函數。 在處理函數內部, `todos`狀態會使用新產生的 todo 清單進行更新,如下所示。 ``` // Define the "updateTodoList" action using the useCopilotAction function useCopilotAction({ // Name of the action name: "updateTodoList", // Description of what the action does description: "Update the users todo list", // Define the parameters that the action accepts parameters: [ { // The name of the parameter name: "items", // The type of the parameter, an array of objects type: "object[]", // Description of the parameter description: "The new and updated todo list items.", // Define the attributes of each object in the items array attributes: [ { // The id of the todo item name: "id", type: "string", description: "The id of the todo item. When creating a new todo item, just make up a new id.", }, { // The text of the todo item name: "text", type: "string", description: "The text of the todo item.", }, { // The completion status of the todo item name: "isCompleted", type: "boolean", description: "The completion status of the todo item.", }, { // The person assigned to the todo item name: "assignedTo", type: "string", description: "The person assigned to the todo item. If you don't know, assign it to 'YOU'.", // This attribute is required required: true, }, ], }, ], // Define the handler function that executes when the action is invoked handler: ({ items }) => { // Log the items to the console for debugging purposes console.log(items); // Create a copy of the existing todos array const newTodos = [...todos]; // Iterate over each item in the items array for (const item of items) { // Find the index of the existing todo item with the same id const existingItemIndex = newTodos.findIndex( (todo) => todo.id === item.id ); // If an existing item is found, update it if (existingItemIndex !== -1) { newTodos[existingItemIndex] = item; } // If no existing item is found, add the new item to the newTodos array else { newTodos.push(item); } } // Update the state with the new todos array setTodos(newTodos); }, // Provide feedback or a message while the action is processing render: "Updating the todo list...", }); ``` 在上面的程式碼下方,新增以下程式碼,程式碼使用`useCopilotAction`掛鉤來設定名為`deleteTodo`的操作,該操作使您能夠刪除待辦事項。 該操作採用名為id 的參數,該參數可讓您透過id 刪除待辦事項,並包含一個處理函數,該函數透過過濾掉具有給定id 的已刪除待辦事項來更新待辦事項狀態。 ``` // Define the "deleteTodo" action using the useCopilotAction function useCopilotAction({ // Name of the action name: "deleteTodo", // Description of what the action does description: "Delete a todo item", // Define the parameters that the action accepts parameters: [ { // The name of the parameter name: "id", // The type of the parameter, a string type: "string", // Description of the parameter description: "The id of the todo item to delete.", }, ], // Define the handler function that executes when the action is invoked handler: ({ id }) => { // Update the state by filtering out the todo item with the given id setTodos(todos.filter((todo) => todo.id !== id)); }, // Provide feedback or a message while the action is processing render: "Deleting a todo item...", }); ``` 之後,請前往`/[root]/src/app/page.tsx`檔案並使用下面的程式碼匯入頂部的 CopilotKit 前端套件和樣式。 ``` import { CopilotKit } from "@copilotkit/react-core"; import { CopilotPopup } from "@copilotkit/react-ui"; import "@copilotkit/react-ui/styles.css"; ``` 然後使用`CopilotKit`包裝`CopilotPopup`和`TodoList`元件,如下所示。 `CopilotKit`元件指定 CopilotKit 後端端點 ( `/api/copilotkit/` ) 的 URL,而`CopilotPopup`則呈現應用程式內聊天機器人,您可以提示產生待辦事項清單。 ``` export default function Home() { return ( <> <Header /> <div className="border rounded-md max-w-2xl mx-auto p-4 mt-4"> <h1 className="text-2xl text-white font-bold mb-4"> Create a to-do list </h1> <CopilotKit runtimeUrl="/api/copilotkit"> <TodoList /> <CopilotPopup instructions={ "Help the user manage a todo list. If the user provides a high level goal, " + "break it down into a few specific tasks and add them to the list" } defaultOpen={true} labels={{ title: "Todo List Copilot", initial: "Hi you! 👋 I can help you manage your todo list.", }} clickOutsideToClose={false} /> </CopilotKit> </div> </> ); } ``` 之後,執行開發伺服器並導航至[http://localhost:3000](http://localhost:3000/) 。您應該會看到應用程式內聊天機器人已整合到待辦事項清單產生器中。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ryeqnymp5lm397thpn5f.png) ### **將 CopilotKit 後端加入博客** 在這裡,我將引導您完成將待辦事項清單產生器與 CopilotKit 後端整合的過程,該後端處理來自前端的請求,並提供函數呼叫和各種 LLM 後端(例如 GPT)。 首先,在根目錄中建立一個名為`.env.local`的檔案。然後將下面的環境變數加入到儲存`ChatGPT` API 金鑰的檔案中。 ``` OPENAI_API_KEY="Your ChatGPT API key” ``` 若要取得 ChatGPT API 金鑰,請導覽至 https://platform.openai.com/api-keys。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x0bibiuouk5wvrxcuy2g.jpeg) 之後,轉到`/[root]/src/app`並建立一個名為`api`的資料夾。在`api`資料夾中,建立一個名為`copilotkit`的資料夾。 在`copilotkit`資料夾中,建立一個名為`route.ts`的文件,其中包含設定後端功能以處理POST 請求的程式碼。 ``` // Import the necessary modules from the "@copilotkit/backend" package import { CopilotRuntime, OpenAIAdapter } from "@copilotkit/backend"; // Define an asynchronous function to handle POST requests export async function POST(req: Request): Promise<Response> { // Create a new instance of CopilotRuntime const copilotKit = new CopilotRuntime({}); // Use the copilotKit to generate a response using the OpenAIAdapter // Pass the incoming request (req) and a new instance of OpenAIAdapter to the response method return copilotKit.response(req, new OpenAIAdapter()); } ``` 如何產生待辦事項列表 ---------- 現在轉到您之前整合的應用程式內聊天機器人,並給它一個提示,例如「我想去健身房做全身運動。加入到我應該遵循的鍛煉程序列表” 生成完成後,您應該會看到應遵循的全身運動程序列表,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/48n9ssymxhm5i2yv0pdf.png) 您可以透過向聊天機器人發出「將待辦事項清單指派給 Doe」之類的提示來將待辦事項清單指派給某人。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6r1f816c1o8da5z0t1kk.png) 您可以透過向聊天機器人提供「將待辦事項清單標記為已完成」等提示來將待辦事項清單標記為已完成。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qx2wpg6ee7hswjl68t4q.png) 您可以透過向聊天機器人發出「刪除待辦事項清單」之類的提示來刪除待辦事項清單。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ksoj6mtlxt8ag8opsocs.png) 恭喜!您已完成本教學的專案。 結論 -- [CopilotKit](https://copilotkit.ai/)是一款令人難以置信的工具,可讓您在幾分鐘內將 AI Copilot 加入到您的產品中。無論您是對人工智慧聊天機器人和助理感興趣,還是對複雜任務的自動化感興趣,CopilotKit 都能讓您輕鬆實現。 如果您需要建立 AI 產品或將 AI 工具整合到您的軟體應用程式中,您應該考慮 CopilotKit。 您可以在 GitHub 上找到本教學的源程式碼:https://github.com/TheGreatBonnie/AIpoweredToDoListGenerator --- 原文出處:https://dev.to/copilotkit/how-to-build-an-ai-powered-to-do-list-nextjs-gpt4-copilotkit-20i4

如何在AWS部署自己的網站

完全控制您的網站,並遵循我們的操作指南。 從頭開始建立和部署網站的好處: - 擁有程式碼並以您認為合適的方式控制它 - 了解 AWS 以及如何將網站部署到 AWS S3 - 了解 DNS 和 Route53 - 如何使用DevOps解決自動化問題 請繼續閱讀以開始使用。 > [在 Twitter 上關注我](https://x.com/rolfstreefkerk),隨時了解有關 AWS 等的最新文章。 您將需要以下內容才能開始 ------------ 1. **靜態站點**,我推薦以下框架之一(我已經使用過): ``` - [Hugo](https://gohugo.io/) ``` ``` - existing [themes](https://themes.gohugo.io/) will get you a website quick, such that you only have to modify color schemes and layouts. ``` ``` - or [Astro](https://astro.build/); if you’d like to integrate React, VueJS etc. code as well. ``` ``` - use their themes page [here](https://astro.build/themes/) to get a starting point. ``` 2. **一個[AWS 帳戶](https://aws.amazon.com/)**,需要信用卡才能設定。 3. **一個域名**,無論您在哪裡註冊。 ``` - In this how-to I use [Porkbun](porkbun.com) as my favorite registrar. ``` 4. **一台電腦**; ``` - [Terraform](https://www.terraform.io/)/[OpenTofu](https://opentofu.org/) installed. We use Terraform in this article. ``` ``` - [AWS CLI](https://aws.amazon.com/cli/) installed with profile configured you want to use for your website deployment. ``` ``` - [Git](https://git-scm.com/downloads) command line tooling. ``` ``` - your code editor of choice, I use [VSCode](https://code.visualstudio.com/). ``` 5. **一個[GitHub](https://github.com/)帳戶**,以便您可以建立我的範例儲存庫。 6. *(可選)電子郵件收件匣供應商*,我使用[Migadu](migadu.com) 。 今天我們在創造什麼? ---------- 我們正在建立以下服務和配置: -------------- - 用於將網站來源檔案傳送到的 AWS S3 儲存桶; - AWS CloudFront 發行版將在全球範圍內快取、優化向您的受眾交付的網站。 - AWS Route53 適合您; ``` - Email service records with DNSSec configuration, ``` ``` - You can then hookup a newsletter service like `ConvertKit.com` ``` ``` - Name Server Configuration for your domain; `yourwebsite.com` ``` ``` - and the CloudFront distribution to optimize your website hosting. ``` - 適用於 CI/CD 管道的 GitHub Actions,可在一分鐘內根據命令部署您的網站。 ![將您的網站部署到 AWS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/40hkfi68ukl8m6vw3y02.png) 在 AWS 上設定您的網域 ------------- 登入您的 AWS 控制台。 1. 登入後,前往 Route53,然後導覽至`Hosted zones` 。 2. 建立您的託管區域並輸入您的網站網域; `yourwebsite.com` 3. 記下`Hosted zone ID` ,我們將在下一步中使用 Terraform 將所有 Route53 記錄自動化到正確的網域。 ![Terraform 託管區域 ID](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ydaop4rxoper3xj7xt1.png) 如果您選擇使用 Terraform 實現自動化; - 從您的網域註冊商(Porkbun 等)匯出名稱伺服器。 - 將託管區域資源配置新增至[我的範例 Terraform 模組](https://github.com/rpstreef/terraform-static-site)中,並將其連接到需要託管區域 ID 的所有相關資源。 ### (可選)電子郵件託管 如果您想設定電子郵件託管解決方案,我使用 migadu.com,保持 Route53 網站開啟。 我們將向 Route53 匯入其他設定文字區塊,以使您的網域與收件匣服務搭配使用。 - 在郵件收件匣服務中,有一個`DNS Configuration`面板。 - 取得`BIND`記錄輸出,複製/貼上所有 DNS 記錄的文字。 > 如果您需要自動發現您的電子郵件的郵件伺服器; > 在提供的 DNS 記錄中檢查這些字串; `_autodiscover`或`autoconfig` ![取得BIND記錄](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1mfczsfjwddthnluqs9d.png) - 然後在 AWS Route53 中,針對您的託管區域; `Import zone file` ,然後複製貼上該對話方塊中的文字行。 ![Route53 託管區域文件](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dj7gfedvyf4egixw2s2c.png) - 現在您可以在郵件應用程式中新增新的電子郵件收件匣。 如果您有`_autodiscover`和/或`autoconfig` DNS 記錄,您可以; - 轉到您的電子郵件應用程式, - 使用新增收件匣;電子郵件和密碼。 - 完成,收件匣已新增,無需進一步配置。 否則,請記下您的郵件收件匣服務 SMTP 和 IMAP 伺服器設定。 使用 Terraform 自動設定您的 AWS 帳戶 -------------------------- 現在我們已經有了網域和郵件收件匣(可選),我們可以設定實際的網站部署。 透過 Forking 建立一個新專案:https://github.com/rpstreef/terraform-yourwebsite.com 這是一個將使用另一個 Git 儲存庫中的 Terraform 模組的範本; https://github.com/rpstreef/terraform-static-site ### 這個模板建立了什麼? 此範本將建立以下資源集; - Terraform 狀態的 S3 儲存桶 - `yourwebsite.com`的 S3 儲存桶 ``` - S3 CORS configuration for ConvertKit.com , this will allow CORS between ConvertKit JavaScript and your domain without warnings. ``` - SSL 的 ACM 憑證、 `*.yourwebsite.com`以及用於自動續訂 SSL 的 Route53 的 ACM 驗證記錄。 - Route53 A 和 AAAA 記錄 (IPv6) - Route53 DNSSec, ``` - only the first step! The second step must be done manually with your Domain Registrar. ``` - 用於重定向到索引的 Lambda 函數可確保您擁有良好的 URL。 ``` - E.g. https://yourwebsite.com/contact instead of https://yourwebsite.com/contact/index.html ``` - CloudFront 用於快取、網頁速度優化以及 SSL 保護。 ### 如何調整模板? 使模板適合您的網站。 請執行下列操作 - 更改`terraform.tfvars`檔案中的這些行: ``` - where you read `yourdomain.com`, ``` ``` - and your `hosted_zone_id` for `yourdomain.com`. ``` ``` - check 404 response at the bottom of the file to see if that matches up with your website structure. Additionally HTTP response codes can be added as blocks; `{}`. ``` 如果您需要額外的 CORS 設置,請按照與`f.convertkit.com`相同的方式加入額外規則。 ``` # General environment = "prod" region = "us-east-1" project = "yourdomain.com" # use tags to track your spend on AWS, seperate by 'product' for instance. tags = { environment = "production" terraform = true product = "yourdomain.com" } # Which config line used in .aws/config aws_profile = "yourdomain-profile" # Route53 hosted_zone_id = "Z000000000" # www.yourdomain.com product_name = "yourdomain" # avoid to use `.`, this cause an error. bucket_name = "yourdomain.com" # your site is deployed here. # S3 bucket CORS settings: bucket_cors = { rule1 = { allowed_headers = ["*"] allowed_methods = ["GET", "PUT", "POST"] allowed_origins = ["https://f.convertkit.com"] expose_headers = ["ETag"] max_age_seconds = 3000 } } domain_names = ["yourdomain.com", "www.yourdomain.com"] custom_error_responses = [{ error_code = 404 error_caching_min_ttl = 10 response_code = 200 response_page_path = "/404.html" }] ``` - 確保`project-state.tf`檔案中的配置正確; ``` - check the bucket name, ``` ``` - and the AWS `profile` name used, e.g. `yourwebsite-profile`. ``` ``` locals { projects_state_bucket_name = "tfstate-yourwebsite.com" } provider "aws" { region = "us-east-1" profile = "yourwebsite-profile" } terraform { # First we need a local state backend "local" { } # After terraform apply, switch to remote S3 terraform state /*backend "s3" { bucket = "tfstate-yourwebsite" key = "terraform.tfstate" region = "us-east-1" profile = "yourwebsite-profile" encrypt = true acl = "private" }*/ } ``` - 如果所有配置都檢查通過; ``` - run `terraform init`, this will download the dependent modules. ``` ``` - then; `terraform apply` > `yes` ``` - 完成部署後,記下輸出中的變數。我們稍後會需要它們。若要稍後檢索這些,請鍵入; `./environments/production`目錄中的`terraform output` 。 > 哪一個先出現?雞還是雞蛋? - 完成後,我們需要調整`project-state.tf`檔案: ``` - Place the `backend "local"` block in comments. ``` ``` - Remove the comments from the `backend "s3"` block. ``` ``` - Migrate the state from `local` to `S3`: ``` ``` - `terraform init -migrate-state` ``` ``` - type: `yes` to copy state from local to s3. ``` 現在它已完全部署,我們已將 Terraform 狀態儲存到 AWS S3,它不再位於您的磁碟上。如果您願意,可以刪除這些`tfstate`檔案。 ### 建立 DNSSec“信任鏈” DNSSec 的好處是建立了「信任鏈」。 這意味著,已驗證; - 您擁有該域名, - 當您導航到該網域時,資訊來自您的伺服器而不是來自其他人的伺服器(例如駭客等) > 如果您想了解有關 DNSSec 的更多訊息, [本文](https://www.csoonline.com/article/569685/dnssec-explained-why-you-might-want-to-implement-it-on-your-domain.html)是一本很好的入門讀物 現在要完成 DNSSec 配置,您必須手動修改網域註冊商資訊。 - 首先,取得DNSSec所需的`DS`記錄; `View information to create DS record` ![路由 53:DNSSec](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zrbuax3y4n65hjr0pm5v.png) - 然後,在下一個畫面中點擊; `Establish a Chain of Trust` 。 您將看到一個概述配置專案的表格。 如果您沒有在 Route53 上註冊網域,請按一下`Another Domain registrar` 在我的網域註冊商 Porkbun 上,畫面如下所示: ![Porkbun.com DNSSec](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yavg03ukeye7ienuuswp.png) - 在`dsData`區塊中輸入以下內容;左邊是 Porkbun 輸入欄位名稱,右邊是值,我將放置在`Route53`中使用的名稱: ``` - Key Tag: `Key tag` ``` ``` - DS Data Algorithm: `Signing algorithm type` ``` ``` - Digest Type: `Digest algorithm type` ``` ``` - Digest: `Digest` ``` > 如果您有不同的註冊商,您需要查看他們的文件,可能會略有不同。 #### 如何檢查您的配置是否有效? - 最後,使用這個線上工具;如果您獲得所有綠色複選標記,請使用 https://dnssec-debugger.verisignlabs.com/ 檢查您的網域。 如果都是綠色的,那就表示你的信任鏈已經成功建立了! 現在我們有了一個 DNSSec 安全性網域配置,其中包含一個透過 CloudFront 使用 SSL 的 S3 靜態託管網站。 - 高效率的 - 便宜的 - 和安全。 上傳您的網站 ------ 我們可以透過 AWS CLI 或 GitHub Actions 使用本地部署設定。 ### 使用腳本進行本地部署 根據您的系統(Linux、Windows、Mac),您可能需要變更此腳本。 在 Linux 上,我們可以使用以下 bash 腳本自動化您的網站部署,如下所示: ``` #! /bin/bash npm run build aws s3 sync dist s3://yourwebsite.com --profile yourwebsite-profile aws cloudfront create-invalidation --distribution-id <CloudFront Distr. Id> --paths "/*" --profile yourwebsite-profile ``` 確保; - 將`npm run build`替換為產生靜態網站建置的腳本。 - 如果您的網站建置位於另一個資料夾中,請取代`aws s3 sync dist`中的`dist` 。 - 替換`<CloudFront Distr. Id>`與您的 CloudFront 指派 ID。 ``` - you can find it in the outputs after `terraform apply` has finished; `cloudfront_distribution_id` ``` ### GitHub 操作 如果您喜歡使用自動化,那麼設定起來非常簡單且便宜。 #### 無論如何,這要花多少錢? |計劃|儲存|分鐘(每月)| | ----------- | -------- | ------------------- | | GitHub 免費 | 500 MB | 2,000 | | GitHub 專業版 | 1 GB | 3,000 | 在達到`Pro` `Minutes per month`上限之前,您可以部署多次: `storage`大小取決於您的儲存庫大小,對於大多數人來說,這將很難達到。 |作業系統 |分鐘乘數 | | ---------------- | ----------------- | | Linux | 1 | |窗戶| 2 | 我們選擇`Linux`建置環境,特別是`ubuntu-latest` ,以充分利用我們的空閒時間。 [在此處](https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions)查看有關 GitHub Action 定價的更多資訊。 #### 它是如何運作的? 若要使用 GitHub Actions 進行部署,請執行下列操作: - 首先,在網站的 GitHub 儲存庫中建立一個新檔案(位於`.github/workflows/deploy-on-comment.yml` 。 - 將以下程式碼新增至文件: > **筆記**;我假設您的網站是基於 Node (v20) 的。在需要的地方進行調整! ``` name: Deploy on Comment on: issue_comment: types: [created, edited] push: branches: - main jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Set up Node.js uses: actions/setup-node@v3 with: node-version: '20' - name: Install dependencies run: npm install - name: Build website run: npm run build - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: us-east-1 - name: Sync build output with S3 bucket run: aws s3 sync ./dist s3://your-s3-bucket-name - name: Invalidate CloudFront cache run: aws cloudfront create-invalidation --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} --paths "/*" ``` 需要在 GitHub 上建立幾個秘密變數,這些變數來自我們之前收到的 Terraform 輸出: - `AWS_ACCESS_KEY_ID` : - `AWS_SECRET_ACCESS_KEY` : - `CLOUDFRONT_DISTRIBUTION_ID` - 如果您需要再次尋找這些內容,請導覽至您的`terraform-yourwebsite.com` git 儲存庫,然後; ``` - `cd ./environments/production` ``` ``` - `terraform output` ``` - 在 GitHub 中的以下位置輸入它們: ![GitHub 秘密配置](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uddfhnlxicco614wxx72.png) - 例如,您現在可以`create an issue` ,詳細說明您網站上的更新。 ``` For each comment that is added, the deployment will start. ``` - 您可以按照`Actions`標籤中所採取的部署步驟和日誌進行操作。 ![GitHub問題,評論部署](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g4srw32r69d97j7za1dj.png) - (可選)如果您想要變更 GitHub 操作以使用`Pull request` ,您可以在部署腳本中進行修改。 ``` > For more alternative triggers, check out the [GitHub Actions documentation](https://docs.github.com/en/actions/using-workflows/triggering-a-workflow). ``` 您的網站上線了! -------- 現在,當您造訪您的網址時; `yourwebsite.com` ,一切都應該啟動並執行。 我們已經建造了什麼; - (可選)使用 Migadu 託管電子郵件(或選擇您擁有的任何託管服務);例如`[email protected]` ``` - You can connect this to your ConvertKit.com mailing list for example. ``` - 您自己的受 DNSSec 保護的個人網域。 ``` - You’ll be certain no hackers can hi-jack your domain. ``` - 您在 \[\[AWS\]\] 上使用 AWS S3 的靜態網站。 ``` - Free web-hosting! ``` - CloudFront 內容傳遞網路 (CDN),支援: ``` - SSL protected website. Form submits are all encrypted by default. ``` ``` - Increased performance in load speeds, latency across the globe. ``` ``` - URL rewrites for static websites. No `index.html` will be displayed when navigating. ``` ``` - and redirects for 404 not found pages. Your visitors will see the `404.html` page instead of an error text message. ``` 問題?來! ----- 您在 AWS 上遇到什麼困難? 您在 AWS 上部署時遇到問題嗎? 你會怎麼做? 請在評論或[Twitter](https://x.com/rolfstreefkerk)上告訴我 感謝您的寶貴時間,直到下次! --- 原文出處:https://dev.to/rolfstreefkerk/how-to-deploy-your-own-website-on-aws-1l05

加入我們參加下一個前端挑戰:六月版

等待已經結束!我們又帶著另一場前端挑戰賽回來了。 持續到**6 月 9 日**,前端挑戰:六月版將提供兩個六月主題提示: **CSS Art**和**Glam Up My Markup** 。對於那些錯過“一位元組解釋器”的人,我們保證很快就會恢復該提示! 像往常一樣,每個提示都會有一名獲勝者。這是贏得吹牛權利的兩次機會、DEV 商店的禮物和專屬 DEV 徽章。 另外,如果您不知道 - 徽章可以疊加在您的個人資料上以炫耀多次獲勝!但當然,這不是關於獲勝的目的地,而是關於*旅程*。我們希望這是一個挑戰自我並享受樂趣的機會。 繼續閱讀以了解每個提示以及如何參與! 我們的兩個提示: -------- ### CSS 藝術:六月 畫出您想到六月時想到的事情。根據您居住的地方,這可能是夏至或落葉和冬季的過渡。或者也許是父親節或驕傲月。 當您想到六月時,您會想到什麼,請告訴我們! 這是供任何想要直接參與的人使用的提交模板,但請在提交之前查看[官方挑戰頁面](https://dev.to/challenges/frontend-2024-05-29)上的所有評審標準和挑戰規則。 {% cta https://dev.to/new?prefill=---%0Atitle%3A%20%0Apublished%3A%20%0Atags%3A%20frontendchallenge%2C%20devchallenge%2C%20css%0A---% 0A%0A\_This%20is%20a%20submission%20for%20%5BFrontend%20Challenge%20v24.04.17%5D(https%3A%2F%2Fdev.to%2Fchallenges%2Ffrontend-2024-05-202020CSS% %3A%20June.\_%0A%0A%23%23%20靈感%0A%3C! --%20什麼%20是%20you%20highlighting%20today%3F%20--%3E%0A%0A%23 %23% 20Demo%20%0A%3C!--%20Show%20us%20your%20CSS%20Art!%20You%20can%20直接%20embed%20an%20editor%20into%20this%20post%20(%200%20(%20the%參見20FAQ%20section% 20of%20the%20challenge%20page)%20or%20you%20can%20share%20an%20image%20of%20your%20project%20and%20share%20a%200%20link%200%200%。 20--%3E% 0A%0A%23%23%20旅程%20%0A%3C!--%20告訴%20us%20about%20your%20process%2C%20what%20you%20learned%2C%20anything%20you %20are%20尤其%20proud%20of %2C%20what%20you%20hope%20to%20do%20next%2C%20etc.%20--%3E%0A%0A%3C!--%20Team%20Submissions%3A% 20Please%20pick%20one%20member%20to %20發布%20the%20submission%20和%20credit%20隊友%20by%20listing%20他們的%20DEV%20用戶名%20直接%20in%20the% 20the%20post。 20。 )。 CSS 藝術提交模板 {% 結束%} ### 讓我的標記變得迷人:海灘 使用 CSS 和 JavaScript 使下面的入門 HTML 標記美觀、互動且有用。我們提供了一個網站的入門模板,其中列出了世界上最好的海灘以及每個海灘的一些資訊。由於該模板不包含照片,因此您可能需要發揮創意,使其具有視覺吸引力。 您提交的內容應該比我們提供的 HTML 更有趣、更具互動性,但也應該可用且易於存取。您不應直接編輯提供的 HTML,除非是透過 JavaScript。我們期待風格和實質。您可以加入基本樣板,包括元標記等以用於演示目的。 ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Best Beaches in the World</title> </head> <body> <header> <h1>Best Beaches in the World</h1> </header> <main> <section> <h2>Take me to the beach!</h2> <p>Welcome to our curated list of the best beaches in the world. Whether you're looking for serene white sands, crystal-clear waters, or breathtaking scenery, these beaches offer a little something for everyone. Explore our top picks and discover the beauty that awaits you.</p> </section> <section> <h2>Top Beaches</h2> <ul> <li> <h3>Whitehaven Beach, Australia</h3> <p>Located on Whitsunday Island, Whitehaven Beach is famous for its stunning white silica sand and turquoise waters. It's a perfect spot for swimming, sunbathing, and enjoying the natural beauty of the Great Barrier Reef.</p> </li> <li> <h3>Grace Bay, Turks and Caicos</h3> <p>Grace Bay is known for its calm, clear waters and powdery white sand. This beach is ideal for snorkeling, diving, and enjoying luxury resorts that line its shore.</p> </li> <li> <h3>Baia do Sancho, Brazil</h3> <p>Baia do Sancho, located on Fernando de Noronha island, offers stunning cliffs, vibrant marine life, and crystal-clear waters, making it a paradise for divers and nature lovers.</p> </li> <li> <h3>Navagio Beach, Greece</h3> <p>Also known as Shipwreck Beach, Navagio Beach is famous for the rusting shipwreck that rests on its sands. Accessible only by boat, this secluded cove is surrounded by towering cliffs and azure waters.</p> </li> <li> <h3>Playa Paraiso, Mexico</h3> <p>Playa Paraiso, located in Tulum, offers pristine white sands and turquoise waters against the backdrop of ancient Mayan ruins. It's a perfect blend of history and natural beauty.</p> </li> <li> <h3>Anse Source d'Argent, Seychelles</h3> <p>Anse Source d'Argent is renowned for its unique granite boulders, shallow clear waters, and soft white sand. This beach is perfect for photography, snorkeling, and relaxation.</p> </li> <li> <h3>Seven Mile Beach, Cayman Islands</h3> <p>Stretching for seven miles, this beach offers soft coral sand, clear waters, and numerous activities such as snorkeling, paddleboarding, and enjoying beachside restaurants and bars.</p> </li> <li> <h3>Bora Bora, French Polynesia</h3> <p>Bora Bora is known for its stunning lagoon, overwater bungalows, and vibrant coral reefs. It's a perfect destination for honeymooners and those seeking luxury and tranquility.</p> </li> <li> <h3>Lanikai Beach, Hawaii</h3> <p>Lanikai Beach features powdery white sand and calm, clear waters, making it a favorite for swimming, kayaking, and enjoying the scenic views of the Mokulua Islands.</p> </li> <li> <h3>Pink Sands Beach, Bahamas</h3> <p>Pink Sands Beach is famous for its unique pink-hued sand, clear waters, and serene atmosphere. It's an idyllic spot for beachcombing, swimming, and relaxing in paradise.</p> </li> </ul> </section> </main> </body> </html> ``` 這是供任何想要直接參與的人使用的提交模板,但請在提交之前查看[官方挑戰頁面](https://dev.to/challenges/frontend-2024-05-29)上的所有評審標準和挑戰規則。 {% cta https://dev.to/new?prefill=---%0Atitle%3A%20%0Apublished%3A%20%0Atags%3A%20devchallenge%2C%20frontendchallenge%2C%20css%2C%20javascript%2C%20frontendchallenge%2C%20css%2C%20javascript%0Ajava ---%0A%0A\_This%20is%20a%20submission%20for%20%5BFrontend%20Challenge%20v24.04.17%5D((https%3A%2F%2Fdev.to%2Fchallenges%2FF-202F%2F%2Fdev.to%2Fchallenges%2FF-202F-frontend5-294-front05-29)% 2C%20Glam%20Up%20My%20Markup%3A%20Beaches\_%0A%0A%23%23%20What%20I%20Built%0A%0A%3C!--%20告訴%20us%20what%20you%20what0%200and% 20what %20you%20were%20looking%20to%20實作。 20direct% 20將%20an%20editor%20嵌入%20this%20post%20(參見%20the%20challenge%20頁的%20the%20FAQ%20section%20)%20或%20you%20can%20share%20an%% 20of%20your%20project%20和%20share%20a%20public%20link%20到%20the%20碼。告訴%20us%20about%20your%20process%2C %20what%20you%20已學會%2C%20anything%20you%20are%20尤其%20proud%20of%2C%20what%20you%20hope%20to%20what%20next20what%20you%20hope%20to%20what%C %20etc.%20--%3E%0A%0A% 3C! --%20Team%20Submissions%3A%20請%20pick%20one%20member%20to%20publish%20the%20submission%20and%20ammacre%20and%200%20and%200%20and%200%20and%200%20and%207%20and%207%20and%207%20and%207%20and%207%20and%207%20and%207%20and%207%20and%207%20and%207%20and%207%20and%207%20and%207%20and%207%20and%207%20and%207%。 20listing%20their%20DEV%20usernames%20directly%20in%20the%20body %20of%20the%20post。%20--%3E%0A%0A%3C! 20考慮%20為%20您的%20程式碼加入%20a%20許可證%20。 %20cover%20image%20to%20your%20post%20(如果%20you%20想要)。 --%3E %} 使我的標記提交模板更加迷人 {% 結束%} 如何參與 ---- 為了參與,您需要使用與每個提示關聯的提交範本來發布貼文。 請在提交之前查看我們的[評審標準、規則、指南和常見問題解答頁面,](https://dev.to/challenges/frontend-2024-05-29)以便您了解我們的參與指南和官方競賽規則(例如資格要求)。 重要的日子 ----- - 5 月 29 日:前端挑戰:六月版開始! - 6 月 9 日:提交截止時間為太平洋夏令時間晚上 11:59 - 6 月 11 日:得獎者公佈 ![驕傲月快樂](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExemNkZWg1YTQ5dWw3Mmg1Z2tpdTdzbnM3c2Zocm1obmwzajBxYXgzNSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/4C0NVY0fI5OOuTOCUe/giphy.gif) 我們很高興看到您六月的專案!問題?請在下面詢問他們。 祝你好運,編碼愉快! --- 原文出處:https://dev.to/devteam/join-us-for-the-next-frontend-challenge-june-edition-3ngl

關於 laravel 多語系 model 的小研究

在研究 TwillCMS 的時候 發現他的多語系 model 功能很好用 比方說 `posts` 的資料表 可以建立 `post_translations` 資料表 然後多語系資料存裡面 就能用一般存取 eloquent attribute 的方式取得翻譯 這是怎麼做到的呢? --- 首先 使用多語的 model 要先使用 Trait `A17\Twill\Models\Behaviors\HasTranslation` 然後要在 `$translatedAttributes` 裡面設定好 查看那個 Trait 的原始碼 會發現要使用 另一個 Trait `Astrotomic\Translatable\Translatable` 原來,底層其實用到了另一個套件 https://github.com/Astrotomic/laravel-translatable --- 繼續翻閱那個套件的原始碼 `Astrotomic\Translatable\Translatable` 會看到 `public function getAttribute` 實際上的功能,就是實作在這邊! 因為在 laravel eloquent 的原始碼 `Illuminate\Database\Eloquent\Model` 有下面這段 ``` public function __get($key) { return $this->getAttribute($key); } ``` 也就是讀取 model attribute 時,其實有經過 php magic function `__get()` 所以 `Translatable` 這個 Trait 就是擴充了這個函數 ``` public function getAttribute($key) { [$attribute, $locale] = $this->getAttributeAndLocale($key); if ($this->isTranslationAttribute($attribute)) { if ($this->getTranslation($locale) === null) { return $this->getAttributeValue($attribute); } // 這邊省略 } return parent::getAttribute($key); } ``` 然後會去呼叫 `public function getTranslation` 這樣,答案揭曉! --- 題外話,翻閱 eloquent model source code 的時候 我發現 `getAttribute` 不是直接寫在 model 內 而是在 `Illuminate\Database\Eloquent\Concerns\HasAttributes` 裡面 eloquent 因為功能太豐富 所以打散在 `Concerns` 資料夾內 其實就是 Traits 的意思 只是因為 `Seperation of Concerns` 原則太有名 所以用 Concerns 來命名 其實也有道理 就好像我們不會把放了一堆類別的資料夾 命名為 `Classes` 這樣 在 laravel source code 放了一堆 interface 的資料夾也是命名為 `Contracts` 的

您可能不知道的 HTML 標籤

開發者們大家好👋 在這篇文章中,我將分享一些在 HTML5 中加入的新的、有用的 html 標籤,用於編寫簡單快速的程式碼來建立複雜、動態、引人入勝且有效的網站。 讓我們開始吧🚀 對話 -- ➡ 現在您可以使用`<dialog>`標籤輕鬆建立對話方塊或彈出視窗。這是建立自訂模式對話方塊的好方法,而無需嚴重依賴**JavaScript** 。 ``` <dialog id="myDialog"> <p>This is a dialog box</p> <button onclick="document.getElementById('myDialog').close()">Close </button> </dialog> <button onclick="document.getElementById('myDialog').showModal()">Open Dialog </button> ``` 範本 -- ➡ `<template>`標籤用作儲存頁面載入時不希望顯示的客戶端內容的容器。可以使用**JavaScript**克隆該內容並將其插入到文件中。 ``` <button onclick="showContent()">Show hidden content</button> <template> <h2>Hello, This is Kiran</h2> <p>Thanks for reading this</p> </template> <script> function showContent() { let temp = document.getElementsByTagName("template")[0]; let clon = temp.content.cloneNode(true); document.body.appendChild(clon); } </script> ``` 圖片 -- ➡ 透過使用`<picture>`標籤,您可以為一張圖片定義多個來源,現在瀏覽器會根據螢幕尺寸、解析度選擇最佳的來源。這對於**響應式**設計特別有用。 ``` <picture> <source media="(min-width:650px)" srcset="img_pink_flowers.jpg"> <source media="(min-width:465px)" srcset="img_white_flower.jpg"> <img src="img_orange_flowers.jpg" alt="Flowers" style="width:auto;"> </picture> ``` 儀表 -- ➡ `<meter>`標籤可用來表示已知範圍內的標量測量,例如磁碟使用情況或查詢結果的相關性。它有助於**直觀地**顯示一定範圍內的值。 ``` <label for="diskUsage">Disk Usage:</label> <meter id="diskUsage" value="0.6">60%</meter> ``` 輸出 -- ➡ `<output>`標籤代表計算的結果。它可以與**JavaScript**一起使用來顯示計算**結果**。 ``` <form oninput="result.value=parseInt(a.value)+parseInt(b.value)"> <input type="number" id="a" value="50"> + <input type="number" id="b" value="25"> = <output name="result" for="a b">75</output> </form> ``` 進步 -- ➡ `<progress>`標籤代表**任務**的完成進度,例如下載或檔案上傳。 ``` <label for="fileProgress">File upload progress:</label> <progress id="fileProgress" value="70" max="100">70%</progress> ``` 標記 -- ➡ `<mark>`標籤用於反白顯示文字。它對於您想要**突出顯示**匹配文字的搜尋結果頁面特別有用。 ``` <p>The word <mark>highlighted</mark> is important.</p> ``` 縮寫 -- ➡ `<abbr>`標籤用於定義縮寫詞或首字母縮寫詞,在**標題**屬性中提供完整的**描述**。 ``` <p>I'm a true<abbr title="Marvel Cinematic Universe">MCU</abbr>fan.</p> ``` 時間 -- ➡ `<time>`標籤用於表示日期、時間或持續時間。它對於使與時間相關的資料變得機器可讀非常有用。 ``` <p>The concert starts at <time datetime="20:00">8 PM</time>.</p> ``` 巴迪 -- ➡ `<bdi>`標籤用於隔離可能與外部其他文字的格式方向不同的文字部分。無論涉及何種語言或文字**方向**,它都能確保您的網頁內容保持**一致**和可讀。 ``` <ul> <li>Product: <bdi>ABC1234</bdi></li> <li>Product: <bdi>مرحبا5678</bdi></li> </ul> ``` 沃伯 -- ➡ `<wbr>`標記指定文字可以在何處**換行**(如有必要)。這對於長單字或 URL 很有用。 ``` <p>Thisisaverylongword<wbr>thatmightneedbreaking.</p> ``` 主要的 --- ➡ `<main>`標籤用於指定文件的**主要內容**。它每頁只能使用一次,並且排除文件中重複的內容,例如頁首、頁尾、導覽和側邊欄。 ``` <main> <h1>Welcome to my blog post</h1> <p>Today we will learn some new html tags</p> </main> ``` 圖片標題 ---- ➡ `<figcaption>`標籤用於為圖形提供**標題**。 ``` <figure> <img src="Thanos.jpg" alt="Thanos image"> <figcaption>Thanos snapping his fingers</figcaption> </figure> ``` 這就是本文的內容。 感謝您的閱讀❤ 在 👉 https://x.com/kiran\_\_a\_\_n X https://github.com/Kiran1689 GitHub 上找到我 https://dev.to/dev\_kiran --- 原文出處:https://dev.to/dev_kiran/html-tags-you-might-not-know-about-3gk7

可以使用 Python 和 AI 建構的 25 個專案

今天,我們將介紹 25 個可以使用 Python 和 AI 模型建構的副專案。 大多數專案都會有生產級程式碼,因此您可以學到很多東西。 我們開始做吧! --- [Taipy](https://github.com/Avaiga/taipy) - 將資料和人工智慧演算法整合到生產就緒的 Web 應用程式中。 ------------------------------------------------------------------------- ![打字](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wd10iiofzmt4or4db6ej.png) 大多數初始專案都會使用 Taipy,所以讓我們討論一下這個概念的用例。 Taipy 是一個開源 Python 庫,可用於輕鬆的端到端應用程式開發,具有假設分析、智慧管道執行、內建調度和部署工具。 需要明確的是,Taipy 用於為基於 Python 的應用程式建立 GUI 介面並改進資料流管理。 關鍵是性能,而 Taipy 是最佳選擇。 雖然 Streamlit 是一種流行的工具,但在處理大型資料集時,其效能可能會顯著下降,這使得它在生產級使用上不切實際。 另一方面,Taipy 在不犧牲性能的情況下提供了簡單性和易用性。透過嘗試 Taipy,您將親身體驗其用戶友好的介面和高效的資料處理。 ![大資料支持](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xnvk0tozn0lgj083rzcb.gif) Taipy 有許多整合選項,可以輕鬆地與領先的資料平台連接。 ![整合](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7yv31uir3erina587zp8.png) 開始使用以下命令。 ``` pip install taipy ``` 他們還使用分散式運算提高了效能,但最好的部分是 Taipy,它的所有依賴項現在都與 Python 3.12 完全相容,因此您可以在使用 Taipy 進行專案的同時使用最新的工具和程式庫。 您可以閱讀[文件](https://docs.taipy.io/en/latest/)。 ![用例](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xdvnbejf9aivxmqsd3hx.png) 另一個有用的事情是,Taipy 團隊提供了一個名為[Taipy Studio](https://docs.taipy.io/en/latest/manuals/studio/)的 VSCode 擴充功能來加速 Taipy 應用程式的建置。 ![太皮工作室](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kc1umm5hcxes0ydbuspb.png) 如果您想閱讀部落格來了解程式碼庫結構,您可以閱讀 HuggingFace[的使用 Taipy 在 Python 中為您的 LLM 建立 Web 介面](https://huggingface.co/blog/Alex1337/create-a-web-interface-for-your-llm-in-python)。 嘗試新技術通常很困難,但 Taipy 提供了 10 多個演示教程,其中包含程式碼和適當的文件供您遵循。我將詳細討論其中一些專案! ![示範](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4wigid2aokt6spkkoivr.png) 這些用例非常驚人,所以一定要檢查一下。 Taipy 在 GitHub 上有超過 8,500 個 Star,並且處於`v3.1`版本,因此它們正在不斷改進。 https://github.com/Avaiga/taipy Star Taipy ⭐️ --- 1.[實時污染儀表板](https://docs.taipy.io/en/release-3.0/knowledge_base/demos/pollution_sensors/) ----------------------------------------------------------------------------------------- 使用工廠周圍的感測器測量空氣品質的用例,展示 Taipy 儀表板流資料的能力。 資料在另一台伺服器上產生並透過 WebSocket 發送到此 Taipy 應用程式。 然後 Taipy 處理資料並將其顯示在儀表板上。 當收到新資料時,儀表板會即時更新。 ![即時污染儀表板](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a0i6pm0ggx6yb655uvd1.png) 如果您想要有關可視化資料流的教程,請查看[有關多線程的文件](https://docs.taipy.io/en/release-3.0/knowledge_base/tips/multithreading/)。 它討論瞭如何建立發送者腳本和接收者腳本,包括套接字參數等。 它涉及到前端和後端的概念。 查看[現場演示](https://realtime-pollution.taipy.cloud/)。 https://github.com/Avaiga/demo-realtime-pollution Star 即時污染儀表板 ⭐️ --- 2.[詐欺檢測](https://github.com/Avaiga/demo-fraud-detection) -------------------------------------------------------- Taipy 應用程式可分析信用卡交易以偵測詐欺行為。 ![詐欺檢測演示](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v90fuimlplxuj4llp1zb.png) 它顯示信用卡交易清單。 用戶可以選擇一個日期範圍來預測詐欺。 然後,該應用程式將使用 XGB 模型將潛在的詐欺交易標記為紅色或黃色。 用戶可以選擇一個交易來查看模型預測的解釋,以及客戶的其他交易。 ![詐欺檢測演示](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8odxbhc9h3z3vzm4i4yk.png) 使用者還可以選擇模型的閾值。閾值是模型輸出,高於該閾值的交易將被視為詐欺。使用者可以根據顯示的混淆矩陣並透過查看誤報和漏報交易來選擇模型。 ![詐欺檢測演示](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s8ri5h0ky5zie53g2wk7.png) 查看[現場演示](https://fraud-detection.taipy.cloud/Transactions)。 https://github.com/Avaiga/demo-fraud-detection 明星詐欺偵測 ⭐️ --- 3.[新冠儀表板](https://github.com/Avaiga/demo-covid-dashboard) --------------------------------------------------------- 這裡使用 2020 年的 Covid 資料集。 頁面顯示有關 COVID-19 的不同圖表和資訊。還有一個預測頁面來預測傷亡人數。 該應用程式包括四個部分,如下所示: > ✅ 國。 - 特定國家/地區的 COVID-19 統計資料。 - 在累積資料視圖和密度資料視圖之間輕鬆切換。 - 用於動態資料探索的互動式長條圖。 - 餅圖顯示病例分佈(確診、復健、死亡)。 ![國家](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j22qqjmpd39qwwb4it4o.png) > ✅ 地圖。 透過動態可縮放顏色編碼地圖直觀地展示 COVID-19 的影響。 ![地圖](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ngdy1pkmujgdel1rz81.png) > ✅ 預測。 透過為不同的預測日期和不同的國家建立場景來產生 COVID-19 預測。 這會分別使用 ARIMA 模型和線性迴歸模型產生 2 個不同的預測(橘色的預測\_x 和綠色的預測\_y)。 透過指定名稱、指定預測日期、選擇國家/地區並點擊「提交」按鈕繼續來啟動新場景。 您可以在“結果”部分的“場景”選項卡中存取它。 ![預測](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8idl5ys0zhkztexujcmh.png) > ✅ 世界。 全球 COVID-19 統計資料透過折線圖和圓餅圖進行匯總。透過更改`Absolute`和`Relative`之間的切換可以看到 Covid 國家/地區影響的比較。 ![世界](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y9kd3rjxa7qjqne798ua.png) 總的來說,對於您的業餘專案和建立非常酷的專案來說,這是一個非常有用的應用程式:) 您可以查看[現場演示](https://covid-dashboard.taipy.cloud/Country)。 https://github.com/Avaiga/demo-covid-dashboard Star Covid 儀表板 ⭐️ --- 4.[建立 LLM 聊天機器人](https://github.com/Avaiga/demo-chatbot) -------------------------------------------------------- 該演示展示了 Taipy 使最終用戶能夠使用 LLM 執行推理的能力。在這裡,我們使用 GPT-3 建立一個聊天機器人,並將對話顯示在互動式聊天介面中。 ![法學碩士聊天機器人](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/olaw6u8w3fj2wrg0au2r.png) 下面給出了主要函數,該函數將字串提示(即用戶訊息)作為輸入,並從 LLM 傳回回應字串。 ``` def request(state: State, prompt: str) -> str: """ Send a prompt to the GPT-3 API and return the response. Args: - state: The current state. - prompt: The prompt to send to the API. Returns: The response from the API. """ response = state.client.chat.completions.create( messages=[ { "role": "user", "content": f"{prompt}", } ], model="gpt-3.5-turbo", ) return response.choices[0].message.content ``` 您可以閱讀有關如何建立此 LLM 聊天機器人的[完整文件](https://docs.taipy.io/en/release-3.0/knowledge_base/tutorials/chatbot/)。 最好的部分是,您可以根據您的使用情況輕鬆更改程式碼以使用任何其他 API 或模型。 您可以查看[現場演示](https://demo-llm-chat.taipy.cloud/)。 https://github.com/Avaiga/demo-chatbot 明星聊天機器人 ⭐️ --- 5.[即時人臉辨識](https://github.com/Avaiga/demo-face-recognition) ----------------------------------------------------------- 該演示將人臉辨識無縫整合到我們的平台中,借助 OpenCV 庫,使用網路攝影機提供即時人臉偵測體驗。 ![即時人臉辨識](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qs9kpit8byxsqq67mhql.png) 您可以非常輕鬆地使用它: A。打開應用程式時,您會透過網路攝影機看到自己。你的臉周圍有一個紅色方塊,上面寫著別人的名字。 b.透過點擊`Capture`按鈕並多次輸入您的名字來訓練模型辨識您。 C。現在點選`Re-train`按鈕。現在應該會出現您的名字。模型現在認出了您。 查看[現場演示](https://face-recognition.taipy.cloud/)。 確保在瀏覽器中啟用相機設置,這是首要條件! 進行人臉偵測和人臉辨識的程式碼位於`src/demo/faces.py`下。自述文件中提供了完整的目錄結構。 https://github.com/Avaiga/demo-face-recognition 明星人臉辨識 ⭐️ --- 6.[庫存可視化](https://github.com/Avaiga/demo-stock-visualization) ------------------------------------------------------------- 在金融市場領域,資料為王。快速、輕鬆地視覺化歷史股票資料並做出預測的能力對於投資者和金融分析師來說至關重要。 這是一個股票資料儀表板,具有互動式視覺元素,可以視覺化歷史股票資料並對 1 至 5 年內的股票進行預測。 ![庫存視覺化](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dz5jagdfvcnup1jq7qgx.png) 由 Facebook 使用 Taipy 和[Prophet Library](https://facebook.github.io/prophet/docs/quick_start.html)建構。此示範適用於高於 3.8 的 Python 版本。 您可以這樣使用它: A。選擇您想要預測的股票程式碼。 b.打開歷史資料面板。 C。選擇預測週期(從 1 到 5)。 d.點選`PREDICT`按鈕。 e.在預測資料面板中查看您的預測。 F。使用不同的程式碼重複嘗試以比較結果。 您也可以透過點擊底部的`More info`按鈕以表格形式取得預測範圍。 您可以在[src 目錄](https://github.com/Avaiga/demo-stock-visualization/tree/develop/src)下找到負責的主要原始碼。 這個完全互動的 Web 應用程式只需不到 120 行 Python 程式碼即可建立。 查看[現場演示](https://stock-visualization.taipy.cloud/)。 https://github.com/Avaiga/demo-stock-visualization 明星股票視覺化 ⭐️ --- 7.[情感分析](https://github.com/Avaiga/demo-sentiment-analysis)。 ------------------------------------------------------------ 情感分析就像一個機器人,可以從人們的言語中讀出他們的感受。 它會根據快樂、悲傷或憤怒等字眼來判斷它們的感覺是好是壞。然後,它告訴我們大多數人在說話時是高興還是悲傷。 因此,只需看看人們所說的內容,它就可以幫助我們了解人們對事物(例如電影或遊戲)的感受! 簡而言之,它是自然語言處理(NLP)中的一種技術,用於確定文本中傳達的情緒基調。它可以幫助企業和個人更好地掌握書面內容所表達的情感和語氣。 結果是一個兩頁的應用程式,它使用情感分析模型來分析輸入和整個文本。 第一頁分析使用者輸入,而第二頁讓使用者選擇要上傳的檔案(文字)。該文本將被分析並顯示背後的情感。 > ✅ 第 1 頁:Line - 分析使用者輸入 我們的情緒分析應用程式的初始頁面名為“Line”,用於即時分析使用者輸入。無論是簡短的句子還是較長的段落,只需將文字輸入或貼到輸入框中,Taipy 就會快速評估文本中傳達的情感。 ![第1頁情緒分析](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/826k2if80arn6y3uz3dz.png) > ✅ 文字 - 上傳和分析文字文件 第二個頁面名為“文字”,允許使用者上傳整個文字檔案 (.txt) 以進行全面的情感分析。 用戶可以從他們的設備中選擇一個文字文件,該應用程式將提供對整個文件所表達的情緒的洞察。 此功能對於處理較長的文字(例如文章、報告或廣泛的客戶回饋)非常有用。 ![第2頁情緒分析](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nb3xmtfi7fz6vm8807ph.png) 您可以查看[現場演示](https://sentiment-analysis.taipy.cloud/line)。 https://github.com/Avaiga/demo-sentiment-analysis 明星情感分析 ⭐️ --- 8.[漂移檢測](https://github.com/Avaiga/demo-drift-detection)- 檢測糖尿病資料集中的漂移。 ----------------------------------------------------------------------- ![漂移檢測](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ey1f4ygskvkk6dwqc2c8.png) 資料漂移是主要在機器學習中使用的概念,其中推理資料的分佈偏離訓練資料的分佈。 各種因素,例如底層資料來源的變化、資料收集過程的變化或資料儲存過程的變化,都可能導致資料漂移。 這通常會導致稱為訓練服務偏差的效能問題,其中用於推理的模型不用於推理資料的分佈並且無法泛化。 存在統計測試來檢測資料集中的漂移。這些檢定計算兩個系列來自同一分佈的機率。如果機率低於閾值,我們認為存在漂移。 如何使用該應用程式? ✅ 選擇比較資料集。 在這裡,我們選擇 data\_big,這是一個與參考資料集類似的資料集,但其中的行具有較高的血壓值。我們在血壓分佈圖上看到,與綠色參考資料集相比,紅色比較資料集的分佈向右移動。 ![資料集](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r1752b8elnpwalycyex4.png) ✅ 按一下此處執行場景。 ![執行應用程式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cel6qa0xxiplxgghwhyi.png) ✅ 在頁面底部可視化結果。 在這裡,我們看到血壓列的柯爾莫哥洛夫檢定的 p 值低於 0.05,這意味著兩個血壓資料集來自同一分佈的機率低於 5%。我們可以拒絕兩個資料集來自相同分佈的假設,並得出血壓列存在漂移的結論。 ![視覺化](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0mh8lftfjiypr27p10ec.png) 您可以按照下面所附的即時示範中的步驟操作,並在 GitHub 上查看原始程式碼。 這使用資料管道來比較資料集並檢測漂移。 ![資料管道](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7s7v5hpicjyoe2eojwx8.png) 查看[現場演示](https://drift-detection.taipy.cloud/)。 https://github.com/Avaiga/demo-drift-detection 星漂移偵測 ⭐️ --- 9.[錢包明智](https://github.com/Ujj1225/from_Taipy-walletWISE) ---------------------------------------------------------- ![錢包明智](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vva4tu9dxrz9fgaiavlb.png) WalletWise 就像是我們財務的友善幫手,幫助我們追蹤收入和支出。它使用 Gemini 進行交易,使用 Taipy 來了解支出。 一些不錯的功能是: ✅ 使用者可以輸入他們的收入和支出以及部門作為標題。這使他們能夠了解和探索他們從哪個部門賺了多少錢以及在哪個部門花了多少錢。 ✅ 對使用者的收入和支出進行分析,以數學方式顯示,並顯示 7 個做出更好、更明智的財務決策的提示。 ✅ 實現了一個視覺化工具,您可以在其中看到您賺錢的不同標題和您花錢的不同標題。 這是一個非常優秀的用例,並且在創造力方面非常好。 您可以閱讀[安裝說明](https://github.com/Ujj1225/from_Taipy-walletWISE?tab=readme-ov-file#installation)並查看[專案演示](https://github.com/Ujj1225/from_Taipy-walletWISE?tab=readme-ov-file#demo)。 https://github.com/Ujj1225/from\_Taipy-walletWISE Star WalletWise ⭐️ --- 10.[太皮象棋](https://github.com/KorieDrakeChaney/taipy-chess) ---------------------------------------------------------- ![棋](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xasxqldf7z1q5ie3r4nn.png) 所有應用程式中我最喜歡的一個,因為我喜歡國際象棋。哈哈! 這是一個基於20,000盤棋的國際象棋視覺化工具。您可以查看所有比賽、他們參加的開局、對手、表現最好的開局以及最成功的開局。 您可以查看資料的熱圖和圖表。 ![總移動熱圖](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jh1llp4vx24xtxjji042.png) 總移動熱圖 ![棋子的第一個動作](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5s9do6lop5immbmjtqwa.png) 棋子的第一個動作 您可以觀看 Korie 的[示範](https://github.com/KorieDrakeChaney/taipy-chess?tab=readme-ov-file#demo)。我愛它 :) 這清楚地表明我們使用 Taipy 的可能性沒有限制。 https://github.com/KorieDrakeChaney/taipy-chess Star Taipy Chess ⭐️ --- 11.[奧運獎牌](https://github.com/enarroied/Olympic-Medals-Taipy-App) ---------------------------------------------------------------- ![奧運獎牌](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qkl0llaopepdb1s2jxy2.png) 這是一個 Taipy 儀表板,顯示從現代奧運會開始到 2024 年初頒發的奧運獎牌資訊(即不包括 2024 年巴黎奧運會)。 ✅ 儀表板有兩個選項卡: - 一個選項卡顯示所有奧運獎牌的總結資料 - 第二個選項卡重點關注奧林匹克委員會(國家,但也包括特別委員會,例如難民委員會等)獲得的獎牌。 它還具有多種類型的圖表,例如長條圖、旭日圖、折線圖、分區統計圖和網格圖。 另外,儀表板會根據所選年份和區域類型動態更新資料。 您可以在這裡觀看演示! https://www.youtube.com/watch?v=\_1X7etBFTk0 其中涉及到許多概念,而 Eric 實現了一個非常優秀的用例! https://github.com/enarroied/Olympic-Medals-Taipy-App 奧運明星獎牌 ⭐️ --- 12. [GPT Researcher](https://github.com/assafelovic/gpt-researcher) - 基於 GPT 的線上研究自主代理。 --------------------------------------------------------------------------------------- ![GPT研究員](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4vbqw84mrere5bcfalhk.png) GPT Researcher 是領先的自主代理,負責處理從準確的來源收集到研究結果組織的一切事務。 好的一點是它也引用了研究結果的來源,從而提高了可信度。我喜歡整個概念:) 一些瘋狂的功能是: ✅ 可以產生長而詳細的研究報告(超過2K字)。 ✅ 每項研究總結 20 多個網路資源,以形成客觀和事實的結論。 ✅ 包括易於使用的網路介面(HTML/CSS/JS)。 ✅ 透過 JavaScript 支援抓取網路資源。 ✅ 追蹤存取和使用的網路資源的追蹤和上下文。 ✅ 將研究報告匯出為 PDF、Word 等。 ![特徵](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t0o49xyzfmzuvmlotgal.png) 開始使用以下命令。 ``` pip install gpt-researcher ``` 您可以這樣使用它。 ``` from gpt_researcher import GPTResearcher query = "why is Nvidia stock going up?" researcher = GPTResearcher(query=query, report_type="research_report") # Conduct research on the given query await researcher.conduct_research() # Write the report report = await researcher.write_report() ``` 請閱讀下面隨附的[安裝說明](https://github.com/assafelovic/gpt-researcher?tab=readme-ov-file#%EF%B8%8F-getting-started)和快速入門指南。 如果您想了解架構的細節,代理商會利用 gpt3.5-turbo 和 gpt-4-turbo(128K 上下文)來完成研究任務。我們僅在必要時才使用每種方法來優化成本。研究任務平均需要大約 3 分鐘才能完成,成本約為 0.1 美元。 ![建築學](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1ls54ua4d3nhn473ek04.png) 您可以閱讀官方部落格了解[GPT Researcher 的工作原理](https://docs.tavily.com/blog/building-gpt-researcher)。 您可以閱讀[常見問題](https://docs.tavily.com/docs/faq)以了解有關準確性等的更多資訊。 您可以閱讀[文件](https://docs.tavily.com/docs/gpt-researcher/getting-started)並存取他們的[官方網站](https://gptr.dev/)。 在這裡觀看演示! ![GPT 研究員演示](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2xfgnes109mqfyk0r3uq.gif) 它在 GitHub 上有 8700 顆星,並且正在不斷改進。 https://github.com/assafelovic/gpt-researcher GPT 明星研究員 ⭐️ --- 13.[私人 GPT](https://github.com/zylon-ai/private-gpt) - 在沒有網路的情況下詢問有關您的文件的問題。 ---------------------------------------------------------------------------- ![私有GPT](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0nshjqmm5xq6kgqkgfdc.png) PrivateGPT 是一個可立即投入生產的 AI 專案,即使在沒有網路連線的情況下,您也可以使用大型語言模型 (LLM) 的功能來詢問有關文件的問題。 100% 私有意味著任何時候都沒有資料離開您的執行環境。 API 分為兩個邏輯區塊: A。高級 API,抽象化了 RAG(檢索增強生成)管道實現的所有複雜性: - 文件攝取:內部管理文件解析、分割、元資料擷取、嵌入產生和儲存。 - 使用所攝取文件中的上下文進行聊天和完成:抽像上下文檢索、提示工程和回應產生。 b.低階 API,允許高階用戶實現複雜的管道: - 嵌入生成:基於一段文字。 - 上下文區塊檢索:給定查詢,從攝取的文件中傳回最相關的文字區塊。 您可以閱讀[安裝指南](https://docs.privategpt.dev/installation/getting-started/installation)來開始。 您可以閱讀[文件](https://docs.privategpt.dev/overview/welcome/introduction)以及所涉及的[詳細架構](https://github.com/zylon-ai/private-gpt?tab=readme-ov-file#-architecture)。 PrivateGPT 現在正在發展成為產生 AI 模型和原語的網關,包括補全、文件攝取、RAG 管道和其他低階建置塊。 他們在 GitHub 上擁有超過 51,000 顆星,並且發展迅速。 https://github.com/zylon-ai/private-gpt 明星私人 GPT ⭐️ --- [14.facefusion](https://github.com/facefusion/facefusion) - 下一代臉部交換器和增強器。 ------------------------------------------------------------------------- ![臉部融合](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ea0w92sm51da799632vr.png) 這是下一代臉部交換器和增強器。有多種用途,您可以輕鬆做到。 他們還提供了一個[研討會部分](https://docs.facefusion.io/workshop),您可以在其中了解如何建立 UI 元件和定義框架處理器。 例如,這就是建立 UI 元件的方式。 ``` // create a new file facefusion/uis/components/example.py // Implement the essential methods of the UI component from typing import Optional import gradio from facefusion.uis.typing import Update EXAMPLE_IMAGE : Optional[gradio.Image] = None def render() -> None: global EXAMPLE_IMAGE EXAMPLE_IMAGE = gradio.Image() def listen() -> None: EXAMPLE_IMAGE.change(update, inputs = EXAMPLE_IMAGE, outputs = EXAMPLE_IMAGE) def update() -> Update: return gradio.update() ``` 您只需加入該元件即可。 ``` from facefusion.uis.components import example ``` ![作坊](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lxznqz8rg23q4tb2brne.png) 安裝可能有點複雜,所以我建議根據您使用的特定環境閱讀[安裝指南](https://docs.facefusion.io/installation)。 您可以使用此命令檢查基準測試。 ``` python run.py --ui-layouts benchmark ``` ![基準](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rdc3s7xwjxoukee784ds.png) 您可以閱讀[文件](https://docs.facefusion.io/)並了解有關[技術術語的](https://docs.facefusion.io/knowledgebase/technical-terms)更多資訊。 他們在 GitHub 上擁有超過 14k 個 star,並且發布了`v2.5`版本。 https://github.com/facefusion/facefusion 明星facefusion ⭐️ --- 15. [H2O LLMStudio](https://github.com/h2oai/h2o-llmstudio) - 用於微調 LLM 的無程式碼 GUI。 --------------------------------------------------------------------------------- ![H2O法學碩士工作室](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a03nefeobwwf9bkhcgwg.png) H2O LLM Studio 是一個開源、無程式碼的 LLM 圖形使用者介面 (GUI),專為微調最先進的大型語言模型而設計。 微調預訓練的語言模型需要編碼專業知識以及有關模型及其超參數的廣泛知識,但是,H2O LLM Studio 使 NLP 從業者能夠輕鬆微調他們的 LLM,無需編碼,並且比定制具有更好的靈活性。 H2O LLM Studio 還可讓您與微調模型聊天並接收有關模型效能的即時回饋。 NLP 從業者和資料科學家尤其可能會發現輕鬆有效地建立和微調大型語言模型很有用。您可以閱讀[詳細的效能統計資料](https://h2oai.github.io/h2o-llmstudio/get-started/llm-studio-performance)及其雲端[架構](https://docs.h2o.ai/haic-documentation/overview/architecture)。 如果您正在開始,我建議您觀看此內容! https://www.youtube.com/watch?v=u48QaIAIFw4 您可以閱讀有關[核心功能的](https://h2oai.github.io/h2o-llmstudio/get-started/core-features)訊息,例如: ✅ 無程式碼微調 ✅ 高度可自訂 ✅ 關於模型性能的即時回饋 您可以使用以下命令啟動 H2O LLM Studio。 ``` make llmstudio ``` 如果你不知道這些概念,他們也有[清晰的概念指南,](https://h2oai.github.io/h2o-llmstudio/concepts)包括 Generative AI、LoRA、Quantization、LLM Backbone 等等。 您可以閱讀[文件](https://docs.h2o.ai/haic-documentation/)。 您可以使用它非常輕鬆且符合標準地製作一個副專案。 他們在 GitHub 上有 3,600 顆星,並且發布了`v1.5`版本。 https://github.com/h2oai/h2o-llmstudio 明星 H2O LLMStudio ⭐️ --- 16. [Mac 上的語音助理](https://github.com/chidiwilliams/GPT-Automator)- 您的語音控制 Mac 助理。 -------------------------------------------------------------------------------- ![GPT自動機](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rdzv06jnr3z33s7qll5k.png) 您的語音控制 Mac 助理。 GPT Automator 可讓您使用語音在 Mac 上執行任務。例如,打開應用程式、尋找餐廳、綜合資訊。太棒了:D 它是在倫敦黑客馬拉松期間建構的。 它有兩個主要部分: A。語音命令:它使用本地執行的 Whisper(Buzz 的一個分支)來產生命令。 b.命令到行動:您向配備了我們編寫的自訂工具的 LangChain 代理程式發出命令。這些工具包括使用 AppleScript 控制電腦的作業系統以及使用 JavaScript 控制活動瀏覽器。最後,就像任何優秀的人工智慧一樣,我們讓代理商使用 AppleScript 說出最終結果「{Result}」(如果您以前沒有使用過,請嘗試在 Mac 終端機中輸入「Hello World!」)。 我們製作了一個自訂工具,讓法學碩士使用 AppleScript 控制電腦。提示符是文件字串: ``` @tool def computer_applescript_action(apple_script): """ Use this when you want to execute a command on the computer. The command should be in AppleScript. Here are some examples of good AppleScript commands: Command: Create a new page in Notion AppleScript: tell application "Notion" activate delay 0.5 tell application "System Events" to keystroke "n" using {{command down}} end tell ... Write the AppleScript for the Command: Command: """ p = subprocess.Popen(['osascript', '-'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate(applescript.encode('utf-8')) if p.returncode != 0: raise Exception(stderr) decoded_text = stdout.decode("utf-8") return decoded_text ``` 如果您想知道它是如何運作的,GPT Automator 使用 OpenAI 的 Whisper 將您的音訊輸入轉換為文字。然後,它使用LangChain Agent 選擇一組操作,包括使用OpenAI 的GPT-3(“text-davinci-003”)從提示符號產生AppleScript(用於桌面自動化)和JavaScript(用於瀏覽器自動化)命令,然後執行產生的腳本。 請記住,這不適用於生產用途。該專案執行從自然語言產生的程式碼,可能容易受到提示注入和類似的攻擊。這項工作是作為概念驗證而進行的。 您可以閱讀[安裝指南](https://github.com/chidiwilliams/GPT-Automator?tab=readme-ov-file#instructions)。 讓我們看看一些提示及其作用: ✅ 求計算結果。 > 提示:“2 + 2 是什麼?” 它將編寫 AppleScript 開啟計算器並輸入 5 \* 5。 ✅ 尋找附近的餐廳。 > 提示:“查找我附近的餐廳” 它將打開 Chrome,谷歌搜尋附近的餐廳,解析頁面,然後返回最上面的結果。有時它很厚顏無恥,反而會打開谷歌地圖結果並說「最好的餐廳是谷歌地圖頁面頂部的餐廳」。其他時候,它會打開 Google 上的頂部連結 - 並卡在 Google 可存取性頁面上... 以下是執行時列印到終端的內容: ``` Command: Find a great restaurant near Manchester. > Entering new AgentExecutor chain... I need to search for a restaurant near Manchester. Action: chrome_open_url Action Input: https://www.google.com/search?q=restaurant+near+Manchester Observation: Thought: I need to read the page Action: chrome_read_the_page Action Input: Observation: Accessibility links Skip to the main content ... # Shortned for brevity Dishoom Manchester 4.7 (3.3K) · £££ · Indian 32 Bridge St · Near John Rylands Library Closes soon ⋅ 11 pm Stylish eatery for modern Indian fare San Carlo 4.2 (2.8K) · £££ · Italian 42 King St W · Near John Rylands Library Closes soon ⋅ 11 pm Posh, sceney Italian restaurant Turtle Bay Manchester Northern Quarter 4.7 Thought: I now know the final answer Final Answer: The 15 best restaurants in Manchester include El Gato Negro, Albert's Schloss, The Refuge, Hawksmoor, On The Hush, Dishoom, Banyan, Zouk Tea Room & Grill, Edison Bar, MyLahore Manchester, Turtle Bay Manchester Northern Quarter, San Carlo, The Black Friar, Mana, and Tast Cuina Catalana. ``` 我不能保證這些餐廳值得,請自行承擔風險。哈哈! ✅ 如果您要求 GPT Automator 擦除您的計算機,它會的。 是的,如果您要求的話,它會擦除您的電腦! 我內心的自我尖叫著要這麼做:) 您可以在這裡查看完整的演示! https://www.loom.com/share/7bfa82c604f3412fbbb04191ce2ae12f --- 17. [RepoChat](https://github.com/pnkvalavala/repochat) - 支援 GitHub 儲存庫互動的聊天機器人助理。 ---------------------------------------------------------------------------------- ![重新聊天](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3gf9bjn2a47f1t43aiju.png) Repochat 是一個互動式聊天機器人專案,旨在使用大型語言模型 (LLM) 參與有關 GitHub 儲存庫的對話。 它允許用戶進行有意義的討論、提出問題並從 GitHub 儲存庫檢索相關資訊。本自述文件提供了在本機電腦上設定和使用 Repochat 的逐步說明。 他們建立了兩個具有不同功能的分支,這對我來說有點新鮮。 ✅ Repochat 的主要分支被設計為完全在您的本機電腦上執行。此版本的 Repochat 不依賴外部 API 呼叫,並且可以更好地控制您的資料和處理。如果您正在尋找獨立的解決方案,那麼主分支就是您的最佳選擇。 ✅ Repochat 的雲端分支主要依賴對外部服務的 API 呼叫來進行模型推理和儲存。它非常適合那些喜歡基於雲端的解決方案並且不想設定本地環境的人。 您可以閱讀[安裝說明](https://github.com/pnkvalavala/repochat?tab=readme-ov-file#installation)。 Repochat 讓您與聊天機器人進行對話。您可以提出問題或提供輸入,聊天機器人將從向量資料庫中檢索相關文件。 然後,它將您的輸入以及檢索到的文件傳送到語言模型以產生回應。 預設情況下,我已將模型設為`codellama-7b-instruct` ,但您可以根據計算機的速度更改它,甚至可以嘗試 13b 量化模型進行回應。 聊天機器人在對話過程中保留記憶以提供上下文相關的回應。 您可以查看[即時網站](https://repochat.streamlit.app/),您可以使用 API 金鑰進行檢查。 你可以觀看這個演示! ![示範](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o7ndxqpwkkww4f5qudiq.gif) 如果您想查看的話,我找到了另一種[選擇](https://github.com/peterw/Chat-with-Github-Repo)。 Repochat 擁有 200 多顆星,並部署在 Streamlit 上。 https://github.com/pnkvalavala/repochat 明星 RepoChat ⭐️ --- 18. [myGPTReader](https://github.com/madawei2699/myGPTReader) - 閱讀並與 AI 機器人聊天。 ------------------------------------------------------------------------------ ![我的GPT閱讀器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/58doii8daomf54te5eca.png) myGPTReader 是 Slack 上的機器人,可以閱讀和總結任何網頁、文件(包括電子書),甚至來自 YouTube 的影片。它可以透過語音與您交流。 一些有價值的功能是: ✅ 使用 myGPTReader 透過對話快速閱讀和理解任何網頁內容,甚至是影片(目前僅支援帶有字幕的 YouTube 影片)。 ![讀者](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x626mtnvqr5vw43938iw.gif) ✅ 使用 myGPTReader 快速閱讀任何文件的內容,支援電子書、PDF、DOCX、TXT 和 Markdown。 ![文件](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5am7njxco7vhronuirgu.gif) ✅ 透過與 myGPTReader 語音對話來練習外語,它可以成為您的私人導師,支援中文、英語、德語和日語。 ![嗓音](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/40pzlgtuhcyxgwa1z2gs.gif) ✅ 內建大量提示模板,使用它們可以更好地與chatGPT對話。 ![問](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ehgn218hwewzeft98xkp.gif) ✅ myGPTReader 每天都會發出最新的熱門新聞,並自動產生摘要,讓您快速了解今日熱點。 您可以造訪[官方網站](https://www.myreader.io/)。 您可以加入擁有超過 5000 名會員的儲存庫上的 Slack 頻道,免費體驗所有這些功能。 它們在 GitHub 上有 4.4k 顆星,並且像此列表中的其他專案一樣使用 Python 建置。 https://github.com/madawei2699/myGPTReader 星 myGPTReader ⭐️ --- 19. [Marker](https://github.com/VikParuchuri/marker) - 將 PDF 快速且高精度地轉換為 Markdown。 --------------------------------------------------------------------------------- ![標記](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9kpdu6jh9qmax5cikbah.png) Marker 將 PDF、EPUB 和 MOBI 轉換為 Markdown。它比牛軋糖快 10 倍,在大多數文件上更準確,並且產生幻覺的風險較低。 我們都知道這有多大幫助,尤其是對於研究論文來說。 ✅ 支援一系列 PDF 文件(針對書籍和科學論文進行了最佳化)。 ✅ 刪除頁首/頁尾/其他工件。 ✅ 將大多數方程式轉換為乳膠。 ✅ 設定程式碼區塊和表格的格式。 ✅ 支援多種語言(儘管大多數測驗是用英語完成的)。請參閱 settings.py 以取得語言列表,或新增您自己的語言列表。 ✅ 適用於 GPU、CPU 或 MPS。 他們也清楚地記錄了[範例](https://github.com/VikParuchuri/marker?tab=readme-ov-file#examples)以及 Marker 和 Nougat 的結果。 ![基準](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nqtl447c9h0taisbj8hl.png) 性能結果採用標記和牛軋糖設置,因此它們在 A6000 上均佔用約 3GB 的 VRAM。 閱讀速度和準確性[基準測試](https://github.com/VikParuchuri/marker?tab=readme-ov-file#benchmarks)以及有關如何執行您自己的基準測試的說明。 例如,請參閱此 PDF: [Think Python](https://greenteapress.com/thinkpython/thinkpython.pdf)和[Marker](https://github.com/VikParuchuri/marker/blob/master/data/examples/marker/thinkpython.md) vs [Nougat](https://github.com/VikParuchuri/marker/blob/master/data/examples/nougat/thinkpython.md)的 markdown 檔案。 閱讀[安裝說明](https://github.com/VikParuchuri/marker?tab=readme-ov-file#installation)。 他們也記錄瞭如何正確使用它: - [轉換單一文件](https://github.com/VikParuchuri/marker?tab=readme-ov-file#convert-a-single-file) - [轉換多個文件](https://github.com/VikParuchuri/marker?tab=readme-ov-file#convert-multiple-files) - [在多個 GPU 上轉換多個文件](https://github.com/VikParuchuri/marker?tab=readme-ov-file#convert-multiple-files-on-multiple-gpus) 他們在 GitHub 上有 8k+ 星,我認為它不再被維護了。 https://github.com/VikParuchuri/marker 星標 ⭐️ --- 20. [Instrukt](https://github.com/blob42/Instrukt) - 終端機中整合人工智慧。 ---------------------------------------------------------------- ![指示](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wsk64pf5yuosui91tmz9.png) Instrukt是一個基於終端的AI整合環境。它提供了一個平台,用戶可以: - 建立並指導模組化人工智慧代理。 - 產生問答的文件索引。 - 建立工具並將其附加到任何代理程式。 用自然語言指導它們,並且為了安全起見,在安全容器(目前使用 Docker 實作)中執行它們,以在其專用的沙盒空間中執行任務。 使用`Langchain` 、 `Textual`和`Chroma`建構。 開始使用以下命令。 ``` pip install instrukt[all] ``` ![指示](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r3aza7hnlji7hbi2o0js.gif) 有許多令人興奮的功能,例如: - 基於終端的介面,讓強力鍵盤使用者無需離開鍵盤即可指示 AI 代理。 - 對您的資料建立索引並讓代理程式檢索它以進行問答。您可以使用簡單的 UI 建立和組織索引。 - 索引建立將自動偵測程式語言並相應地優化拆分/分塊策略。 - 在安全的 Docker 容器內執行代理程式以確保安全和隱私。 - 整合的 REPL-Prompt 可實現與代理程式的快速交互,以及用於開發和測試的快速回饋循環。 - 您可以使用自訂命令自動執行重複任務。它還具有內建的提示/聊天歷史記錄。 您可以閱讀有關所有[功能的](https://github.com/blob42/Instrukt?tab=readme-ov-file#features)資訊。 您可以閱讀[安裝指南](https://blob42.github.io/Instrukt/install.html)。 您還可以使用內建的 IPython 控制台來除錯和內省代理,這是一個簡潔的小功能。 ![控制台除錯](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qaan8np68e3fk1yueexm.png) Instrukt 已獲得 AGPL 許可證,這意味著任何人都可以將其用於任何目的。 可以肯定地說,Instrukt 是您觸手可及的終端人工智慧指揮官。 這是一個新專案,因此他們在 GitHub 上有大約 200 多顆星,但用例非常好。 https://github.com/blob42/Instrukt 舊指令 ⭐️ --- 21.[微代理](https://github.com/aymenfurter/microagents)- 能夠自我編輯提示的代理。 ------------------------------------------------------------------ ![微代理](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nuxv03dgb03s04dkmkm9.png) 它是一個實驗框架,用於動態建立自我改進的代理來回應任務。 微代理代表了一種建立自我改進代理的新方法。小型微服務大小(因此稱為微代理)代理是根據用戶分配給助手的任務動態生成的,評估其功能,並在成功驗證後存儲以供將來重用。 這使得跨聊天會話的學習成為可能,使系統能夠獨立推斷任務執行的方法。 這是使用`Python` 、 `OpenAI's GPT-4 Turbo`和`Text-Embedding-Ada-002`建構的。 您可以閱讀[安裝說明](https://github.com/aymenfurter/microagents?tab=readme-ov-file#installation)。他們提到您應該擁有一個可以存取 gpt-4-turbo 和 text-embedding-ada-002 的 OpenAI 帳戶。 讓我們看一個獲取天氣預報代理的範例。 ``` You are an adept weather informant. Fetch the weather forecast by accessing public API data using this Python code snippet: ``python import requests import json def fetch_weather_forecast(location, date): response = requests.get(f"https://api.met.no/weatherapi/locationforecast/2.0/compact?lat={location[0]}&lon={location[1]}") weather_data = response.json() for day_data in weather_data['properties']['timeseries']: if date in day_data['time']: print(day_data['data']['instant']['details']) break `` # Example usage: fetch_weather_forecast((47.3769, 8.5417), '2024-01-22T12:00:00Z') Note: Replace the (47.3769, 8.5417) with the actual latitude and longitude of the location and the date string accordingly. ``` 如果您想知道如何建立代理,那麼此架構圖將對此進行解釋。 ![圖表](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7oi4fbt6e5qauqo293qt.png) 您可以看到[工作演示](https://github.com/aymenfurter/microagents?tab=readme-ov-file#demo)。 他們在 GitHub 上有大約 700 顆星,值得一看。 https://github.com/aymenfurter/microagents 明星微代理 ⭐️ --- 22. [Resume Matcher](https://github.com/srbhr/Resume-Matcher) - 一個改進你的履歷的免費工具。 ------------------------------------------------------------------------------ ![履歷表匹配器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/av3u7dd3d3rm7k108w7i.png) Resume Matcher 是一款開源免費工具,可用於完善您的履歷。根據職位描述自訂您的履歷。找到匹配的關鍵字,提高可讀性,並深入了解您的履歷。 它是如何運作的? 履歷表匹配器使用 Python 讀取您的履歷和職位描述,就像 ATS 一樣。 它建議透過以下方式進行更改,使您的履歷適合 ATS: ✅ 解析:它使用 Python 分解你的履歷和工作描述。 ✅ 關鍵字提取:該工具從職位描述中尋找重要關鍵字,例如技能和資格。 ✅ 關鍵術語提取:辨識職位描述中的主題以了解其上下文。 ✅ 向量相似度:使用 FastEmbedd,它將您的履歷與職位描述進行比較,看看它們的匹配程度。配對得越好,通過 ATS 篩選的機會就越高。 ![使命](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kintryj2685slbya98zr.png) 您可以閱讀[安裝說明](https://github.com/srbhr/Resume-Matcher?tab=readme-ov-file#how-to-install)。 您可以查看[現場演示](https://resume-matcher.streamlit.app/)或自述文件中附帶的演示。 Resume Matcher 是由 Saurabh Rai 建立的一個令人驚嘆的專案,他還在 DEV 上寫了很棒的帖子! 它在 GitHub 上有 4.5k 顆星,並且仍然維護良好。 https://github.com/srbhr/Resume-Matcher 明星簡歷匹配器 ⭐️ --- 23.[背景移除器](https://github.com/nadermx/backgroundremover)- 讓您可以透過簡單的 CLI 使用 AI 從影像和影片中移除背景。 ------------------------------------------------------------------------------------------ ![背景去除劑](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v8bppslk45ci383wpman.png) 這是一個使用 AI 從圖像和影片中刪除背景的命令列工具。 首先從 pypi 安裝 backgroundremover。 ``` pip install --upgrade pip pip install backgroundremover ``` 也可以在不透過 pip 安裝的情況下執行它,只需克隆 git 以在本地啟動虛擬環境安裝要求並執行。 您可以使用的一些命令: - 從本機檔案圖像中刪除背景 ``` backgroundremover -i "/path/to/image.jpeg" -o "output.png" ``` - 從本地影片中刪除背景並將其覆蓋在圖像上 ``` backgroundremover -i "/path/to/video.mp4" -toi "/path/to/videtobeoverlayed.mp4" -o "output.mov" ``` 您可以檢查可以透過 CLI 使用的所有[命令](https://github.com/nadermx/backgroundremover?tab=readme-ov-file#usage-as-a-cli)。 您甚至可以將它用作圖書館。 ``` from backgroundremover.bg import remove def remove_bg(src_img_path, out_img_path): model_choices = ["u2net", "u2net_human_seg", "u2netp"] f = open(src_img_path, "rb") data = f.read() img = remove(data, model_name=model_choices[0], alpha_matting=True, alpha_matting_foreground_threshold=240, alpha_matting_background_threshold=10, alpha_matting_erode_structure_size=10, alpha_matting_base_size=1000) f.close() f = open(out_img_path, "wb") f.write(img) f.close() ``` 您可以閱讀[安裝說明](https://github.com/nadermx/backgroundremover?tab=readme-ov-file#installation)並觀看[現場演示](https://www.backgroundremoverai.com/)。 > 輸入與輸出。 ![輸入影像](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b0rwjaxxw460lugle5z2.png) 他們在 GitHub 上有 6k star,我們絕對可以用它來學習一些重要的概念。 https://github.com/nadermx/backgroundremover 明星背景去除器 ⭐️ --- 24. [Tkinter Designer](https://github.com/ParthJadhav/Tkinter-Designer) - 建立 Python GUI 的簡單快速的方法。 ------------------------------------------------------------------------------------------------- ![Tkinter 設計師](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9dt5ij4fu948yz4fwnqc.png) Tkinter Designer 的建立是為了加快 Python 中的 GUI 開發過程。它使用著名的設計軟體Figma,讓用Python建立漂亮的Tkinter GUI變得輕而易舉。 Tkinter Designer 使用 Figma API 來分析設計檔案並建立 GUI 所需的對應程式碼和檔案。 如果您想知道它是如何工作的? 使用者唯一需要做的就是使用 Figma 設計一個介面,然後將 Figma 檔案 URL 和 API 令牌貼到 Tkinter Designer 中。 Tkinter Designer 將自動產生在 Tkinter 中建立 GUI 所需的所有程式碼和映像。 ![怎麼運作的](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lb2pjsige0b9w5rynipm.png) 您可以閱讀逐步指南\](https://github.com/ParthJadhav/Tkinter-Designer/blob/master/docs/instructions.md),了解如何正確使用它,該指南有多種語言版本。 您可以在這裡觀看演示! https://www.youtube.com/watch?v=Qd-jJjduWeQ 他們還展示了您可以使用它輕鬆複製的[網站範例](https://github.com/ParthJadhav/Tkinter-Designer?tab=readme-ov-file#-examples)。 ![網站範例](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdiyhj08bwatasonpekm.png) 它們在 GitHub 上有 8,300 顆星,並被大約 100 名開發人員使用。 https://github.com/ParthJadhav/Tkinter-Designer Tkinter 明星設計師 ⭐️ --- 25. [Open Interpreter](https://github.com/OpenInterpreter/open-interpreter) - 電腦自然語言介面。 --------------------------------------------------------------------------------------- ![打開解釋器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/av7udc5fibj1wz88w0u8.png) Open Interpreter 允許法學碩士在本地執行程式碼(Python、Javascript、Shell 等)。您可以在安裝後執行 $terpreter,透過終端機中類似 ChatGPT 的介面與 Open Interpreter 聊天。 這為電腦的通用功能提供了自然語言介面: ✅ 建立和編輯照片、影片、PDF 等。 ✅ 控制 Chrome 瀏覽器執行研究、繪製、清理和分析大型資料集。 我不了解你,但他們的[網站](https://www.openinterpreter.com/)讓我驚嘆不已! 使用此命令快速啟動。 ``` pip install open-interpreter // After installation, simply run: interpreter ``` 您可以閱讀[快速入門指南](https://docs.openinterpreter.com/getting-started/introduction)。 您應該閱讀[與 ChatGPT 程式碼解釋器的比較](https://github.com/OpenInterpreter/open-interpreter?tab=readme-ov-file#comparison-to-chatgpts-code-interpreter)以及可以使用的[命令](https://github.com/OpenInterpreter/open-interpreter?tab=readme-ov-file#commands)。 您可以閱讀[文件](https://docs.openinterpreter.com/getting-started/introduction)。 Open Interpreter 可與託管語言模型和本機語言模型搭配使用。託管模型速度更快、功能更強大,但需要付費。本地模型是私有且免費的,但能力往往較差。 它們在 GitHub 上擁有超過 48k 顆星,並被 300 多名開發人員使用。 https://github.com/OpenInterpreter/open-interpreter Star 開放式解釋器 ⭐️ --- 獲得經驗並提高編碼水平的最佳方法是建立業餘專案。 我希望您能夠建立其中一些專案,或至少獲得靈感。 評論下來告訴其他人任何其他很酷的 Python 專案:) 祝你有美好的一天! 關注 Taipy 以了解更多此類內容。 https://dev.to/taipy --- 原文出處:https://dev.to/taipy/25-projects-that-you-can-build-with-python-and-ai-12i9

使用 matcha.css 讓你的新網站瞬間變漂亮!

您是否曾經想過在開始一個新的網路專案時從「空白頁」開始是多麼的簡單? ![空白頁](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/syhnfxnz7i2cs3exqhf4.png) *請注意缺少任何樣式或自訂屬性*。 ``` <html> <body> <h1>Hello world!</h1> <p> This is my new project, and it's still under construction. Please be indulgent 😊 </p> <p> Also, check out my work on <a href="https://github.com/octocat">GitHub</a>! </p> <blockquote> <p> Imagination is more important than knowledge </p> <cite>Albert Einstein</cite> </blockquote> <menu> <li>Home</li> <li> About me <menu> <li>My works</li> <li>My passions</li> </menu> </li> <li>Contact</li> </menu> <form> <h2>Contact me</h2> <label> Your email: <input type="email" name="email" placeholder="Your email" required> </label> <label> Your message: <small>255 characters max</small> <textarea name="message" placeholder="Your message" required></textarea> </label> <button type="submit">Send</button> </form> </body> </html> ``` 哎喲!如果你向你的朋友展示這一點,同時聲稱你是網頁開發人員,他們會開始質疑你到底在做什麼... 預設瀏覽器樣式表通常非常簡單,當您製作應用程式原型、生成靜態 HTML 頁面、Markdown 生成的文件等並且不想深入研究時,如果沒有“視覺上不錯”的東西,可能會令人沮喪過早地接觸CSS 的複雜性。 好訊息!我有一些適合你的東西: [matcha.css](https://matcha.mizu.sh) ! 只需將以下內容加入到您的文件中即可看到奇蹟發生: ``` <link rel="stylesheet" href="https://matcha.mizu.sh/matcha.css"> ``` ![抹茶頁面](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r2duw5mfvuxcf0s56w7f.png) 沒有真正做任何事情,我們就得到了一個尊重用戶對淺色/深色模式的偏好、漂亮的字體和適當的間距的頁面,我們的`<menu>`實際上看起來像一個菜單,而且我們的`<form>`甚至還有「必填欄位」也相當不錯所需投入的指標。 所有這些都無需任何建置步驟、JavaScript、配置或重構。 這是因為[matcha.css](https://matcha.mizu.sh)使用語意樣式。例如,它假設嵌套在另一個`<menu>`中的`<menu>`應產生一個子選單,而`<label><input required></label>`應向使用者澄清輸入是強制性的。 還有一些「嵌入式」CSS 庫,但我相信這是最完整的一個,因為它提供了額外的開箱即用的現代樣式,如語法突出顯示、簡單佈局、實用程式類別等。 ![抹茶](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cuq2b9axcybs26lbkple.png) [matcha.css](https://matcha.mizu.sh)也很容易自訂(它沒有任何`!important`規則,甚至提供了建立自訂建置的幫助器),同時也是可逆的(如果您決定遷移出去,只需刪除樣式表)。 最後但並非最不重要的一點是,它是完全免費和開源的! https://github.com/lowlighter/matcha 請參閱[matcha.mizu.sh](https://matcha.mizu.sh)了解完整概述! --- 原文出處:https://dev.to/lowlighter/make-naked-websites-look-great-with-matchacss-4ng7