🔍 搜尋結果:`

🔍 搜尋結果:`

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

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

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

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

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

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

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

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

✨ 用您的文件訓練 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

🚀 5 個專業技巧,打造無與倫比的 GitHub 自述文件! 🥊

嗨朋友👋 今天,我們來看看如何編寫一個**KILLER GitHub README 文件。** ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d1qn6ckiea6i22zira7k.gif) 自述文件是任何剛接觸您的儲存庫的人的第一個接觸點。 **完善的自述文件可以提供資訊、吸引並邀請參與**。 當您啟動新專案或開發人員參與您的儲存庫至關重要時,這特別有用。 一個很好的例子是,當您向[Quine's Creator Quests](https://www.quine.sh/?utm_source=devto&utm_campaign=killer_github_profile) 提交儲存庫時,這是一種獎勵開發人員的新型開源「賞金」創造社區喜愛的酷炫新倉庫。 在創作者任務中,擁有一份寫得好的自述文件確實可以幫助您將獎品帶回家。 :錢_帶_翅膀: 如果您想參與,請在 [Quine](https://www.quine.sh/?utm_source=devto&utm_campaign=killer_github_profile) 免費註冊並前往 _Quests_。 --- ## 1️⃣ 自述文件...這是什麼? 🔮 README 文件,通常是“.txt”或“.md”文件,是專案中最重要的文件。它是開源專案的登陸頁面,也是最明顯的文件部分。 _這是 Hoppscotch 專案的自述文件範例。_ [![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hwp0o21ft5cmsm6v8bvg.png)](https://github.com/hoppscotch/hoppscotch) 您的自述文件是**您為專案定調的地方**。 這就是為什麼應該包含幾個元素的原因。 ⬇️ --- ## 2️⃣ 10 個建置模組🧱 自述文件可以透過多種方式編寫;有些比其他更好。 以下是您應該能夠在專業自述文件中找到的主要部分類型。 如果以下內容與您的專案相關,我建議您嘗試將它們加入到您的自述文件中。 👇 **1.想出一個名字** 大量的儲存庫沒有好名字。花點時間想一個令人難忘的名字總是一個好的開始。 **2.寫一個介紹** 解釋專案目的和目標受眾的摘要。 **3.目錄** 許多存儲庫未能加入此部分。組織良好的目錄有助於使您的儲存庫清晰易懂。 **4.先決條件和安裝說明** 列出各種逐步安裝說明。 _這裡的清晰度至關重要。_ ✨ **5.使用者指南/示範** 本節重點介紹如何透過範例和可選命令使用您的專案。我喜歡的一種方法是加入_如何執行專案_以及用戶如何使用它的螢幕記錄。 **6。文件/幫助中心** 如果您編寫了文件、常見問題或可以充當_幫助中心_的空間,請在此處寫下它或加入其連結。 **7.貢獻** 簡要解釋如何為您的文件做出貢獻並加入指向您的 CONTRIBUTING.md 文件的連結。在這裡或在單獨的部分中,您還可以藉此機會列出您的貢獻者並感謝他們。 **8.致謝** 使用或協助取得外部資源。僅當與您的儲存庫相關時才需要此部分。 **9.聯絡資訊** 如果您正在嘗試發展專案並建立協作,本節非常有用。 **10.權限資訊** 這是指您為專案選擇的許可證類型。澄清其他人如何使用您的內容至關重要。 👍 --- ## 3️⃣ 美化它💄 自述文件可以用各種文字格式編寫,其中 Markdown 是最常見的一種。 Markdown 可讓您使用簡單的純文字語法,支援建立標題、清單、連結和其他元素,使文件更具可讀性和組織性。 **Markdown 也支援 HTML 程式碼**,這拓寬了您可以做的事情的範圍。 👇 --- ### 標識 如果您已經為專案建立了徽標,則將其新增至自述文件的開頭是標準做法。 為此,請在自述文件中新增並修改以下程式碼: ``` <p align="center"> <!-- You can add your logo in the _src_ below --> <img src="https://www.amug.com/wp-content/uploads/2016/09/you-logo-here-300x106.png" /> </p> ``` --- ### 徽章 您經常會發現好的自述文件在其介紹部分中提供了徽章。 這些可以看起來像這樣: <p對齊=“中心”> <!-- 您可以在此處新增您的徽章 --> <!-- 如果您從未加入過徽章,請前往 https://img.shields.io/badges/static-badge --> <!-- 按照說明產生 URL 連結加入到下面 --> <img src="https://img.shields.io/badge/STARS-20K-green" /> <img src="https://img.shields.io/badge/FORKS-15K-blue" /> <img src="https://img.shields.io/badge/npm-v.0.21.0-red" /> <img src="https://img.shields.io/badge/LICENSE-MIT-green" /> </p> 正如您所看到的,這些突出顯示了維護人員想要闡明的一些領域。 以下是將 _static_ 徽章新增至自述文件的方法: ``` <p align="center"> <!-- You can add your badges here --> <!-- If you have never added badges, head over to https://img.shields.io/badges/static-badge, follow the instructions and generate URL links to add below --> <img src="https://img.shields.io/badge/STARS-20K-green" /> <img src="https://img.shields.io/badge/FORKS-15K-blue" /> <img src="https://img.shields.io/badge/npm-v.0.21.0-red" /> <img src="https://img.shields.io/badge/LICENSE-MIT-green" /> </p> ``` **注意**:_有一些高級動態選項我們不會在這裡討論。_ --- ### 圖示 近年來,圖標變得相當突出。 您可以將它們新增至您的_聯絡資訊_或您的_技術堆疊_部分。您可以找到 X(_以前的 Twitter_)和 Linkedin 的圖示範例。 <p對齊=“左”> <a href="https://twitter.com/fernandezbap" target="blank"><imgalign="center" src="https://img.shields.io/badge/X-000000?style=for - the-badge&logo=x&logoColor=white" alt="fernandezbap" /></a> </p> <p對齊=“左”> <a href="https://www.linkedin.com/in/baptiste-fernandez-%E5%B0%8F%E7%99%BD-0a958630/" target="blank"><img src="https: //img.shields.io/badge/LinkedIn-0077B5?style=for-the-badge&logo=linkedin&logoColor=white" alt="https://img.shields.io/badge/LinkedIn-0077B5?style=for-the -badge&logo=linkedin&logoColor=white" /></a> </p> 建立您自己的: 1️⃣ 前往此儲存庫[此處](https://github.com/alexandresanlim/Badges4-README.md-Profile#-social-) 2️⃣ 找到_社交_和/或_技術堆疊_,然後“複製”其連結 3️⃣ 將連結「貼上」到如下所示的「href」中 ``` <p align="left"> <!-- Add your own socials inside "href" --> <a href="https://twitter.com/fernandezbap" target="blank"><img align="center" src="https://img.shields.io/badge/X-000000?style=for-the-badge&logo=x&logoColor=white" alt="fernandezbap" /></a> </p> <p align="left"> <a href="https://www.linkedin.com/in/baptiste-fernandez-%E5%B0%8F%E7%99%BD-0a958630/" target="blank"><img src="https://img.shields.io/badge/LinkedIn-0077B5?style=for-the-badge&logo=linkedin&logoColor=white" alt="https://img.shields.io/badge/LinkedIn-0077B5?style=for-the-badge&logo=linkedin&logoColor=white" /></a> </p> ``` --- ## 4️⃣ 進階技巧🛠️ ### 互動內容🎥 您可以考慮嵌入影片或小部件來獲取互動式內容。 在自述文件的_演示_部分中,您可以嵌入影片。 **⭐️提示:** 有時,影片的尺寸太大,將影片上傳到 YouTube 然後連結出來更有意義。 如果您想讓它更加直觀,這裡有加入圖片/影片縮圖的程式碼,一旦您點擊它,該縮圖就會重定向到您的 YouTube 影片。 ``` <p align="center"> <a href="THE LINK TO YOUR YOUTUBE VIDEO HERE"> <img src="YOUR IMAGE/VIDEO THUMBNAIL SOURCE HERE"/> </a> </p> ``` --- ### Markdown 掌握 ✨ 有許多進階 Markdown 功能可以讓你的 README 看起來更漂亮。 您可以查看這個很酷的[repo](https://github.com/DavidWells/advanced-markdown),其中列出了其中一些功能。 我特別喜歡的一個是 **_切換列表_** 或更常見的名稱是_可折疊部分。_ 它們對於使您的自述文件看起來簡潔明了特別有用。這是一個例子: ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0f6phwz4cwzqnm2zm5k9.png) 以下是用於建立您自己的切換清單的 MARKDOWN 模板: ``` <details> <summary>Toggle List Example</summary> ### Heading 1. ABC 2. DEF * Hello </details> ``` --- ### 額外提示🎖️ 如果您想在_貢獻者部分_中表彰您的貢獻者,您應該查看 [AllContributors](https://allcontributors.org/)。 您可以利用其機器人自動將所有最新貢獻者新增至您的自述文件。 以下是您可以獲得的內容的範例: [![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7m55yntosug8hnz3i0gm.png)](https://allcontributors.org/) 您還應該查看[表情符號關鍵文件](https://allcontributors.org/docs/en/emoji-key),它使您能夠對不同類型的貢獻進行分類。 ⚡️ ---- ## 5️⃣ 利用預製模板📝 ### 有沒有為您建立自述文件的網站? 🤔 絕對地! 您可以**在那裡找到大量自述文件生成器。** 我掃描並挑選了我最喜歡的三個給你: 1️⃣ [自述文件範本](https://www.readme-templates.com/) 2️⃣ [Readme.so](https://readme.so/editor) 3️⃣ [Vercel 的 ReadME 產生器](https://readme-gen.vercel.app/) 我對這些的建議是使用它們作為基礎,然後自訂它們。 ⭐️ --- ### 你有給我一個自述文件範本嗎? 👀 我找到你了,朋友。 🫶 您可以在[此處](https://github.com/quine-sh/README-Template)找到我現成的範本。 [![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ckzpitoxwi14usaymqsw.png)](https://github.com/quine-sh/README-Template) 要開始使用它: 1️⃣ `Fork` 倉庫 2️⃣ 將滑鼠停留在自述文件的「編輯」部分 3️⃣開始填寫您的資料✍️ 如果您發現此模板有用,如果您能通過**加星標**_給予它一些愛_,我將不勝感激。 🌟 --- 建立良好的自述文件是一項重要技能。 🛠️ 如何建構它可能是_存儲庫成功的決定因素。_ 請務必在評論部分分享您的專案及其完善的新自述文件! 您還可以利用這項新技能來建立很酷的編碼專案並競爭以獲得報酬。 🙌 如果您對此感興趣,請登入 Quine 並探索[任務](https://www.quine.sh/?utm_source=devto&utm_campaign=killer_github_profile),這是一個編碼、享受樂趣並獲得一些超棒獎勵的地方! 💰 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jk3g038eogbe84g7nph9.gif) 下週見。 你的開發夥伴, 巴巴💚 --- 原文出處:https://dev.to/quine/5-pro-tips-for-an-unbeatable-readme-143i

🏆如何使用 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

我該如何教 Git

--- 標題:我如何教 Git 發表:真實 描述: 標籤: git, 學習 canonical_url:https://blog.ltgt.net/teaching-git/ 封面圖片:https://marklodato.github.io/visual-git-guide/conventions.svg.png # 使用 100:42 的比例以獲得最佳效果。 # 發佈時間: 2023-11-26 19:17 +0000 --- 我使用 Git 已經十幾年了。八年前,我必須為一家即將建立開源專案的合作夥伴公司舉辦有關 Git(和 GitHub)的培訓課程,我將在這裡向您介紹我的教學方式。順便說一句,從那時起,我們在工作中建立了使用相同(或類似)方法的內部培訓課程。話雖如此,我並沒有發明任何東西:這很大程度上受到了其他人之前寫的內容的啟發,包括[the <cite>Pro Git</cite> book](https://git-scm. com/book/),儘管順序不同,但 <abbr title="in my view">IMO</abbr> 可以有所作為。 我寫這篇文章的原因是,多年來,我不斷看到人們實際上使用 Git,但沒有真正理解他們在做什麼;他們正在使用 Git。他們要么被鎖定在一個非常具體的工作流程中,他們被告知要遵循,並且無法適應另一個開源專案正在使用的工作流程(這也適用於開源維護人員並不真正了解外部貢獻者如何使用 Git) ),或者如果任何事情沒有按照他們想像的方式執行,或者他們在呼叫Git 命令時犯了錯誤,他們就會完全迷失。我受到 [Julia Evans](https://jvns.ca) 對 Git 的(更新)興趣的啟發而寫下來,因為她有時會在社交網絡上徵求評論。 我的目標不是真正教你有關 Git 的知識,而是更多地分享我教授 Git 的方法,以便其他可能會教導的人從中獲得靈感。因此,如果您正在學習 Git,那麼這篇文章並不是專門為您而寫的(抱歉),因此可能不是自給自足的,但希望其他學習資源的連結足以填補空白,使其成為也是有用的學習資源。如果您是視覺學習者,這些外部學習資源都是有插圖的,甚至是視覺學習的。 ## 心理模型 一旦我們清楚了為什麼我們使用VCS(版本控制系統)來記錄_commits_ 中的更改(或者換句話說,我們_將我們的更改_提交到歷史記錄;我假設你對這個術語有一定的熟悉),讓我們多了解一下Git具體來說。 我認為理解 Git 至關重要的一件事是獲得其背後概念的準確心理模型。 首先,這並不是很重要,但Git 實際上並沒有記錄_changes_,而是記錄我們文件的_snapshots_(至少在概念上是這樣;它將使用_packfiles_ 來有效地儲存內容,並且在某些情況下方實際上會儲存_changes_ –diffs–),並且會按需產生差異。不過,這有時會顯示在某些命令的結果中(例如為什麼某些命令顯示一個檔案被刪除而另一個檔案被加入,而其他命令顯示一個檔案被重新命名)。 現在讓我們深入探討一些 Git 概念,或是 Git 如何實現一些常見的 VCS 概念。 ### 犯罪 Git _commit_ 是: * 一個或多個父親提交,或第一次提交沒有父親提交 (_root_) * 提交訊息 * 作者和作者日期(實際上是帶有時區偏移的時間戳) * 提交者和提交日期 * 和我們的檔案:相對於儲存庫根的路徑名、_mode_(UNIX 檔案系統權限)及其內容 每次提交都會獲得一個標識符,該標識符是透過計算該資訊的 SHA1 雜湊值確定的:更改逗號,您將獲得不同的 SHA1,即不同的_提交物件_。 (<abbr title="For What it's value">Fwiw</abbr>,Git 正在慢慢[轉向 SHA-256](https://git-scm.com/docs/hash-function-transition) 作為哈希功能)。 #### 旁白:SHA1 是如何計算的? Git 的儲存是_內容尋址_,這表示每個_物件_都使用直接從其內容派生的名稱進行存儲,並採用 SHA1 雜湊的形式。 從歷史上看,Git 將所有內容儲存在文件中,我們仍然可以這樣推理。文件的內容儲存為 _blob_,目錄儲存為 _tree_(一個文字文件,列出目錄中的文件及其名稱、模式和表示其內容的 _blob_ 的 SHA1,以及其子目錄及其名稱和 SHA1他們的_樹_) 如果您想了解詳細訊息,Julia Evans(再次)寫了一篇令人驚嘆的[博客文章](https://jvns.ca/blog/2023/09/14/in-a-git-repository-- where-do-your-檔案-即時-/);或者您可以[從 <cite>Pro Git</cite> 書中閱讀](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects)。 <圖> <img src=https://git-scm.com/book/en/v2/images/commit-and-tree.png width=800 height=443 alt='包含5 個框的圖表,分為3 列,每個框標有 5 位 SHA1 前綴;左邊的子標籤為“commit”,包含元資料“tree”,中間是框的 SHA1,“author”和“committer”的值均為“Scott”,文字為“The initial commit of我的專案”;中間的框被子標記為“tree”,包括三行,每行標記為“blob”,其餘 3 個框的 SHA1 以及看起來像文件名的內容:“README”、“LICENSE”和“test.rb” ”;最後 3 個框,在右側垂直對齊,都是子標籤為「blob」的內容,包含看起來像是 README、LICENSE 和 Ruby 原始檔內容開頭的內容;有箭頭連結框:提交指向樹,樹指向 blob。'> <figcaption>提交及其樹(來源:<a src=https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell><cite>Pro Git</引用></a>)</figcaption> </圖> _commit_ 中的_父親提交_ 建立一個代表我們歷史的[有向無環圖](https://en.wikipedia.org/wiki/Directed_acirclic_graph):_有向無環圖_ 由連結的節點(我們的提交)組成與有向邊一起(每個提交連結到其父提交,有一個方向,因此_directed_)並且不能有循環/循環(提交永遠不會是它自己的祖先,它的祖先提交都不會連結到它作為父提交)。 <圖> <img src=https://git-scm.com/book/en/v2/images/commits-and-parents.png width=800 height=265 alt='包含 6 個框排列成 2 行 3 列的圖表;第一行的每個框都標有 5 位 SHA1 前綴,子標籤為“commit”,元資料“tree”和“parent”均帶有 5 位 SHA1 前綴(每次都不同)、“author”和“ committer」的值都是“Scott”,以及一些代表提交訊息的文字;左邊的盒子沒有「父」值,另外兩個盒子將左邊的盒子的 SHA1 作為「父」;這些框之間有一個箭頭,指向代表「父」的左側;順便說一句,左邊的框與上圖中的提交框具有相同的 SHA1 和相同的內容;最後,每個提交框也指向其下方的一個框,每個框都標記為「快照 A」、「快照 B」等,並且可能代表從每個提交連結的「樹」物件。'> <figcaption>提交及其父級(來源:<a src=https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell><cite>Pro Git</ cite ></a>)</figcaption> </圖> ### 參考文獻、分支和標籤 現在 SHA1 哈希對於人類來說是不切實際的,雖然 Git 允許我們使用唯一的 SHA1 前綴而不是完整的 SHA1 哈希,但我們需要更簡單的名稱來引用我們的提交:輸入 _references_。這些是我們選擇的提交的_標籤_(而不是 Git)。 有幾種_參考_: * _branches_ 是_moving_ 引用(請注意,`main` 或`master` 並不特殊,它們的名稱只是一個約定) *_標籤_是_不可變_引用 * `HEAD` 是一個特殊的引用,指向_當前提交_。它通常指向一個分支而不是直接指向一個提交(稍後我們會看到原因)。當一個引用指向另一個引用時,這稱為[_符號引用_](https://blog.ltgt.net/confusing-git-terminology/#reference-symbolic-reference)。 * Git 會在某些操作期間為您設定其他特殊參考(`FETCH_HEAD`、`ORIG_HEAD` 等) <圖> <img src=https://git-scm.com/book/en/v2/images/branch-and-history.png width=800 height=430 alt='帶有 9 個框的圖; 6 個盒子的排列方式與上圖相同,並且標記相同(三個提交及其 3 個樹);最右邊(最新)提交上方的兩個框,箭頭指向它,分別標記為“v1.0”和“master”;最後一個框位於“master”框上方,有一個箭頭指向它,並標記為“HEAD”。'> <figcaption>分支及其提交歷史記錄(來源:<a src=https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell><cite>Pro Git< /引用></a>)</figcaption> </圖> ### 三個狀態 當您在 Git 儲存庫中工作時,您在 Git 歷史記錄中操作和記錄的檔案位於您的_工作目錄_中。要建立提交,您需要在 [_index_](https://blog.ltgt.net/confusing-git-terminology/#index-staged-cached) 或_暫存區域_中_暫存_檔案。完成後,您附加一則提交訊息並將您的_staged_檔案移至_history_。 為了關閉循環,_工作目錄_是根據_歷史記錄_中的給定提交進行初始化的。 <圖> <img src=https://git-scm.com/book/en/v2/images/areas.png width=800 height=441 alt='包含3 位參與者的序列圖:「工作目錄」、「暫存區域」和「.git directpry(儲存庫)」;有一條“簽出專案”訊息從“.git 目錄”到“工作目錄”,然後從“工作目錄”到“暫存區域”進行“階段修復”,最後從“暫存區域”進行“提交”區域」到「.git 目錄」。'> <figcaption>工作樹、暫存區域和 Git 目錄(來源:<a href="https://git-scm.com/book/en/v2/Getting-Started-What-is-Git%3F#_the_third_states" ><cite>Pro Git</cite></a>)</figcaption> </圖> ### 旁白:忽略文件 並非所有檔案都需要_追蹤_歷史記錄:由建置系統(如果有)產生的檔案、特定於您的編輯器的檔案以及特定於您的作業系統或其他工作環境的檔案。 Git 允許定義要忽略的檔案或目錄的命名模式。這實際上並不意味著Git 會忽略它們並且無法_跟踪_,但如果不跟踪它們,多個Git 操作將不會向您顯示它們或操縱它們(但您可以手動將它們加入到歷史記錄中,並且從那時起,他們將不再被_忽略_)。 忽略檔案是透過將路徑名稱(可能使用 glob)放入忽略檔案中來完成的: * 儲存庫中任何位置的 `.gitignore` 檔案定義了包含目錄的忽略模式;這些忽略文件會在歷史記錄中被跟踪,作為開發人員之間共享它們的一種方式;在這裡,您將忽略建置系統產生的那些檔案(Gradle 專案的“build/”,Eleventy 網站的“_site/”等) * `.git/info/excludes` 是您機器上的本機儲存庫;很少使用,但有時很有用,所以很高興了解一下 * 最後 `~/.config/git/ignore` 對機器來說是全域的(對你的使用者);在這裡,您將忽略特定於您的電腦的文件,例如特定於您使用的編輯器的文件,或特定於您的作業系統的文件(例如macOS 上的“.DS_Store”或Windows 上的“Thumbs. db”) ) ### 加起來 這是所有這些概念的另一種表示: <圖> <img src=https://marklodato.github.io/visual-git-guide/conventions.svg width=907 height=529 alt='有 10 個框的圖; 5 個框在中心排成一行,標有 5 位 SHA1 前綴,它們之間有從右向左指向的箭頭;一條註釋將它們描述為“提交物件,由 SHA-1 哈希標識”,另一條註釋將其中一個箭頭描述為“子項指向父項”;一對框(看起來像一個水平分割成兩個框的單一框)位於最右邊(最新)提交的上方,有一個向下指向它的箭頭,該對的上面的框被標記為“HEAD”並描述為“引用當前分支”;下面的框被標記為“main”並被描述為“目前分支”;第七個框位於另一個提交上方,有一個向下指向它的箭頭;它被標記為“穩定”並被描述為“另一個分支”;最後兩個框位於提交歷史記錄下,一個在另一個之上;最底部的框標記為“工作目錄”並描述為“您'看到'的文件”,它和提交歷史記錄之間的另一個框標記為“階段(索引)”並描述為“要存取的文件”在下次提交中”。'> <figcaption>提交、引用和區域(來源:<a href=https://marklodato.github.io/visual-git-guide/index-en.html#conventions><cite>可視化 Git 參考</cite >< /a>,馬克‧洛達托)</figcaption> </圖> ## 基本操作 這就是我們開始討論 Git 指令以及它們如何與圖表互動的地方: * `git init` 初始化一個新的儲存庫 * `git status` 取得檔案狀態的摘要 * `git diff` 顯示任意兩個工作目錄、索引、`HEAD` 之間的更改,或實際上任何提交之間的更改 * `git log` 顯示並搜尋您的歷史記錄 * 建立提交 * `git add` 將檔案加入_index_ * `git commit` 將_index_ 轉換為_commit_ (帶有新增的_commit 訊息_) * `git add -p` 以互動方式將檔案新增至 _index_:選擇要新增的變更以及僅將哪些變更保留在工作目錄中,逐一檔案、逐個部分(稱為 _hunk_) * 管理分支機構 * `gitbranch` 顯示分支,或建立分支 *`git switch`(也稱為`git checkout`)將分支(或任何提交,實際上是任何_樹_)簽出到您的工作目錄 * `git switch -b` (也稱為 `git checkout -b`)作為 `gitbranch` 和 `gitswitch` 的捷徑 * `git grep` 搜尋您的工作目錄、索引或任何提交;這是一種增強的“grep -R”,它支援 Git * `gitblame` 來了解更改給定文件每一行的最後一次提交(因此,誰應該為錯誤負責) * `git stash` 將未提交的更改放在一邊(這包括_staged_文件,以及工作目錄中的_tracked_文件),然後_unstash_它們。 ### 提交、分支切換和 HEAD 當您建立提交(使用「git commit」)時,Git 不僅建立_提交物件_,還移動「HEAD」以指向它。如果「HEAD」實際上指向一個分支(通常是這種情況),Git 會將該分支移動到新的提交(並且「HEAD」將繼續指向該分支)。每當當前分支是另一個分支的祖先(該分支指向的提交也是另一個分支的一部分)時,提交將使“HEAD”移動相同,並且分支將_發散_。 當您切換到另一個分支(使用“git switch”或“git checkout”)時,“HEAD”會移至新的目前分支,並且您的工作目錄和索引將設定為重新組合該提交的狀態(未提交的更改將暫時保留;如果 Git 無法做到這一點,它將拒絕切換)。 如需更多詳細資訊和視覺表示,請參閱[commit](https://marklodato.github.io/visual-git-guide/index-en.html#commit) 和[checkout](https://marklodato. github .io/visual-git-guide/index-en.html#checkout)Mark Lotato 的<cite>可視化Git 參考</cite>的部分(請注意,該參考是幾年前寫的,當時`git switch ` 和 ` git Restore` 不存在,而 `git checkout` 是我們所擁有的一切;因此 _checkout_ 部分涵蓋的內容比 `git switch` 多一點)。 當然,<cite>Pro Git</cite> 這本書也是一個很好的視覺表示參考; [<cite>Branches in a Nutshell</cite> 子章節](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell) 涵蓋了所有內容的很大一部分上述的。 ### 旁白:Git 是保守的 正如我們在上面所看到的,由於其_內容尋址存儲_,對提交的任何“更改”(例如使用“git commit --amend”)實際上都會導致不同的提交(不同的 SHA1)。 _舊提交_不會立即消失:Git 使用_垃圾收集_最終刪除無法從任何_引用_存取的提交。這意味著,如果您設法找回提交SHA1,則可以恢復許多錯誤(“git reflog”可以在此處提供幫助,或者符號“<branch-name>@{<n>}”,例如“main@{ 1}”) ` main` 在更改之前指向的最後一次提交)。 ### 使用分支機構 我們在上面已經看到了分支是如何發散的。 但分歧要求最終_合併_變回來(使用“git merge”)。 Git 在這方面非常擅長(我們稍後會看到)。 合併的一個特殊情況是目前分支是要合併到的分支的祖先。在這種情況下,Git 可以執行 [_fast-forward merge_](https://blog.ltgt.net/confusing-git-terminology/#can-be-fast-forwarded)。 由於兩個分支之間的操作可能始終針對同一對分支,因此 Git 允許您設定一個分支來追蹤另一個分支。另一個分支被稱為_追蹤_它的分支的_上游_。例如,設定時,「git status」將告訴您兩個分支彼此之間有多少分歧:目前分支是[_最新_](https://blog.ltgt.net/confusing-git-terminology /#your- branch-is-up-to-date-with-originmain) 及其上游分支,_後面_和[可以快轉](https://blog.ltgt.net/confusing-git-terminology/ #can-be- fast-forwarded),_超前_許多提交,或它們有分歧,每個提交都有一定數量。其他命令將使用該資訊為參數提供良好的預設值,以便可以省略它們。 要整合來自另一個分支的更改,而不是合併,另一種選擇是_cherry-pick_(使用同名命令)單一提交,而不包含其歷史記錄:Git 將計算該提交帶來的更改並將相同的更改應用於當前分支,建立一個與原始分支類似的新提交(如果您想了解更多有關Git 實際操作方式的訊息,請參閱Julia Evans 的[<cite>如何gitcherry-pick 和revert 使用3 路合併< /cite> ](https://jvns.ca/blog/2023/11/10/how-cherry-pick-and-revert-work/))。 最後,工具帶中的另一個指令是「rebase」。 您可以將其視為一次進行許多選擇的方法,但它實際上更強大(正如我們將在下面看到的)。但在其基本用途中,它只是這樣:您給它一系列提交(在作為起點的任何提交和作為終點的現有分支之間,預設為當前分支)和一個目標,並且它會挑選所有這些提交位於目標之上,並最終更新用作終點的分支。這裡的指令的形式是`git rebase --onto=<target> <start> <end>`。與許多 Git 命令一樣,參數可以省略,並且具有預設值和/或特定含義:因此,`git rebase` 是 `git rebase --fork-point upper` 的簡寫,其中 `upstream` 是 [upstream]當前分支的(https://blog.ltgt.net/confusing-git-terminology/#untracked-files-remote-tracking-branch-track-remote-branch)(我會忽略`--fork-point`這裡,它的作用很微妙,在日常使用上並不那麼重要),它本身就是`git rebase upper HEAD` 的簡寫(其中`HEAD` 必須指向一個分支),它本身就是`git rebase 的簡寫-- on=upstream uploaded `,`git rebase --onto=upstream $(git merge-baseupstream HEAD) HEAD` 的簡寫,並將rebase `upstream` 的最後一個共同祖先與當前分支之間的所有提交另一方面,手和當前分支(即自從它們分歧以來的所有提交),並將它們重新應用到“上游”之上,然後更新當前分支以指向新的提交。明確使用`--onto` (其值與起始點不同)實際上很少見,請參閱[我之前的文章](https://blog.ltgt.net/confusing-git-terminology/#git- rebase- --onto) 對於一個用例。 我們無法在沒有互動式變體「git rebase -i」的情況下呈現「git rebase」:它以與非互動式變體完全相同的行為開始,但在計算需要完成的操作之後,它將允許您對其進行編輯(作為編輯器中的文字文件,每行一個操作)。預設情況下,所有選定的提交都是精心挑選的,但您可以對它們重新排序,跳過某些提交,甚至將某些提交合併到單一提交中。實際上,您可以挑選最初未選擇的提交,甚至建立合併提交,從而完全重寫整個歷史記錄!最後,您還可以停止對其進行編輯(然後使用“git commit --amend”,和/或可能在繼續變基之前建立新的提交),和/或在兩次提交之間執行給定的命令。最後一個選項非常有用(例如,驗證您沒有在歷史記錄的每個點上破壞您的專案),您可以在`--exec` 選項中傳遞該命令,Git 將在每個重新基底提交之間執行它(這也適用於非互動式變基;在互動模式下,當能夠編輯變基場景時,您將看到在每個櫻桃選擇行之間插入執行行)。 更多詳細資訊和視覺表示,請參閱[merge](https://marklodato.github.io/visual-git-guide/index-en.html#merge)、[cherry pick](https://marklodato . github.io/visual-git-guide/index-en.html#cherry-pick) 和 [rebase](https://marklodato.github.io/visual-git-guide/index-en.html#rebase) Mark Lodato 的<cite>視覺化Git 參考</cite> 部分,以及[<cite>基本分支和合併</cite>](https://git-scm.com/book/en/v2/Git-分支-基本-分支和合併),[<cite>變基</cite>](https://git-scm.com/book/en/v2/Git-Branching-Rebasing)和[<cite>重寫歷史< /cite>](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History) <cite>Pro Git</cite> 書的子章節。 您也可以查看 David Drysdale 的 [<cite>Git Visual Reference</cite>](https://lurklurk.org/gitpix/gitpix.html) 中的「分支和合併」圖。 ## 與他人合作 目前,我們只在我們的儲存庫中進行本地工作。 但 Git 是專門為與他人合作而建構的。 讓我介紹一下_遙控器_。 ### 遙控器 當您_複製_儲存庫時,該儲存庫將成為本機儲存庫的_遠端_,名為「origin」(就像「main」分支一樣,這只是預設值,名稱本身沒有什麼特別的,除了有時用作省略命令參數時的預設值)。然後,您將開始工作,建立本地提交和分支(因此從遠端_forking_),同時遠端可能會從其作者那裡獲得更多提交和分支。因此,您需要將這些遠端變更同步到本機儲存庫,並希望快速了解與遠端相比您在本機所做的變更。 Git 處理這個問題的方式是在一個特殊的命名空間中記錄它所知道的遠端(主要是分支)的狀態:「refs/remote/」。這些被稱為[_遠端追蹤分支_](https://blog.ltgt.net/confusing-git-terminology/#untracked-files-remote-tracking-branch-track-remote-branch)。 Fwiw,本機分支儲存在「refs/heads/」命名空間中,標籤儲存在「refs/tags/」中(來自遠端的標籤通常直接「匯入」到「refs/tags/」中,因此例如您會遺失位置資訊他們來自)。您可以根據需要擁有任意多個遙控器,每個遙控器都有一個名稱。 (請注意,遙控器不一定位於其他電腦上,它們實際上可以位於同一台電腦上,直接從檔案系統存取,因此您無需進行任何設定即可使用遙控器。) ### 取得 每當你從遠端 _fetch_ 時(使用 `git fetch`、`git pull` 或 `git Remote update`),Git 都會與它對話以下載它還不知道的提交,並更新 _remote-tracking遠端分支_ 。要取得的確切引用集以及取得它們的位置將傳遞給 `git fetch` 命令(如 [refspecs](https://blog.ltgt.net/confusing-git-terminology/#refspecs) )以及儲存庫的` .git/config` 中定義的預設值,預設由`git clone` 或`git remote add` 配置以取得所有分支(遠端上的`refs/heads/` 中的所有內容)並放置它們位於` refs/remote/<remote>` 中(因此`origin` 遙控器的`refs/remote/origin/` )具有相同的名稱(因此遙控器上的`refs/heads/main` 變成`refs/remote / origin/main` 本地)。 <圖> <img src=https://git-scm.com/book/en/v2/images/remote-branches-5.png width=800 height=577 alt='帶有3 個大方框的圖表,代表機器或儲存庫,包含代表提交歷史的較小框和箭頭;一個框標記為“git.outcompany.com”,子標記為“origin”,並包含名為“master”的分支中的提交;另一個框標記為“git.team1.outcompany.com”,子標記為“teamone”,並包含名為“master”的分支中的提交; 「origin」和「teamone」中的提交 SHA1 雜湊值相同,除了「origin」在其「master」分支上多了一個提交,即「teamone」在「後面」;第三個框標記為“我的電腦”,它包含與其他兩個框相同的提交,但這次分支被命名為“origin/master”和“teamone/master”;它還在名為“master”的分支中包含另外兩個提交,與遠端分支的較早點不同。'> <figcaption>遠端和遠端追蹤分支(來源:<a href=https://git-scm.com/book/en/v2/Git-Branching-Remote-Branches><cite>Pro Git</cite>< / a>)</figcaption> </圖> 然後,您將使用與分支相關的命令來獲取從_遠端追蹤分支_到本地分支的更改(“git merge”或“git rebase”),或“git pull”,這只不過是“git fetch”的簡寫` 後面跟著 `git merge` 或 `git rebase`。 <abbr title="By the way">順便說一句</abbr>,在很多情況下,當你建立本地分支時,Git 會自動將_遠端追蹤分支_設定為本地分支的_上游_(它會告訴你相關資訊)當這種情況發生時)。 ### 推 要與其他人共用您的更改,他們可以將您的儲存庫新增為遠端儲存庫並從中_pull_(意味著透過網路存取您的電腦),或者您可以_push_到遠端儲存庫。 (如果您要求某人從您的遙控器中提取更改,這稱為..._拉請求_,您可能在 GitHub 或類似服務中聽說過這個術語。) 推送與提取類似,相反:您將提交發送到遠端並更新其分支以指向新提交。作為安全措施,Git 只允許遠端分支_快速轉送_;如果您想推送以非快轉方式更新遠端分支的更改,則必須使用「git push --force-with-lease」(或「git push --force」)_force_它,但要小心:`-- force-with-lease`將首先確保您的_遠端追蹤分支_與遠端分支是最新的,以確保自上次_fetched_以來沒有人將變更推送到分支;` --force` 不會執行該檢查,而是按照您的指示執行操作,風險由您自己承擔)。 與「git fetch」一樣,您可以將要更新的分支傳遞給「git push」命令,但如果您不這樣做,Git 會提供良好的預設行為。如果你不指定任何東西,Git 會從目前分支的上游推斷遠程,所以大多數時候 `git push` 相當於 `git push origin`。這實際上是“git Push origin main”的簡寫(假設當前分支是“main”),它本身是“git Push origin main:main”的簡寫,是“git Push origin refs/heads/main:refs/”的簡寫heads/main`,意思是將本地的`refs/heads/main`推送到`origin`遠端的`refs/heads/main`。有關使用不同來源和目標指定 _refspecs_ 的一些用例,請參閱[我之前的文章](https://blog.ltgt.net/confusing-git-terminology/#refspecs)。 <圖> <img src=https://lurklurk.org/gitpix/push2.svg width=1052 height=744 alt='代表「git push」指令的圖表,有四個 git 圖表(點,有些有標籤,用線連接) 排列成兩行兩列;列之間的箭頭表示左列是「之前」狀態,右列是「之後」狀態;上面一行中的圖位於雲內部,代表遠端儲存庫,並且有兩個分支,“master”和“other”,它們偏離了共同的祖先;左下圖與上面的圖形狀相同,只是標籤更改為“origin/master”和“origin/other”,並且每個分支有更多提交:與“origin”分支相比,“master”分支有兩個額外的提交/master”,而“other”比“origin/other”多了一個提交;與左上圖相比,右上圖在其「master」分支中多了兩次提交;右下圖與左下圖相同,除了「origin/master」現在指向與「master」相同的提交;換句話說,在「之前」狀態下,遠端缺少三個提交,而在「git Push」之後,本地「master」分支的兩個提交被複製到遠端,而「其他」保持不變。'> <figcaption><code>git Push</code>(資料來源:<a href=https://lurklurk.org/gitpix/gitpix.html><cite>Git 視覺參考</cite></a>,David Drysdale )</圖標題> </圖> 更多詳細資訊和視覺表示,請參閱[<cite>遠端分支</cite>](https://git-scm.com/book/en/v2/Git-Branching-Remote-Branches),[< cite >使用遙控器</cite>](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes),以及[<cite>為專案做出貢獻</ cite> ](https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project) <cite>Pro Git</cite> 書的子章節,以及「處理遠程來自David Drysdale 的[<cite>Git Visual Reference</cite>](https://lurklurk.org/gitpix/gitpix.html) 的「儲存庫」圖表。 <cite>Pro Git</cite> 的<cite>為專案做出貢獻</cite>一章也涉及在GitHub 等平台上為開源專案做出貢獻,您必須先_fork_儲存庫,然後透過_pull requests_進行貢獻(或_合併請求_)。 ## 最佳實踐 這些是針對初學者的,希望不會引起太多爭議。 嘗試保留_clean_歷史記錄: * 明智地使用合併提交 * 清晰且高品質的提交訊息(請參閱[<cite>提交指南</cite>](https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project #_commit_guidelines)在<cite>Pro Git</cite> 中) * make _atomic_ commits:每個提交應該獨立於歷史記錄中跟隨它的提交進行編譯和執行 這僅適用於您與他人分享的歷史記錄。 在本地,想怎麼做就怎麼做。對於初學者,我會給以下建議: * 不要直接在“main”(或“master”,或您在遠端上沒有專門擁有的任何分支)上工作,而是建立本機分支;它有助於解耦不同任務的工作:即將開始處理另一個錯誤或功能,同時等待有關當前任務的說明的更多詳細資訊?切換到另一個分支,稍後您可以透過切換回來回到該分支;它還使從遠端更新變得更容易,因為如果您的本地分支只是同名遠端分支的副本,沒有任何本地更改(除非您想推送這些更改),您確信不會發生衝突到該分支) * 毫不猶豫地重寫你的提交歷史記錄(`git commit --amend` 和/或 `git rebase -i`),但不要太早這樣做;在工作時堆疊許多小提交是完全可以的,並且只在共享之前重寫/清理歷史記錄 * 同樣,請毫不猶豫地重新調整本機分支以整合上游變更(直到您共用該分支,此時您將遵循專案的分支工作流程) 如果出現任何問題並且您迷路了,我的建議是使用 `gitk` 或 `gitk HEAD @{1}`,也可能使用 `gitk --all` (我在這裡使用 `gitk` 但使用任何工具你喜歡),可視化你的Git 歷史並嘗試了解發生了什麼。由此,您可以回滾到先前的狀態(`git reset @{1}`)或嘗試修復問題(擇優選擇提交等)。合併失敗,您可以使用“git rebase --abort”或“git merge - -abort」等命令中止並回滾到先前的狀態。 為了讓事情變得更簡單,請不要猶豫,在任何可能具有破壞性的命令(`git rebase`)之前,建立一個分支或標籤作為“書籤”,如果事情沒有按預期進行,您可以輕鬆重置。當然,在執行這樣的命令後,請檢查歷史記錄和文件,以確保結果是您所期望的。 ## 進階概念 這只是其中的一小部分,還有更多值得探索! * 分離的「HEAD」:[`git checkout` 手冊頁](https://git-scm.com/docs/git-checkout#_detached_head) 有一個關於該主題的很好的部分,另請參閱[我之前的帖子](https ://blog.ltgt.net/confusing-git-terminology/#detached-head-state),要獲得良好的視覺表示,請參閱[<cite>使用分離的HEAD 進行提交</ cite>](https:// /marklodato.github.io/visual-git-guide/index-en.html#detached) Mark Lodato 的 <cite>視覺化 Git 參考</cite> 部分。 * Hooks:這些是可執行檔(大多數情況下是 shell 腳本),Git 將執行它們來回應儲存庫上的操作;人們使用它們在每次提交之前檢查程式碼(如果失敗則中止提交),產生或後處理提交訊息,或在有人推送到儲存庫後觸發伺服器上的操作(觸發建置和/或部署)。 * 一些很少需要的命令可以在您真正需要時節省您的時間: * `git bisect`:一個進階命令,透過測試多個提交(手動或透過腳本)來幫助您找出哪個提交引入了錯誤;對於線性歷史,這是使用二分法並且可以手動完成,但是一旦您有許多合併提交,這就會變得更加複雜,並且最好讓 git bisect 來完成繁重的工作。 * `git filter-repo`:實際上是一個[第三方命令](https://github.com/newren/git-filter-repo),作為Git 自己的`filter-branch` 的替代品,它允許重寫儲存庫的整個歷史記錄,以刪除錯誤新增的文件,或協助將儲存庫的一部分提取到另一個儲存庫。 我們完成了。 有了這些知識,人們應該能夠將任何 Git 命令映射到如何修改提交的_有向無環圖_,並了解如何修復錯誤(在錯誤的分支上執行合併?基於錯誤的分支重新建置?)並不是說理解這些事情會很容易,但至少應該是可能的。 --- 原文出處:https://dev.to/tbroyer/how-i-teach-git-3nj3

🧙‍♂️ CSS 技巧:從 height: 0 變成 height: auto!

如果你使用 CSS 時間夠長,那麼你很可能至少嘗試過一次從 `height: 0` 到 `auto` 的**轉換**...結果卻發現它**確實如此。行不通!** 😢 ➡️幸運的是,今天這個問題實際上有一個**解決方案**:它在底層使用**CSS Grid**,而且它**如此簡單**並且**完美執行!** 讓我們從一個實際的例子開始。我建立了這個簡單的**手風琴**: https://codepen.io/francescovetere/pen/MWLXjNx 它的 **HTML** 非常簡單: ``` <div class="accordion"> <div class="accordion-title">Hover me!</div> <div class="accordion-body"> <div> <p>Lorem ipsum ...</p> </div> </div> </div> ``` 如果您將滑鼠**懸停在手風琴上,您會注意到出現**下拉選單**。這很酷,但是如果我們想讓它看起來有一個很好的**平滑過渡怎麼辦?** **我實際上嘗試**在之前的程式碼筆中通過在“height”屬性上加入一些過渡來做到這一點: ``` .accordion-body { height: 0; transition: 500ms height ease; } .accordion:hover .accordion-body { height: auto; } ``` ❌不幸的是,**這不起作用**:從 `height: 0` 轉換到 `height: auto`,正如我之前所說,** CSS 是不可能的。** 🤔 如何解決這個問題? 好吧,**第一個解決方案**可以將 `height` 屬性設為 **固定數字**,而不是 `auto`。 這是可行的,但它**不是一個很好的方法**:為了計算這個固定數字,我們必須求助於**JavaScript**,以便計算我們的“.accordion-body”實際上是多少高. .. **不是我們真正的目標!** 😕 我們仍然可以實現這種效果,但是使用 **僅 CSS** 解決方案嗎? 💡 **實際上,是的!** 為什麼我們不直接使用 `max-height` 來代替呢? ``` .accordion-body { max-height: 0; transition: 500ms max-height ease; } .accordion:hover .accordion-body { max-height: 200px; } ``` 這將是結果: https://codepen.io/francescovetere/pen/ExrRNVa 由於我們為「max-height」定義了一個**固定值**,瀏覽器現在能夠**正確地執行轉換**。 😕 **唯一的問題**是,由於我們為 `max-height` 定義了一個固定值,現在內容可能 **可能溢出**: https://codepen.io/francescovetere/pen/VwgdmbW 如果您確定您的內容永遠不會達到特定高度...那麼使用此方法就完全沒問題!** 只需為“max-height”使用適當的值,然後您就可以了很好,可以走了。 **但請注意**,“max-height”的**值越高,**過渡變得越奇怪**(嘗試在之前的程式碼筆中加入“max-height: 1000px”,然後查看事情發生了怎樣的變化!)。 🤔 **我們可以做得更好嗎?** 我們可以先避免任何固定的 `height`/`max-height` 嗎? 🎉 **CSS 格來拯救!** ✅ 我們實際上可以使用一個**巧妙的技巧**,它基本上包括使用**單一網格專案**製作**CSS網格**。 那麼我們真正要做的就是採用「grid-template-rows」並使其從「0fr」過渡到「1fr」:這樣,我們的網格項將從 0 過渡到「自然」高度。就是這麼簡單: ``` .accordion-body { display: grid; grid-template-rows: 0fr; transition: 250ms grid-template-rows ease; } .accordion:hover .accordion-body { grid-template-rows: 1fr; } .accordion-body > div { overflow: hidden; } ``` 這感覺**乾淨多了。** 沒有固定的高度,沒有花哨的東西,只是我們的手風琴按預期工作。 **太棒了!** 😄 https://codepen.io/francescovetere/pen/BaGwBRx 此解決方案的**一個警告**是,您實際上需要為“.accordion-body”的內部“div”設定“overflow:hidden”才能使其工作。在我看來,這個額外的 CSS 是完全值得的,但請在評論中告訴我你的想法! 🎁 **額外小提示** 這個技巧之所以有效,是因為「網格模板行」(以及更一般地說,「網格軌道」)的「動畫性」。 對於某些瀏覽器來說,這是一個**新功能**:如果您存取[此頁面](https://caniuse.com/mdn-css_properties_grid-template-rows_animation),您會注意到**網格追蹤可動畫性**例如,僅從 **版本 107 開始登陸 **Chrome**。 在我撰寫本文時,所有主要瀏覽器都支援此功能,但如果您想在生產程式碼中使用此功能,請務必首先**檢查相容性**! --- 就這樣!如果您已經了解這個很棒的 CSS 功能,請隨時發表評論並告訴我! 😉 下次見! 👋 --- 原文出處:https://dev.to/francescovetere/css-trick-transition-from-height-0-to-auto-21de

大資料模型 📊 與電腦記憶體 💾

資料管道是任何資料密集型專案的支柱。 **隨著資料集的成長**超出記憶體大小(「核心外」),**有效處理它們變得具有挑戰性**。 Dask 可以輕鬆管理大型資料集(核心外),提供與 Numpy 和 Pandas 的良好相容性。 ![管道](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m6nswebbzlo96ml1ofeb.png) --- 本文重點介紹 **Dask(用於處理核心外資料)與 Taipy** 的無縫集成,Taipy** 是一個用於 **管道編排和場景管理** 的 Python 庫。 --- ## Taipy - 您的 Web 應用程式建構器 關於我們的一些資訊。 **Taipy** 是一個開源程式庫,旨在輕鬆開發前端 (GUI) 和 ML/資料管道。 不需要其他知識(沒有 CSS,什麼都不需要!)。 它旨在加快應用程式開發,從最初的原型到生產就緒的應用程式。 ![QueenB 星星](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bvt5qn1yadra3epnb07v.gif) https://github.com/Avaiga/taipy 我們已經快有 1000 顆星了,沒有你就無法做到這一點🙏 --- ## 1. 範例應用程式 透過範例最好地演示了 Dask 和 Taipy 的整合。在本文中,我們將考慮包含 4 個任務的資料工作流程: - **資料預處理與客戶評分** 使用 Dask 讀取和處理大型資料集。 - **特徵工程和分割** 根據購買行為對客戶進行評分。 - **細分分析** 根據這些分數和其他因素將客戶分為不同的類別。 - **高價值客戶的總統計** 分析每個客戶群以獲得見解 我們將更詳細地探討這 4 個任務的程式碼。 請注意,此程式碼是您的 Python 程式碼,並未使用 Taipy。 在後面的部分中,我們將展示如何使用 Taipy 對現有資料應用程式進行建模,並輕鬆獲得其工作流程編排的好處。 --- 該應用程式將包含以下 5 個檔案: ``` algos/ ├─ algo.py # Our existing code with 4 tasks data/ ├─ SMALL_amazon_customers_data.csv # A sample dataset app.ipynb # Jupyter Notebook for running our sample data application config.py # Taipy configuration which models our data workflow config.toml # (Optional) Taipy configuration in TOML made using Taipy Studio ``` --- ## 2. Taipy 簡介 - 綜合解決方案 [Taipy](https://docs.taipy.io/) **不只是另一個編排工具**。 Taipy 專為 ML 工程師、資料科學家和 Python 開發人員設計,帶來了幾個基本且簡單的功能。 以下是**一些關鍵要素**,使 Taipy 成為令人信服的選擇: 1. **管道執行註冊表** 此功能使開發人員和最終用戶能夠: - 將每個管道執行註冊為「*場景*」(任務和資料節點圖); - 精確追蹤每個管道執行的沿襲;和 - 輕鬆比較場景、監控 KPI 並為故障排除和微調參數提供寶貴的見解。 2. **管道版本控制** Taipy 強大的場景管理使您能夠輕鬆調整管道以適應不斷變化的專案需求。 3. **智能任務編排** Taipy 讓開發人員可以輕鬆地對任務和資料來源網路進行建模。 此功能透過以下方式提供對任務執行的內建控制: - 並行執行您的任務;和 - 任務“跳過”,即選擇要執行的任務並 要繞過哪個。 4. **任務編排的模組化方法** 模組化不僅僅是 Taipy 的一個流行詞;這是一個核心原則。 設定可以互換使用的任務和資料來源,從而產生更乾淨、更易於維護的程式碼庫。 --- ## 3. Dask 簡介 Dask 是一個流行的分散式運算 Python 套件。 Dask API 實作了熟悉的 Pandas、Numpy 和 Scikit-learn API - ,這使得許多已經熟悉這些 API 的資料科學家更愉快地學習和使用 Dask。 如果您是 Dask 新手,請查看 Dask 團隊撰寫的精彩 Dask [10 分鐘簡介](https://docs.dask.org/en/stable/10-minutes-to-dask.html)。 --- ## 4. 應用:顧客分析 (*algos/algo.py*) ![DAG 架構](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9ru69b6jmhl73s9xxx2n.png) *我們的 4 項任務的圖表(在 Taipy 中可視化),我們將在下一節中對其進行建模。* 我們現有的程式碼(不含 Taipy)包含 4 個函數,您也可以在上圖中看到: - 任務 1:*預處理和評分* - 任務 2:*特徵化與細分* - 任務 3:*分段分析* - 任務 4:*high_value_cust_summary_statistics* 您可以瀏覽以下定義了 4 個函數的 *algos/algo.py* 腳本,然後繼續閱讀每個函數的簡要說明: ``` ### algos/algo.py import time import dask.dataframe as dd import pandas as pd def preprocess_and_score(path_to_original_data: str): print("__________________________________________________________") print("1. TASK 1: DATA PREPROCESSING AND CUSTOMER SCORING ...") start_time = time.perf_counter() # Start the timer # Step 1: Read data using Dask df = dd.read_csv(path_to_original_data) # Step 2: Simplify the customer scoring formula df["CUSTOMER_SCORE"] = ( 0.5 * df["TotalPurchaseAmount"] / 1000 + 0.3 * df["NumberOfPurchases"] / 10 + 0.2 * df["AverageReviewScore"] ) # Save all customers to a new CSV file scored_df = df[["CUSTOMER_SCORE", "TotalPurchaseAmount", "NumberOfPurchases", "TotalPurchaseTime"]] pd_df = scored_df.compute() end_time = time.perf_counter() # Stop the timer execution_time = (end_time - start_time) * 1000 # Calculate the time in milliseconds print(f"Time of Execution: {execution_time:.4f} ms") return pd_df def featurization_and_segmentation(scored_df, payment_threshold, score_threshold): print("__________________________________________________________") print("2. TASK 2: FEATURE ENGINEERING AND SEGMENTATION ...") # payment_threshold, score_threshold = float(payment_threshold), float(score_threshold) start_time = time.perf_counter() # Start the timer df = scored_df # Feature: Indicator if customer's total purchase is above the payment threshold df["HighSpender"] = (df["TotalPurchaseAmount"] > payment_threshold).astype(int) # Feature: Average time between purchases df["AverageTimeBetweenPurchases"] = df["TotalPurchaseTime"] / df["NumberOfPurchases"] # Additional computationally intensive features df["Interaction1"] = df["TotalPurchaseAmount"] * df["NumberOfPurchases"] df["Interaction2"] = df["TotalPurchaseTime"] * df["CUSTOMER_SCORE"] df["PolynomialFeature"] = df["TotalPurchaseAmount"] ** 2 # Segment customers based on the score_threshold df["ValueSegment"] = ["High Value" if score > score_threshold else "Low Value" for score in df["CUSTOMER_SCORE"]] end_time = time.perf_counter() # Stop the timer execution_time = (end_time - start_time) * 1000 # Calculate the time in milliseconds print(f"Time of Execution: {execution_time:.4f} ms") return df def segment_analysis(df: pd.DataFrame, metric): print("__________________________________________________________") print("3. TASK 3: SEGMENT ANALYSIS ...") start_time = time.perf_counter() # Start the timer # Detailed analysis for each segment: mean/median of various metrics segment_analysis = ( df.groupby("ValueSegment") .agg( { "CUSTOMER_SCORE": metric, "TotalPurchaseAmount": metric, "NumberOfPurchases": metric, "TotalPurchaseTime": metric, "HighSpender": "sum", # Total number of high spenders in each segment "AverageTimeBetweenPurchases": metric, } ) .reset_index() ) end_time = time.perf_counter() # Stop the timer execution_time = (end_time - start_time) * 1000 # Calculate the time in milliseconds print(f"Time of Execution: {execution_time:.4f} ms") return segment_analysis def high_value_cust_summary_statistics(df: pd.DataFrame, segment_analysis: pd.DataFrame, summary_statistic_type: str): print("__________________________________________________________") print("4. TASK 4: ADDITIONAL ANALYSIS BASED ON SEGMENT ANALYSIS ...") start_time = time.perf_counter() # Start the timer # Filter out the High Value customers high_value_customers = df[df["ValueSegment"] == "High Value"] # Use summary_statistic_type to calculate different types of summary statistics if summary_statistic_type == "mean": average_purchase_high_value = high_value_customers["TotalPurchaseAmount"].mean() elif summary_statistic_type == "median": average_purchase_high_value = high_value_customers["TotalPurchaseAmount"].median() elif summary_statistic_type == "max": average_purchase_high_value = high_value_customers["TotalPurchaseAmount"].max() elif summary_statistic_type == "min": average_purchase_high_value = high_value_customers["TotalPurchaseAmount"].min() median_score_high_value = high_value_customers["CUSTOMER_SCORE"].median() # Fetch the summary statistic for 'TotalPurchaseAmount' for High Value customers from segment_analysis segment_statistic_high_value = segment_analysis.loc[ segment_analysis["ValueSegment"] == "High Value", "TotalPurchaseAmount" ].values[0] # Create a DataFrame to hold the results result_df = pd.DataFrame( { "SummaryStatisticType": [summary_statistic_type], "AveragePurchaseHighValue": [average_purchase_high_value], "MedianScoreHighValue": [median_score_high_value], "SegmentAnalysisHighValue": [segment_statistic_high_value], } ) end_time = time.perf_counter() # Stop the timer execution_time = (end_time - start_time) * 1000 # Calculate the time in milliseconds print(f"Time of Execution: {execution_time:.4f} ms") return result_df ``` --- ### 任務 1 - 資料預處理與客戶評分 Python 函數:*preprocess_and_score* 這是管道中的第一步,也許也是最關鍵的一步。 它使用 **Dask** 讀取大型資料集,專為大於記憶體的計算而設計。 然後,它根據“*TotalPurchaseAmount*”、“*NumberOfPurchases*”和“*AverageReviewScore*”等各種指標,在名為 *scored_df* 的 DataFrame 中計算“*Customer Score*”。 使用 Dask 讀取和處理資料集後,此任務將輸出一個 Pandas DataFrame,以供其餘 3 個任務進一步使用。 --- ### 任務 2 - 特徵工程與分割 Python 函數:*featureization_and_segmentation* 此任務採用評分的 DataFrame 並新增功能,例如高支出指標。 它還根據客戶的分數對客戶進行細分。 --- ### 任務 3 - 細分分析 Python 函數:*segment_analysis* 此任務採用分段的 DataFrame 並根據客戶細分執行分組分析以計算各種指標。 --- ### 任務 4 - 高價值客戶的總統計 Python 函數:*high_value_cust_summary_statistics* 此任務對高價值客戶群進行深入分析並傳回匯總統計資料。 --- ## 5. 在 Taipy 中建模工作流程 (*config.py*) ![工作室中的 DAG](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5kyz7k3akkcbs48psodi.png) *Taipy DAG — Taipy「任務」為橘色,「資料節點」為藍色。* 在本節中,我們將建立對變數/參數進行建模的Taipy 配置(表示為[“資料節點”](https://docs.taipy.io/en/latest/manuals/core/concepts/data-node/ ))和 Taipy 中的函數(表示為 [“Tasks”](https://docs.taipy.io/en/latest/manuals/core/concepts/task/))。 --- 請注意,以下 *config.py* 腳本中的此配置類似於定義變數和函數 - 只不過我們定義的是「藍圖變數」(資料節點)和「藍圖函數」(任務)。 我們通知 Taipy 如何呼叫我們之前定義的函數、資料節點的預設值(我們可能會在執行時覆蓋)以及是否可以跳過任務: ``` ### config.py from taipy import Config from algos.algo import ( preprocess_and_score, featurization_and_segmentation, segment_analysis, high_value_cust_summary_statistics, ) # -------------------- Data Nodes -------------------- path_to_data_cfg = Config.configure_data_node(id="path_to_data", default_data="data/customers_data.csv") scored_df_cfg = Config.configure_data_node(id="scored_df") payment_threshold_cfg = Config.configure_data_node(id="payment_threshold", default_data=1000) score_threshold_cfg = Config.configure_data_node(id="score_threshold", default_data=1.5) segmented_customer_df_cfg = Config.configure_data_node(id="segmented_customer_df") metric_cfg = Config.configure_data_node(id="metric", default_data="mean") segment_result_cfg = Config.configure_data_node(id="segment_result") summary_statistic_type_cfg = Config.configure_data_node(id="summary_statistic_type", default_data="median") high_value_summary_df_cfg = Config.configure_data_node(id="high_value_summary_df") # -------------------- Tasks -------------------- preprocess_and_score_task_cfg = Config.configure_task( id="preprocess_and_score", function=preprocess_and_score, skippable=True, input=[path_to_data_cfg], output=[scored_df_cfg], ) featurization_and_segmentation_task_cfg = Config.configure_task( id="featurization_and_segmentation", function=featurization_and_segmentation, skippable=True, input=[scored_df_cfg, payment_threshold_cfg, score_threshold_cfg], output=[segmented_customer_df_cfg], ) segment_analysis_task_cfg = Config.configure_task( id="segment_analysis", function=segment_analysis, skippable=True, input=[segmented_customer_df_cfg, metric_cfg], output=[segment_result_cfg], ) high_value_cust_summary_statistics_task_cfg = Config.configure_task( id="high_value_cust_summary_statistics", function=high_value_cust_summary_statistics, skippable=True, input=[segment_result_cfg, segmented_customer_df_cfg, summary_statistic_type_cfg], output=[high_value_summary_df_cfg], ) scenario_cfg = Config.configure_scenario( id="scenario_1", task_configs=[ preprocess_and_score_task_cfg, featurization_and_segmentation_task_cfg, segment_analysis_task_cfg, high_value_cust_summary_statistics_task_cfg, ], ) ``` 號 您可以在[此處的文件](https://docs.taipy.io/en/latest/manuals/core/config/)中閱讀有關配置場景、任務和資料節點的更多資訊。 --- ### Taipy Studio [Taipy Studio](https://docs.taipy.io/en/latest/manuals/studio/config/) **是來自Taipy 的VS Code 擴充功能**,讓您**透過簡單的方式建置和視覺化您的管道拖放互動**。 Taipy Studio 提供了一個圖形編輯器,您可以在其中建立 Taipy 配置**存儲在 TOML 文件中**,您的 Taipy 應用程式可以加載並執行這些配置。 編輯器將場景表示為圖形,其中節點是資料節點和任務。 --- *作為本節中 config.py 腳本的替代方案,您可以使用 Taipy Studio 產生 config.toml 設定檔。 本文的倒數第二部分將提供有關如何使用 Taipy Studio 建立 config.toml 設定檔的指南。* --- ## 6. 場景建立與執行 執行 Taipy 場景涉及: - 載入配置; - 執行 Taipy Core 服務;和 - 建立並提交場景以供執行。 這是基本的程式碼模板: ``` import taipy as tp from config import scenario_cfg # Import the Scenario configuration tp.Core().run() # Start the Core service scenario_1 = tp.create_scenario(scenario_cfg) # Create a Scenario instance scenario_1.submit() # Submit the Scenario for execution # Total runtime: 74.49s ``` --- ### 跳過不必要的任務執行 Taipy 最實用的功能之一是,如果任務的輸出已經計算出來,它能夠跳過任務執行。 讓我們透過一些場景來探討這一點: --- #### 更改付款閾值 ``` # Changing Payment Threshold to 1600 scenario_1.payment_threshold.write(1600) scenario_1.submit() # Total runtime: 31.499s ``` *發生了什麼事*:Taipy 夠聰明,可以跳過任務 1,因為付款閾值只影響任務 2。 在這種情況下,透過使用 Taipy 執行管道,我們發現執行時間減少了 50% 以上。 --- #### 更改細分分析指標 ``` # Changing metric to median scenario_1.metric.write("median") scenario_1.submit() # Total runtime: 23.839s ``` *會發生什麼事*:在這種情況下,只有任務 3 和任務 4 受到影響。 Taipy 巧妙地跳過任務 1 和任務 2。 --- #### 更改總計統計類型 ``` # Changing summary_statistic_type to max scenario_1.summary_statistic_type.write("max") scenario_1.submit() # Total runtime: 5.084s ``` *發生了什麼事*:這裡,只有任務 4 受到影響,Taipy 僅執行此任務,跳過其餘任務。 Taipy 的智慧任務跳過功能不僅能節省時間,還能節省時間。它是一個資源優化器,在處理大型資料集時變得非常有用。 --- ## 7. Taipy Studio 您可以使用 Taipy Studio 建置 Taipy *config.toml* 設定檔來取代定義 *config.py* 腳本。 ![Studio 內的 DAG](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ct0bcisreqmg56mk4fgm.png) 首先,使用擴展市場安裝 [Taipy Studio ](https://marketplace.visualstudio.com/items?itemName=Taipy.taipy-studio)擴充。 --- ### 建立配置 - **建立設定檔**:在 VS Code 中,導覽至 Taipy Studio,然後透過點擊參數視窗上的 + 按鈕啟動新的 TOML 設定檔。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8jqe1fq87jaauf56b7hg.png) - 然後右鍵單擊它並選擇 **Taipy:顯示視圖**。 ![配置顯示視圖](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v7rkyipli0oq13iw8mxc.png) - **新增實體**到您的 Taipy 配置: 在 Taipy Studio 的右側,您應該會看到一個包含 3 個圖示的列表,可用於設定管道。 ![配置圖示](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tyxvv15nu9xr87n5y7q1.png) 1. 第一項是新增資料節點。您可以將任何 Python 物件連結到 Taipy 的資料節點。 2. 第二項用於新增任務。任務可以連結到預先定義的 Python 函數。 3. 第三項是新增場景。 Taipy 讓您在一個配置中擁有多個場景。 --- #### - 資料節點 **輸入資料節點**:建立一個名為“*path_to_data*”的資料節點,然後導航到“詳細資料”選項卡,新增屬性“*default_data*”,並將“*SMALL_amazon_customers_data.csv*”貼上為您的資料的路徑資料集。 --- **中間資料節點**:我們需要再增加四個資料節點:「*scored_df*」、「*segmented_customer_df*」、「*segment_result*」、「*high_value_summary_df*」。透過 Taipy 的智慧設計,您無需為這些中間資料節點進行任何配置;系統會巧妙地處理它們。 --- **具有預設值的中間資料節點**:我們最終定義了另外四個中間資料節點,並將「*default_data*」屬性設為以下內容: - payment_threshold: “1000:int” ![資料節點檢視](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/odkrz0pq2dhqpm0gnta2.png) - 分數閾值:“1.5:浮動” - 測量:“平均值” -summary_statistic_type:“中位數” --- #### - 任務 點擊新增任務按鈕,您可以配置新任務。 新增四個任務,然後**將每個任務連結到「詳細資料」標籤下的對應函數**。 Taipy Studio 將掃描您的專案資料夾並提供可供選擇的分類函數列表,並按 Python 檔案排序。 --- **任務 1** (*preprocess_and_score*):在 Taipy studio 中,您可以按一下「任務」圖示以新增任務。 您可以將輸入指定為“*path_to_data*”,將輸出指定為“*scored_df*”。 然後,在「詳細資料」標籤下,您可以將此任務連結到 *algos.algo.preprocess_and_score* 函數。 ![任務流程及評分](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wnc57wbxafjh2s3m6fat.png) --- **任務 2** (*featurization_and_segmentation*):與任務 1 類似,您需要指定輸入 (“*scored_df*”、“* payment_threshold*”、“*score_threshold*”) 和輸出 (“*segmented_customer_df*”) ” )。將此任務連結到 *algos.algo.featurization_and_segmentation* 函數。 ![任務特徵化](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mbtm200u9meq1x1rcy2w.png) --- **任務 3** (*segment_analysis*):輸入為“*segmented_customer_df*”和“*metric*”,輸出為“*segment_result*”。 連結到 *algos.algo.segment_analysis* 函數。 ![任務片段分析](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wnnl1w1q0blebzbyawvt.png) --- **任務 4** (high_value_cust_summary_statistics):輸入包含「*segment_result*」、「*segmented_customer_df*」和「*summary_statistic_type*」。輸出為“*high_value_summary_df*”。連結到 *algos.algo.high_value_cust_summary_statistics* 函數。 ![任務統計](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tynu6e718z1dwf8id05m.png) --- ## 結論 Taipy 提供了一種**智慧方式來建立和管理資料管道**。 特別是可跳過的功能使其成為優化運算資源和時間的強大工具,在涉及大型資料集的場景中特別有用。 Dask 提供了資料操作的原始能力,而 Taipy 增加了一層智能,使您的管道不僅強大而且智能。 --- 其他資源 如需完整程式碼和 TOML 配置,您可以存取此 [GitHub 儲存庫](https://github.com/Avaiga/demo-dask-customer-analysis/tree/develop)。若要深入了解 Taipy,請參閱[官方文件](https://docs.taipy.io/en/latest/)。 一旦您了解 Taipy 場景管理,您就可以更有效率地為最終用戶建立資料驅動的應用程式。只需專注於您的演算法,Taipy 就會處理剩下的事情。 --- ![很多](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ua3x4t3yttba6g25jjqo.gif) 希望您喜歡這篇文章! --- 原文出處:https://dev.to/taipy/big-data-models-vs-computer-memory-4po6

使用 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