🔍 搜尋結果:JS

🔍 搜尋結果:JS

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

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

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

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

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

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

✨ 用您的文件訓練 ChatGPT 🪄 ✨

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

告別 Docker Volume 👋

曾經嘗試過在 Web 應用程式中使用 Docker 磁碟區進行熱重載嗎?如果你有跟我一樣可怕的經歷,你會喜歡 Docker 剛剛發布的最新功能:**docker-compose watch**!讓我向您展示如何升級現有專案以獲得出色的 Docker 開發設置,您的團隊*實際上*會喜歡使用它 🤩 TL;DR:看看這個 [docker-compose](https://github.com/Code42Cate/hackathon-starter/blob/main/docker-compose.yml) 檔案和 [官方文件](https://docs.docker.com/compose/file-watch/) 讓我們開始吧! ![旋轉僧侶](https://media.giphy.com/media/e06Wc1bfzPQXnXyhLW/giphy.gif) ## 介紹 Docker 剛剛發布了[Docker Compose Watch](https://docs.docker.com/compose/file-watch/) 和[Docker Compose Version 2.22](https://docs.docker.com/compose/release-notes/) #2220).有了這個新功能,您可以使用“docker-compose watch”代替“docker-compose up”,並自動將本機原始程式碼與 Docker 容器中的程式碼同步,而無需使用磁碟區! 讓我們透過使用我[之前寫過的](https://dev.project) 來看看它在實際專案中的工作原理。 在這個專案中,我有一個帶有前端、後端以及一些用於 UI 和資料庫的附加庫的 monorepo。 ``` ├── apps │   ├── api │   └── web └── packages ├── database ├── eslint-config-custom ├── tsconfig └── ui ``` 兩個應用程式(「api」和「web」)都已經進行了docker 化,而Dockerfile 位於專案的根目錄中([1](https://github.com/Code42Cate/hackathon-starter/blob/main/api.Dockerfile ), [2](https://github.com/Code42Cate/hackathon-starter/blob/main/web.Dockerfile)) `docker-compose.yml` 檔案如下所示: ``` services: web: build: dockerfile: web.Dockerfile ports: - "3000:3000" depends_on: - api api: build: dockerfile: api.Dockerfile ports: - "3001:3000"from within the Docker network ``` 這已經相當不錯了,但如您所知,在開發過程中使用它是一個 PITA。每當您更改程式碼時,您都必須重建 Docker 映像,即使您的應用程式可能支援開箱即用的熱重載(或使用 [Nodemon](https://www.npmjs.com/package/nodemon) 如果不)。 為了改善這一點,Docker Compose Watch [引入了一個新屬性](https://docs.docker.com/compose/file-watch/#configuration),稱為「watch」。 watch 屬性包含一個所謂的 **rules** 列表,每個規則都包含它們正在監視的 **path** 以及一旦路徑中的文件發生更改就會執行的 **action**。 ## 同步 如果您希望在主機和容器之間同步資料夾,您可以新增: ``` services: web: # shortened for clarity build: dockerfile: web.Dockerfile develop: watch: - action: sync path: ./apps/web target: /app/apps/web ``` 每當主機上的路徑“./apps/web/”中的檔案發生變更時,它將同步(複製)到容器的“/app/apps/web”。目標路徑中的附加應用程式是必要的,因為這是我們在 [Dockerfile](https://github.com/Code42Cate/hackathon-starter/blob/main/web.Dockerfile) 中定義的「WORKDIR」。如果您有可熱重新加載的應用程式,這可能是您可能會使用的主要內容。 ## 重建 如果您有需要編譯的應用程式或需要重新安裝的依賴項,還有一個名為 **rebuild** 的操作。它將重建並重新啟動容器,而不是簡單地在主機和容器之間複製檔案。這對你的 npm 依賴關係非常有幫助!讓我們補充一下: ``` services: web: # shortened for clarity build: dockerfile: web.Dockerfile develop: watch: - action: sync path: ./apps/web target: /app/apps/web - action: rebuild path: ./package.json target: /app/package.json ``` 每當我們的 package.json 發生變化時,我們都會重建整個 Dockerfile 以安裝新的依賴項。 ## 同步+重啟 除了同步和重建之外,中間還有一些稱為同步+重新啟動的操作。此操作將首先同步目錄,然後立即重新啟動容器而不重建。大多數框架通常都有無法熱重載的設定檔(例如「next.config.js」)(僅同步是不夠的),但也不需要緩慢重建。 這會將您的撰寫文件更改為: ``` services: web: # shortened for clarity build: dockerfile: web.Dockerfile develop: watch: - action: sync path: ./apps/web target: /app/apps/web - action: rebuild path: ./package.json target: /app/package.json - action: sync+restart path: ./apps/web/next.config.js target: /app/apps/web/next.config.js ``` ## 注意事項 一如既往,沒有[免費午餐](https://en.wikipedia.org/wiki/No_free_lunch_in_search_and_optimization)和一些警告😬 新的“watch”屬性的最大問題是路徑仍然非常基本。文件指出,尚不支援 Glob 模式,如果您想具體說明,這可能會導致「大量」規則。 以下是一些有效和無效的範例: ✅ `應用程式/網路` 這將會符合`./apps/web`中的*所有*檔案(例如`./apps/web/README.md`,還有`./apps/web/src/index.tsx`) ❌ `build/**/!(*.spec|*.bundle|*.min).js` 遺憾的是(還沒?) 支持 Glob ❌ `~/下載` 所有路徑都是相對於專案根目錄的! ## 下一步 如果您對 Docker 設定仍然不滿意,還有很多方法可以改進它! 協作是軟體開發的重要組成部分,[孤島工作](https://www.personio.com/hr-lexicon/working-in-silos/)可能會嚴重損害您的團隊。緩慢的 Docker 建置和複雜的設定沒有幫助!為了解決這個問題並促進協作文化,您可以使用 Docker 擴展,例如 [Livecycle](https://hub.docker.com/extensions/livecycle/docker-extension?utm_source=github&utm_medium=code42cate&utm_campaign=docker-composeub&utm_medium=code42cate&utm_campaign=docker-composeub&utm_medium=code42cate&utm_campaign=docker-composeub&utm_medium=code42cate&utm_campaign=docker-composeub&utm_medium=code42cate&utm_campaign=docker-composeub&utm_medium=code42cate&utm_campaign=docker-composeub&utm_medium=code42cate&utm_campaign=docker-composeub&utm_medium=code42cate&utm_campaign=docker-composeub&utm_medium=code42cate&utm_campaign=docker-composeub&utm)立即與您的隊友分享您本地的docker-compose 應用程式。由於您已經在使用 Docker 和 docker-compose,因此您需要做的就是安裝 [Docker 桌面擴充](https://hub.docker.com/extensions/livecycle/docker-extension?utm_source=github&utm_medium=code42cate&utm_campaign=hackathonstarter )並點擊共享切換按鈕。然後,您的應用程式將透過隧道連接到網路,您可以與您的團隊分享您的唯一 URL 以獲取回饋!如果您想查看 Livecycle 的更多用例,我在[這篇文章](https://dev.to/code42cate/how-to-win-any-hackathon-3i99)中寫了更多相關內容:) 像往常一樣,確保您的 Dockerfile 遵循最佳實踐,尤其是在多階段建置和快取方面。雖然這可能會使編寫初始 Dockerfile 變得更加困難,但它將使您的 Docker 應用程式在開發過程中使用起來更加愉快。 建立一個基本的“.dockerignore”檔案並將依賴項安裝與程式碼建置分開還有很長的路要走! ## 結論 一如既往,我希望你今天學到新東西了!如果您在設定 Docker 專案時需要任何協助,或者您有任何其他回饋,請告訴我 乾杯,喬納斯:D --- 原文出處:https://dev.to/code42cate/say-goodbye-to-docker-volumes-j9l

使用 VSCode 更快輸入程式碼的技巧分享

VSCode 寫程式技巧分享! ## 更聰明地複製、貼上 我見過人們透過執行以下操作來複製貼上程式碼: 1. 將滑鼠遊標移至單字開頭。 2. 按住左鍵點選。 3. 一直拖曳到單字最後。 4. 釋放左鍵點選。 5. 右鍵點選所選內容。 6. 按一下「複製」。 7. 在 VS Code 的檔案總管中捲動以尋找目標檔案。 8. 點選目標檔案。 9. 將遊標移到檔案中的所需位置。 8. 右鍵點選目標位置。 9. 按一下「貼上」。 這是一個有點慢的過程。特別是如果您需要多次應用此操作...改進複製貼上的一些方法是: - 使用“CTRL + C”進行**複製**,使用“CTRL + V”進行**貼上**。 - 使用“CTRL + SHIFT + 左/右箭頭”**增加/減少單字選擇**。 - 使用“SHIFT + 左/右”箭頭**按字元增加/減少選擇**。 - 點擊 VS Code 中的一行程式碼並按下「CTRL + X」將**將該行放入剪貼簿**。在任何地方使用“CTRL + V”都會**在其中插入該行程式碼**。 - 使用「ALT + 向上/向下箭頭」**將一行程式碼向上/向下移動**一個位置。 更聰明地複製貼上也意味著更聰明地導航。 ## 更聰明地導航 使用組合鍵“CTRL + P”,而不是手動瀏覽資源管理器窗格。這樣,您可以按名稱搜尋文件。這是一個“智能”搜尋,意味著它不僅會查找包含搜尋文本的單詞,還會查找組合,例如“prodetcon”還將查找“project-details-container.component.ts”。使用「CTRL + P」比看到有人在檔案總管窗格中掙扎要快得多,這本身就是一種痛苦(雙關語)。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tgi4edfc61mjln700yon.gif) 不要透過捲動來尋找文件內的某些程式碼,而是使用以下組合鍵: - `CTRL + G`:轉到行 - `CTRL + F`:在檔案中搜尋(使用`ENTER`鍵導航到下一個符合專案) - `CTRL + 點選類別/函數/等`:轉到所述類別/函數/等的定義。 使用“CTRL + TAB”在上次開啟的檔案和目前開啟的檔案之間切換(或使用“TAB”進一步切換到其他開啟的檔案)。這比將遊標移到工作列、查找正確的標籤並點擊它打開要快得多。 > 注意:在 VS Code 中,以這種方式在開啟的檔案之間進行切換非常有效率。另外,在 Windows 中使用「ALT + TAB」在開啟的視窗之間切換。 ## 更聰明地重新命名 不要自己重命名變數的每一次出現。它既耗時又容易出錯。相反,請轉到該變數的定義並按“F2”,重命名它,然後按“ENTER”。這將改變每一次發生的情況。這不僅適用於變數,也適用於函數、類別、介面等。這也適用於跨文件。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jls5prhd81ua6o24w6hl.gif) ## 使用 Emmet [Emmet](https://emmet.io/) 是一個內容/程式碼輔助工具,可以更快、更有效率地編寫程式碼。它是[VS Code 的標準](https://code.visualstudio.com/docs/editor/emmet),因此不需要任何插件。這個概念很簡單:您開始輸入 Emmet 縮寫,按下“TAB”或“ENTER”,就會出現該縮寫的完整 Emmet 片段。 Emmet 縮寫的範例可以是「.grid>.col*3」。當您按下「TAB」或「ENTER」時,VS Code 會為您填寫整段程式碼: ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7wt5s8wb3splmpvylkhm.gif) Emmet 的一大優點是您也可以產生 [“lorem ipsum” 文字](https://docs.emmet.io/abbreviations/lorem-ipsum/)。例如,`ul>li*4>lorem4`將產生一個包含 4 個元素的無序列表,每個清單專案包含 4 個隨機單字。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3pgcocuj4r7nfbt6wvyb.gif) ## 使用格式化程式 使用 VS Code 中的程式碼格式化程式來格式化程式碼。我強烈推薦[Prettier](https://prettier.io/docs/en/)。 使用程式碼格式化程式的好處之一是它還可以「美化」您的程式碼。因此,如果您從根本沒有佈局的地方複製貼上程式碼,您可以點擊格式組合鍵(“CTRL + ALT + F”)等等,您的程式碼現在“美化”了,更重要的是,可讀了。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/505nwj0kpv7eotq5e8ns.gif) > 注意:一個好的提示是在儲存時套用格式。您可以在設定中變更此設定(尋找「儲存時格式」)。 格式化不僅對你自己有用,而且對整個團隊有用,因為它強制團隊的程式碼更加一致。看看我的另一篇文章[在Angular 專案中強制執行前端指南](https://dev.to/kinginit/enforcing-front-end-guidelines-in-an-angular-project-4199) 了解更多資訊關於它。 ## 使用程式碼片段 程式碼片段是模板,可以更輕鬆地編寫重複的程式碼片段,例如 for 迴圈、while 語句等。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9v9g9v8mhlojm00aex6g.gif) 透過使用程式碼片段,您可以透過輸入最少的內容輕鬆建立程式碼區塊。您可以使用內建的程式碼片段,使用提供程式碼片段的擴展,甚至建立您自己的程式碼片段! 內建程式碼片段提供了多種語言的模板,例如 TypeScript、JavaScript、HTML、CSS 等。例如,您可以使用它輕鬆建立「switch」語句,如上所示。 VS Code Marketplace 有多個擴充功能可以提供您程式碼片段。例如 [Angular 片段](https://marketplace.visualstudio.com/items?itemName=johnpapa.Angular2)、[Tailwind UI 片段](https://marketplace.visualstudio.com/items?itemName=evondev.tailwindui-marketplace.visualstudio.com/items?itemName=evondev.tailwindui-evondev.tailwindui-片段)、[Bootstrap 片段](https://marketplace.visualstudio.com/items?itemName=thekalinga.bootstrap4-vscode) 等。 最後,您可以建立自己的片段。您可以為特定語言建立全域程式碼片段,也可以建立特定於專案的程式碼片段。我不會在這裡詳細介紹任何細節,但請查看有關[如何建立自己的片段](https://code.visualstudio.com/docs/editor/userdefinesnippets#_create-your-own-snippets)。 ## 利用“量子打字” 我將其稱為“量子輸入”,因為這確實加快了您在 VS Code 中輸入程式碼的速度。這都是關於多重選擇的。當您需要更改或新增文字到多行時,VS Code 允許您透過選擇這些多行並同時開始在這些行上鍵入來完成此操作。 按住“SHIFT + ALT”並拖曳多條線以進行選擇。您將看到這些行上出現多個鍵入遊標。只需開始輸入,文字就會同時加入。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nl37ca8jwo8sfnm07j0p.gif) 如果您想將相同的文字新增至多個位置但它們不對齊,您可以按住「ALT」同時按一下您要鍵入相同文字的所有位置。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/loqu6id3968iilj1o1jl.gif) 您也可以按住“ALT”並同時選擇多個單字。無需單擊某個位置,只需拖曳進行選擇,然後釋放左鍵單擊或雙擊即可選擇單個單字,同時按住“ALT”鍵。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o05eg38ejzcxhtotlza9.gif) ## 快速環繞選擇 程式碼通常必須用方括號、圓括號或大括號括起來。或某些內容需要用引號(單引號或雙引號)引起來。為此,人們通常會轉到起始位置,輸入起始括號,將遊標移到結束位置,然後輸入結束括號。更有效的方法是選擇需要包圍的零件,然後簡單地鍵入起始括號。 VS Code 會夠聰明,知道整個部分需要被包圍。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/upt3uxf00b9j9ujoetq4.gif) 這適用於 `(`、`{`、`[`、`<`、`'` 和 `"`。 ## 利用 VS Code 重構技巧 您可以使用 VS Code 自動重構程式碼片段。例如,您可以讓 VS Code 為您產生它們,而不是編寫自己的 getter 和 setter。 要重構某些內容,只需選擇需要重構的內容,右鍵單擊,然後單擊“重構...”,甚至更快:使用“CTRL + SHIFT + R”。 根據您所在的文件,VS Code 可以為您提供多種重構。例如,對於 TypeScript,您可以使用「提取函數」、「提取常數」或「產生 get 和 set 存取器」。請參閱 [此處](https://code.visualstudio.com/docs/typescript/typescript-refactoring) 的 TypeScript 完整清單。 ## 使用正規表示式搜尋和替換 正規表示式 (RegEx) 可能是開發人員工具包中非常強大的工具,值得您花時間更好地熟悉它們。您不僅可以在自己的程式碼中使用它(例如,驗證模式、字串替換等),還可以在 VS Code 中使用它進行高級搜尋和替換。 ### 例子 在您所在的專案中,一些 CSS 選擇器以 `app-` 開頭並以 `-container` 結尾。由於新的指導方針,他們希望您將後綴“-container”更改為“-wrapper”。您可以嘗試進行簡單的搜尋和替換,方法是尋找“-container”並將其替換為“-page”,但是當您進行替換時,您會看到某些出現的內容已被替換,而這本不應該是這樣的(例如,名為“.unit-container-highlight”的 CSS 選擇器變成“.unit-wrapper-highlight”)。 透過RegEx,我們可以進行更細粒度的搜尋。使用捕獲組,我們可以提取我們想要保留的單詞,同時替換其餘的單詞。正規表示式看起來像是「app-([a-z\-]+)-container」。我們想要替換結果,使其以“-page”結尾。替換字串將類似於“app-$1-wrapper”。只要確保您選取了“使用正規表示式”即可。 > 注意:儘管正則表達式允許更細粒度的搜尋,但在進行實際替換之前請檢查搜尋窗格中的結果! ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jojfb84q3ir9c2js61c7.png) 您可以透過允許搜尋僅應用於某些文件來獲得更多控制。範例可以只是 HTML 檔案(`*.html`),甚至只是整個資料夾(`src/app/modules`)。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1m03sd4p8nffhga5jveb.png) 如果您想在搜尋之前嘗試 RegEx 以確保它是正確的,請使用線上 RegEx 測試器,例如 [Regex101](https://regex101.com/)。如果您沒有或很少有 RegEx 經驗,請查看 [https://regexlearn.com/](https://regexlearn.com/learn/regex101)。 ## 使用工具自動化單調的工作 有時我們必須做一些單調的工作,例如建立模擬資料、為類別中的每個欄位建立函數、根據介面的屬性建立 HTML 清單專案等。俗話說: > 更聰明地工作,而不是更努力工作! 使用工具來自動化此類單調的工作,而不是自己完成所有繁瑣的工作。我常用的工具有: - [NimbleText](https://nimbletext.com/live):根據給定格式將行輸入轉換為特定輸出。 - [Mockaroo](https://www.mockaroo.com/):產生模擬資料並以多種格式(JSON、CSV、XML 等)輸出。 - [JSON Generator](https://json-generator.com/):也產生模擬資料,但專門針對 JSON。它有點複雜,但它允許定制結果。 使用 NimbleText 的一個很好的例子是基於幾個欄位在 HTML 中建立整個表單。我們有一個要在表單中顯示的欄位清單。每個欄位都有一個標籤和一個輸入。讓我們建立一些資料供 NimbleText 進行轉換: ``` first name, text last name, text email, email street, text number, number city, text postal code, text ``` 這裡我們有 7 行和 2 列。每行代表表單欄位。第一列是標籤的名稱,第二列是 HTML 輸入的類型。 在 NimbleText 中,我們保留設定不變(列分隔符號“,”和行分隔符號“\n”)。 每個表單欄位都應該位於類別為「.form-field」的「div」中,其中包含帶有文字的「label」和表單欄位的「input」。 NimbleText 的模式如下: ``` <div class="form-field">   <label for="<% $0.toCamelCase() %>"><% $0.toSentenceCase() %>:</label>   <input id="<% $0.toCamelCase() %>" type="$1"/> </div> ``` 當我們查看輸出時,我們發現**大量**工作已經為我們完成: ``` <div class="form-field">   <label for="firstName">First name:</label>   <input id="firstName" type="text"/> </div> <div class="form-field">   <label for="lastName">Last name:</label>   <input id="lastName" type="text"/> </div> <div class="form-field">   <label for="email">Email:</label>   <input id="email" type="email"/> </div> <div class="form-field">   <label for="street">Street:</label>   <input id="street" type="text"/> </div> <div class="form-field">   <label for="number">Number:</label>   <input id="number" type="number"/> </div> <div class="form-field">   <label for="city">City:</label>   <input id="city" type="text"/> </div> <div class="form-field">   <label for="postalCode">Postal code:</label>   <input id="postalCode" type="text"/> </div> ``` 因此,盡可能發揮創意並使用這些工具。 ## 結論 在 VS Code 中更快編碼取決於了解快捷鍵並充分利用 IDE 的強大功能。以下是所提及內容的快速總結: 1. 使用快捷鍵進行複製貼上。 2. 透過搜尋取代手動導航,導航更有效率。 3. 使用“F2”重新命名,而不是手動執行。 4. 使用 Emmet。 5. 使用格式化程式來獲得整潔的大綱(以及其他優點)。 6. 使用程式碼片段。 7. 量子型。 8. 使用VS Code重構。 9. RegEx 可以幫助您進行搜尋和取代。 10. 使用NimbleText等工具將單調的工作自動化。 我希望您喜歡閱讀本文。如果您知道有人可能需要一些幫助來更快地編碼,請隨時分享這篇文章! 如果您有任何疑問,請隨時與我們聯繫!謝謝! --- 原文出處:https://dev.to/kinginit/how-to-code-faster-vs-code-edition-4pa

🚀 Kubernetes 上的 GITLAB:終極部署指南! 🌟

## TL;DR 🔍 探索在 Kubernetes 上部署 GitLab 的逐步指南,並專注於 Omnibus 套件配置。了解如何設定 PostgreSQL、SMTP、Container Registry、Sidekiq、Prometheus 指標和備份。使用 Glasskube GitLab Kubernetes Operator 探索替代方案,簡化流程並將設定時間縮短至僅 5 分鐘。 --- ## 我們需要您的回饋! 🫶 在下面的評論中分享您的想法!讓我們知道您想要更多內容的主題。如果本指南有幫助,請點擊貓並留下一顆星,以支持我們建立更多以開發人員為中心的內容。您的回饋很重要! [![Glasskube Github](https://cms.glasskube.eu/uploads/CTA_51bbe3bb2a.png) ](https://github.com/glasskube/operator) --- ## 讓我們開始吧🏌️ [GitLab](https://glasskube.eu/en/s/kubernetes-operator/gitlab/) 是一個以軟體工程團隊為導向的開源 DevSecOps 平台。 有兩種方式可用於在 Kubernetes 叢集上部署 GitLab: 1.GitLab雲端原生混合 2.GitLab包(綜合) ## GitLab 雲端原生混合 部署 GitLab Cloud 原生混合架構僅在查詢特定用例中才有意義。例如 - 如 [GitLab 決策樹](https://docs.gitlab.com/ee/administration/reference_architectures/#decision-tree) 所示 - 如果 GitLab 實例需要為至少 3,000 個使用者提供服務。對於這種部署,GitLab 元件將單獨安裝在不同的 docker 容器中。最低要求為 10 個節點,總共 19 個 vCPU 和 60 GB 內存,這將導致每月數千美元的雲端成本。 <img width="100%" style="width:100%" src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbHl6eXltNWw0ZDNjbnNqbDdicXBpbzNpdX6e nlfaWQmY 3Q9Zw/2aIZfQdC2V7bBvU5t2/giphy.gif"> 因此,在大多數情況下,GitLab 雲端原生是不需要的,而且過於複雜。 **那麼,如何在 Kubernetes 上部署 GitLab Omnibus?** ## Kubernetes 上的 GitLab Omnibus 使用「omnibus」這個名稱,GitLab 也發布了一體化軟體包,這些軟體包可以作為docker 映像[`hub.docker.com/r/gitlab/gitlab-ce`](https://hub.docker. com /r/gitlab/gitlab-ce),可以透過環境變數輕鬆配置。正確進行設定可以輕鬆在 Kubernetes 上部署 GitLab。 應正確配置以下重要元件,以便歸檔合理的 Kubernetes 設定: 1.PostgreSQL資料庫 2. SMTP配置 3. Kubernetes 上的 GitLab 容器註冊表 4. Sidekiq、Gitaly 和 Puma 配置 5. 普羅米修斯指標 6. Kubernetes 上的 GitLab 備份 ### Kubernetes 上的 GitLab:設定外部 PostgreSQL 資料庫 建立 PostgreSQL 資料庫可以使用 [CloudNativePG PostgreSQL Operator](https://cloudnative-pg.io/) 來完成。安裝完 Operator 後,可以透過套用 Kubernetes CR 來部署新的 Postgres 叢集: ``` kind: Cluster apiVersion: postgresql.cnpg.io/v1 metadata: name: gitlab spec: enableSuperuserAccess: false instances: 2 bootstrap: initdb: database: gitlabhq_production owner: gitlab storage: size: 20Gi ``` 重要的是,資料庫名稱為“gitlabhq_product”,而配置的“gitlab”資料庫使用者是資料庫的擁有者。在本例中,我們建立了一個由兩個 PostgreSQL 副本組成的集群,以確保資料庫保持可用,即使執行主副本的節點發生故障也是如此。這稱為高可用性 (HA)。 現在,我們建立了自己的 PostgreSQL 集群,必須在「gitlab.rb」檔案中停用綜合映像中包含的資料庫。 ``` postgresql['enable'] = false gitlab_rails['db_adapter'] = 'postgresql' gitlab_rails['db_encoding'] = 'unicode' gitlab_rails['db_host'] = 'gitlab-pg-rw' gitlab_rails['db_password'] = '<your-password>' ``` 很好,我們已經為 GitLab 安裝提供了雲端原生資料庫。 <img width="25%" style="width:25%" src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExc2VydjJoN3BsN2RycDY5N3prZHhoaW8xdTYxenJoN3BsN2RycDY5N3prZHhoaW8xdTYxenhkbHdtBlcMpm30FmDFt0Fyg n;ipPlcmDMlcM40M400005450000200020025400000 mY3 Q9Zw/SVH9y2LQUVVCRcqD7o/giphy.gif"> #### Kubernetes 上的 GitLab:設定 SMTP 憑證 為了確保您的 GitLab 實例能夠傳送電子郵件,您需要設定 SMTP 伺服器。這也可以在 `gitlab.rb` 檔案中完成。 ``` gitlab_rails['smtp_enable'] = ... gitlab_rails['smtp_address'] = ... gitlab_rails['smtp_port'] = ... gitlab_rails['smtp_user_name'] = ... gitlab_rails['smtp_password'] = ... gitlab_rails['smtp_tls'] = ... gitlab_rails['gitlab_email_from'] = ... ``` 例如,可以在 Brevo 平台上建立用於交易電子郵件的免費 smtp 伺服器。 <img width="25%" style="width:25%" src="https://media.giphy.com/media/0IR3vO2bWY1AQPAsAn/giphy.gif"> #### Kubernetes 上的 GitLab:容器註冊表 在眾多功能中,GitLab 支援充當容器註冊表。這是透過捆綁 docker 容器註冊表的參考實作來實現的,但預設情況下它是禁用的,因為正確設定它相當麻煩。 > **重要** > 重要的是要了解 GitLab 綜合容器中的所有請求將首先由內部 nginx 伺服器處理,然後分發到元件。 容器註冊表僅適用於 TLS 加密連接,因此我們需要透過入口負載平衡器停用 TLS 終止,並將加密流量直接傳送到 GitLab 容器。如果使用 NGINX 入口控制器,可以透過在入口中新增以下註解來完成:「nginx.ingress.kubernetes.io/ssl-passthrough: true」。 之後,必須套用以下`gitlab.rb`配置: ``` registry['enable'] = true gitlab_rails['registry_enabled'] = true registry['token_realm'] = "https://" + ENV['GITLAB_HOST'] gitlab_rails['registry_enabled'] = true gitlab_rails['registry_host'] = ENV['GITLAB_REGISTRY_HOST'] gitlab_rails['registry_api_url'] = "http://localhost:5000" registry_external_url 'https://' + ENV['GITLAB_REGISTRY_HOST'] registry_nginx['redirect_http_to_https'] = true registry_nginx['listen_port'] = 5443 registry_nginx['ssl_certificate'] = "/etc/gitlab/ssl/tls.crt" registry_nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/tls.key" registry_nginx['real_ip_trusted_addresses'] = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'] registry_nginx['server_names_hash_bucket_size'] = 128 ``` 此外,可以配置 S3 儲存桶(或相容的雲端儲存端點),以便所有影像層不會儲存在磁碟區內,而是儲存在可擴充的 S3 中 ### Kubernetes 上的 GitLab:Sidekiq、Gitaly 和 Puma 配置 Puma(Ruby 的 HTTP 伺服器)、Sidekiq(作業排程器)和 Gitaly(GitLabs Git 橋接器)都可以使用自訂數量的工作執行緒/執行緒來啟動。最佳配置在很大程度上取決於您的用例和需要支援的使用者數量。合理的最小配置是: ``` puma['worker_processes'] = 2 sidekiq['max_concurrency'] = 9 ``` ### Kubernetes 上的 GitLab:Prometheus 指標 GitLab 綜合包在鏡像中附帶了自己的 prometheus 實例。由於大多數 Kubernetes 叢集中已經存在 Prometheus 實例,因此可以安全地停用包含的 Prometheus。 ``` prometheus_monitoring['enable'] = false ``` ServiceMonitor 可以用來告訴正在執行的 Prometheus 實例在下列連接埠上監視 GitLabs 元件所公開的指標端點: - 8082(sidekiq) - 9168(gitlab) - 9236(義大利) ### Kubernetes 上的 GitLab:備份 嗯,在 Kubernetes 上備份 GitLab 本身就是一個技術指南。最簡單的方法是使用 [Velero](https://velero.io/) 等備份解決方案備份完整的命名空間。 ## 玻璃立方體 GitLab Kubernetes Operator 了解Glasskube GitLab [Kubernetes Operator](https://glasskube.eu/en/r/glossary/kubernetes-operator/) - 我們的開發團隊對繁瑣的配置過程、耗時的設定和不斷的故障排除感到沮喪的產物與 GitLab 部署相關。為了應對這些挑戰,我們精心設計了一個簡化整個體驗的解決方案。 Glasskube GitLab Kubernetes Operator 部署一個完全配置的 GitLab 實例,其所有功能均透過自訂資源定義 (CRD) 巧妙抽象化。感謝操作員,花費過多時間進行設定和更新的日子已經結束。現在,您可以在短短 5 分鐘內輕鬆啟動新的 GitLab 實例。嘗試一下並向我們提供反饋! ### 安裝 Glasskube 運算符 第一步是透過 Helm Chart 安裝 Glasskube: ``` helm repo add glasskube https://charts.glasskube.eu/ helm repo update helm install my-glasskube-operator glasskube/glasskube-operator ``` 完整的文件可以在我們的[入門](https://glasskube.eu/docs/getting-started/install/)文件中找到。 ### 部署 GitLab > **重要** > 必須在「LoadBalancer」或「Ingress Host」上設定 DNS 專案。 SSL 憑證是 > 如果配置了“ClusterIssuer”,則由“LoadBalancer”或“cert-manager”自動產生。 **gitlab.yaml** ``` apiVersion: "v1" kind: "Secret" metadata: name: "gitlab-smtp" stringData: username: "..." password: "..." --- apiVersion: v1 kind: Secret metadata: name: gitlab-registry-secret stringData: accessKey: "..." secretKey: "..." --- apiVersion: "glasskube.eu/v1alpha1" kind: "Gitlab" metadata: name: "gitlab" spec: host: "gitlab.mycompany.eu" sshEnabled: true sshHost: "ssh.gitlab.mycompany.eu" smtp: host: "..." port: 465 fromAddress: "[email protected]" authSecret: name: "gitlab-smtp" tlsEnabled: true registry: host: "registry.gitlab.mycompany.eu" storage: s3: bucket: gitlab-registry accessKeySecret: name: gitlab-registry-secret key: accessKey secretKeySecret: name: gitlab-registry-secret key: secretKey region: ... ``` ``` kubectl apply -f gitlab.yaml ``` <img width="25%" style="width:25%" src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExa2RldHpiYnMzOWdlZTgwdWtqOHN3N3eG9I0NjVyd2l4m Y3Q9Zw/YRhUem7n2UaF9EK2PH/giphy.gif"> 就是這樣! 完整的自訂資源文件可以在 Glasskube 文件中找到 關於 [GitLab](https://glasskube.eu/docs/crd-reference/gitlab/) ### 在 Kubernetes 上部署 GitLab Runner <img width="25%" style="width:25%" src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZDJsN2x6NTI0dWdhZW55MjBzMjFobHdtbDRtaHEycXlidWdhZW55MjBzMjFobHdtbDRtaHEycXlidWdhZW55MjBzMjFobHdtbDRtaHEycXlidGt. WQmY 3Q9Zw/945jGDodvZCDe/giphy.gif"> GitLab 上的運作程序是為持續整合和持續交付 (CI/CD) 管道提供支援的執行代理。 他們負責執行作業,即管道中的各個步驟或任務。 Glasskube Gitlab Kubernetes Operator 讓在 Gitlab 中新增執行器變得如此簡單。首先,需要透過「https://{{host}}/admin/runners/new」建立一個新的執行程式。之後,這些執行器令牌只需加入到「gitlab.yaml」規格中,Glasskube Kubernetes Operator 將自動產生並將這些執行器與 Gitlab 實例連接起來。 ``` runners: - token: glrt-xxxxXX-xxxxxXXXXX # can be generated at https://{{host}}/admin/runners/new ``` 完整的自訂資源文件可以在關於 [GitLab runner] 的 Glasskube 文件中找到(https://glasskube.eu/docs/crd-reference/gitlab/runner/) 現在安裝完成了。 Kubernetes Operator 的更新將自動更新 GitLab。 ## 結論 在 Kubernetes 上為少於 1000 個使用者部署 GitLab 實例不應該使用 GitLabs 雲端原生混合 Helm Charts 來完成,而應該使用專門安裝的 GitLab Omnibus 套件與雲端原生基礎架構結合來完成。 Glasskube Kubernetes 操作員負責處理這個問題。 --- [![玻璃立方體Github]( https://cms.glasskube.eu/uploads/CTA_51bbe3bb2a.png) ](https://github.com/glasskube/operator) --- 星星冰塊: # [`glasskube/operator`](https://github.com/glasskube/operator) --- 原文出處:https://dev.to/glasskube/gitlab-on-kubernetes-the-ultimate-deployment-guide-188b

✨ 每個開發者都需要了解的 7 個人工智慧庫(成為奇才)🧙‍♂️ 🪄

## 長篇大論;博士 如今,任何開發人員都可以利用人工智慧來建立強大的東西。 無需成為機器學習專家。 這裡有 7 個最好的庫,您可以使用它來增強您的開發並透過最先進的 AI 功能給用戶留下深刻的印象。 這些可以為你的專案帶來神奇的力量,所以不要忘記給他們加星號並支持他們🌟 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/she8nk1oksxmem791o09.gif) --- ## 1. [CopilotKit](https://github.com/RecursivelyAI/CopilotKit):將 AI 功能引入 React 應用程式。 (ChatBot 和 CopilotTexarea) ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0zxu7wrchaod8eyvq46b.png) 常見的法學碩士產品用例被製作成簡單且可自訂的反應元件。 具有兩個元件: CopilotPortal:加入可以在您的應用程式內回答問題並採取行動的法學碩士! CopilotTextarea:任何具有 Github Copilot 功能的 <textarea/> 的直接替代品。 ``` import "@copilotkit/react-ui/styles.css"; import { CopilotProvider } from "@copilotkit/react-core"; import { CopilotSidebarUIProvider } from "@copilotkit/react-ui"; export default function App(): JSX.Element { return ( <CopilotProvider chatApiEndpoint="/api/copilotkit/chat"> <CopilotSidebarUIProvider> <YourContent /> </CopilotSidebarUIProvider> </CopilotProvider> ); } ``` {% cta https://github.com/RecursivelyAI/CopilotKit %} Star CopilotKit ⭐️ {% endcta %} --- ## 2. Tavily GPT 研究員 - 取得法學碩士學位以搜尋網路和資料庫 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/61mwfvsi4n9rnjet0j52.png) Tavilly 可讓您將 GPT 支援的研究和內容產生工具新增至您的 React 應用程式中,從而增強其資料處理和內容建立功能。 ``` # Create an assistant assistant = client.beta.assistants.create( instructions=assistant_prompt_instruction, model="gpt-4-1106-preview", tools=[{ "type": "function", "function": { "name": "tavily_search", "description": "Get information on recent events from the web.", "parameters": { "type": "object", "properties": { "query": {"type": "string", "description": "The search query to use. For example: 'Latest news on Nvidia stock performance'"}, }, "required": ["query"] } } }] ) ``` {% cta https://github.com/assafelovic/gpt-researcher %} 明星塔維利 ⭐️ {% endcta %} --- ## 3. Pezzo.ai - 可觀測性、成本和即時工程平台 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nxvbgi5zkghkb0t64npw.jpeg) 用於管理 OpenAI 通話的集中平台。 優化您的提示和令牌使用。追蹤您的人工智慧使用情況。 免費且易於整合。 ``` const prompt = await pezzo.getPrompt("AnalyzeSentiment"); const response = await openai.chat.completions.create(prompt); ``` {% cta https://github.com/pezzolabs/pezzo %} 明星 Pezzo ⭐️ {% endcta %} --- ## 4. LangChain - 將人工智慧整合到行動鏈中。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8s87kvm5jt5wmsv702r1.png) 易於使用的 API 和函式庫,用於將 LLM 新增到應用程式中。 將不同的人工智慧元件和模型連接在一起。 輕鬆嵌入上下文和語義資料以實現強大的整合。 ``` from langchain.llms import OpenAI from langchain import PromptTemplate llm = OpenAI(model_name="text-davinci-003", openai_api_key="YourAPIKey") # Notice "food" below, that is a placeholder for another value later template = """ I really want to eat {food}. How much should I eat? Respond in one short sentence """ prompt = PromptTemplate( input_variables=["food"], template=template, ) final_prompt = prompt.format(food="Chicken") print(f"Final Prompt: {final_prompt}") print("-----------") print(f"LLM Output: {llm(final_prompt)}") ``` {% cta https://github.com/langchain-ai/langchain %} 星朗鏈 ⭐️ {% endcta %} --- ## 5. [Weaviate](https://github.com/weaviate/weaviate) - 用於人工智慧增強專案的向量資料庫 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/brp7plpkk9sy44ubc14t.png) Weaviate 是一個針對大型資料集快速、高效搜尋而最佳化的向量資料庫。 它支援與 OpenAI 和 Hugging Face 等提供者的 AI 模型和服務集成,從而實現資料分類和自然語言處理等高級任務。 它是一種雲端原生解決方案,具有高度可擴展性,可以滿足不斷變化的資料需求。 ``` import weaviate import json client = weaviate.Client( embedded_options=weaviate.embedded.EmbeddedOptions(), ) uuid = client.data_object.create({ }) obj = client.data_object.get_by_id(uuid, class_name='MyClass') print(json.dumps(obj, indent=2)) ``` {% cta https://github.com/weaviate/weaviate %} 星織 ⭐️ {% endcta %} --- ## 6. [PrivateGPT](https://github.com/imartinez/privateGPT) - 與您的文件聊天,100% 私密 💡 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ap81ce5j9chc5c543jl6.jpg) PrivateGPT 允許在應用程式內進行安全的、GPT 驅動的文件交互,確保資料隱私並增強上下文感知處理能力。 PrivateGPT 透過本地處理和儲存文件和上下文來確保隱私,而無需將資料傳送到外部伺服器。 ``` from privategpt import PrivateGPT, DocumentIngestion, ChatCompletion client = PrivateGPT(api_key='your_api_key') def process_documents_and_chat(query, documents): ingestion_result = DocumentIngestion(client, documents) chat_result = ChatCompletion(client, query, context=ingestion_result.context) return chat_result documents = ['doc1.txt', 'doc2.txt'] query = "What is the summary of the documents?" result = process_documents_and_chat(query, documents) print(result) ``` {% cta https://github.com/weaviate/weaviate %} 星織 ⭐️ {% endcta %} --- ## 7. SwirlSearch - 人工智慧驅動的搜尋。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/extnr9oxhubs6m9x817a.png) LLM 支援的搜尋、摘要和輸出。 同時搜尋多個內容來源並產生整合輸出。 功能強大,可自訂各種資料來源的應用程式內整合。 {% cta https://github.com/swirlai/swirl-search %} 星旋搜尋 ⭐️ {% endcta %} --- 謝謝閱讀! 我希望這些可以幫助您使用人工智慧建立一些很棒的東西。 如果您喜歡並評論您想看到的任何其他庫或主題,請按讚。 --- 原文出處:https://dev.to/copilotkit/7-ai-libraries-every-dev-needs-to-know-to-be-a-wiz-4lim

🏆如何使用 Taipy 和 PySpark 掌握 📊 大資料管道 🐍

本文將透過一個簡單的範例來示範如何**將 PySpark 與 Taipy 整合**,以將您的 **大資料處理需求** 與 **智慧作業執行** 結合。 #### 讓我們開始吧! ![開始使用](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gyd6pq09thphujynk66n.gif) <小時/> ### 將 PySpark 與 Taipy 結合使用 Taipy 是一個**強大的工作流程編排工具**,具有**易於使用的框架**,可輕鬆應用於您現有的資料應用程式。 Taipy 建立在堅實的概念基礎上: - **場景、任務和資料節點** - 這些概念非常強大,允許開發人員**輕鬆地對其管道進行建模**,即使在沒有明確支援的情況下使用第3 方包也是如此。 <小時/> ![QueenB](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bdhmkkqpyjxko242wa2v.gif) {% cta https://github.com/Avaiga/taipy %} Star ⭐ Taipy 儲存庫 {% endcta %} 我們感謝任何幫助我們發展社區的幫助🌱 <小時/> *如果您已經熟悉 PySpark 和 Taipy,則可以跳至「2. Taipy 設定 (*config.py*)」。 *該部分深入探討了為 Taipy 任務定義函數來執行 PySpark 應用程式的本質。否則,請繼續閱讀!* <小時/> ### 一個簡單的例子:*palmerpenguins* 我們以 [palmerpenguins](https://allisonhorst.github.io/palmerpenguins/) 資料集為例: ``` >>> penguin_df ┌───────┬─────────┬───────────┬────────────────┬───────────────┬───────────────────┬─────────────┬────────┬──────┐ │ index │ species │ island │ bill_length_mm │ bill_depth_mm │ flipper_length_mm │ body_mass_g │ sex │ year │ ├───────┼─────────┼───────────┼────────────────┼───────────────┼───────────────────┼─────────────┼────────┼──────┤ │ 0 │ Adelie │ Torgersen │ 39.1 │ 18.7 │ 181.0 │ 3750.0 │ male │ 2007 │ │ 1 │ Adelie │ Torgersen │ 39.5 │ 17.4 │ 186.0 │ 3800.0 │ female │ 2007 │ │ 2 │ Adelie │ Torgersen │ 40.3 │ 18.0 │ 195.0 │ 3250.0 │ female │ 2007 │ │ 3 │ Adelie │ Torgersen │ NaN │ NaN │ NaN │ NaN │ NaN │ 2007 │ │ 4 │ Adelie │ Torgersen │ 36.7 │ 19.3 │ 193.0 │ 3450.0 │ female │ 2007 │ │ ... │ ... │ ... │ ... │ ... │ ... │ ... │ ... │ ... │ └───────┴─────────┴───────────┴────────────────┴───────────────┴───────────────────┴─────────────┴────────┴──────┘ ``` <小時/> 該資料集僅包含 344 筆記錄——幾乎不是一個需要 Spark 處理的資料集。 然而,該資料集是可存取的,且其大小與演示 Spark 與 Taipy 的整合無關。 如果必須使用更大的資料集進行測試,您可以根據需要多次複製資料。 ![DAG 應用程式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/exxtbt00ia5y6avzcy8z.png) *我們簡單的企鵝應用程式的 DAG* <小時/> 我們將設計一個執行**兩個主要任務**的工作流程: #### 1- Spark 任務(*spark_process*): - 載入資料; - 依「*物種*」、「*島嶼*」和「*性別*」將資料分組; - 求其他欄位的平均值(「*bill_length_mm*」、「*bill_depth_mm*」、「*flipper_length_mm*」、「*body_mass_g*」); - 儲存資料。 #### 2- Python 任務(*過濾器*): - 載入Spark任務之前儲存的輸出資料; - 給定“*物種*”、“*島嶼*”和“*性別*”,傳回聚合值。 我們的小專案將包含 4 個檔案: ``` app/ ├─ penguin_spark_app.py # the spark application ├─ config.py # the configuration for our taipy workflow ├─ main.py # the main script (including our application gui) ├─ penguins.csv # the data as downloaded from the palmerpenguins git repo ``` <小時/> 您可以找到每個檔案的內容(*penguins.csv* 除外,您可以從 [palmerpenguins 儲存庫](https://github.com/allisonhorst/palmerpenguins/blob/main/inst/extdata/penguins.csv 取得) )在本文的程式碼區塊中。 <小時/> ## 1. Spark 應用程式 (*penguin_spark_app.py*) 通常,我們使用 *spark-submit* 命令列實用程式來執行 PySpark 任務。 您可以在他們自己的文件中閱讀有關以這種方式提交Spark 作業的內容和原因的更多資訊[此處](https://spark.apache.org/docs/latest/submitting-applications.html) 。 當使用 Taipy 進行工作流程編排時,我們可以繼續做同樣的事情。 唯一的區別是,我們不是在命令列中執行命令,而是讓工作流程管道產生一個[子進程](https://docs.python.org/3/library/subprocess.html),它使用以下命令執行Spark 應用程式*火花提交*。 在開始討論之前,我們首先**看看我們的 Spark 應用程式**。 只需瀏覽一下程式碼,然後**繼續閱讀有關此腳本功能的簡短說明**: ``` ### app/penguin_spark_app.py import argparse import os import sys parser = argparse.ArgumentParser() parser.add_argument("--input-csv-path", required=True, help="Path to the input penguin CSV file.") parser.add_argument("--output-csv-path", required=True, help="Path to save the output CSV file.") args = parser.parse_args() import pyspark.pandas as ps from pyspark.sql import SparkSession def read_penguin_df(csv_path: str): penguin_df = ps.read_csv(csv_path) return penguin_df def clean(df: ps.DataFrame) -> ps.DataFrame: return df[df.sex.isin(["male", "female"])].dropna() def process(df: ps.DataFrame) -> ps.DataFrame: """The mean of measured penguin values, grouped by island and sex.""" mean_df = df.groupby(by=["species", "island", "sex"]).agg("mean").drop(columns="year").reset_index() return mean_df if __name__ == "__main__": spark = SparkSession.builder.appName("Mean Penguin").getOrCreate() penguin_df = read_penguin_df(args.input_csv_path) cleaned_penguin_df = clean(penguin_df) processed_penguin_df = process(cleaned_penguin_df) processed_penguin_df.to_pandas().to_csv(args.output_csv_path, index=False) sys.exit(os.EX_OK) ``` <小時/> 我們可以透過在終端機中輸入以下命令來提交此 Spark 應用程式以供執行: ``` spark-submit --master local[8] app/penguin_spark_app.py \ --input-csv-path app/penguins.csv \ --output-csv-path app/output.csv ``` <小時/> 它將執行以下操作: 1.提交*penguin_spark_app.py*應用程式在8個CPU核心上本地執行; 2. 從 *app/penguins.csv* CSV 檔案載入資料; 3. 依「*物種*」、「*島嶼*」和「*性別*」分組,然後按平均值聚合其餘欄位; 4. 將產生的 DataFrame 儲存到 *app/output.csv*。 此後,*app/output.csv* 的內容應如下所示: ![資料](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1bjjxzb6vzypq2cj3mzl.png) <小時/> 另請注意,我們已對 **Spark 應用程式進行了編碼以接收 2 個命令列參數**: 1.  - *input-csv-path* :輸入企鵝 CSV 檔案的路徑;和 2.  - *output-csv-path* :Spark 應用程式處理後儲存輸出 CSV 檔案的路徑。 <小時/> ## 2. Taipy 設定 (*config.py*) 此時,我們有了 *penguin_spark_app.py* PySpark 應用程式,並且需要建立一個 **Taipy 任務來執行此 PySpark 應用程式**。 再次快速瀏覽 *app/config.py* 腳本,然後繼續閱讀: ``` ### app/config.py import datetime as dt import os import subprocess import sys from pathlib import Path import pandas as pd import taipy as tp from taipy import Config SCRIPT_DIR = Path(__file__).parent SPARK_APP_PATH = SCRIPT_DIR / "penguin_spark_app.py" input_csv_path = str(SCRIPT_DIR / "penguins.csv") # -------------------- Data Nodes -------------------- input_csv_path_cfg = Config.configure_data_node(id="input_csv_path", default_data=input_csv_path) # Path to save the csv output of the spark app output_csv_path_cfg = Config.configure_data_node(id="output_csv_path") processed_penguin_df_cfg = Config.configure_parquet_data_node( id="processed_penguin_df", validity_period=dt.timedelta(days=1) ) species_cfg = Config.configure_data_node(id="species") # "Adelie", "Chinstrap", "Gentoo" island_cfg = Config.configure_data_node(id="island") # "Biscoe", "Dream", "Torgersen" sex_cfg = Config.configure_data_node(id="sex") # "male", "female" output_cfg = Config.configure_json_data_node( id="output", ) # -------------------- Tasks -------------------- def spark_process(input_csv_path: str, output_csv_path: str) -> pd.DataFrame: proc = subprocess.Popen( [ str(Path(sys.executable).with_name("spark-submit")), str(SPARK_APP_PATH), "--input-csv-path", input_csv_path, "--output-csv-path", output_csv_path, ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) try: outs, errs = proc.communicate(timeout=15) except subprocess.TimeoutExpired: proc.kill() outs, errs = proc.communicate() if proc.returncode != os.EX_OK: raise Exception("Spark training failed") df = pd.read_csv(output_csv_path) return df def filter(penguin_df: pd.DataFrame, species: str, island: str, sex: str) -> dict: df = penguin_df[(penguin_df.species == species) & (penguin_df.island == island) & (penguin_df.sex == sex)] output = df[["bill_length_mm", "bill_depth_mm", "flipper_length_mm", "body_mass_g"]].to_dict(orient="records") return output[0] if output else dict() spark_process_task_cfg = Config.configure_task( id="spark_process", function=spark_process, skippable=True, input=[input_csv_path_cfg, output_csv_path_cfg], output=processed_penguin_df_cfg, ) filter_task_cfg = Config.configure_task( id="filter", function=filter, skippable=True, input=[processed_penguin_df_cfg, species_cfg, island_cfg, sex_cfg], output=output_cfg, ) scenario_cfg = Config.configure_scenario( id="scenario", task_configs=[spark_process_task_cfg, filter_task_cfg] ) ``` 您也可以**使用[Taipy Studio](https://docs.taipy.io/en/latest/manuals/studio/config/)** 建立Taipy 配置,這是一個Visual Studio Code 擴展,它提供了圖形編輯器建構 Taipy *.toml* 設定檔。 <小時/> ### Taipy 中的 PySpark 任務 我們對產生這部分 DAG 的程式碼部分特別感興趣: ![DAG](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/19t1otocpcrsa5qtdt2n.png) <小時/> 讓我們提取並檢查 *config.py* 腳本的相關部分,該腳本在 Taipy 中建立「*spark_process*」Spark 任務(及其 3 個關聯的資料節點),如上圖所示: ``` ### Code snippet: Spark task in Taipy # -------------------- Data Nodes -------------------- input_csv_path_cfg = Config.configure_data_node(id="input_csv_path", default_data=input_csv_path) # Path to save the csv output of the spark app output_csv_path_cfg = Config.configure_data_node(id="output_csv_path") processed_penguin_df_cfg = Config.configure_parquet_data_node( id="processed_penguin_df", validity_period=dt.timedelta(days=1) ) # -------------------- Tasks -------------------- def spark_process(input_csv_path: str, output_csv_path: str) -> pd.DataFrame: proc = subprocess.Popen( [ str(Path(sys.executable).with_name("spark-submit")), str(SPARK_APP_PATH), "--input-csv-path", input_csv_path, "--output-csv-path", output_csv_path, ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) try: outs, errs = proc.communicate(timeout=15) except subprocess.TimeoutExpired: proc.kill() outs, errs = proc.communicate() if proc.returncode != os.EX_OK: raise Exception("Spark training failed") df = pd.read_csv(output_csv_path) return df spark_process_task_cfg = Config.configure_task( id="spark_process", function=spark_process, skippable=True, input=[input_csv_path_cfg, output_csv_path_cfg], output=processed_penguin_df_cfg, ) ``` <小時/> 由於我們設計 *penguin_spark_app.py* Spark 應用程式來接收 2 個參數(*input_csv_path* 和 *output_csv_path*),因此我們選擇將這 2 個參數表示為 Taipy 資料節點。 請注意,**您的用例可能有所不同,您可以(並且應該!)根據您的需求修改任務、函數和關聯的資料節點**。 例如,您可以: 1. 有一個 Spark 任務,執行一些例行 ETL 並且不回傳任何內容; 2. 偏好對輸入和輸出路徑進行硬編碼,而不是將它們持久化為資料節點;或者 3. 將其他應用程式參數儲存為資料節點並將其傳遞給 Spark 應用程式。 然後,我們將 *spark-submit* 作為 Python 子進程執行,如下所示: ``` subprocess.Popen( [ str(Path(sys.executable).with_name("spark-submit")), str(SPARK_APP_PATH), "--input-csv-path", input_csv_path, "--output-csv-path", output_csv_path, ], ) ``` <小時/> 回想一下,清單元素的順序應保留以下格式,就像它們在命令列上執行一樣: ``` $ spark-submit [spark-arguments] <pyspark-app-path> [application-arguments] ``` <小時/> 同樣,根據我們的用例,我們可以根據需要指定不同的 Spark-submit 腳本路徑、Spark 參數(我們在範例中未提供任何參數)或不同的應用程式參數。 <小時/> ### 讀取並回傳*output_csv_path* 請注意,*spark_process* 函數的結束如下: ``` def spark_process(input_csv_path: str, output_csv_path: str) -> pd.DataFrame: ... df = pd.read_csv(output_csv_path) return df ``` <小時/> 在我們的例子中,我們希望 Taipy 任務在 Spark -  處理資料後輸出資料,以便可以將其寫入 *processed_penguin_df_cfg* [Parquet 資料節點](https://docs.taipy.io/en/latest /手冊/核心/配置/資料節點配置/#parquet)。 我們可以做到這一點的一種方法是手動讀取輸出目標(在本例中為 *output_csv_path*),然後將其作為 Pandas DataFrame 傳回。 但是,如果您不需要 Spark 應用程式的返回資料,您可以簡單地讓 Taipy 任務(透過 *spark_process* 函數)返回 *None*。 <小時/> ### 快取 Spark 任務 由於我們將 *spark_process_task_cfg* 配置為 *True*,當重新執行該場景時,Taipy 將 **跳過 ** ***spark_process 的重新執行*** **任務** 並重複使用持久化任務輸出:* processed_penguin_df_cfg* Pandas DataFrame。 但是,我們也為 *processed_penguin_df_cfg* 資料節點定義了 1 天的 *validity_period*,因此如果 DataFrame 最後一次快取超過一天,Taipy 仍會重新執行任務。 <小時/> ## 3. 建構 GUI (*main.py*) 我們將透過**建立我們在本文開頭看到的 GUI** 來完成我們的應用程式: ![應用程式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bvfpy6aobtbzdhbf55sv.png) <小時/> 如果您不熟悉 Taipy 的 GUI 功能,可以在此處找到[快速入門](https://docs.taipy.io/en/latest/getting_started/getting-started-gui/)。 無論如何,您只需為 *app/main.py* 複製並貼上以下程式碼,因為它不是我們的重點: ``` ### app/main.py from pathlib import Path from typing import Optional import taipy as tp from config import scenario_cfg from taipy.gui import Gui, notify valid_features: dict[str, list[str]] = { "species": ["Adelie", "Chinstrap", "Gentoo"], "island": ["Torgersen", "Biscoe", "Dream"], "sex": ["Male", "Female"], } selected_species = valid_features["species"][0] selected_island = valid_features["island"][0] selected_sex = valid_features["sex"][0] selected_scenario: Optional[tp.Scenario] = None data_dir = Path(__file__).with_name("data") data_dir.mkdir(exist_ok=True) def scenario_on_creation(state, id, payload): _ = payload["config"] date = payload["date"] label = payload["label"] properties = payload["properties"] # Create scenario with selected configuration scenario = tp.create_scenario(scenario_cfg, creation_date=date, name=label) scenario.properties.update(properties) # Write the selected GUI values to the scenario scenario.species.write(state.selected_species) scenario.island.write(state.selected_island) scenario.sex.write(state.selected_sex.lower()) output_csv_file = data_dir / f"{scenario.id}.csv" scenario.output_csv_path.write(str(output_csv_file)) notify(state, "S", f"Created {scenario.id}") return scenario def scenario_on_submission_change(state, submittable, details): """When the selected_scenario's submission status changes, reassign selected_scenario to force a GUI refresh.""" state.selected_scenario = submittable selected_data_node = None main_md = """ <|layout|columns=1 4|gap=1.5rem| <lhs|part| # Spark with **Taipy**{: .color-primary} ## Scenario <|{selected_scenario}|scenario_selector|on_creation=scenario_on_creation|> ---------- ## Scenario info <|{selected_scenario}|scenario|on_submission_change=scenario_on_submission_change|> |lhs> <rhs|part|render={selected_scenario}| ## Selections <selections|layout|columns=1 1 1 2|gap=1.5rem| <|{selected_species}|selector|lov={valid_features["species"]}|dropdown|label=Species|> <|{selected_island}|selector|lov={valid_features["island"]}|dropdown|label=Island|> <|{selected_sex}|selector|lov={valid_features["sex"]}|dropdown|label=Sex|> |selections> ---------- ## Output **<|{str(selected_scenario.output.read()) if selected_scenario and selected_scenario.output.is_ready_for_reading else 'Submit the scenario using the left panel.'}|text|raw|class_name=color-primary|>** ## Data node inspector <|{selected_data_node}|data_node_selector|display_cycles=False|> **Data node value:** <|{str(selected_data_node.read()) if selected_data_node and selected_data_node.is_ready_for_reading else None}|> <br/> ---------- ## DAG <|Scenario DAG|expandable| <|{selected_scenario}|scenario_dag|> |> |rhs> |> """ def on_change(state, var_name: str, var_value): if var_name == "selected_species": state.selected_scenario.species.write(var_value) elif var_name == "selected_island": state.selected_scenario.island.write(var_value) elif var_name == "selected_sex": state.selected_scenario.sex.write(var_value.lower()) if __name__ == "__main__": tp.Core().run() gui = Gui(main_md) gui.run(title="Spark with Taipy") ``` <小時/> 然後,從專案資料夾中,您可以執行主腳本,如下所示: ``` $ taipy run app/main.py ``` <小時/> ## 結論 現在您已經看到如何將 PySpark 與 Taipy 結合使用的範例,請繼續嘗試使用這兩個工具來**增強您自己的資料應用程式**! 如果您一直在努力應對其他工作流程編排工具減慢您的工作並妨礙您的工作,請不要讓它阻止您嘗試 Taipy。 Taipy 易於使用,並且努力不限制自己可以使用的第 3 方軟體包 - **其強大而靈活的框架使其可以輕鬆適應任何資料應用程式**。 <小時/> ![GIF 結束](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/839kmsq22emwpkuerxys.gif) 希望您喜歡這篇文章! <小時/> 您可以在此[儲存庫](https://medium.com/r?url=https%3A%2F%2Fgithub.com%2FAvaiga%2Fdemo-pytorch-penguin-app)上找到所有程式碼和資料。 --- 原文出處:https://dev.to/taipy/how-to-master-big-data-pipelines-with-taipy-and-pyspark-14oe

我在 4 天內將我的業餘專案銷售額,從 0 美元增加到 200 美元

這是我的 side project(基本上是Next.Js SaaS Boilerplate [Indiespace](https://www.indiespace.store/))如何在沒有任何社交媒體存在的情況下在4 天內從0 美元到200 美元以上的故事。 **想法:** 在過去的一個半月裡,我一直在致力於這個專案,最初的計劃是建立一些我可以使用的東西,因為我喜歡開始新專案,但常常對從頭開始設置一切的感覺感到不知所措。所以我開始建立這個樣板,其中包括2 個登陸頁面、2 個等待清單頁面和一個帶有登陸頁面的SAAS 套件以及所有很酷的功能,如深色和淺色主題、訂閱、一次性付款、交易電子郵件、身份驗證、SEO 等。 **期望:** 我對是否有人會購買它或我將如何推銷它的期望為零,因為就像其他所有開發人員一樣,他們推出了包含大量內容的樣板,而我沒有觀眾。所以我按照自己的步調建立它並不斷加入功能。 **發射,市場投入:** 我的發布計劃是在 3 個平台上發布相關內容: - 推特 - 產品搜尋 - 副業專案 因此,在 11 月 17 日星期五,我安排了 11 月 18 日星期六在 Product Hunt 上發布,並在 Sideprojects 上發布,然後就去睡覺了。我大約在星期六下午 2 點醒來,在手機上看到 Product Hunt 通知,所以我打開筆記型電腦並進入 Gmail,然後看到一封來自 Stripe 的電子郵件,我打開它並: ![獨立空間條紋螢幕截圖](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/st98nlxavyzodugtutl0.png) 所以現在我很困惑我到底在看什麼,並質疑自己這是從購買我的樣板的人那裡得到的嗎?我打開了產品搜尋,自發布以來已經 1 小時了,我以 30 多票排名第六,沒有進行任何行銷或要求人們投票,並且已經完成銷售。我笑得像個孩子一樣啊啊啊啊啊啊啊啊? 然後我在 Twitter 上發帖,並請一些朋友點贊,發布後獲得了第七名,點贊數超過 150,頁面瀏覽量 1700 次,Twitter 上的關注者超過 10 人。 ![獨立空間頁面瀏覽量](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9xlk6809b0lbh52thq12.png) **課程:** 我知道這不是很多錢,我知道與我從全職工作中賺到的錢相比,這不算什麼,但說實話,建立自己的數位產品,行銷它,甚至從中賺取一分錢都會給你帶來好處。如此多的幸福。如果你是像我一樣的開發人員,或者想要建立一些東西並且對如何進行一切感到困惑的人,請不要擔心將你的專案公開,讓人們決定,盡你的努力,並繼續前進。 在這一點上,我知道我的 [Indiespace](https://www.indiespace.store/) 並不完美,現在還有很多事情要做,我還必須加入該死的文件,而且我已經討厭設定內容層文件ugghhhh,但是,是的,我將繼續加入功能,以提高我作為開發人員的生產力。 **最後但同樣重要的:** 如果您想要建立 SaaS 應用程式,我建議您嘗試 [Indiespace.store](https://www.indiespace.store/),如果您是剛起步且負擔不起的人,請嘗試在Twitter 上給我留言,我將非常樂意為您提供協助。 --- 原文出處:https://dev.to/salmandotweb/i-took-my-side-project-from-0-to-180-in-4-days-2i09

使用 Markdown 與 React 在 Github 文件中顯示淺色模式、深色模式

警告:所表達的觀點可能不適合所有受眾! 😂 ## 簡介 在本文結束時,您將了解並能夠根據使用者偏好 - **深色**或**淺色**模式展示您的 Markdown 影像。 1. 我將介紹如何在 GitHub README.md 中加入兩個圖像 - 根據所選的“主題”,您的圖像將正確回應。 2. 我將引導您在 Markdown 中合併影像的過程,並示範如何使用 React 使它們回應。 😎 ___ ## 你使用淺色還是深色? 我不了解你的情況,但無論平台如何,如果他們可以選擇在淺色和深色模式之間切換,那就沒有競爭了。 淺色主題正在切換為深色,事實上,當然在我寫這篇文章的時候! ![深色主題](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s0yfjc2yv5pfyacu74go.png) 話雖如此,在軟體開發的快速發展中,創造無縫的使用者體驗至關重要。 這種體驗的一部分涉及適應使用者偏好,例如淺色和深色模式。 我還記得幾年前,Github 宣布了用戶可以切換到「深色模式」的選項,這是一件非常大的事情。 ![GitHub 深色主題](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qi10urco3o6fdojm6ipf.png) 【Github揭曉黑暗主題的重要時刻】(https://t.co/HEotvXVJ7R) 🤩 2020 年 12 月 8 日🎆 近年來,使用者介面中深色和淺色模式選項的出現已成為一種流行趨勢。 我絕對不是唯一一個喜歡使用深色主題選項的人,根據 Android 用戶的說法,[91.8% 的用戶更喜歡深色模式](https://www.androidauthority.com/dark-mode-poll-results-1090716/) 所以我們可以猜測這個數字在所有作業系統中都相當高。 這當然可能會引起激烈的爭論,所以我會盡力將自己的觀點降到最低。 ![輕模式迷因](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m3yiepj8a46rwhu69fgw.png) ## 改善使用者體驗 主要目標是透過在應用程式中提供選項來改善用戶體驗。 有多種方法可以建立每個圖像的多個版本,在本教程中我們不會深入討論細節。 只要確保您的圖像在兩個主題中脫穎而出並具有透明背景,您就會獲得成功。 **_讓我們開始派對吧!_** ## GitHub 自述文件中的響應式圖像 您有一個專案並想讓您的 GitHub 專案 README.md 真正流行嗎? 無論使用者使用什麼淺色主題,我們都需要一種方法來指定圖像應在 Markdown 中顯示哪種主題(淺色或深色)。 當您想要根據使用者選擇的配色方案優化圖片的顯示時,這特別有用,並且它涉及將 **HTML `<picture>`** 元素與 `prefers-color-scheme` 媒體功能結合使用如下所示。 繼續將圖片檔案直接拖曳到 GitHub 中並放在“srcset=”後面。 ``` <picture> <source media="(prefers-color-scheme: dark)" srcset="https://github.com/boxyhq/.github/assets/66887028/df1c9904-df2f-4515-b403-58b14a0e9093"> <source media="(prefers-color-scheme: light)" srcset="https://github.com/boxyhq/.github/assets/66887028/e093a466-72ea-41c6-a292-4c39a150facd"> <img alt="BoxyHQ Banner" src="https://github.com/boxyhq/jackson/assets/66887028/b40520b7-dbce-400b-88d3-400d1c215ea1"> </picture> ``` 瞧! ![SAML Jackson 暗模式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q51g41fjfqnposn50una.png) ![SAML Jackson 燈光模式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d0xzs88txjylnixilaqu.png) 太好了,你有 5 秒嗎? https://github.com/boxyhq/jackson --- ## 使用 React 在 Markdown 中回應影像 假設今天我將像平常一樣用 Markdown 編寫博客,並將其發佈到我的網站上。 我使用的圖像需要根據使用者偏好做出回應,但在 Markdown 中不可能偵聽本地儲存和設定狀態中的「主題」變更。 ![本機儲存](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vrjz4to8x17h63dtybxn.png) 值得慶幸的是,如果我們將 React 匯入到 Markdown 檔案中,但先建立一個元件,就有一種方法可以解決這個困境。 ## 反應文件 ``` src/components/LightDarkToggle.js import React, { useEffect, useState } from 'react'; function ToggleImages() { // Define a state variable to track the user's login status const [currentTheme, setcurrentTheme] = useState(localStorage.getItem('theme')); // Add an event listener for the 'storage' event inside a useEffect useEffect(() => { const handleStorageChange = (event) => { console.log('Storage event detected:', event); // Check the changed key and update the state accordingly console.log("event", event.key) if (event.key === 'theme') { setcurrentTheme(event.newValue); } }; window.addEventListener('storage', handleStorageChange); // Clean up the event listener when the component unmounts return () => { window.removeEventListener('storage', handleStorageChange); }; }, []); // The empty dependency array ensures that this effect runs once when the component mounts return ( <div className="image-container"> {currentTheme == 'light'? ( <img id="light-mode-image" src="/img/blog/boxyhq-banner-light-bg.png" alt="Light Mode Image" ></img> ):( <img id="dark-mode-image" src="/img/blog/boxyhq-banner-dark-bg.png" alt="Dark Mode Image" ></img> )} </div> ); } export default ToggleImages; ``` 我在程式碼中加入了註釋和一些控制台日誌,以幫助了解正在發生的事情,但讓我們快速分解它。 - React useState 鉤子管理 `currentTheme` 的狀態,它代表使用者選擇的儲存在本機儲存中的主題。 - useEffect 掛鉤用於為「儲存」事件新增事件偵聽器。當儲存事件發生時(表示本機儲存發生變化),元件會檢查變更的鍵是否為“theme”,並相應地更新“currentTheme”狀態。 - 此元件根據使用者選擇的主題呈現不同的影像,如果主題是“淺色”,則顯示淺色模式影像;如果主題是其他主題,則顯示深色模式影像。 酷,讓我們繼續吧! ## 降價文件 讓我們為新部落格建立一個 .md 檔案。 ``` --- slug: light-and-dark-mode-responsive-images title: 'Light and Dark Mode Responsive Images' tags_disabled: [ developer, react, javascript, open-source, ] image: /img/blog/light-dark.png author: Nathan Tarbert author_title: Community Engineer @BoxyHQ author_url: https://github.com/NathanTarbert author_image_url: https://boxyhq.com/img/team/nathan.jpg --- import ToggleImages from '../src/components/LightDarkToggle.js'; ## 🤩 Let's start this blog off with a bang! Our business logo is now responsive with each user's preference, whether it's **light** or **dark** mode! <div> <ToggleImages /> </div> More blog words... ``` 此時,我們只需匯入 React 元件並將其呈現在 Markdown 檔案中。 由於這是一個 Next.js 應用程式,讓我們啟動伺服器“npm run dev”並查看結果。 ![貓鼓滾](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lyjzjqgcwaubyj5ve1o3.gif) ![網站深色模式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qraltb34mrl9y8j9jppq.png) 並切換到淺色主題 ![網站燈光模式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u33jgzha5fbfy6tlb4hs.png) 讓我們打開控制台來查看我們的事件 ![console.log](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qpxz5gbhwt308vatsnkp.png) 你有它! 這些是在 Markdown 中展示響應式映像的幾種方法,其中一個範例使用 React 來幫助我們在本地儲存中設定狀態。 我希望您喜歡這篇文章,如果您喜歡開發,請在 [X (Twitter)](https://twitter.com/nathan_tarbert) 上關注我,我們下次再見! --- 原文出處:https://dev.to/nathan_tarbert/the-zebras-guide-to-showcase-your-images-in-light-dark-17f5

增強您的 Windows 開發能力:WSL 終極指南🚀📟

## 你好!我是[鮑里斯](https://www.martinovic.dev/)! 我是一名軟體工程師,專門從事保險工作,教授其他開發人員,並在會議上發言。多年來,我使用了相當多的不同開發環境和作業系統,除了 .Net 開發之外,我個人從來不喜歡在 Windows 中進行開發。這是為什麼?讓我們更深入地研究一下。 好吧,我的大部分問題都可以歸結為一個詞:**麻煩**。無論是在日常使用中處理Windows,您都會經常遇到作業系統本身的不同方式帶給您的困擾。這樣的例子很多,無論是登錄問題、套件管理、切換節點版本或 Windows 更新,這些問題本身就可以讓人們放棄作業系統。 所以你可以明白為什麼我開始與下圖的烏鴉產生連結。 ![](https://res.cloudinary.com/practicaldev/image/fetch/s--KiM-kkXF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3gpwe8ax86eeh6ccpgsi.png) 我並沒有放棄尋找可行的解決方案。而且,我(有點)找到了它。 ## 什麼是 WSL?我為什麼要對它感興趣? Windows Subsystem for Linux(或 WSL)讓開發人員可以直接在 Windows 上執行功能齊全的本機 GNU/Linux 環境。換句話說,我們可以直接執行Linux,而無需使用虛擬機器或雙重開機系統。 **第一個很酷的事情是 WSL 允許您永遠不用切換作業系統,但仍然可以在作業系統中擁有兩全其美的優點。** 這對我們普通用戶意味著什麼?當您查看WSL 在實踐中的工作方式時,它可以被視為一項Windows 功能,直接在Windows 10 或11 內執行Linux 作業系統,具有功能齊全的Linux 檔案系統、Linux 命令列工具、*** *** 和****** Linux GUI 應用程式(*真的很酷,順便說一句*)。除此之外,與虛擬機器相比,它使用的運作資源要少得多,並且不需要單獨的工具來建立和管理這些虛擬機器。 WSL 主要針對開發人員,因此本文將重點放在開發人員的使用以及如何使用 VS Code 設定完全工作的開發環境。在本文中,我們將介紹一些很酷的功能以及如何在實踐中使用它們。另外,理解新事物的最好方法就是實際開始使用它們。 ### 覺得這篇文章有用嗎? 我們正在 [Wasp](https://wasp-lang.dev/) 努力建立這樣的內容,更不用說建立一個現代的開源 React/NodeJS 框架了。 表達您支援的最簡單方法就是為 Wasp 儲存庫加註星標! 🐝 但如果您可以查看[存儲庫](https://github.com/wasp-lang/wasp)(用於貢獻,或只是測試產品),我們將不勝感激。點擊下面的按鈕給黃蜂星一顆星並表示您的支持! ![wasp_arnie_handshake](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/axqiv01tl1pha9ougp21.gif) https://github.com/wasp-lang/wasp ## 在 Windows 作業系統上安裝 WSL 為了在 Windows 上安裝 WSL,請先啟用 [Hyper-V](https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/quick-start/enable-hyper-v )架構是微軟的硬體虛擬化解決方案。要安裝它,請右鍵單擊 Windows 終端機/Powershell 並以管理員模式開啟它。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6wm5xniz2nehrccczeh6.png) 然後,執行以下命令: ``` Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All ``` 這將確保您具備安裝的所有先決條件。然後,在管理員模式下開啟 Powershell(最好在 Windows 終端機中完成)。然後,執行 ``` wsl —install ``` 有大量的 Linux 發行版需要安裝,但 Ubuntu 是預設安裝的。本指南將介紹許多控制台命令,但其中大多數將是複製貼上過程。 如果您之前安裝過 Docker,那麼您的系統上很可能已經安裝了 WSL 2。在這種情況下,您將收到安裝所選發行版的提示。由於本教程將使用 Ubuntu,因此我建議執行。 ``` wsl --install -d Ubuntu ``` 安裝 Ubuntu(或您選擇的其他發行版)後,您將進入 Linux 作業系統並出現歡迎畫面提示。在那裡,您將輸入一些基本資訊。首先,您將輸入您的用戶名,然後輸入密碼。這兩個都是 Linux 特定的,因此您不必重複您的 Windows 憑證。完成此操作後,安裝部分就結束了!您已經在 Windows 電腦上成功安裝了 Ubuntu!說起來還是感覺很奇怪吧? ### 等一下! 但在我們開始討論開發環境設定之前,我想向您展示一些很酷的技巧,這些技巧將使您的生活更輕鬆,並幫助您了解為什麼 WSL 實際上是 Windows 用戶的遊戲規則改變者。 WSL 的第一個很酷的事情是您不必放棄目前透過 Windows 資源管理器管理檔案的方式。在 Windows 資源管理器的側邊欄中,您現在可以在網路標籤下找到 Linux 選項。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/647jdnzilrucsijtye3v.png) 從那裡,您可以直接從 Windows 資源管理器存取和管理 Linux 作業系統的檔案系統。這個功能真正酷的是,你基本上可以在不同的作業系統之間複製、貼上和移動文件,沒有任何問題,這開啟了一個充滿可能性的世界。實際上,您不必對文件工作流程進行太多更改,並且可以輕鬆地將許多專案和文件從一個作業系統移動到另一個作業系統。如果您在 Windows 瀏覽器上下載 Web 應用程式的映像,只需將其複製並貼上到您的 Linux 作業系統中即可。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iqjsd1oz5a4alu6q08re.png) 我們將在範例中使用的另一個非常重要的事情是 WSL2 虛擬路由。由於您的作業系統中現在有作業系統,因此它們有一種通訊方式。當您想要存取 Linux 作業系統的網路時(例如,當您想要存取在 Linux 中本機執行的 Web 應用程式時),您可以使用 *${PC-name}.local*。對我來說,由於我的電腦名稱是 Boris-PC,所以我的網路位址是 boris-pc.local。這樣你就不必記住不同的 IP 位址,這真的很酷。如果您出於某種原因需要您的位址,您可以前往 Linux 發行版的終端,然後輸入 ipconfig。然後,您可以看到您的 Windows IP 和 Linux 的 IP 位址。這樣,您就可以毫無摩擦地與兩個作業系統進行通訊。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lkhcfiybnobuoziitwtm.png) 我想強調的最後一件很酷的事情是 Linux GUI 應用程式。這是一項非常酷的功能,有助於使 WSL 對普通用戶更具吸引力。您可以使用流行的套件管理器(例如 apt(Ubuntu 上的預設值)或 flatpak)在 Linux 系統上安裝任何您想要的應用程式。然後,您也可以從命令列啟動它們,應用程式將啟動並在 Windows 作業系統中可見。但這可能會引起一些摩擦並且不方便用戶使用。此功能真正具有突破性的部分是,您可以直接從 Windows 作業系統啟動它們,甚至無需親自啟動 WSL。因此,您可以建立捷徑並將它們固定到「開始」功能表或任務欄,沒有任何摩擦,並且實際上不需要考慮您的應用程式來自哪裡。為了演示,我安裝了 Dolphin 檔案管理器並透過 Windows 作業系統執行它。您可以在下面看到它與 Windows 資源管理器並排的操作。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yq1nxj244jd1fci13oay.png) ## WSL 開發入門 在了解了 WSL 的所有酷炫功能後,讓我們慢慢回到教學的正軌。接下來是設定我們的開發環境並啟動我們的第一個應用程式。我將設定一個 Web 開發環境,我們將使用 [Wasp](https://wasp-lang.dev/) 作為範例。 如果你不熟悉的話,Wasp 是一個類似 Rails 的 React、Node.js 和 Prisma 框架。這是開發和部署全端 Web 應用程式的快速、簡單的方法。對於我們的教程,Wasp 是一個完美的候選者,因為它本身不支援 Windows 開發,而只能透過 WSL 來支持,因為它需要 Unix 環境。 讓我們先開始安裝 Node.js。目前,Wasp 要求使用者使用 Node v18(版本要求很快就會放寬),因此我們希望從 Node.js 和 NVM 的安裝開始。 但首先,讓我們先從 Node.js 開始。在 WSL 中,執行: ``` sudo apt install nodejs ``` 為了在您的 Linux 環境中安裝 Node。接下來是 NVM。我建議存取 https://github.com/nvm-sh/nvm 並從那裡獲取最新的安裝腳本。目前下載的是: ``` curl -o- [https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh](https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh) | bash ``` 之後,我們在系統中設定了 Node.js 和 NVM。 接下來是在我們的 Linux 環境中安裝 Wasp。 Wasp 安裝也非常簡單。因此,只需複製並貼上此命令: ``` curl -sSL [https://get.wasp-lang.dev/installer.sh](https://get.wasp-lang.dev/installer.sh) | sh ``` 並等待安裝程序完成它的事情。偉大的!但是,如果您從 0 開始進行 WSL 設置,您會注意到下面有以下警告:看起來“/home/boris/.local/bin”不在您的 PATH 上!您將無法透過終端名稱呼叫 wasp。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/em932e89tlzajv4rm6up.png) 讓我們快速解決這個問題。為了做到這一點,讓我們執行 ``` code ~/.profile ``` 如果我們還沒有 VS Code,它會自動設定所需的一切並啟動,以便您可以將命令新增至檔案末端。每個人的系統名稱都會有所不同。例如我的是: ``` export PATH=$PATH:/home/boris/.local/bin ``` 偉大的!現在我們只需要將節點版本切換到 v18.14.2 即可確保與 Wasp 完全相容。我們將一次性安裝並切換到 Node 18!為此,只需執行: ``` nvm install v18.14.2 && nvm use v18.14.2 ``` 設定 Wasp 後,我們希望了解如何執行應用程式並從 VS Code 存取它。在幕後,您仍將使用 WSL 進行開發,但我們將能夠使用主機作業系統 (Windows) 中的 VS Code 來完成大多數事情。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/orifa202sph4swgbir2d.png) 首先,將 [WSL 擴充功能](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-wsl) 下載到 Windows 中的 VS Code。然後,讓我們啟動一個新的 Wasp 專案來看看它是如何運作的。開啟 VS Code 命令面板(ctrl + shift + P)並選擇「在 WSL 中開啟資料夾」選項。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l1le8xvk6a8a8teog8eo.png) 我打開的資料夾是 ``` \\wsl.localhost\Ubuntu\home\boris\Projects ``` 這是我在 WSL 中的主資料夾中的「Projects」資料夾。我們可以透過兩種方式知道我們處於 WSL 中:頂部欄和 VS Code 的左下角。在這兩個地方,我們都編寫了 WSL: Ubuntu,如螢幕截圖所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mzhu765415sravn3vypu.png) ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cpy4kggtsobod1vk1dqn.png) 進入該資料夾後,我將打開一個終端。它還將已經連接到 WSL 中的正確資料夾,因此我們可以開始工作了!讓我們執行 ``` wasp new ``` 命令建立一個新的 Wasp 應用程式。我選擇了基本模板,但您可以自由建立您選擇的專案,例如[SaaS 入門](https://github.com/wasp-lang/SaaS-Template-GPT) 具有 GPT、Stripe 等預先配置。如螢幕截圖所示,我們應該將專案的當前目錄變更為正確的目錄,然後用它來執行我們的專案。 ``` wasp start ``` ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l453mcae56kfa3yrm7j4.png) 就像這樣,我的 Windows 電腦上將打開一個新螢幕,顯示我的 Wasp 應用程式已開啟。涼爽的!我的位址仍然是預設的 localhost:3000,但它是從 WSL 執行的。恭喜,您已透過 WSL 成功啟動了您的第一個 Wasp 應用程式。這並不難,不是嗎? ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vfyfok2eg0xjhqcqhgoe.png) 對於我們的最後一個主題,我想重點介紹使用 WSL 的 Git 工作流程,因為它的設定相對輕鬆。您始終可以手動進行 git config 設置,但我為您提供了一些更酷的東西:在 Windows 和 WSL 之間共享憑證。要設定共享 Git 憑證,我們必須執行以下操作。在 Powershell(在 Windows 上)中,設定 Windows 上的憑證管理員。 ``` git config --global credential.helper wincred ``` 讓我們在 WSL 中做同樣的事情。 ``` git config --global credential.helper "/mnt/c/Program\ Files/Git/mingw64/bin/git-credential-manager.exe" ``` 這使我們能夠共享 Git 使用者名稱和密碼。 Windows 中設定的任何內容都可以在 WSL 中運作(反之亦然),我們可以根據需要在 WSL 中使用 Git(透過 VS Code GUI 或透過 shell)。 ## 結論 透過我們在這裡的旅程,我們了解了 WSL 是什麼、它如何有助於增強 Windows PC 的工作流程,以及如何在其上設定初始開發環境。 Microsoft 在這個工具方面做得非常出色,並且確實使 Windows 作業系統成為所有開發人員更容易使用和可行的選擇。我們了解如何安裝啟動開發所需的開發工具以及如何掌握基本的開發工作流程。如果您想深入了解該主題,這裡有一些重要的連結: - [https://wasp-lang.dev/](https://wasp-lang.dev/) - [https://github.com/microsoft/WSL](https://github.com/microsoft/WSL) - [https://learn.microsoft.com/en-us/windows/wsl/install](https://learn.microsoft.com/en-us/windows/wsl/install) - [https://code.visualstudio.com/docs/remote/wsl](https://code.visualstudio.com/docs/remote/wsl) --- 原文出處:https://dev.to/wasp/supercharge-your-windows-development-the-ultimate-guide-to-wsl-195m

🦃 Reacts-giving:為專業人士提供 11 個 React 元件👩🏻‍🌾🍁

## 簡介 我收集了最好的 React 元件,您可以使用它來建立強大的 Web 應用程式。 每個都有自己的味道。 別忘了表達你的支持🌟 現在,讓我們仔細閱讀這段程式碼! 🍽️ ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j2p4508nvzg74qd060lx.gif) --- ## 1. [CopilotPortal](https://github.com/RecursivelyAI/CopilotKit):將可操作的 GPT 聊天機器人嵌入您的網路應用程式中。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0s5nodilnbgy2myna6ny.png) 將 GPT 支援的聊天機器人插入您的 React 應用程式中。 可以將 RAG 與雲端和應用程式狀態即時整合。 需要幾行程式碼才能嵌入。 ``` import "@copilotkit/react-ui/styles.css"; import { CopilotProvider } from "@copilotkit/react-core"; import { CopilotSidebarUIProvider } from "@copilotkit/react-ui"; export default function App(): JSX.Element { return ( <CopilotProvider chatApiEndpoint="/api/copilotkit/chat"> <CopilotSidebarUIProvider> <YourContent /> </CopilotSidebarUIProvider> </CopilotProvider> ); } ``` https://github.com/RecursivelyAI/CopilotKit --- ## 2. [ClickVote](https://github.com/clickvote/clickvote) - 按讚、投票並查看任何上下文 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xubftfmy9xum98zjgv5m.png) 輕鬆將點讚、按讚和評論加入到您的網路應用程式中。 用於加入這些元件的簡單反應程式碼。 ``` import { ClickVoteProvider } from '@clickvote/react'; import { ClickVoteComponent } from '@clickvote/react'; import { LikeStyle } from '@clickvote/react'; <ClickVoteProvider> <ClickVoteComponent id={CONTEXT} voteTo={ID}> {(props) => <LikeStyle {...props} />} </ClickVoteComponent> </ClickVoteProvider> ``` https://github.com/clickvote/clickvote --- ## 3. [React Flow](https://github.com/xyflow/xyflow) - 建立可拖曳工作流程的最佳方式! ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8hy0bsacbfzctin4r7tq.png) 專為建立基於節點的編輯器和互動式圖表而客製化的 React 元件。 它具有高度可自訂性,提供拖放功能以實現高效的工作流程建立。 ``` import ReactFlow, { MiniMap, Controls, Background, useNodesState, useEdgesState, addEdge, } from 'reactflow'; <ReactFlow nodes={nodes} edges={edges} onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} onConnect={onConnect} > <MiniMap /> <Controls /> <Background /> </ReactFlow> ``` https://github.com/xyflow/xyflow --- ## 4. [CopilotTextarea](https://github.com/RecursivelyAI/CopilotKit/tree/main/CopilotKit/packages/react-textarea) - React 應用程式中的 AI 驅動寫作 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uye8z6aac1015iiqd3lk.png) 具有 Github CopilotX 功能的任何 React `<textarea>` 的直接替代品。 自動完成、插入、編輯。 可以即時或由開發人員提前提供任何上下文。 ``` import { CopilotTextarea } from "@copilotkit/react-textarea"; import { CopilotProvider } from "@copilotkit/react-core"; // Provide context... useMakeCopilotReadable(...) // in your component... <CopilotProvider> <CopilotTextarea/> </CopilotProvider>` ``` https://github.com/RecursivelyAI/CopilotKit --- ## 5. [Novu](https://github.com/novuhq/novu) - 將應用程式內通知新增至您的應用程式! ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c81fkg15xucqbyg4xctt.png) 用於在一個地方管理所有通訊管道的簡單元件和 API:電子郵件、SMS、Direct 和 Push 您可以使用此 React 元件為您的應用程式新增應用程式內通知。 ``` import { NovuProvider, PopoverNotificationCenter, NotificationBell, IMessage, } from "@novu/notification-center"; <NovuProvider subscriberId={"SUBSCRIBER_ID"} applicationIdentifier={"APPLICATION_IDENTIFIER"} > <PopoverNotificationCenter colorScheme="dark"> {({ unseenCount }) => <NotificationBell unseenCount={unseenCount} />} </PopoverNotificationCenter> </NovuProvider> ``` https://github.com/novuhq/novu --- ## 6. [ReactIcons](https://github.com/react-icons/react-icons) - 最受歡迎的反應圖示集合 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l1sj51u7omogoa5v7di6.png) 輕鬆將 Font Awesome、Material Design 等中的流行圖標加入到您的 React 應用程式中。 為開發人員提供簡單、廣泛的選擇。 ``` import { FaBeer } from "react-icons/fa"; function Question() { return ( <h3> Lets go for a <FaBeer />? </h3> ); } ``` https://github.com/react-icons/react-icons --- ## 7. [React-dropzone](https://github.com/react-dropzone/react-dropzone) - 新增 HTML5 拖放 UI。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/apr4lbjc1i0glbs0kize.png) 用於實作 HTML5 拖放區域的簡單 React 鉤子,重點放在檔案互動。 它提供了一個易於使用的介面,用於向 React 應用程式加入檔案拖放功能。 ``` import React from 'react'; import {useDropzone} from 'react-dropzone'; const Basic = (props)=>{ const {acceptedFiles, getRootProps, getInputProps} = useDropzone(); const files = acceptedFiles.map(file => ( <li key={file.path}> {file.path} - {file.size} bytes </li> )); return ( <section className="container"> <div {...getRootProps({className: 'dropzone'})}> <input {...getInputProps()} /> <p>Drag 'n' drop some files here, or click to select files</p> </div> <aside> <h4>Files</h4> <ul>{files}</ul> </aside> </section> ); } export default Basic; ``` https://github.com/react-dropzone/react-dropzone --- ## 8. [React ChartJS 2](https://github.com/reactchartjs/react-chartjs-2) - 建立和整合各種圖表。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k820fg3ep6cocfukqdny.png) 用於在 React 應用程式中繪製圖表的即插即用解決方案,類似於 Chart.js 功能。 啟用動態、互動式圖表。 適用於即時資料或預定義資料集。 ``` import React from 'react'; import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js'; import { Doughnut } from 'react-chartjs-2'; ChartJS.register(ArcElement, Tooltip, Legend); const data = { labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'], datasets: [ { label: '# of Votes', data: [12, 19, 3, 5, 2, 3], backgroundColor: [ 'rgba(255, 99, 132, 0.2)', ], borderColor: [ 'rgba(255, 99, 132, 1)', ], borderWidth: 1, }, ], }; export default function ShowChart() { return <Doughnut data={data} />; } ``` https://github.com/reactchartjs/react-chartjs-2 ## 9. [Redux](https://github.com/reduxjs/redux) - 可預測的狀態容器庫 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a7iv2maik6xq4w5yl21y.png) JavaScript 應用程式中 Redux 的無縫補充,提供可靠的狀態管理。 確保一致的應用程式行為。 便於輕鬆除錯和測試。 與各種庫整合。 https://github.com/reduxjs/redux --- ## 10. [Blueprint](https://github.com/palantir/blueprint) - Palantir 的密集 UI 庫 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/176noa7v8f25ll0jixqn.png) 提供一組用於建立複雜且資料豐富的介面的元件和樣式。 設計和開發具有現代外觀和感覺的類似桌面的 Web 應用程式。 由 Palantir 開發 ``` import React from 'react'; import '@blueprintjs/core/lib/css/blueprint.css'; import { H3, H4, OL, Pre } from "@blueprintjs/core"; function App() { return ( <div style={{ display: 'block', width: 500, padding: 30 }}> <h4>ReactJS Blueprint HTML Elements Component</h4> Heading Component: <H4>H4 Size Heading</H4> <H3>H3 Size Heading</H3> <br></br> OrderList Component: <OL> <li>1st item</li> <li>2nd item</li> </OL> Pre Component: <Pre>Sample Pre</Pre> </div> ); } ``` https://github.com/palantir/blueprint --- ## 11. [Headless UI](https://github.com/tailwindlabs/headlessui) - 可存取的 Tailwind 整合 UI 元件。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vsxfiivef7du8u3g5i5l.png) 在 React 和 Vue 應用程式中建立可存取的 UI 元件。 適用於即時資料或預定義資料集,使其成為現代 Web 開發專案的寶貴補充 ``` import React, { useState } from 'react'; import { Dialog } from '@headlessui/react'; function MyDialog() { let [isOpen, setIsOpen] = useState(true); return ( <Dialog open={isOpen} onClose={() => setIsOpen(false)} className="relative z-50"> {/* The backdrop, rendered as a fixed sibling to the panel container */} <div className="fixed inset-0 bg-black/30" aria-hidden="true" /> {/* Full-screen container to center the panel */} <div className="fixed inset-0 flex w-screen items-center justify-center p-4"> {/* Your dialog content goes here */} </div> </Dialog> ); } ``` https://github.com/tailwindlabs/headlessui --- 保存這些元件,以便像朝聖者一樣專業地建造。 謝謝大家,節日快樂! --- 原文出處:https://dev.to/copilotkit/reacts-giving-11-react-components-for-aspiring-pros-eck

🚀 發送 Github 星星監測通知的 4 種方式 ⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️

# 簡介 在上一篇文章中,我討論了建立一個[GitHub stars 監視器](https://dev.to/triggerdotdev/take-nextjs-to-the-next-level-create-a-github-stars-monitor-130a)。 在這篇文章中,我想向您展示如何每天了解新星的資訊。 我們將學習: - 如何建立通用系統來建立和使用提供者。 - 如何使用提供者發送通知。 - 使用不同提供者的不同用例。 ![通知](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5uwpjomw3pbrpq885q8z.gif) --- ## 你的後台工作平台🔌 [Trigger.dev](https://trigger.dev/) 是一個開源程式庫,可讓您使用 NextJS、Remix、Astro 等為您的應用程式建立和監控長時間執行的作業!   [![GiveUsStars](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bm9mrmovmn26izyik95z.gif)](https://github.com/triggerdotdev/trigger.dev) 請幫我們一顆星🥹。 這將幫助我們建立更多這樣的文章💖 https://github.com/triggerdotdev/trigger.dev --- ## 讓我們來設定一下 🔥 我們將建立不同的提供者來通知我們何時有新的明星。我們將設定「電子郵件」、「簡訊」、「Slack」和「Discord」通知。 我們的目標是讓每個貢獻者都足夠簡單,以便在未來貢獻更多的提供者。 每個提供者都會有一組不同的參數,有些只有“API 金鑰”,有些則有電話號碼,具體取決於提供者。 為了驗證這些金鑰,讓我們安裝“zod”;它是一個很棒的庫,可以定義模式並根據模式檢查資料。 您可以透過執行以下命令開始: ``` npm install zod --save ``` 完成後,建立一個名為「providers」的新資料夾,然後在其中建立一個名為「register.provider.ts」的新檔案。 這是文件的程式碼: ``` import {Schema} from "zod"; export function registerProvider<T>( name: string, options: {active: boolean}, validation: Schema<T>, run: (libName: string, stars: number, values: T) => Promise<void> ) { // if not active, we can just pass an empty function, nothing will run if (!options.active) { return () => {}; } // will validate and remove unnecessary values (Security wise) const env = validation.parse(process.env); // return the function we will run at the end of the job return async (libName: string, stars: number) => { console.log(`Running provider ${name}`); await run(libName, stars, env as T); console.log(`Finished running provider ${name}`); } } ``` 程式碼不多,但可能有點複雜。 我們首先建立一個名為「registerProvider」的新函數。該函數獲得一個通用類型“T”,基本上是我們所需的環境變數。 然後我們還有 4 個參數: - 名稱 - 可以是「Twilio」、「Discord」、「Slack」或「Resend」中的任何一個。 - 選項 - 目前,一個參數是提供者是否處於活動狀態? - 驗證 - 在這裡,我們在 .env 檔案中傳遞所需參數的「zod」模式。 - run - 實際上用於發送通知。請注意,傳入其中的參數是庫名稱、星星數量以及我們在「validation」中指定的環境變數 **然後我們就有了實際的功能:** 首先,我們檢查提供者是否處於活動狀態。如果沒有,我們發送一個空函數。 然後,我們驗證並提取我們在模式中指定的變數。如果變數缺少 `zod` 將發送錯誤並且不會讓應用程式執行。 最後,我們傳回一個函數,該函數會取得庫名稱和星星數量並觸發通知。 在我們的「providers」資料夾中,建立一個名為「providers.ts」的新文件,並在其中新增以下程式碼: ``` export const Providers = []; ``` 稍後,我們將在那裡加入所有提供者。 --- ## 修改 TriggerDev 作業 本文是上一篇關於建立 [GitHub stars 監視器](https://dev.to/triggerdotdev/take-nextjs-to-the-next-level-create-a-github-stars-monitor-130a)。 編輯檔案 `jobs/sync.stars.ts` 並將以下程式碼加入檔案底部: ``` const triggerNotification = client.defineJob({ id: "trigger-notification", name: "Trigger Notification", version: "0.0.1", trigger: invokeTrigger({ schema: z.object({ stars: z.number(), library: z.string(), providerNumber: z.number(), }) }), run: async (payload, io, ctx) => { await io.runTask("trigger-notification", async () => { return Providers[payload.providerNumber](payload.library, payload.stars); }); } }); ``` 此作業取得星星數量、圖書館名稱和提供者編號,並從先前定義的提供者觸發特定提供者的通知。 現在,我們繼續修改“getStars”,在函數末尾加入以下程式碼: ``` for (let i = 0; i < Providers.length; i++) { await triggerNotification.invoke(payload.name + '-' + i, { library: payload.name, stars: stargazers_count - payload.previousStarCount, providerNumber: i, }); } ``` 這將觸發每個圖書館的通知。 完整頁面程式碼: ``` import { cronTrigger, invokeTrigger } from "@trigger.dev/sdk"; import { client } from "@/trigger"; import { prisma } from "../../helper/prisma"; import axios from "axios"; import { z } from "zod"; import {Providers} from "@/providers/providers"; // Your first job // This Job will be triggered by an event, log a joke to the console, and then wait 5 seconds before logging the punchline. client.defineJob({ id: "sync-stars", name: "Sync Stars Daily", version: "0.0.1", // Run a cron every day at 23:00 AM trigger: cronTrigger({ cron: "0 23 * * *", }), run: async (payload, io, ctx) => { const repos = await io.runTask("get-stars", async () => { // get all libraries and current amount of stars return await prisma.repository.groupBy({ by: ["name"], _sum: { stars: true, }, }); }); //loop through all repos and invoke the Job that gets the latest stars for (const repo of repos) { await getStars.invoke(repo.name, { name: repo.name, previousStarCount: repo?._sum?.stars || 0, }); } }, }); const getStars = client.defineJob({ id: "get-latest-stars", name: "Get latest stars", version: "0.0.1", // Run a cron every day at 23:00 AM trigger: invokeTrigger({ schema: z.object({ name: z.string(), previousStarCount: z.number(), }), }), run: async (payload, io, ctx) => { const stargazers_count = await io.runTask("get-stars", async () => { const {data} = await axios.get(`https://api.github.com/repos/${payload.name}`, { headers: { authorization: `token ${process.env.TOKEN}`, }, }); return data.stargazers_count as number; }); await io.runTask("upsert-stars", async () => { await prisma.repository.upsert({ where: { name_day_month_year: { name: payload.name, month: new Date().getMonth() + 1, year: new Date().getFullYear(), day: new Date().getDate(), }, }, update: { stars: stargazers_count - payload.previousStarCount, }, create: { name: payload.name, stars: stargazers_count - payload.previousStarCount, month: new Date().getMonth() + 1, year: new Date().getFullYear(), day: new Date().getDate(), }, }); }); for (let i = 0; i < Providers.length; i++) { await triggerNotification.invoke(payload.name + '-' + i, { library: payload.name, stars: stargazers_count - payload.previousStarCount, providerNumber: i, }); } }, }); const triggerNotification = client.defineJob({ id: "trigger-notification", name: "Trigger Notification", version: "0.0.1", trigger: invokeTrigger({ schema: z.object({ stars: z.number(), library: z.string(), providerNumber: z.number(), }) }), run: async (payload, io, ctx) => { await io.runTask("trigger-notification", async () => { return Providers[payload.providerNumber](payload.library, payload.stars); }); } }); ``` 現在,有趣的部分🎉 讓我們繼續建立我們的提供者! 首先建立一個名為「providers/lists」的新資料夾 --- ## 1. Discord ![Discord](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sqw7u3s19vtffxc197up.png) 建立一個名為「discord.provider.ts」的新檔案並新增以下程式碼: ``` import {object, string} from "zod"; import {registerProvider} from "@/providers/register.provider"; import axios from "axios"; export const DiscordProvider = registerProvider( "discord", {active: true}, object({ DISCORD_WEBHOOK_URL: string(), }), async (libName, stars, values) => { await axios.post(values.DISCORD_WEBHOOK_URL, {content: `The library ${libName} has ${stars} new stars!`}); } ); ``` 如您所見,我們正在使用 `registerProvider` 建立一個名為 DiscordProvider 的新提供程序 - 我們將名稱設定為“discord” - 我們將其設定為活動狀態 - 我們指定需要一個名為「DISCORD_WEBHOOK_URL」的環境變數。 - 我們使用 Axios 的簡單 post 指令將資訊加入支票中。 若要取得“DISCORD_WEBHOOK_URL”: 1. 前往您的 Discord 伺服器 2. 點選其中一個頻道的“編輯” 3. 轉到“整合” 4. 點選“建立 Webhook” 5. 點選建立的 webhook,然後點選“複製 webhook URL” 在根專案上編輯“.env”檔案並加入 ``` SLACK_WEBHOOK_URL=<your copied url> ``` ![Spidy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oyxvihf75afjubopy6dp.png) --- ## 2. Slack ![Slack](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9t9ep538nt39j0xylcqp.png) 建立一個名為「slack.provider.ts」的新檔案並新增以下程式碼: ``` import {object, string} from "zod"; import {registerProvider} from "@/providers/register.provider"; import axios from "axios"; export const SlackProvider = registerProvider( "slack", {active: true}, object({ SLACK_WEBHOOK_URL: string(), }), async (libName, stars, values) => { await axios.post(values.SLACK_WEBHOOK_URL, {text: `The library ${libName} has ${stars} new stars!`}); } ); ``` 如您所見,我們正在使用 `registerProvider` 建立一個名為 SlackProvider 的新提供者 - 我們將名稱設定為“slack” - 我們將其設定為活動狀態 - 我們指定需要一個名為「SLACK_WEBHOOK_URL」的環境變數。 - 我們使用 Axios 的簡單 post 指令將資訊加入支票中。 要取得“SLACK_WEBHOOK_URL”: 1. 使用下列 URL 建立新的 Slack 應用程式:https://api.slack.com/apps?new_app=1 2. 選擇第一個選項:“從頭開始” 3. 指定應用程式名稱(任意)以及您想要新增通知的 Slack 工作區。點擊“建立應用程式”。 4. 在“新增特性和功能”中,按一下“傳入掛鉤” 5. 在啟動傳入 Webhooks 中,將其變更為「開啟」。 6. 按一下「將新 Webhook 新增至工作區」。 7. 選擇您想要的頻道並點選「允許」。 8. 複製 Webhook URL。 在根專案上編輯“.env”檔案並加入 ``` SLACK_WEBHOOK_URL=<your copied url> ``` ![SlackBot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/stlaf1xmprg629tjz7wv.png) --- ## 3. 電子郵件 ![電子郵件](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wq6t424munx90pdtzp7c.png) 您可以使用不同類型的電子郵件提供者。例如,我們將使用**Resend**來傳送電子郵件。 為此,讓我們在我們的專案上安裝重新發送: ``` npm install resend --save ``` 建立一個名為「resend.provider.ts」的新檔案並新增以下程式碼: ``` import {object, string} from "zod"; import {registerProvider} from "@/providers/register.provider"; import axios from "axios"; import { Resend } from 'resend'; export const ResendProvider = registerProvider( "resend", {active: true}, object({ RESEND_API_KEY: string(), }), async (libName, stars, values) => { const resend = new Resend(values.RESEND_API_KEY); await resend.emails.send({ from: "Eric Allam <[email protected]>", to: ['[email protected]'], subject: 'New GitHub stars', html: `The library ${libName} has ${stars} new stars!`, }); } ); ``` 如您所見,我們正在使用 `registerProvider` 建立一個名為 ResendProvider 的新提供程序 - 我們將名稱設定為“重新發送” - 我們將其設定為活動狀態 - 我們指定需要一個名為「RESEND_API_KEY」的環境變數。 - 我們使用重新發送庫向自己發送一封包含新星數的電子郵件。 若要取得“RESEND_API_KEY”: 1. 建立一個新帳戶:https://resend.com 2. 前往「API 金鑰」或使用此 URL https://resend.com/api-keys 3. 按一下“+ 建立 API 金鑰”,新增金鑰名稱,選擇“傳送存取”並使用預設的“所有網域”。單擊新增。 4. 複製 API 金鑰。 在根專案上編輯“.env”檔案並加入 ``` RESEND_API_KEY=<your API key> ``` ![埃里克·阿拉姆](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bhk2hd2f53yfojn96yf3.png) --- ## 4.簡訊 ![Twilio](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/036fdgpt0mp5h7wrisrn.png) SMS 有點複雜,因為它們需要多個變數。 為此,我們在專案中安裝 Twilio: ``` npm install twilio --save ``` 建立一個名為「twilio.provider.ts」的新檔案並新增以下程式碼: ``` import {object, string} from "zod"; import {registerProvider} from "@/providers/register.provider"; import axios from "axios"; import client from 'twilio'; export const TwilioProvider = registerProvider( "twilio", {active: true}, object({ TWILIO_SID: string(), TWILIO_AUTH_TOKEN: string(), TWILIO_FROM_NUMBER: string(), TWILIO_TO_NUMBER: string(), }), async (libName, stars, values) => { const twilio = client(values.TWILIO_SID, values.TWILIO_AUTH_TOKEN); await twilio.messages.create({ body: `The library ${libName} has ${stars} new stars!`, from: values.TWILIO_FROM_NUMBER, to: values.TWILIO_TO_NUMBER, }); } ); ``` 如您所見,我們正在使用 `registerProvider` 建立一個名為 TwilioProvider 的新提供者 - 我們將名稱設定為“twilio” - 我們將其設定為活動狀態 - 我們指定需要環境變數:`TWILIO_SID`、`TWILIO_AUTH_TOKEN`、`TWILIO_FROM_NUMBER` 和 `TWILIO_TO_NUMBER` - 我們使用 Twilio「建立」功能發送簡訊。 取得“TWILIO_SID”、“TWILIO_AUTH_TOKEN”、“TWILIO_FROM_NUMBER”和“TWILIO_TO_NUMBER” 1. 在 https://twilio.com 建立一個新帳戶 2. 標記您要使用它來發送簡訊。 3. 點選“取得電話號碼” 4. 複製“帳戶 SID”、“身份驗證令牌”和“我的 Twilio 電話號碼” 在根專案上編輯“.env”檔案並加入 ``` TWILIO_SID=<your SID key> TWILIO_AUTH_TOKEN=<your AUTH TOKEN key> TWILIO_FROM_NUMBER=<your FROM number> TWILIO_TO_NUMBER=<your TO number> ``` ![TwilioSMS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/474q2p4ejvji18xuo9om.png) --- ## 建立新的提供者 正如您所看到的,現在建立提供者非常容易。 您也可以使用開源社群來建立新的提供程序,因為他們只需要在「providers/list」目錄中建立一個新檔案。 最後要做的事情是編輯“providers.ts”檔案並加入所有提供程序。 ``` import {DiscordProvider} from "@/providers/list/discord.provider"; import {ResendProvider} from "@/providers/list/resend.provider"; import {SlackProvider} from "@/providers/list/slack.provider"; import {TwilioProvider} from "@/providers/list/twilio.provider"; export const Providers = [ DiscordProvider, ResendProvider, SlackProvider, TwilioProvider, ]; ``` 請隨意建立更多推播通知、網路推播通知、應用程式內通知等提供者。 你就完成了🥳 --- ## 讓我們聯絡吧! 🔌 作為開源開發者,我們邀請您加入我們的[社群](https://discord.gg/nkqV9xBYWy),以做出貢獻並與維護者互動。請隨時造訪我們的 [GitHub 儲存庫](https://github.com/triggerdotdev/trigger.dev),貢獻並建立與 Trigger.dev 相關的問題。 本教學的源程式碼可在此處取得: [https://github.com/triggerdotdev/blog/tree/main/stars-monitor-notifications](https://github.com/triggerdotdev/blog/tree/main/stars-monitor-notifications) 感謝您的閱讀! --- 原文出處:https://dev.to/triggerdotdev/top-4-ways-to-send-notifications-about-new-stars-1cgb

作為開發者賺取額外現金的 50 種方法💰

目前大環境不好,但作為開發人員,我們擁有一套獨特的技能,如果您知道在哪裡尋找,這些技能的需求量很大! 這篇文章簡要概述了 50 個作為開發人員可以用來賺取額外收入的副業 --- ### 1. 銷售注意力 基於參與度的收入是指您將根據使用者在您的網站、個人資料或消費您的內容上花費的時間來獲得收入分成。它通常很小,至少對於較小的網站或創作者來說是這樣,但隨著時間的推移,它會增加,任何人都可以啟用它 - 所以你不會有任何損失。 - [Brave](https://creators.brave.com/) - 為使用 Brave 瀏覽器造訪您的網站、個人資料或查看您的內容的使用者付費。資金以 [BAT](https://basicattentiontoken.org/) 形式存入您的 Uphold 帳戶,然後可以以美元、英鎊或歐元形式提取至您的銀行帳戶 - [Flattr](https://flattr.com/) - 付費使用 Flattr 的用戶將其資金分配給用戶存取過其內容的創作者 > 幾年前,我親自報名了 Brave Rewards。在驗證了我的網域和個人資料的所有權後,我每月一直賺幾英鎊 - 到目前為止大約 200 英鎊以上(儘管我是 Firefox 用戶!)。雖然不多,但只需付出很少的努力,就值得了。 有關其工作原理的更多訊息,請查看 [webmonetization.org](https://webmonetization.org/) 規範,該規範利用了 [付款指針](https://paymentpointers.org/)通過[ILP](https://interledger.org/) 透過使用簡單的`<link rel="monetization" href="your-pointer-here" />` 標籤來串流來自支援WM 的訪客的收入。 --- ### 2. API 即服務 RapidAPI 等平台可讓您從 API 中[賺取被動收入](https://rapidapi.com/guides/earn-a-passive-venue-by-monetizing-apis-as-a-developer)。 建置並部署簡單的 API 後,您可以將其匯入 RapidAPI Hub,選擇使用和定價計劃,然後點擊發布。您的 API 可大可小,如您所願。 如果您正在為一個簡單的第一個專案尋找靈感,請考慮將開放資料集轉換為 API。對於初學者,RapidAPI 有一個關於如何入門的[影片系列](https://rapidapi.com/courses/build-and-sell-your-own-api)。其他想法可能包括將現有套件包裝為 API、向其他服務(如 OpenAI)加入功能或建置執行一些簡單計算的端點。 --- ### 3. 發放賞金 這些是開源專案的熱門功能請求。用戶可以在承諾一定金額的情況下提供“賞金”,然後將其支付給第一個完成並合併該功能的開發人員。 - [BOSS.dev](https://www.boss.dev/) - 完成功能請求和錯誤修復,獎金從 30 美元到 1000 美元不等。 --- ### 4. 贊助商 如果您在 GitHub 或其他平台上有業務,那麼啟用贊助是一種為您的工作帶來收入的有益方式。 不要忘記啟用贊助商按鈕。這適用於各種平台以及 GitHub 贊助商 - [GitHub Sponsors](https://github.com/sponsors) - 對於開發人員(無論規模大小)來說都是一個不錯的選擇。如果支持者已經在 GitHub 上,則零費用且進入門檻低 - [Patreon](https://www.patreon.com/) - 允許向您的支持者提供福利和獨家內容。如果您在 GitHub 以外的其他平台上有業務,這是一個不錯的選擇 - [LibrePay](https://liberapay.com/) - 針對那些建立開源內容的人 - [Open Collective](https://opencollective.com/) - 如果您正在為特定專案籌集資金,並使用收益來支持該專案(而不是個人),那麼這是一個不錯的選擇 - [Steday](https://steadyhq.com/en) - [TideLift](https://tidelift.com) - 更針對那些開發企業級開源專案的人,潛在收入更大,但僅限於最大的專案 - [LFX](https://lfx.linuxfoundation.org/) - 由 Linux 基金會提供 > 贊助(特別是GitHub 贊助商)是我個人最喜歡的方法之一,因為付費是可選的,所以你不會阻止那些無力承擔費用的人存取,而且那些支持你的人已經知道他們會預先得到什麼,所以您永遠不會讓客戶失望。 --- ### 5. 小費 您可能遇到過這樣的情況,您發現某個部落格文章、SO 答案、GitHub 儲存庫或論壇回應非常有幫助,以至於您希望可以為作者買一杯啤酒來表示感謝。 支援這些小額一次性付款的平台可以免費註冊,並且在您的個人資料中或在部落格文章末尾加入「提示」按鈕不會有任何損失。 - [Ko-fi](https://ko-fi.com/) - [請我喝杯咖啡](https://www.buymeacoffee.com/) - [Tipeee](https://en.tipeee.com/) - [PayPal Me](https://www.paypal.com/paypalme/) 提示:不要乞求。建立一些有用的內容,然後將提示連結放在底部。 --- ### 6. 企業贊助 許多具有一定下載量/經常性用戶的開源專案將開始被希望贊助創作者作品的公司接洽,以換取他們的公司徽標+連結包含在自述文件頂部附近。與個人贊助不同,這些贊助通常起價為 100-500 美元/月,專案使用量越大,贊助金額就越多。 --- ### 7. 黑客松 編碼競賽一直在遠端進行。這些通常由公司贊助,並向獲獎者支付現金獎勵。 - [程式碼之夏](https://summerofcode.withgoogle.com/) - 由 Google 執行,您將收到[貢獻者津貼](https://developers.google.com/open-source/gsoc/help/student-stipends) 成功接受後,金額從750 美元到6000 美元不等,金額取決於您所在的國家/地區和專案規模 - [CodeHeat](https://codeheat.org/) - 由 FOSS Asia 運營,每兩週 100 新元,外加較小的獎品 - [HackerEarth](https://www.hackerearth.com/challenges/hackathon/) - [Hackathon.com](https://www.hackathon.com/online) - [Devfolio 黑客松](https://devfolio.co/hackathons/upcoming) > 當我還是學生時,我[曾經參加過很多](https://alicia.omg.lol/hackathons) 黑客馬拉松(大部分是面對面的),並且經常能夠通過參加各種活動來資助我的暑假活動!這也是認識新朋友、學習新事物的好方法,而且非常有趣! --- ### 8. 依賴套件的贊助 如果您有一個軟體包(例如 NPM 模組),那麼在您的設定檔中啟用贊助將允許您的程式碼的使用者在財務上做出貢獻。 - NPM 資金 - 您可能熟悉執行“npm 基金”,並查看您正在使用的正在尋求資金的軟體包清單。新增了 [npm 基金](https://docs.npmjs.com/cli/v6/commands/npm-fund),以便更輕鬆地向專案所依賴的依賴項的維護者捐款。如果您維護 NPM 包,只需在 package.json 中包含「funding」字段,用戶將能夠更輕鬆地支援您。 - [StackAid](https://www.stackaid.us/) - 只需安裝 StackAid GitHub 應用程式並連結您的 Stripe 帳戶,直接或間接使用您專案的支持者捐贈的部分資金將分配給您每個月 - GitHub Sponsors - GitHub Sponsors 再次出現,因為它[讓用戶提供他們最常用的依賴項](https://github.com/sponsors/explore) - 儘管這是一個手動過程,而不是自動的。 --- ### 9. 回報問題 如果您注重安全性,或喜歡在應用程式中尋找錯誤和漏洞,那麼這款就適合您。最受歡迎的平台是[HackerOne](https://hackerone.com/opportunities/all/search?ordering=Highest+bounties),每個負責任地披露的錯誤都可以在其中賺取20 到200,000 美元的收入。 許多其他網站也直接提供負責任的揭露政策,他們會獎勵您的工作。如果您對此感興趣,我在以下位置保留了 1000 多個賞金計劃的清單:[https://bug-bounties.as93.net](https://bug-bounties.as93.net) > 我個人透過這種方法取得了很大的成功,而且也很有趣 - 所以我強烈推薦它! 其他值得查看的平台包括: - [HackerOne](https://www.hackerone.com/) - 排名第一的平台,最多的賞金以及良好的保護和支付率 - [Immunefi](https://immunefi.com/) - 專門針對 Web3 - [BugCrowd](https://www.bugcrowd.com/bug-bounty-list/) - [Intigriti](https://www.intigriti.com/) - [issuehunt](https://issuehunt.io/) --- ### 10.開放核心模型 這是您的大部分程式碼都是開源的,但某些擴充功能或附加元件(特別是針對企業客戶的擴充或附加元件)被授權為專有的。 因此,開發者可以在其他開源專案中自由使用該軟體。然而,公司必須為使用企業特定的模組或整合付費。 請記住,這通常說起來容易做起來難。您需要能夠分離專有功能,而大公司通常會採取一切措施(包括違反許可限制)來避免付費。 --- ### 11. 付費升級套件 這些服務可以輕鬆為常見註冊管理機構提供高級/付費方案。例如,如果您希望分發 NPM 模組的高級版本,或對特定軟體包功能收費,這可能是個不錯的選擇。 - [PrivJS](https://www.privjs.com/) - 分發 Node 套件的進階版本 - [CodeShip](https://codecodeship.com/) - 私人註冊中心,用戶需要付費才能使用你的包 --- ### 12.贊助支持 在開源專案中加入專業支援計劃選項使客戶能夠支付一次性或持續的幫助和支援費用,以啟動和執行。 這可以透過您自己的系統啟用,也可以使用現有的贊助平台(例如Patreon 和GitHub Sponsors),或使用專門的服務(例如[Otechie](https://otechie.com/))來啟用,該服務加入了付費功能+ 支援通過嵌入的聊天對話框。 [Calendly](https://calendly.com/) 等工具可以讓客戶將時間放入日曆中,或者對於較大的專案,投資專用的客戶支援平台,例如[HelpScout](https://www.helpscout.com/) 可能會讓這件事變得更容易。 --- ### 13. 寫文件 - [撰寫文件](https://www.writethedocs.org/) 是所有文件的首選位置。 - [文件季節](https://developers.google.com/season-of-docs) - 在 Google 的支持下,每年都有技術作家為開源專案做出貢獻。參與專案將獲得 5,000 至 15,000 美元的贈款,然後通常透過 Open Collective 分發給貢獻者。 - 如果你環顧四周,你會發現還有很多產品正在尋找技術作家。 Julia 列出了一份[好名單](https://dev.to/juliafmorgado/get-paid-to-write-technical-articles-16cl),列出了願意付費讓你撰寫技術內容的公司 - 版權也屬於這一類。 [scripted](https://www.scripted.com/) 等服務可讓您透過校對或編輯其他文字內容來賺錢。 即使只是記錄您自己的和其他開發人員的儲存庫也是一個不錯的起點。 如果專案被記錄下來,它的價值就會大幅增加。如果沒有文件,潛在使用者、客戶或開發人員將不知道它的用途、如何使用它、如何在其基礎上建立或如何做出貢獻。 > 我可能是唯一的一個,但我個人喜歡寫文件。 [我的所有專案](https://github.com/Lissy93?tab=repositories) 包括完整的使用、開發和貢獻文件。這促進了它們的成功和採用。我覺得如果你不花一點時間向人們展示如何使用它,那麼花幾個小時建立一些很棒的東西是沒有意義的。 --- ### 14. 廣告 在你跳過這一點之前——我也討厭廣告。它們很煩人,並且經常涉及某種形式的跟踪,從而損害用戶的隱私。但是,對於開源專案,還有一些其他選項沒有這些缺點。 - [Ethical Ads](https://www.ethicalads.io/) - [Carbon Ads](https://www.carbonads.net/open-source) 如果您正在維護獲得穩定流量的 GitHub 儲存庫、網站、部落格或服務,那麼這是一個不錯的選擇。通常每月至少需要約 10,000 個用戶,但如果您每月獲得 50,000 以上的用戶,您將獲得更好的回報。 --- ### 15. 出售你的程式碼 > 我個人不同意這種方法,只是因為出售的許多程式碼都是開源軟體的糟糕的重新設計版本,並且並不總是給予原始作者適當的榮譽。也就是說,一些開發商確實設法讓它發揮作用,建造簡單的專案然後將其出售。 - [IndieMaker](https://indiemaker.co/) - 出售您的整個專案 - [PieceX](https://www.piecex.com/) - 出售現成的原始碼 - [Codester](https://www.codester.com/info/seller) - 針對 PHP 和 Wordpress --- ### 16.銷售內容 當您查看開發人員的副業時,這是一個常見的建議。但有充分的理由 - 如果您能夠建立高品質的內容,您可以賺到很多錢。特別是如果您對新興領域有深入的了解。 銷售內容的熱門網站包括: - [GumRoad](https://gumroad.com/) - 程式碼、課程、貼文、藝術、設計、媒體(10% 費用) - [AppSumo](https://sell.appsumo.com/) - 程式碼、應用程式、擴充功能、課程、範本等 --- ### 17.寫作 這是一套獨特的技能。要么您非常擅長編寫引人入勝的內容,要么您對特定的熱門領域有深入的了解。否則,如果您對此感興趣,請考慮電子書出版,如果您的書不成功,也不會造成任何損失。 - [LeanPub](https://leanpub.com/) - 一個自助出版技術/開發電子書和課程的平台,具有豐厚的收入模式(您可以保留 70%) - [Amazon KDP](https://kdp.amazon.com/en_US/) - 發佈至 Amazon Kindle,並立即向全球數百萬用戶提供(亞馬遜將收取至少 30% 的佣金,可能會更多)小出版商) - [SmashWords](https://www.smashwords.com/) 和 [Draft2Digital](https://draft2digital.com/sw/) - 分發給全球其他電子書賣家,這是一種簡單的開始出版。他們收取的佣金比亞馬遜少,但比 LeanPub 多。 --- ### 18.補助金 補助金和企業贊助涉及多個領域,包括開源、創新、DeFi、人工智慧等。它們通常是為了幫助您在從事特定工作時支付短期生活費用。 - [GitHub Sponsors](https://github.com/sponsors) - 為個人和組織提供經濟支援開源開發者的平台。金額依贊助情況而有所不同。 - [Google Summer of Code (GSoC)](https://summerofcode.withgoogle.com/) - 學生開發者為開源專案做出貢獻的全球計劃,津貼通常為 1500 美元到 3300 美元不等。 - [Mozilla 開源支援 (MOSS)](https://www.mozilla.org/en-US/moss/) - 為開源軟體開發提供資助,特別是與 Mozilla 使命相符的專案。 - [Linux 基金會資助](https://www.linuxfoundation.org/) - 為從事 Linux 基金會專案的開發人員提供各種資助和獎學金。 - [NumFOCUS 小額發展補助金](https://numfocus.org/programs/small-development-grants) - 支援資料科學和科學計算的小型專案。資助金額各不相同(所有申請人均分配 285,000 美元)。 - [Apache 軟體基金會贊助](https://www.apache.org/foundation/sponsorship.html) - 對 Apache 軟體專案的財務支持,重點關注 Apache 軟體生態系統。 - [Outreachy](https://www.outreachy.org/) - 為技術領域代表性不足的群體提供為期三個月的實習機會,津貼通常約為 5,500 美元。 - [奈特基金會](https://knightfoundation.org/grants/) - 為促進優質新聞業的技術專案提供資助。根據專案範圍的不同,贈款金額差異很大。 - [原型基金](https://prototypefund.de/) - 在六個月內提供高達 47,500 歐元的開源原型支持,重點支持德國的軟體開發人員。 - [斯隆基金會](https://sloan.org/programs/digital-technology) - 為開放科學社群計畫提供資助,特別是那些增強研究中的開源軟體的計畫。 - [Chan Zuckerberg Initiative 開源軟體專案](https://chanzuckerberg.com/rfa/) - 專注於支援對生物醫學研究至關重要的開源軟體。資助金額各不相同。 - [Raspberry Pi 基金會](https://www.raspberrypi.org/grants/) - 為涉及 Raspberry Pi 和計算教育的教育計畫提供補助。 - [GitCoin](https://gitcoin.co/) - 一個為開源專案提供資金的眾籌平台,特別是在以太坊和 Web3 領域。資金根據社區支持而有所不同。 - [NLnet 基金會](https://nlnet.nl/foundation/) - 支援網路科技與網路研究計畫。補助金額各不相同。 - [開放技術基金](https://www.opentech.fund/) - 支持開發促進人權和開放社會的開放技術的專案。資金各不相同。 --- ### 19. 舉辦活動 活動空間是一個利潤豐厚的行業,尤其是如果您能夠舉辦一場精彩的活動並為自己贏得大型贊助商的話。雖然不適合所有人,但舉辦活動可以帶來以下 10 個潛在收入來源: - **門票銷售**:透過收取入場費來產生收入。使用 [Eventbrite](https://www.eventbrite.co.uk/)、[Meetup](https://meetup.com/) 或 [Ticketmaster](https://ticketmaster.com) 等平台取得門票管理。 - **贊助**:確保科技公司的財務捐助,以換取活動中的促銷機會。 - **研討會和培訓課程**:提供特定技術或程式語言的專業實務學習經驗,收取額外費用。 - **虛擬活動**:使用[Zoom](https://zoom.us/)、[WebEx](https://www.webex.com/) 或 [Hopin](https://hopin.com/)。 - **黑客馬拉松**:舉辦收取報名費的程式設計競賽,或尋找贊助商來支付費用並提供獎金。 - **社交活動**:針對技術專業人士的社交活動收費,可能會吸引招聘公司的贊助。 - **演講活動**:利用您在特定技術領域的專業知識,組織並負責演講活動或小組討論。 - **企業培訓及靜修**:為企業內部培訓或團隊建立活動提供活動組織服務。 - **聯盟行銷**:在活動期間利用科技產品或服務的聯盟行銷來獲取額外收入。 - **產品發布**:與科技公司合作舉辦產品發布活動,為您提供收費的組織服務。 --- ### 20.研究 您的意見很有價值,尤其是作為開發人員。有些研究人員會付錢給你參加他們的研究、調查或智庫。通常,好的研究機會很少而且相距甚遠,或者報酬相當低。 這類工作的熱門平台包括:[Testable Minds](https://minds.testable.org/)、[Respondent](https://app.respondent.io/signup) --- ### 21. 建立課程 - [Skillshare](https://www.skillshare.com/teach) - 根據課程觀看分鐘數提供付款以及推薦獎金。 - [Coursera](https://www.coursera.org/for-universities) - 與機構合作提供課程;付款通常基於收入分享協議。 - [LinkedIn Learning](https://www.linkedin.com/learning/instructors) - 講師可以為專業人士建立課程;薪酬詳細資訊由 LinkedIn 安排。 - [Thinkific](https://www.thinkific.com/) - 提供建立、行銷和銷售線上課程的工具,具有各種定價計劃,包括免費選項。 - [Kajabi](https://kajabi.com/) - 線上課程、行銷、支付和網站建立的一體化平台。 - [Podia](https://www.podia.com/) - 提供一個用於舉辦課程、網路研討會和數位下載的平台,並直接向觀眾銷售。 - [Pluralsight](https://www.pluralsight.com/teach) - 專注於科技與創意課程;根據課程的受歡迎程度向教師支付版稅。 - [MasterClass](https://www.masterclass.com/teach) - 高品質、名人主導的課程;講師通常是各自領域的知名專家或名人。 - [uTeach](https://ueach.io/) - [NewLine](https://www.newline.co/) --- ### 22.時事通訊 隨著流行的社群媒體管道變得更加集中和受控,電子郵件通訊和基於訂閱的 RSS 來源正在慢慢捲土重來。 這種模式的工作方式要么是提供對技術主題或新聞的有價值的見解,並建立一個龐大的(因此有價值的)訂閱者基礎,要么是向少數用戶收取更新費用。 提供此功能的流行平台包括: - [子堆疊](https://substack.com) - [ButtonDown](https://buttondown.email/) - [ConvertKit](https://convertkit.com/) - [穩定](https://steadyhq.com) - [幽靈](https://ghost.org/) --- ### 23. 僅限會員的網站 - [MemberSpace](https://www.memberspace.com/) - 讓您能夠為網站的某些部分付費,僅供會員使用 - [Patreon](https://www.patreon.com/) - 因設定具有獨家內容和福利的會員等級而廣受歡迎。 - [Substack](https://substack.com/) - 新聞通訊的理想選擇;提供付費訂閱獨家內容的能力。 - [Ghost](https://ghost.org/) - 內建會員和訂閱功能的專業發布平台。 - [Podia](https://www.podia.com/) - 允許銷售會員資格、線上課程和數位下載。 - WordPress 與 [MemberPress 外掛程式](https://memberpress.com/) - 供 WP 使用者建立會員網站的外掛程式。 - [Wild Apricot](https://www.wildapricot.com/) - 與您的網站整合的會員管理軟體。 - [Kajabi](https://kajabi.com/) - 提供用於建立線上課程、會員網站等的工具,重點是行銷。 - [Mighty Networks](https://www.mightynetworks.com/) - 建立一個包含會員資格、訂閱和課程的社群。 --- ### 24. VIP 貼文 還有許多公司會為您在其平台上分享的優質貼文付費。這既可以提高您的知名度(幫助您擴大人脈並獲得未來的工作),也可以帶來一些短期收入。 如果您正在努力獲得這些計劃的錄取,請先編寫自己的帖子並將其發佈到流行的基於開發的社交網絡(例如 DEV.to!)。這將增強您的寫作技巧,並幫助您向潛在公司展示您的知識。 例如,以下網站將為高品質的訪客貼文付費: - [Linode](https://www.linode.com/lp/write-for-linode/) - [日誌火箭](https://blog.logrocket.com/become-a-logrocket-guest-author/) - [Smashing 雜誌](https://www.smashingmagazine.com/contact/?Becoming%20an%20Author/Reviewer%20(自動回覆)) - [Auth0](https://auth0.com/apollo-program) - [CSS 技巧](https://css-tricks.com/guest-writing-for-css-tricks/) - [DelftStack](https://www.delftstack.com/write-for-us/) - [DigitalOcean](https://www.digitalocean.com/community/pages/write-for-digitalocean) - [Infatica](https://infatica.io/contribute/) - [蜜罐](https://blog.honeypot.io/write-for-honeypot/) - [進階編碼](https://premiumcoding.com/write-for-us-premiumcoding/) - [反思](https://reflectoring.io/contribute/become-an-author/) - [Strapi](https://strapi.io/write-for-the-community) - [Android 權威](https://www.authoritymedia.com/jobs) - [SitePoint](https://www.sitepoint.com/write-for-us/) - [TutorialsPoint](https://www.tutorialspoint.com/about/tutorials_writing.htm) - [真正的Python](https://realpython.com/jobs/tutorial-writer/) - [Dart Creations](https://www.dart-creations.com/about-us/write-for-us.html) Dmytro Spilka 編制了一份包含 300 多個[接受訪客貼文的網站](https://solvid.co.uk/180-websites-that-accept-guest-posts/) 的清單。另一個很棒的清單[由 Julia 在 Dev.to 上整理](https://dev.to/juliafmorgado/get-paid-to-write-technical-articles-16cl)。 --- ### 25. 諮詢 您可能沒有意識到,您從日常工作中累積的技能和經驗對許多公司來說可能非常有價值。尤其是尚無法聘請全職專家的新創公司和小型企業。對能夠提供最新趨勢、工具和最佳實踐見解的專業人士的需求非常高。 尖端: - 以適當的速度開始的最佳方式是透過網路和口碑。但如果做不到這一點,總有自由工作網站可以幫助您累積經驗。 - 記錄你所獲得的經驗,或在你工作的過程中建立一個投資組合,因為這將幫助你在未來獲得更好的工作。 - 在開始任何專案之前,請先明確您的空閒時間、條款、日薪和工作範圍。 - 切勿拒絕潛在的聯絡人。您會驚訝地發現,即使多年後,誰可能會重新與您聯繫並尋求諮詢支援。 --- ### 26. 指導 無論您的級別如何,您作為開發人員的經驗都可以真正幫助經驗不足的其他人。指導是一種非常有益的方式,可以幫助他人,同時也能帶來一些額外的收入。 - [MentorCruise](https://mentorcruise.com/) - 主要是長期的,按月付費,非常適合建立專業關係(每個學員每月賺取 50-500 美元) - [CodeMentor](https://www.codementor.io/) - 更適合短期,按小時收費,非常適合解決特定問題(每小時賺取 60-300 美元) --- ### 27.輔導 隨著 CompSci 現在成為國家課程的一部分(至少在英國和大部分歐洲),大量學生(11 歲至 18 歲以上)正在尋找導師來幫助他們獲得編碼技能並準備考試。收入範圍為每小時 15 美元到 150 美元以上,具體取決於級別、經驗和背景。 - [Super Prof](https://www.superprof.co.uk/) - 列出您的全球服務(30-300 美元/小時) - [The Profs](https://www.theprofs.co.uk/become-a-private-tutor/) - 經過驗證的導師(收入未知) - [我的導師](https://www.mytutor.co.uk/) - 僅限英國,(22-55 英鎊/小時) - [Tutor.com](https://www.tutor.com/) - 美國高中學費($75-$100/小時) --- ### 28.社群媒體 市場存在巨大空白,等待主流社群媒體平台上真正優秀的、注重發展的影響者來填補。 許多社群媒體平台允許您透過內容貨幣化,您通常會按觀看次數付費,金額根據內容類別、地區和聲譽而有所不同。但請注意,您通常必須擁有一定數量的追蹤者才有資格,而且您還將受到「演算法」的支配。 - YouTube - 每年至少需要 1,000 訂閱者 + 4,000 小時觀看時間 - X - 需要 Twitter Blue 訂閱,無最低追蹤人數 - TikTok - 需要至少 10k 追蹤者 + 100k 瀏覽量/月 - Instagram - 需要至少 10k 追蹤者 - Snap - 1,000 名追蹤者,1,000 次瀏覽/月,10 多個每月貼文 - Facebook - 10k 追蹤者或 600k 影片觀看分鐘 - Twitch - 350 位每月付費訂閱者 --- ### 29.品牌優惠 繼上面的社群媒體部分之後,一旦您成功突破了數百名訂閱者,您可能還可以開始考慮品牌交易,這有助於帶來額外收入。同樣,這些需要您的受眾達到一定程度的參與度,您可能還需要同意提供贊助的公司的條款。 --- ### 30.串流媒體 開發串流是一個快速成長的利基市場,不要指望立即[加入排行榜](https://twitch.pages.dev/),但它可能是一個很好的起點,特別是如果您已經有串流媒體經驗(例如影片遊戲)。 Nick Taylor 寫了一篇關於 [開發串流媒體入門的精彩文章](https://dev.to/nickytonline/getting-started-with-streaming-on-twitch-4im7)。 --- ### 31.SaaS 如果您能夠做到這一點,那麼它就是開源專案的最佳收入模式之一。您的程式碼仍然是 100% 免費和開源的,用戶仍然可以免費下載和自行託管它,但您還提供付費/託管計劃,您可以在其中託管應用程式並負責小型伺服器的所有伺服器管理經常性費用。 此模型符合開源精神,同時也使您的應用程式可供更廣泛的用戶使用。 [Stripe](https://stripe.com/docs/payments) 等服務讓您的應用程式接受付款和新增訂閱功能變得非常簡單。 --- ### 32.微型 SaaS 如果從頭開始建立一個生產就緒的應用程式聽起來像是一項艱鉅的任務(因為它確實如此!),那麼另一種方法就是 Micro-SaaS 應用程式。這些是較小的應用程式,它們執行一項非常具體的任務,例如: - 自動執行重複和/或乏味的任務。 - 執行目前手動計算的計算。 - 連接不同的系統。 - 取代 Excel 電子表格解決方法。 - 填補宿主生態系中缺失功能的空白 - 加強報告 --- ### 33. 寫外掛 與 SaaS 應用程式不同,一旦建置並發布了擴展,通常不需要太多的持續管理。您也可能會發現,如果您的專案為已經完善的網站加入功能,那麼您的專案會更容易快速獲得關注。 儘管網路擴展似乎是一個過時的或完全飽和的市場,但仍然有很多可以做的事情,而且這些對於新開發人員來說都是很棒的專案。 以下是一些可以幫助您入門的想法: - [WA Web Plus](https://chrome.google.com/webstore/detail/wa-web-plus-by-elbruz-tec/ekcgkejcjdcmonfpmnljobemcbpnkamh) 已下載 200 萬次(22k 評級),收費 12 美元/每個用戶的月。為什麼不為 Telegram、Threema、Wire、Messenger 等建立類似的東西呢? - Runkeeper擁有4500萬用戶,但UI在資料顯示方式方面有所欠缺。為什麼不建立一個擴充功能來加入更好的報告、過濾以及與相關外部資料的組合? (與 [Elevate for Strava](https://chrome.google.com/webstore/detail/elevate-for-strava/dhiaggccakkgdfcadnklkbljcgicpckn) 類似,但適用於 RunKeeper) - 選擇一個提供基本服務但 UI 過於不切實際的網站(也許是 Microsoft Azure?),然後建立一個擴充功能以簡化導覽、顯示關鍵指標或提供不那麼難看的使用者體驗 - 使用人工智慧增強任何現有網站。這比聽起來容易得多,您的擴充功能可以利用 OpenAI 的 API 等服務來總結網頁,或重新措辭選定的內容(用於複製/貼上到作業中!?) - 如果您知道某個網站的使用者數量很高,但 UI 很糟糕,那麼一個簡單的擴充想法可以是應用 CSS 覆蓋來重新設計它的樣式。例如亞馬遜、雅虎、Instagram 都是高流量網站,設計改善空間巨大(深色模式?!) - 即使是簡單的獨立擴展應用程式也可能具有很大的潛力。就像番茄計時器、貨幣轉換器、IP 位址小部件或只是一個網路應用程式快捷方式。 --- ### 34. 發布應用程式 建立簡單的應用程式或遊戲,並將其在平台應用程式商店上提供,使您能夠透過簡單的盈利模型來瞄準數百萬客戶。所有主流應用程式商店 - Google Play、Apple App Store、Windows Store、Steam 等都提供對付費應用程式、進階功能和應用程式內購買的支援。 請記住,在發布第一個應用程式之前通常需要支付安裝費,應用程式商店也會從您的收入中抽取一部分,並且小型創作者獲得單次或雙次下載的情況並不罕見。人物。 --- ### 35. 為小型企業開發網站 許多小型企業都專注於自己的業務,沒有時間或專業知識建立自己的網站。作為開發人員,這是我們能夠很快完成的事情,如果您也託管他們的網站,您將能夠收取定期付款。 一旦您開始進行網頁設計和開發,並為一些客戶提供服務,您就會發現透過口碑和展示您的作品集來找到未來的工作要容易得多。 為了在這方面取得成功,您可能還需要設計、溝通和銷售方面的技能。 --- ### 36. 兜售網域 隨著新 TLD 的湧入,域名經銷商市場正在迎來第二波受歡迎。域名翻轉涉及註冊未來可能有價值的域名,然後將其轉售給想要將該名稱用於企業或專案的買家。 雖然這可能有利可圖,但它確實涉及高風險,並且需要對市場有充分的了解。 尖端: - 研究簡短或令人難忘的域名,或者可能具有較高關鍵字潛力的域名(您可以使用諸如使用 Google 關鍵字規劃師等工具來幫助進行這項研究) - 停放您目前未使用的域名,以便您同時獲得一些廣告收入 - 查看最近過期的域名,特別是那些正在使用的域名,因為這些域名可能會收到流量 - 接收流量的網域更有價值。因此,請考慮在您持有網域時為其建立網站、應用程式或登入頁面 --- ### 37. 使用者測試 開發應用程式的公司通常需要獲得用戶的回饋。這就是用戶測試服務的用武之地。您花 10-30 分鐘嘗試給定的網站或應用程式,然後提供反饋或填寫調查,並獲得報酬! 儘管並非特定於開發人員,但憑藉您的技術背景,您會發現自己具有獨特的優勢,可以快速完成這些工作並提供良好的反饋,從而使您比普通用戶更快地賺錢。您還將獲得有關用戶測試流程如何運作的寶貴見解,這可能對您在自己的應用程式上進行委託測試時有用。 - [嘗試我的 UI](https://www.trymyui.com/) - 每個網站或應用程式測試平均費用為 10 美元 - [Userlytics](https://www.userlytics.com/user-experience-research/paid-ux-testing/) - 根據測試的複雜程度和長度,賺取 5 至 50 美元之間 - [使用者測試](https://www.usertesting.com/get-paid-to-test) - 透過 PayPal 付款,在測試會話期間需要螢幕共用和/或網路攝影機存取。每次測試賺取約 10 美元,較長時間或現場會議的某些測試最高可支付 50 美元 - [TestingTime](https://www.testingtime.com/en/become-a-paid-testuser/) - 面對面或視訊通話測試的選項。不太定期,但測試時間更長。當您考慮到會話之間的延遲時,報酬比其他選擇更低 - [IntelliZoom](https://www.intellizoom.com/) - 每 10 分鐘學習可賺取 2 至 10 美元。透過 PayPal 付款,延遲 3-5 天 --- ### 38.微任務 與開發人員的具體關係不大,但如果您來自技術背景,您可能會發現這些工作比那些沒有開發技能的工作更有利可圖。 - [Amazon Mechanical Turk](https://www.mturk.com/) - 外包虛擬微任務的眾包市場 - [Sequence Works](https://sequence.work/contributors/) - 影像標註、資料標記與分類 - [App Jobber](https://en.appjobber.com/) - 市場調查,去商店拍攝特定植入式廣告的照片 - [GigWalk](https://www.gigwalk.com/gigwalkers/) - 應用程式為基礎的行動微任務 - 請造訪 [GigWorker.com](https://gigworker.com/) 以了解更多微任務和零工工作 --- ### 39. 調查 儘管對具有某些技能(如軟體工程)的參與者的需求較高,但調查的報酬往往很低,因此可以賺更多一點。即便如此,除非您有大量時間,或使用比美元弱得多的貨幣,否則這可能不是一個好的選擇。 這些通常涉及測試新產品或服務,並提供回饋 - 或回答問題以協助市場研究活動。 有很多不同的基於調查的公司,所以我不會全部連結到它們。但 [Swagbucks](https://www.swagbucks.com/)、[20Cogs](https://20cogs.co.uk/)、[TestingTime](https://www.testingtime.com/en/become-a-paid-testuser) 是一些著名的。 --- ### 40.去中心化節點 這可能不適合所有人,因為收益通常以加密貨幣形式支付,而加密貨幣的波動性非常大。但是,您可以自願為許多 Web3 專案執行節點(通常在 Rasperry Pi、雲端伺服器或備用筆記型電腦上),這將為您支付正常執行時間、頻寬、磁碟空間、運算、IP/代理或其他一些費用。計算資源。 作為開發人員,管理基礎設施是我們所擅長的,因此,如果您有任何閒置資源,您也許可以將它們投入使用,並在睡覺時賺取一些額外的現金。 - [Storj](https://www.storj.io/node):執行Storj節點,用於去中心化雲端運算 - [Network3](https://network3.io/):用於訓練和驗證模型的 AIoT 第 2 層 - [Flux](https://runonflux.io/):去中心化基礎設施 - [Mysterium](https://mystnodes.com/): P2P VPN 節點 - [Koii](https://www.koii.network/node): 分散式雲 - [Helium](https://www.helium.com/mine):提供遠端物聯網設備無線連接 - [Filecoin](https://filecoin.io/):它是一個去中心化儲存網絡,將雲端儲存轉變為演算法市場。用戶可以出租閒置的儲存空間並賺取 Filecoin 代幣。 - [Sia Network](https://sia.tech/host):這是一個由區塊鏈技術所保障的去中心化儲存平台。 Sia 透過去中心化網路儲存和加密您的檔案。您可以透過出租未使用的硬碟空間來賺取 Siacoins。 - [Crust Network](https://wiki.crust.network/docs/en/nodeOverview):與 Filecoin、Sia 類似,Crust 支援 IPFS 等多種儲存層協議,並為應用層提供儲存介面。 - [Arweave](https://www.arweave.org/):一個基於區塊鏈的平台,以永久和去中心化的方式提供資料儲存。透過託管資料,用戶可以獲得 Arweave 代幣獎勵。 - [BitTorrent](https://docs.btfs.io/v2.0/docs/install-run-btfs20-node):該平台標記了世界上最大的文件共享協議,使用戶能夠通過在網路上。 - [HOLO](https://holo.host/):Holochain 應用程式 (hApps) 的點對點託管平台。在電腦上託管 hApp 的用戶將獲得 HOT 代幣獎勵。 --- ### 41.其他 Web3 方法 加密產業還有許多其他賺取被動收入的方式,從PoS 質押、持有生息數位資產、借貸、流動性挖礦、雲端挖礦、賺取股息的代幣、流動性挖礦、交易、本地/ PoW 思維、NFT 等等。很少。 我不會在這裡連結到任何具體細節,因為這是一個風險非常高的行業,因此您自己進行研究很重要。但作為技術專家,我們能夠理解任何給定協議或 Web3 資產背後的基本概念,並確定其可行性。 我的建議是閱讀白皮書,如果你不能立即理解它,那就遠離它!這是狂野的西部,所以除非一個專案的基本面是堅實的,否則你應該做好失去投資的任何資金的準備。 --- ### 42. 聯盟行銷 聯盟行銷對於那些剛開始的人來說是眾所周知的無利可圖,但我將其包含在此處是因為作為開發人員,有一定的空間來自動化許多過程。此外,您行銷的服務越細分,支付的佣金通常就越高。因此,如果您融入了技術社區,您可能處於銷售小批量高回報服務的有利位置。 同樣,如果您已經有了追蹤者(社交、部落格、YouTube 頻道...),那麼聯盟行銷可能更有意義,因為如果您獲得了大量點擊。 值得注意的是,您可能不應該在未透露它是附屬連結的情況下共享附屬連結。並儘量避免宣傳您自己沒有使用過或不會推薦給朋友的產品。 > 作為範例,[此處](https://notes.aliciasykes.com/p/3Ia4JzPw43) 是我使用過且擁有附屬帳戶的一些服務。我從未從其中任何一個身上賺過任何有意義的錢。 --- ### 43.經銷商 這涉及建立一個應用程式來包裝現有服務,同時加入 USP - 技術、客戶支援、UI 或其他功能。如果您有行銷或銷售背景,這可能適合您。如果您想加入功能或使流程自動化,那麼將需要大量的前期工作,但您將能夠更好地獲得收入。 您可以在大多數主要行業中找到提供經銷商計劃的服務提供者。 一些例子包括: - [Supermetrics](https://supermetrics.com/):行銷報告、分析、資料整合、20% 經常性佣金。 - [Keap](https://keap.com/):CRM、銷售與行銷自動化、20-30% 經常性佣金。 - [Klaviyo](https://www.klaviyo.com/):電子郵件與簡訊行銷,5–15% 一次性付款,10–20% 收入分成。 - [Drift](https://www.drift.com/):即時聊天軟體,20%收益分成。 - [ActiveCampaign](https://www.activecampaign.com/):電子郵件行銷、CRM、20–30% 佣金或折扣模式。 - [HubSpot](https://www.hubspot.com/):CRM、入站行銷、銷售、20% 營收分成。 - [Gorgias](https://www.gorgias.com/):電子商務幫助台,20% 收入分成。 - [Shopify](https://www.shopify.com/):電子商務平台,佣金20%,Shopify Plus 10%。 - [LiveChat](https://partners.livechat.com/):客戶服務平台,即時聊天,委託20%。 - [GetResponse](https://www.getresponse.com/):電子郵件行銷、線上活動管理、子帳號 35% 折扣、35% 經常性佣金。 --- ### 44. 人體實驗 這與技術根本無關。但作為程式設計師,我們通常可以在任何地方工作 - 那麼為什麼不在有報酬的地方編寫程式碼呢? 通常,您的收入在 2,000 美元到 10,000 美元之間,具體取決於試用期、持續時間、是否為住宅和具體情況。 像[流感營](https://flucamp.com/) 這樣的地方將支付您 4,000 英鎊,讓您在舒適的酒店式套房中入住兩週,同時他們會測試新的治療方法。那些患有氣喘等特定疾病的人可能可以透過參加更專業的試驗來賺取更多收入 --- ### 45.自由職業 自由職業可能會根據您的技能、經驗和您所在的領域而有所不同。對於新自由工作者來說,某些領域(例如網頁開發)的費率往往非常低,但您擁有的經驗和客戶滿意度越高,您就越能夠充電。 開發人員零工工作的三個主要平台是: - [Fiverr](https://www.Fiverr.com/):Fiverr 以其多元化的市場而聞名,非常適合剛開始從事自由職業的開發人員 - [Upwork](https://www.upwork.com/work):Upwork 迎合了廣泛的專業人士,但它對經驗豐富的開發人員特別有利。它提供了長期合約和高薪工作的潛力。該平台適合喜歡從事更實質專案的人。 - [People per Hour](https://www.peopleperhour.com/):這個平台對歐洲市場的開發者有好處。它強調當地的商業聯繫,並在短期和長期專案之間提供良好的平衡。 --- ### 46. 說話 面對面的和移除的開發者聚會和活動在全球各地不斷發生。這些活動需要演講者,許多人願意付費以獲得良好的演講。支付的金額根據規模、觀眾、主題、演講者(你!)和其他因素而有很大差異。通常,您必須先自願在當地的小型技術聚會上發表演講,然後逐步提高。 --- ### 47. 遠端技術支持 這不是最迷人的角色,但較小的公司通常無法聘請全職的專門技術支援人員,因此您可以找到很多兼職工作。如果您擁有雲端經驗或認證,這些的薪資等級會大幅提高。只需查看任何求職板(例如 [WeWorkRemotley](https://weworkremotely.com/)),您就會看到大量職位。 請注意,您通常需要在特定時間內有空,並期望您可以在給定的時間內回覆。在申請之前,請確保這是您可以解決的問題。 --- ### 49. 投資 是的,這不是副業——但聽我說完… 如果您每年的收入為 6 萬美元,生活成本為 4 萬美元,那麼 5 年後您可能會有 10 萬美元的儲蓄。如果您將其投資於年平均回報率為10 - 15% 的標準普爾500 指數- 您每月可能會從您的投資中獲得超過1,000 美元的額外收入,並且您的投資能力越強,收入就會不斷增加儲蓄(當然,投資可以減少也可以增加)。這已經比這裡列出的許多副業更好的回報了! --- ### 50. 就業 我們不要忘記,儘管目前情況看起來很艱難,但身為開發人員,即使只有一兩年的經驗,我們也處於非常幸運的地位,與平均收入者相比,我們的薪水很高。 如果你的工作不適合你——換公司通常是提高薪資的可靠方法,如果你不喜歡目前的工作,這可能是值得考慮的事情。 也許經歷了這一切之後,你所追求的不是副業,而是更好的「主業」? --- ## 真實的說話 儘管您可能會在 IndieHackers 和 Instagram 上看到一些內容,但副業並不是全部。這通常需要大量的工作,但回報卻非常有限。因此,在在這裡或其他地方進行任何事情之前,請退後一步,思考「我為什麼要這樣做?」。如果您這樣做是為了累積經驗、學習新技能並享受樂趣——那就太好了。如果你這樣做是為了快速致富 - 你可能會非常失望。 還有一點要注意的是,儘管看起來不公平,但與那些剛起步的人相比,那些已經擁有強大追隨者或幾個成功的開源專案的人將處於更好的位置來利用機會。 因此,從短期來看,作為一名開發人員,您的時間可能會更好地花在提升自己身上。如果您不確定從哪裡開始,這裡有 5 個關鍵提示: - **網絡** - 建立你的網絡,參加聚會、黑客馬拉松和開發活動,加入社區,結交朋友 - **開源** - 將您的工作放在那裡,公開學習,建立您感興趣的迷你專案,並且不要害怕失敗 - **經驗** - 獲得實務經驗,申請實習機會,作為自由開發人員提供服務 - **基礎知識** - 確保您對電腦科學基礎知識有深入的了解,其餘的就會容易得多 - **玩得開心!** - 你自然會在你真正熱愛的領域做得更好。如果你不喜歡你正在做的事情,請退一步,考慮不同的方法是否更適合你 --- ## 免責聲明 - 以上列表僅供您參考。 - 我沒有親自測試過這裡列出的所有服務。 - 如果您有 - 我很想聽聽您的回饋。同樣,如果有任何需要加入或刪除的內容,請在下面告訴我。 - 並非所有服務都在所有國家/地區提供(此清單主要針對英國/歐洲和美國🇬🇧🇪🇺🇺🇸) - 有些平台會抽取您的收入。這通常是一個很小的數額,但重要的是你要考慮到 - 如果您已經擁有大量追隨者或流行的開源專案,賺錢通常會容易得多 - 有些方法涉及風險。儘管我已盡力強調這一點,但請記住,您的投資可能會下降而不是上升 - 您的結果可能會有所不同 - 沒有保證 --- 原文出處:https://dev.to/lissy93/50-ways-to-bring-in-extra-cash-as-a-developer-19b6

😂11 個有趣的 Python 函式庫,讓你的一天更美好☀️

## **簡介** 在這篇文章中,我將介紹 11 個我玩得很開心並想與您分享的函式庫。 ![介紹 GIF](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e5jwsqkoegdsvksunxjk.gif) --- ## 1. [Taipy](https://github.com/Avaiga/taipy) Taipy 是一個開源程式庫,旨在輕鬆開發前端 (GUI) 和 ML/資料管道。 不需要其他知識(不需要 CSS,不需要 JS,什麼都不需要!)。 建立您夢想的應用程式得益於: - 完整的客製化和互動 - 多頁和多用戶 - 管道圖形編輯器 - ...還有更多。 您甚至可以將以下所有庫與 Taipy 一起使用,並使其更加有趣。 你的想像力是極限! ![太皮描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g40itmwejvzgrak3vfyn.gif) --- ![QueenB 星星](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bvt5qn1yadra3epnb07v.gif) https://github.com/Avaiga/taipy 我們已經快有 1000 顆星了,沒有你就無法做到這一點🙏 --- ## 2.[Asciimatics](https://github.com/peterbrittain/asciimatics) Asciimatics 是一個可讓您建立全螢幕文字 UI 的程式庫。這個圖書館是出於對 80 年代的懷舊之情而建造的。 以下是它們的一些主要功能: - 彩色/樣式文字 - 遊標定位 - 鍵盤輸入(無阻塞或迴聲) ![Asciimatics 描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lnvqj0yl0k45tdqxovzv.gif) --- ## 3.[Arcade](https://github.com/pythonarcade/arcade) 沒有遊戲,樂趣怎麼可能有意義呢?正如庫名稱所暗示的,它允許建立 2D 影片遊戲。是的,這看起來確實很難,但 Arcade 讓它變得容易。因此,「pip install arcade」並開始遊戲或從一些[現有遊戲](https://api.arcade.academy/en/latest/sample_games.html)中汲取靈感! ![街機描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n0q7asll2jijtbs5opfh.gif) --- ## 4.[Rich](https://github.com/Textualize/rich) Rich 將為您的生活增添色彩! Rich 在您的終端機中寫入豐富的文字(顏色+樣式),因此它們最終可以反映您是誰。 豐富包括: - 不同的風格 - 降價 - 進度條 - 亞洲語言支持 - ... 和更多! ![豐富描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xo7ga376dr248zwvk91z.png) --- ## 5.[PyEphem](https://github.com/brandon-rhodes/pyephem) PyEphem 是一個在天空中迷失的好工具。該庫允許進行天文學計算;以下是一些有趣的用例: - 尋找行星在天空中的位置 - 確定春分和至日的日期 - 計算月相的日期 - ... 還有很多! 這個圖書館一定會激發您的好奇心。 ![PyEphem 描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rxtv1assuqhip7qy2i4i.png) --- ## 6.[tqdm](https://github.com/tqdm/tqdm) 光是這個圖書館的名字就已經很有趣了。 Tqdm 表示兩件事: - 是西班牙文「我非常愛你」的縮寫 (*te quiero demasiado)* - 源自阿拉伯語單字“*taqaddum* ()”,意思是進步,這實際上是對該庫所做工作的一個很好的介紹。 Tqdm 是任何循環的包裝器,它將透過進度條追蹤進度。您可以自訂它,讓等待任務完成不再那麼無聊! ![tqdm 描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zexvoycl39mvfl0sl9rh.gif) --- ## 7. [Pygame](https://github.com/pygame/pygame) Pygame 讓編寫視訊遊戲變得容易。該庫將使您可以存取多種多媒體元素,例如: - 圖形 - 聲音 - 輸入設備 現在,發揮您的創造力,建立您自己的 2D 互動遊戲或探索一些[可用遊戲](https://www.pygame.org/tags/all)。 ![Pygame 描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ylzvyo099rgqbj05zxj3.png) --- ## 8.[Turtle](https://docs.python.org/3/library/turtle.html) 這個函式庫有所不同,因為它是 Python 語言的一部分,因此不需要 *pip install*。 Turtle 是為了教育目的而建立的,幫助新程式設計師透過在畫布上設計圖形來使用 Python 進行編碼。一種進入 Python 或享受一點創意樂趣的有趣方式。 ![海龜描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/afsr71qceis4pjhhbp7z.png) --- ## 9.[MoviePy](https://github.com/Zulko/moviepy) MoviePy 是用 Python 編輯影片的便利工具。其功能包括: - 切割 - 連接 - 過濾 - 和轉變。 您可以閱讀許多不同的格式,包括 GIF。 ![MoviePy 描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nqehjt9nxea75dkjci96.png) --- ## 10. [Emoji](https://github.com/carpedm20/emoji) 透過此庫在 Python 程式中使用表情符號。它只是使用 *emojize()* 函數將 unicode 表情符號轉換為表情符號名稱。其他功能包括加入表情符號、計算表情符號等… ![表情符號描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r8yz3fmhxpcw49bea7m0.png) --- ## 11.[Pyjokes](https://github.com/pyjokes/pyjokes) 有了 pyjokes,您只需安裝一點就能獲得點播笑聲。這個圖書館會給你一些俏皮的笑話;您甚至可以選擇語言和類別。 玩得開心! ![Pyjokes 描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dbmwu55pqu1j94r56sut.png) --- 希望您喜歡這篇文章! 我是一名新手作家,歡迎任何改進建議! ![菜鳥 gif](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q7qr8qikry1a2rf6x9dh.gif) 如果您有任何疑問,請隨時與我們聯繫。 --- 原文出處:https://dev.to/taipy/11-fun-python-libraries-to-make-your-day-better-4gpc

分享從十開始的轉職之路

哈囉大家好,我加入這平台有九個多月,後來神隱一段時間,趁現在的我有空閒時間,趕緊與大家分享我的轉職之路。 神隱的這段時間想當然是成功轉職,跑去上班,才有辦法與大家分享我的轉職心得XDD。開始上班後,因為有太多東西都是第一次接觸到,忙得不可開交,所以拖到現在才上來分享,抱歉><" ! 那麼就進入正文吧! 我大學是有接觸過程式的經驗的,正確來說,應該是這科系本來就會接觸到( C、C++、Java、Python等),但因為學校的特殊性,也有碰到硬體,當時我是比較傾向於硬體,所以程式沒甚麼太專研,成績有過就好,後來畢業後也是從事硬體職位,程式幾乎只記得超基礎的東西。後來的職涯目標轉變,所以重新開始寫程式,跟別人不太一樣的地方大概就是有一點程式的基礎,但我也沒接觸過網頁,所以是從大概距離起跑點十的位置前進吧 ? 哈哈哈 ~ 確定目標後就果斷離職,離職後開始全力進修學網頁(3月開始),也設定了進度: 三月 ~ 六月 : 學會HTML、 CSS、JS最基本語法。 六月 : 開始準備履歷作品、學 Vue 及投履歷 七月 : 邊面試、邊修履歷及作品 八月 : 開始上班。 一開始在找資源時,因緣際會下,發現到這平台,就跟著站長的那些課程進度走。我就真的只有上站長這裡的課程,完全沒去其他地方,例如udemy、Hahow 好學校等。沒錯......因為我想省錢,當時是想說先上看看,真的不行再去買那些課程,結果就一路到現在都還沒買其他課程......。 跟了這平台的課程後,也認識到了一些學習前端的免費資源,像是W3Schools等,所以雖然是跟著進度上,但我是搭配著上。甚麼意思呢 ? 舉例來說,在學HTML時,站長在文內可能只說學第幾個(A)跟第幾個(B)即可完成這作業,但在W3Schools,A跟B之間可能還有其他的部分,所以我是照W3Schools裡的HTML的順序學下來,學到課程內要學的A跟B,就先去完成作業,再接下去學。因為也有提供中文的W3Schools,所以也是這樣喔 ! 寫過的就當作複習,加深印象,打好基礎~ 而在自學中,最難的事之一就是自律這件事,所以我每天都一定強迫自己最晚十點到圖書館,午休一個小時,因為有找個兼職,要維持生活開銷,所以到六點就會離開圖書館。除了休息那一個小時外,我都是在學習,沒有中途玩手機還是做其他事喔 ! (有時覺得用眼過度,抬頭看窗外,那不到三分鐘的事不算~),一周有六天都是如此,禮拜天就會給自己放鬆一下,只到下午兩、三點。 一直這狀態直到六月,開始準備作品,當時也是爬文、看別人都是怎麼準備、展示自己的作品的,就發現大多的人都會做個人網頁。而要怎麼做個人網頁呢?就繼續爬爬爬...,就認識到GitHub有提供GitHub Pages,能把自己的作品上傳到這後,別人就可以看到你的作品。真的是非常方便,而且又是免費的阿 ! 剛好課程也有教到Git指令,所以就架了一個自己的履歷網頁~ 準備得差不多後,就照著站長建議的,把工作經驗一年以下、無經驗的、可以應屆畢業生(雖然我不是),全都投了一遍,只留自己夢想公司,這樣就能先累積面試經驗,並依面試得到的反應去修改自己的作品,再最後投夢想公司。能投得都投後,就開始照課程學框架Vue,若有做出作品就隨時更新到我的個人履歷網頁上。 面試時,大多都是看履歷問問題,有些有筆試,但都是像網路上大家說的那樣,前端必學觀念,沒遇過上機coding的。有些有手寫code,通常只有1-2題,印象中,有個題目是要你把字串反過來,怎麼寫 ? 只要把網路上大家說的必考題目弄熟,基本沒有太大問題。 而個人面試時有遇到的幾個問題,第一個是作品有點陽春Q,本身也沒有UI/UX的經驗等,所以是這職位轉過來的在這點上就會很加分呢 ! 我只能靠時間一點點的累積、培養了QQ。再來就是很常被問自學的困難點,又加上我只在這平台學習(雖然他們不知道),但我認為最重要的還是有沒有決心想學前端,如果有就一定會去找資源,遇到問題,也不會那麼容易就挫折、放棄,畢竟已經下定決心自學的人都早就帶有這覺悟了吧 ? 就像在學Vue時,可能在這課程學到的不完善,但我認為這裏只是給一個學習的方向,那麼就可以朝這方向去找更多資訊,也能知道哪邊需要在著重釐清概念,就可以去看這部分的影片! 之後,運氣非常好的收到幾個offer,並在八月如預期開始上班,真是太開心了( 灑花~ 最後就說一些建議,雖然還是菜鳥,但應該可以多少能給一些方向 ? 這些建議是,若我今天還沒開始上班,我還會做甚麼事,大家就當作參考吧~ 假如我能帶著上班後遇到問題的記憶回到還沒就職時,我會: 1. 把JS基礎打好,多找題目做! 2. (假如學的框架是Vue),慢慢開始從API換成CLI,並多做作品,熟悉框架物件等等之間的傳遞。 3. 認識更多套件,並學會使用,可以的話套看看在框架上,並把每個套件組合成一個流暢性的作品。 4. Code Review : 會打程式固然重要,但專案的架構、流程設計的好,真是輕鬆一半,也會更清楚程式間是如何傳遞的! 5. 課程的每個作業能把它做到最好就做到最好~ 就先大概這樣吧,真的是有太多東西等著我去學,希望未來的我不要菜味這麼重,也能為大家提出更實質性的建議或幫助,不管是自學的還是轉職的,大家一起加油吧 ! ( 我可能又會神隱一段時間,但有時間,一定會繼續完成站長的課程 & 與大家分享工作心得的 !

Laravel + GraphQL 接案心得&範例分享 Part 2:前端 Query/Mutation 與 React 串接範例

在上一篇文章,我簡單介紹了 GraphQL 的好處,以及如何在 laravel 中實作 這一篇文章,接著介紹一下如何在前端使用 React 進行整合 # 實務範例與 API 線上試玩 上一篇文章我用 graphql + laravel 實作了簡單的電商後台 api https://graphql-laravel-example.tw/graphiql 這次我用 Next.js 開發了簡單的電商前端 web app https://graphql-react-example.vercel.app/ 歡迎試玩看看!可以瀏覽商品、輸入信箱訂閱電子報 --- 在前端發送 query 的程式碼,可以參考 https://github.com/howtomakeaturn/graphql-react-example/blob/main/app/page.js 在前端發送 mutation 的程式碼,可以參考 https://github.com/howtomakeaturn/graphql-react-example/blob/main/app/newsletter.js 我使用原生的 fetch 函數呼叫 graphql api,所以您用任何一款 http 函式庫也都可以做到 狀態管理我用 Next.js 社群的 swr 當作範例,您完全可以自由使用任何 state manager # 優點介紹 我認為前端可以自主決定,要撈取哪些資料,是 graphql 最強大的功能! 後端設計好各種 type 之後,前端就可以自行根據 playground 試玩 api! https://graphql-laravel-example.tw/graphiql 可以彈性、自由撈取資料,連關聯資料都可以巢狀撈取! ``` const gql = `query { products { id name description featured_image price comments { content user { name } } }, }`; ``` 大幅減低後端開發時間、前後端溝通時間、以及處理不同情境需要新增多組類似 api 的時間! # 完整程式碼 前端完整程式碼請參考 https://github.com/howtomakeaturn/graphql-react-example 上次的後端 graphql 試玩 https://graphql-laravel-example.tw/ 後端完整程式碼 https://github.com/howtomakeaturn/graphql-laravel-example # 結論 上面 graphql + laravel + react 的範例 我認為原始碼非常單純、易讀,容易開發、也容易維護 您應該可以根據我提供的範例,在專案中試著導入使用 我在替客戶導入 graphql + laravel + react 的時候,發現網路上教學雖然很多,但是缺少範例 所以我製作這些 sample project 方便大家參考&入門 大家有機會的話一定要試試看 graphql 的威力! (此為系列文章,更多內容會在近期發佈) --- # 系列文章 - [Laravel + GraphQL 接案心得&範例分享 Part 1:強大優點、API 線上試玩、工具介紹](https://codelove.tw/@howtomakeaturn/post/yx08mx) - [Laravel + GraphQL 接案心得&範例分享 Part 2:前端 Query/Mutation 與 React 串接範例](https://codelove.tw/@howtomakeaturn/post/2an0Gx)

用 React 和 Node.js 建立 GPT Web 應用程式產生器 - 在 4 個月內,從點子到 25,000 個應用程式

我們正在開發 [Wasp](https://github.com/wasp-lang/wasp) - 一個基於 React、Node.js 和 Prisma 建置的全端 Web 框架。自從 GPT 出現以來,我們想知道是否可以使用它來更快地建立 Web 應用程式。這讓我們想到了 [MAGE - 一個由 GPT 驅動的 Web 應用程式產生器](https://usemage.ai/),它可以根據簡短的描述建立完整的堆疊程式碼庫。 我們已經寫過[MAGE 可以(和不能)做什麼](https://dev.to/wasp/gpt-web-app-generator-let-ai-create-a-full-stack-react-nodejs-codebase-based-on-your-description-2g39)和[它在幕後的工作原理](https://wasp-lang.dev/blog/2023/07/17/how-we-built-gpt-web-app-generator)。這是關於它的起源和採用的故事 - 為什麼我們決定建立它,開發人員如何發現它,以及他們實際上用它做什麼。 ## 為什麼要建構另一個 AI 編碼代理? 我們很晚才進入整個 GPT 編碼代理遊戲。在我們開始考慮建立自己的工具之前,Smol AI、GPT Engineer 和 MetaGPT 等工具就已經受到了廣泛的關注,我們對此也很清楚。 ![編碼代理景觀](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zw6vyjt79bxrsyvdhl78.png) 那為什麼還要麻煩呢?事實是,這些代理程式都不是專門為建立 Web 應用程式而設計的,而這正是我們看到機會的地方。 儘管使用 GPT 代理進行編碼可以讓人感覺超級強大,但它們通常會很慢並且沒有抓住要點,需要多次迭代,最終使該過程相當麻煩且昂貴。 有了這些經驗,我們想知道,*「如果我們為特定堆疊中的 Web 應用程式製作一個高度專業化的編碼代理,而不做其他事情會怎麼樣?它能變得更容易、更快、更便宜嗎?」* 儘管我們對此很感興趣,但作為一個兼顧多個優先事項的小團隊,我們仍然相當懷疑,幾乎放棄了整個專案。事實證明,它的效果比我們預期的還要好。 ## 第 1 步:建造它🛠️ ![運作方式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lkqk1i0p311u9zd9ouk6.png) 在決定 MAGE(*Magic App Generator*)的 v0 版本時,我們考慮了多種選擇。第一個也是最直接的一個是將其與 Wasp CLI 集成,因為我們已經擁有了圍繞它的所有工具。然後,開發人員將執行“wasp new myProject -ai”,而不是執行“wasp new myProject -ai”,CLI 會要求他們提供應用程式描述和其他所需的輸入,然後在終端中完成所有工作。 另一方面,我們知道在開始描述您的應用程式之前下載並安裝 Wasp 是一個很大的要求。最重要的是,CLI 中的使用者介面功能非常有限。這就是我們選擇網路介面的原因 - 零摩擦開始,我們可以讓應用程式建立流程變得非常簡單且美觀。 花了幾週的時間來建構它,最終的結果是一個[用Wasp 製作的完全開源的Web 應用程式](https://github.com/wasp-lang/wasp/tree/wasp-ai/wasp-ai)可以在 https://usemage.ai/ 上免費使用,或在本地託管以獲得更多控制和靈活性(例如,使用您自己的 OpenAI API 金鑰)。 ### 超級具體(僅限網頁應用程式)得到了回報! ![法師計畫](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xqkrqj8b3we67ufxl8gm.png) 如上所述,我們的主要賭注是建立一個專門的編碼代理來建立全端 Web 應用程式,而不是其他任何東西,這得到了回報。它使我們能夠預先為代理提供大量上下文和結構,並引入專門的啟發式方法來修復錯誤。此外,由於 Wasp 的高級抽象(例如身份驗證、專案結構、查詢和作業系統等),我們顯著減少了錯誤的表面積。 另一個結果是執行時間顯著減少,甚至更重要的是成本減少。典型的MAGE 建立的Web 應用程式成本在0.10 至0.20 美元之間,而一般編碼代理[同一應用程式的花費可能高達10 美元](https://wasp-lang.dev/blog/2023/08/01/smol-ai-vs-wasp-ai#thoughts--further-considerations)(所有價格均在 OpenAI 23 年 11 月 7 日定價更新公告之前)。 您可以閱讀有關Wasp 內部工作原理的更多資訊[此處](https://wasp-lang.dev/blog/2023/07/17/how-we-built-gpt-web-app-generator),以及它的比較其他編碼代理[此處](https://wasp-lang.dev/blog/2023/07/17/how-we-built-gpt-web-app-generator)。 ## 第 2 步:啟動它🚀 ![圖表](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/icff70qgd5ozu23ghgw7.png) 開發產品是一回事,但傳播產品並讓人們使用它則完全是另一回事。在我們建立了 MAGE 並在團隊內對其進行了測試之後,問題是下一步該做什麼?我們如何真正聯繫開發人員並開始接收回饋? ### 社區啟動 - 生命的證明 ![waspularity](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nwcmspqyer7hxjysjyjo.png) 由於當時我們已經擁有一個[擁有大約 1,000 名成員的 Wasp 社區](https://discord.gg/rzdnErX),因此我們[發布了 MAGE 作為我們發布週 #3 的一部分](https://wasp-lang.dev/blog/2023/06/22/wasp-launch-week-third#gpt-web-app-generator--friday-waspularity---tutorial-o-thon)。這是一個很好的測試平台,我們可以看到第一個應用程式正在建置。儘管如此,更多的開發人員本可以從更簡單的方式來啟動他們的 React 和 Node.js 專案中受益,但他們卻無法找到 MAGE。 ### 啟動 Product Hunt — 首次「真正」使用 ![mage-ph](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w3z5dkjuxn8502699s5a.png) 這就是為什麼我們決定在內部社群啟動後將 MAGE 放在 Product Hunt 上。儘管Product Hunt 不是一個特定於開發工具的平台,並且吸引了來自不同背景的人群,但仍然有很多開發人員在使用它,而且我們[之前有很好的經驗](https://www.producthunt.com/products/wasp-lang-alpha/launches) 與它。 Product Hunt 對於[獲得Wasp 的第一批用戶並在GitHub 上獲得1,000 顆星](https://wasp-lang.dev/blog/2022/09/29/journey-to-1000-gh-stars) 至關重要,因此我們想再試一次。 有些人在發布準備工作上投入了大量精力,需要提前幾個月才能準備好,但我們沒有那個時間,只是想盡快完成。我們要求我們的朋友和社區查看 [MAGE on Product Hunt](https://www.producthunt.com/products/wasp-lang-alpha#gpt-webapp-generator-for-react-node-js) 並提供支持我們在發布當天就進入了當天的前十名產品,後來又躋身本週排名第二的開發者工具! 這就是我們的目標,因為排名前十的產品最終會出現在第二天的電子報中,有超過 100 萬訂閱者閱讀。很快,我們看到建立的應用程式數量顯著增加,新的人開始加入我們的 Discord 社群! ### 有機成長(又稱「無所事事」)階段 在 Product Hunt 推出後,我們放鬆了行銷工作,因為其他與 Wasp 相關的任務趕上了團隊。我們必須為即將到來的[發布週#4](https://wasp-lang.dev/blog/2023/10/13/wasp-launch-week-four)做準備,所以 MAGE 最終被擱置了。在我們決定投入更多資源之前,我們也想看看社區如何接受它。 我們發布了更多後續文章,「[它是如何在後台工作的](https://wasp-lang.dev/blog/2023/07/17/how-we-built-gpt-web-app-generator)”和“[MAGE vs. 一般編碼代理](https://wasp-lang.dev/blog/2023/08/01/smol-ai-vs-wasp-ai)”,獲得平均數量牽引力,但沒有爆炸。我們在 Reddit 和 Hackernews 上也沒有什麼成功,感覺社群已經厭倦了所有的人工智慧內容。 儘管如此,使用 MAGE 建立的應用程式數量持續增長(約 200 個應用程式/天),偶爾會出現小規模爆炸。我們無法真正追蹤用戶來自哪裡 - MAGE 似乎是透過封閉社區和時事通訊中的口碑傳播的。 ### YouTube 和 TikTok 影片 - 病毒式傳播(每天 1,300 個應用程式!) 在我們的第 4 週發布週結束後,我們意識到,在近 2 個月的時間裡,在我們付出最小努力的情況下,MAGE 一直在不斷成長。這向我們表明它具有一定的實際價值,因此我們決定對其進行更多投資。 ![matt_yt_video](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sztjfowul34w6uqwzb56.png) 我們決定與該領域的影響者碰碰運氣。我們不想簡單地支付廣告費用,而是希望與真正對我們正在建立的產品感興趣並且想要客觀地審查 MAGE 的人合作。我們發現 [Matthew Berman](https://www.youtube.com/watch?v=KQrGu8cnwvA&t=2s&ab_channel=MatthewBerman) 涵蓋了 LLM 領域從最新模型到工具的所有內容,他將 MAGE 視為非常適合他的觀眾。 該影片在幾週內就準備好了,當它發佈時,觀看次數很快就達到了約 25,000 次。許多觀眾對透過 Web 介面從單一提示中獲取全端程式碼庫的可能性感到興奮,我們看到使用率和開發人員再次嘗試。 ![tiktok_video](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cnfmgt026dvf5yn0a9sm.png) 大約一周後,我們看到建立的應用程式數量再次大幅增加,但無法弄清楚它來自哪裡。我們做了一些搜尋,在TikTok 上找到了一位開發者@techfren,他製作了[一個關於它的短影片](https://www.tiktok.com/@techfren/video/7288306291733269778)(MAGE 甚至最終無法在就是那個!),一天之內瀏覽量猛增至 20 萬次,並迅速走紅。如今,它的瀏覽量已接近 100 萬。 ## 現實 - 所有這些應用程式會發生什麼? 儘管 25,000 個建立的應用程式聽起來令人印象深刻,但退後一步並進一步細分是很好的。 與大多數人工智慧驅動的編碼工具一樣,想要建立自己產品的開發人員和非開發人員都對該領域抱持極大的興趣和好奇心。許多人甚至沒有想要建立的具體專案,但他們熱衷於嘗試一下,看看它是如何運作的。此外,由於法學碩士不是確定性的,因此還沒有任何工具能夠完美執行,並且經常會出現需要手動幹預和編碼知識的小錯誤。 雖然我們對此非常明確(https://wasp-lang.dev/blog/2023/07/10/gpt-web-app-generator#what-kind-of-apps-can-i-build-with-it )和其他[挑戰](https://wasp-lang.dev/blog/2023/07/10/gpt-web-app-generator#challenges)GPT驅動的工具面臨,MAGE仍然吸引了一部分的用戶對建置自己的產品感到興奮,但不精通編碼,需要幫助下載、執行和修復應用程式。另一方面,它是一種非常友好且易於參與 Web 開發和編碼的方式。我們不會阻止非編碼人員嘗試,而是盡可能透明地管理期望。 因此,大約 40% 的所有建立的應用程式都會下載並在本地執行。 ## 結論 事實證明,我們對 MAGE 的實驗非常成功,甚至超越了我們最初的預期。除了許多現有的通用編碼代理之外,高度專業化和結構化的方法可以以低廉的價格產生更好、更一致的結果。 此外,開發人員對啟動全端應用程式的簡單方法(其中包含最佳實踐)感到興奮,並且正在積極尋找這樣的解決方案並在彼此之間共享。 我預計人工智慧輔助的 SaaS 新創公司將成為 Web 開發的未來。如果有人可以使用已經為其應用程式定制的資料模型和 CRUD 邏輯來建立他們的應用程式,那麼為什麼有人會使用通用樣板啟動器呢?另一個問題是誰以及如何具體實現這一點,但我預計未來每個主流框架都會有一個。 ## 祝你好運! 我希望這篇概述對您有所幫助,並讓您了解建立和行銷新的(人工智慧驅動的)開發工具時幕後的情況。請記住,這是我們獨特的經歷,每個故事都是不同的,因此對一切都持保留態度,只選擇對您和您的產品有意義的內容。 我們祝您好運,如果您有任何疑問或想了解 [MAGE](https://usemage.ai/) 和 [Wasp](https://github.com/wasp-lang/wasp)! --- 原文出處:https://dev.to/wasp/how-we-built-a-gpt-web-app-generator-for-react-nodejs-from-idea-to-25000-apps-in-4-months-1aol