2025年12月14日 星期日

Github Copilot 1 : 透過ChatGPT將畫面直接產生出 Blazor 頁面

Github Copilot 1 : 透過ChatGPT將畫面直接產生出 Blazor 頁面

前言

最近這段時間,都在從事於 FHIR & AI 相關的醫療專案開發,在這一年中,透過AI的輔助,讓許多系統的開發可以順利、快速、有效地進行,而在這些AI工具中,Github Copilot 無疑是我最常使用的工具之一。而面對身旁的同事們,常常會問到:「你是怎麼使用 Github Copilot 來加速開發的?」、「Github Copilot 究竟能幫助我們多少?」等問題。為了讓大家更了解這個工具的強大功能,所以,我將會透過一系列的文章,逐步來分享我在這些應用的經驗。這篇文章將分享我如何利用 Github Copilot 來快速產生 Blazor 頁面。

首先,為了要能夠快速協助醫生將其需求,在最短的時間內,採用 MVP 的方式開發出來,透過不斷的迭帶,來讓醫生能夠在最短的時間內,看到系統的雛形,並且能夠快速地進行調整與修改。

所以,一開始在雙方進行需求溝通的過程中,將採用另外一種模式,讓醫師使用簡報檔案,來描述其需求畫面,在簡報畫面中,加入相關註解與說明,而且版面配置也請醫師儘可能的透過簡報來呈現出來。

依據以往的開發方式,將會需要採用類似 Figma 這類工具,並且將這些簡報檔案,設計出來,並且進行切板與接下來轉換成 Blazor 頁面。如此,這樣又多出了一個過程,衍伸出來的也包含了討論與修正的時間,而且,對於網頁設計師而言,收到這些視覺設計稿之後,還需要進行切版,並且轉換成 Blazor 頁面,這些都會增加開發的時間。

以往,我可能會將該畫面貼到 ChatGPT 這類工具,並且請它協助我產生出 HTML 與 CSS 的程式碼,不過,這樣的方式,需要多次的透過剪貼簿來進行將內容轉移。這樣的操作體驗似乎也是不太方便與流暢。

在當時我對於 Github Copilot 這樣的工具也不太孰悉,不過,當下我便想到,或許可以嘗試看看,是否能夠直接將這些簡報畫面,貼到 Visual Studio Code 的編輯器中,並且請 Github Copilot 協助我來產生出 Blazor 頁面的程式碼。因此,立馬進行與驗證這樣的想法。

底下為 AI 臨床試驗平台的儀表板畫面,這是醫師直接使用 PowerPoint 繪製出來的畫面。

首先,我這裡將這個畫面貼到 OpenAI 的 ChatGPT 網頁中,並且請它協助我產生出 Blazor 頁面的程式碼,底下為我給 ChatGPT 的指令內容:

因為這個時間點,GPT的最新版本為 5.1 版本,當時僅有 4.1 版,因此,為了要能夠重現當時狀況,這裡還是採用 4.1 的版本

將這個畫面轉成 html & css

建立 Blazor 專案

  • 這裡建立一個 Blazor Server 專案
  • 在 [Components] > [Pages] 目錄下,建立一個 [Sample1.razor] & [Sample1.razor.css] 檔案 
  • 將產生出來的程式碼,貼到 Sample1.razor 頁面中。
  • 這是完成的程式碼
@page "/Sample1"

    <style>
        body {
          background: #ececec;
          margin: 0;
          font-family: "Segoe UI", Arial, "Microsoft JhengHei", "微軟正黑體", sans-serif;
        }
        .container {
          margin: 20px auto;
          padding: 24px;
          background: #fff;
          border-radius: 10px;
          max-width: 1150px;
          box-shadow: 0 0 10px #bbb;
        }
        .title {
          font-size: 2em;
          font-weight: 600;
          letter-spacing: 2px;
          margin-bottom: 18px;
        }
        .title span {
          font-weight: 900;
          font-size: 1.2em;
          letter-spacing: 4px;
        }
        .flex-row {
          display: flex;
          gap: 16px;
        }
        .section {
          border: 2px solid #d2d2d2;
          border-radius: 8px;
          padding: 10px 12px 16px 12px;
          background: #f6f8fa;
          flex: 1;
          min-width: 340px;
          margin-bottom: 16px;
        }
        .subject-no {
          background: #fff;
          border: 2px solid #d2d2d2;
          border-radius: 8px;
          margin-bottom: 8px;
          padding: 6px 12px;
          font-size: 1.15em;
          font-weight: 600;
          color: #1a1a1a;
          letter-spacing: 1px;
        }
        .subject-no .highlight {
          color: #e6001f;
          font-weight: 900;
          margin-left: 10px;
          letter-spacing: 2px;
        }
        .sub-label {
          font-size: 0.95em;
          color: #428bca;
          font-weight: 400;
          margin-left: 7px;
          letter-spacing: 1px;
        }
        .table-block {
          width: 100%;
          border-collapse: collapse;
          margin-top: 6px;
        }
        .table-block th,
        .table-block td {
          border: 1px solid #bbb;
          padding: 6px 8px;
          font-size: 1em;
          text-align: left;
          background: #fff;
        }
        .table-block th {
          background: #ececec;
          font-weight: 700;
        }
        .table-block .sub-field {
          color: #9e005d;
          font-weight: 600;
          font-size: 0.92em;
        }
        .table-block .calculation {
          color: #6a7287;
          font-size: 0.90em;
          font-style: italic;
        }
        .section-header {
          font-size: 1.12em;
          font-weight: 700;
          letter-spacing: 1px;
          margin-bottom: 6px;
          background: #fff3e0;
          padding: 4px 8px;
          border-radius: 4px;
        }
        .ct-section {
          flex: 0 0 350px;
          display: flex;
          flex-direction: column;
          align-items: center;
          background: #fff;
          border: 2px solid #c876da;
          border-radius: 10px;
          padding: 0 0 18px 0;
          margin-left: 10px;
          min-width: 330px;
          max-width: 370px;
          margin-bottom: 16px;
        }
        .ct-title {
          background: linear-gradient(90deg, #b30086 0%, #b300b3 100%);
          color: #fff;
          font-size: 2em;
          font-weight: 700;
          text-align: center;
          padding: 12px 0;
          width: 100%;
          border-radius: 8px 8px 0 0;
          letter-spacing: 1.5px;
          margin-bottom: 0;
        }
        .ct-image {
          width: 92%;
          margin: 20px 0 16px 0;
          border-radius: 6px;
          background: #111;
          display: flex;
          align-items: center;
          justify-content: center;
          min-height: 190px;
          position: relative;
          overflow: hidden;
        }
        .ct-image img {
          width: 100%;
          border-radius: 6px;
          object-fit: contain;
          background: #222;
        }
        .ct-image .l3-label {
          position: absolute;
          top: 50%;
          left: 50%;
          transform: translate(-50%, -20%);
          color: #e386e5;
          font-size: 2.1em;
          font-weight: 700;
          letter-spacing: 2px;
          pointer-events: none;
          text-shadow: 0 0 10px #fff, 0 0 12px #c44;
        }
        .ct-report {
          width: 94%;
          margin: 0 auto;
          border: 2px solid #b300b3;
          border-radius: 0 0 8px 8px;
          background: #fff;
          font-size: 1.5em;
          text-align: center;
          color: #1a1a1a;
          padding: 8px 0;
          font-weight: 600;
          margin-top: 6px;
          margin-bottom: 0;
        }
        /* 底部按鈕列 */
        .footer-buttons {
          margin-top: 18px;
          display: flex;
          gap: 16px;
          justify-content: center;
        }
        .footer-buttons .btn {
          padding: 16px 32px;
          font-size: 1.12em;
          border-radius: 18px;
          border: none;
          font-weight: bold;
          color: #fff;
          cursor: pointer;
          min-width: 130px;
          box-shadow: 0 2px 6px #aaa;
          letter-spacing: 2px;
          transition: background 0.15s;
        }
        .btn-clinic { background: #2ecc71; }
        .btn-img    { background: #b55ca6; }
        .btn-blood  { background: #26a5dd; }
        .btn-ctcae  { background: #69c830; }
        .btn-ctcae strong { font-weight: 900; letter-spacing: 1.5px; }
        .btn-form   { background: #268dce; }
        .btn-follow { background: #94259b; }
        .footer-buttons .btn:hover { filter: brightness(0.95); }
    </style>
  
    <div class="container">
        <div class="title">
            Basic clinical presentation <span>臨床資訊</span>
        </div>
        <div class="flex-row">
            <!-- 左側:臨床資料 -->
            <div class="section" style="flex:1.1;">
                <div class="subject-no">
                    病人編號 Subject No
                    <span class="highlight">NCKUH-E001</span>
                    <span class="sub-label">(E代表內膜癌, O代表卵巢癌)</span>
                </div>
                <table class="table-block">
                    <tr>
                        <th style="width:120px;">臨床資訊, 癌別<br>EC or OC(自填)</th>
                        <td></td>
                    </tr>
                    <tr>
                        <th>年齡 (Age)</th>
                        <td class="sub-field">數值型<br>20歲~80歲</td>
                    </tr>
                    <tr>
                        <th>月經狀態</th>
                        <td class="sub-field">數值型<br>0停經、1未停經</td>
                    </tr>
                    <tr>
                        <th>身高(Height) cm</th>
                        <td class="sub-field">數值型<br>140cm~180cm</td>
                    </tr>
                    <tr>
                        <th>體重(BW) Kg</th>
                        <td class="sub-field">數值型<br>30kg~120kg</td>
                    </tr>
                    <tr>
                        <th>BMI (Kg/m²)</th>
                        <td class="calculation">
                            計算公式說明如下:<br>
                            體重(公斤) ÷ 身高(公尺) ÷ 身高(公尺)
                        </td>
                    </tr>
                    <tr>
                        <th>體表面積(BSA) m²</th>
                        <td class="calculation">
                            計算公式說明如下:<br>
                            體重(公斤) x 身高(公分) ÷ 3600,開根號
                        </td>
                    </tr>
                    <tr>
                        <th>腰圍(AC) cm</th>
                        <td class="sub-field">自填</td>
                    </tr>
                    <tr>
                        <th>日常體能狀態(PS)</th>
                        <td class="sub-field">數值型<br>0, 1, 2</td>
                    </tr>
                </table>
            </div>
            <!-- 中間:癌症狀態 -->
            <div class="section" style="flex:0.9;">
                <div class="section-header">癌症狀態 癌別 卵巢/子宮</div>
                <table class="table-block">
                    <tr>
                        <th style="width:180px;">癌症分期(2023 FIGO)</th>
                        <td class="sub-field">拉選式</td>
                    </tr>
                    <tr>
                        <th>AJCC c stage</th>
                        <td class="sub-field">拉選式</td>
                    </tr>
                    <tr>
                        <th>AJCC p stage</th>
                        <td class="sub-field">拉選式</td>
                    </tr>
                    <tr>
                        <th>組織型態</th>
                        <td class="sub-field">拉選式</td>
                    </tr>
                    <tr>
                        <th>MMR protein</th>
                        <td class="sub-field">拉選式</td>
                    </tr>
                    <tr>
                        <th>p53</th>
                        <td class="sub-field">拉選式</td>
                    </tr>
                    <tr>
                        <th>Hormon status</th>
                        <td class="sub-field">拉選式</td>
                    </tr>
                </table>
            </div>
            <!-- 右側:CT 影像區 -->
            <div class="ct-section">
                <div class="ct-title">CT Image</div>
                <div class="ct-image">
                    <!-- 替換成你自己的 CT 影像路徑 -->
                    <img src="https://cdn.openai.com/chatgpt/image_example_l3_ct.png" alt="CT L3" />
                    <div class="l3-label">L3</div>
                </div>
                <div class="ct-report">Report</div>
            </div>
        </div>
        <!-- 底部按鈕列 -->
        <div class="footer-buttons">
            <button class="btn btn-clinic">臨床資料</button>
            <button class="btn btn-img">影像資料</button>
            <button class="btn btn-blood">抽血資料</button>
            <button class="btn btn-ctcae"><strong>CTCAE 5.0</strong></button>
            <button class="btn btn-form">問卷</button>
            <button class="btn btn-follow">追蹤資料</button>
        </div>
    </div>

這樣的處理過程,似乎能夠讓一個圖片畫面快速的轉換成為 Blazor 的頁面,但是,似乎還是有些不太流暢的地方,因為當要進行修正或調整的時候,還是需要反覆地將修改後的程式碼貼回到 ChatGPT 內,並將生成後的結果再度貼回到 VS 2026 內,重新執行來觀看結果,若此時將 HTML & CCS 分離出來,將 CSS 內容儲存到 [Sample1.razor.css] 內,剪貼的過程會更加複雜與繁瑣,接下來,我將會嘗試看看,是否能夠直接在 Visual Studio 2026 編輯器中,直接貼上這個圖片,並且請 Github Copilot 協助我來產生出 Blazor 頁面的程式碼。 




Github Copilot 4 : 透過Prompt或圖片變更Blazor頁面風格

Github Copilot 4 : 透過Prompt或圖片變更Blazor頁面風格

在上一篇文章將原先醫師提供的 PowerPoint 畫面所欠缺的最下方的頁籤按鈕列,成功地加入到 Blazor 頁面中之後,接下來將會嘗試看看,是否能夠利用 Github Copilot 來協助我,對於這個頁面進行一些調整與改善的工作。

這是上一篇文章的頁面截圖

在接下來的這篇文章中,將會來探討如何透過 Github Copilot 4 所提供的功能,來協助我對於這個 Blazor 頁面的風格進行一些調整與變更。透過輸入適當的 Prompt 來觀看與了解如何進行這樣的操作。

修正 [Sample2.razor] 的頁面風格

  • 延續剛剛建立的專案,打開 [Sample2.razor] 頁面

  • 切換到 [GitHub Copilot] 視窗

  • 點選該視窗中右上方的 [建立新執行緒] 按鈕

  • 現在已經開啟了一個新一輪的對話 

  • 在對話視窗中,輸入以下的 Prompt 指令,然後按下 Enter 鍵

    我需要這個頁面呈現以綠色為主配色風格,表現出具有朝氣與希望
    
  • 由於 Github Copilot 預設設定會加入 [使用中文件] 的選項,因此,對於已經開啟且正在顯示視窗內的 [Sample2.razor] 頁面,會自動進行一些調整

  • 一旦 AI 分析與生成結束後,可以從視窗中看到底下結果 

  • 從 Github Copilot視窗中,可以觀察到

    • 這裡讀取了兩個檔案,分別是 [Sample2.razor] 與 [Sample2.razor.css]
    • 從 Prompt 的敘述,AI 可以理解到,這次的動作,不需要修改 Razor 結構,而是需要調整 CSS 的部分,也就是要調整 [Sample2.razor.css] 檔案
    • AI 從 [Sample2.razor] 檔案中,看的出來這是一個偏向醫療方面的應用網頁設計
    • 因此,依據 Prompt 需求,AI 會將配色調整為綠色系
  • 在 [GitHub Copilot] 視窗中,點選 [Sample2.razor.css] 產出內容的右上方之 [套用] 按鈕

  • 現在在 Visual Studio 2026 中,正在修改的程式碼視窗,自動切換到 [Sample2.razor.css] 檔案

  • 點選該視窗,按下 [Tab] 鍵,將剛剛 AI 產出的 CSS 程式碼,套用到這個檔案中

  • 儲存 [Sample2.razor.css] 檔案

  • 底下是 Github Copilot 對於這次請求的處理摘要

  • 重點變更說明(方便你之後微調):

    • 整體背景:改成淡綠漸層 #f0f8f4 → #e8f7f0,營造清爽與自然感。
    • 文字主色:標題、Panel 標頭改為深綠 (#145c32),穩定又不壓抑。
    • 重點色:原本的洋紅 / 桃紅,全部換成偏亮的綠色系(#0f9d58, #16a34a 等)。
    • CT 面板:保留黑底醫療影像感,加入綠色光暈與文字霓虹綠,讓畫面有科技感又不陰鬱。
    • Report 按鈕與頁籤:改為圓角膠囊形綠色按鈕,帶陰影與 hover 效果,表現「行動、希望」。
    • 如果你有既定品牌綠色(例如某個 HEX 色碼),可以提供色碼,我可以幫你再收斂到單一主色+兩個輔助色的完整色票。

檢視修正結果

  • 執行這個專案
  • 在瀏覽器中,打開 https://localhost:7299/Sample2 頁面
  • 將會看到底下結果 
  • 可以看到,整個頁面的配色風格,已經變更為綠色系,呈現出具有朝氣與希望的感覺

Sample2.razor.css 原始程式碼

.clinical-page {
    padding: 32px;
    background: linear-gradient(135deg, #f0f8f4, #e8f7f0);
    font-family: "Segoe UI", system-ui, -apple-system, BlinkMacSystemFont, "Microsoft JhengHei", sans-serif;
    color: #234038;
}

.page-title {
    font-size: 32px;
    font-weight: 600;
    margin: 0 0 24px;
    color: #145c32;
}

.page-title .title-zh {
    margin-left: 8px;
    font-size: 28px;
    color: #1f7a3a;
}

.clinical-layout {
    display: grid;
    grid-template-columns: 2.2fr 1.6fr 1.4fr;
    gap: 16px;
}

.panel {
    background-color: #ffffff;
    border-radius: 12px;
    box-shadow: 0 0 0 1px rgba(29, 134, 73, 0.12), 0 6px 16px rgba(15, 94, 54, 0.08);
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

/* 左邊病人資訊 */
.panel-patient .panel-header {
    padding: 12px 16px;
    border-bottom: 1px solid rgba(22, 96, 60, 0.25);
    background: linear-gradient(90deg, #e6f7ee, #f3fbf7);
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 8px;
    font-size: 14px;
    color: #145c32;
}

.subject-no {
    font-weight: 800;
    color: #0d8040;
    margin-left: 6px;
}

.subject-note {
    font-size: 12px;
    color: #497562;
}

/* 中間癌症狀態 */
.panel-cancer .panel-header-highlight {
    padding: 10px 16px;
    background: linear-gradient(90deg, #c4f1d6, #e0f8eb);
    border-bottom: 1px solid rgba(22, 96, 60, 0.25);
    font-weight: 600;
    font-size: 14px;
    color: #145c32;
}

/* CT Panel */
.panel-ct {
    background-color: #ffffff;
    border-radius: 18px;
    box-shadow: 0 0 0 2px rgba(0, 158, 96, 0.45);
    padding-bottom: 16px;
}

.ct-header {
    background: linear-gradient(90deg, #0f9d58, #0b8043);
    color: #ffffff;
    font-weight: 700;
    text-align: center;
    padding: 16px 8px;
    font-size: 22px;
    letter-spacing: 0.04em;
}

/* 保留黑底 CT 畫面,但加一點綠色光感讓感覺現代、有希望 */
.ct-content {
    padding: 20px 24px 12px;
}

.ct-image-frame {
    border-radius: 12px;
    background: radial-gradient(circle at top, #1c3b32 0, #050909 60%, #000 100%);
    padding: 10px;
}

.ct-image-placeholder {
    position: relative;
    width: 100%;
    height: 190px;
    background: radial-gradient(circle at center, #1a4734 0, #000 65%);
    border-radius: 8px;
    overflow: hidden;
    box-shadow: inset 0 0 18px rgba(19, 176, 102, 0.6);
}

.ct-level-label {
    position: absolute;
    left: 50%;
    bottom: 24px;
    transform: translateX(-50%);
    font-size: 40px;
    font-weight: 700;
    color: #e3ffe8;
    text-shadow: 0 0 6px #52ff9a, 0 0 16px #00d46d;
}

/* Report 按鈕:綠色行動按鈕,表現「行動力 / 希望」 */
.report-button {
    margin: 16px 24px 0;
    width: calc(100% - 48px);
    padding: 10px;
    border-radius: 999px;
    border: none;
    background: linear-gradient(135deg, #12b981, #16a34a);
    color: #ffffff;
    font-size: 18px;
    font-weight: 600;
    cursor: pointer;
    transition: transform 0.12s ease-out, box-shadow 0.12s ease-out, filter 0.12s ease-out;
    box-shadow: 0 6px 16px rgba(22, 163, 74, 0.45);
}

.report-button:hover {
    filter: brightness(1.05);
    transform: translateY(-1px);
    box-shadow: 0 10px 24px rgba(22, 163, 74, 0.6);
}

.report-button:active {
    transform: translateY(0);
    box-shadow: 0 4px 12px rgba(22, 163, 74, 0.5);
}

/* 通用表格樣式:略帶綠色邊與標題底色 */
.info-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}

.info-table th,
.info-table td {
    border: 1px solid rgba(148, 191, 164, 0.9);
    padding: 6px 8px;
    vertical-align: top;
}

.info-table th {
    width: 45%;
    background: linear-gradient(90deg, #e7f6ed, #f3fbf8);
    font-weight: 600;
    color: #145c32;
}

.info-table td {
    background-color: #ffffff;
}

.value-text {
    color: #047857;
}

.value-text .hint {
    color: #658a75;
    font-size: 11px;
}

.link-like {
    color: #0f766e;
    text-align: right;
}

/* 底部頁籤列:綠色主題 Tabs */
.clinical-tabs {
    margin-top: 24px;
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.tab-button {
    border-radius: 999px;
    border: 1px solid rgba(22, 163, 74, 0.4);
    background-color: #f0fdf4;
    color: #166534;
    padding: 8px 16px;
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
    transition: background-color 0.12s ease-out, color 0.12s ease-out, box-shadow 0.12s ease-out, transform 0.12s ease-out;
}

.tab-button:hover {
    background-color: #bbf7d0;
    box-shadow: 0 3px 10px rgba(21, 128, 61, 0.25);
    transform: translateY(-1px);
}

.tab-clinical {
    background: linear-gradient(135deg, #22c55e, #16a34a);
    color: #ffffff;
    border-color: transparent;
    box-shadow: 0 4px 14px rgba(34, 197, 94, 0.55);
}

/* RWD 簡單處理 */
@media (max-width: 992px) {
    .clinical-layout {
        grid-template-columns: 1fr;
    }

    .panel-ct {
        order: -1;
    }
}

再次進行調整

  • 面對這樣的結果,若覺得還不夠理想,還是可以請 Github Copilot 再次進行不同顏色與想法調整
  • 現在要來嘗試針對 Razor 檔案進行修正
  • 切換到 [GitHub Copilot] 視窗
  • 在此,並不需要在 Github Copilot 視窗中,建立新的對話執行緒
  • 直接在現有的對話視窗中,輸入以下的 Prompt 指令,然後按下 Enter 鍵
    對於 #Sample2.razor  檔案中,身高 (Height) 與 cm文字出現了斷行,在這裡不需要這樣,而是顯示在同一行
    
  • 底下畫面為這次操作的截圖 
  • 在 Github Copilot 聊天視窗中,按下 [傳送] 按鈕
  • 一旦 AI 完成分析後,將會看到底下的輸出 
  • 從這裡可以看到,AI 已經理解到這次的需求,是要調整 Razor 檔案中的結構
  • 在 [GitHub Copilot] 視窗中,點選 [Sample2.razor] 產出內容的右上方之 [套用] 按鈕

檢視修正結果

  • 再次執行這個專案
  • 在瀏覽器中,打開 https://localhost:7299/Sample2 頁面
  • 將會看到底下結果 
  • 可以看到,身高 (Height) 與 cm 文字已經顯示在同一行,不會再出現斷行的情況