前言

大家在做後台系統開發時,有沒有遇到過這樣的場景?運營同事跑過來説:“我想要個簡單的庫存報表,這就這幾個字段,能不能馬上弄好?”

這時候,你看着手頭堆積如山的需求,心裏可能在想:要是能直接跟電腦説一句“給我個庫存表”,界面就能自己長出來該多好啊!

這就是 NL2UI (Natural Language to User Interface) 的終極夢想——用自然語言直接生成界面。但説實話,讓 AI 直接寫 Vue 代碼稍微有點“嚇人”,代碼質量不可控不説,改起來還費勁。

今天,我們換個思路。我們不追求一步到位的“全自動”,而是基於 華為雲 DevUI MateChat 組件,打造一個“受限但絕對可靠”的 UI 生成引擎。

我們會用一套自己定義的 JSON DSL(領域特定語言) 作為中間層,讓 AI 做“填空題”,而不是“作文題”。這樣既利用了 AI 的理解能力,又保證了生成的界面是 100% 可用的。

為了方便大家驗證,我把這個引擎的完整代碼都開源了。大家可以去 GitCode 倉庫 https://gitcode.com/kaminono/MateChatNL2UIEngine 看看源碼,或者直接點這個 https://mate-chat-nl-2-ui-engine-components.vercel.app/ 在線體驗一下“説話變界面”。

一、 架構思考:為什麼我們需要一個“中間商”?

在動手寫代碼之前,我們得先定個基調。要實現 NL2UI,我們面臨兩個選擇:是讓 AI 直接吐出 Vue 代碼,還是讓它生成一個 JSON 數據?

我們堅定地選擇了後者。直接生成代碼就像是“開盲盒”,你永遠不知道 AI 會不會引入什麼奇怪的依賴。

而生成 JSON DSL 就穩妥多了。我們把 DevUI 的組件——比如 d-card、d-form、d-chart——看作是樂高積木。我們只允許 AI 挑選這些積木來搭建頁面。

我們可以把整個流程看作一個流水線:

  1. Input: 用户在 MateChat 輸入自然語言。
  2. Reasoning: LLM 基於 System Prompt 進行意圖識別,轉化為標準 JSON。
  3. Parser: 前端引擎攔截消息,正則清洗數據,校驗 JSON 合法性。
  4. Render: 遞歸組件讀取 JSON,動態映射為 DevUI 組件。

這種“控制反轉”的設計,是我們保證系統高可靠性的基石。

二、 核心實現:給 AI 立規矩,教它説“DSL”

搞定了架構,我們來看看核心代碼是怎麼實現的。這個引擎的“大腦”在 useNlParser.ts 文件裏。

我們需要利用 Prompt Engineering(提示詞工程),把我們的 DSL 語法“喂”給大模型。我們得明確告訴它:你只能用白名單裏的組件,輸出格式必須是標準的 JSON。

看看這段真實的代碼,我們在 System Prompt 裏做了非常嚴格的約束:

// playground/src/nl2ui-engine/composables/useNlParser.tsconst systemPrompt = `
你是一個專業的前端 UI 構建專家。你的任務是將用户的自然語言需求轉換為特定的 UI DSL (JSON 格式)。

### 🔴 嚴禁使用不存在的組件!只能使用以下白名單:
1. 佈局: "d-row", "d-col"
2. 容器: "d-card" (必須包含 children), "d-form" (children 必須是 d-form-item)
3. 表單項: "d-form-item" (props: label), "d-input", "d-select", "d-button"
4. 圖表: "simple-stat", "simple-chart"

### 輸出格式規範 (JSON)
必須嚴格遵守以下 JSON 結構,不要包含 markdown 代碼塊標記:
{
  "page": { "title": "頁面標題", "layout": "grid" | "default" },
  "components": [
    {
      "component": "d-card",
      "props": { "title": "卡片標題" },
      "children": [ ... ]
    }
  ]
}
`;

通過這種方式,無論用户怎麼描述需求,AI 最終吐出來的都是我們能看懂、能渲染的標準數據。這就像是給 AI 戴上了“緊箍咒”,讓它的創造力在規則的軌道上運行。

在實際開發中,LLM 經常會在返回的 JSON 外面包裹 Markdown 標記(如 ```json ... ```)。如果不處理,JSON.parse 必掛。 我們在解析層做了一層“清洗”:

// 解析邏輯片段
const parseResponse = (content: string) => {
  // 1. 利用正則提取最外層的 {} 內容,去除廢話和 markdown 符號
  const jsonMatch = content.match(/\{[\s\S]*\}/);
  if (!jsonMatch) return null;
  
  try {
    return JSON.parse(jsonMatch[0]);
  } catch (e) {
    console.error("JSON 解析失敗,AI 生成了非法格式", e);
    // 這裏甚至可以觸發一個重試機制
    return null;
  }
}

三、 渲染引擎:把 JSON 變現為 DevUI 組件

拿到了 JSON 數據,下一步就是把它變成真實的界面。我們在 DslRenderer.vue 裏實現了一個遞歸渲染器。

這個組件的設計非常巧妙,它利用了 Vue 的 h() 函數和 defineAsyncComponent。我們建立了一個組件註冊表,按需加載 DevUI 的組件。

這裏有個關鍵點:對於 AI 可能產生的“幻覺”(比如生成了不存在的組件),我們做了兜底處理。

// playground/src/nl2ui-engine/components/DslRenderer.vue// 1. 建立組件白名單映射const componentRegistry: Record<string, any> = {
  'd-card': defineAsyncComponent(() => import('vue-devui/card')),
  'd-form': defineAsyncComponent(() => import('vue-devui/form')),
  'd-input': defineAsyncComponent(() => import('vue-devui/input')),
  // ... 其他組件
};

// 2. 核心渲染函數const renderNode = (node: any): any => {
  // 兜底策略:如果 AI 生成了純文本,直接渲染文本if (typeof node === 'string') return String(node);

  let Component = componentRegistry[node.component];
  
  // 錯誤處理:遇到未知組件,渲染一個紅框提示,而不是讓頁面崩潰if (!Component) {
    return h('div', { style: 'border: 1px dashed red;' }, `[未知: ${node.component}]`);
  }

  // 遞歸渲染子節點const children = node.children?.map(renderNode);
  
  return h(Component, node.props, { default: () => children });
};

這段代碼保證了渲染器的健壯性。哪怕 AI 偶爾“發瘋”,我們的頁面也不會白屏,開發者一眼就能看出是哪裏出了問題。

遞歸渲染樹 (The Recursive Magic)是引擎最精妙的地方。因為 UI 結構是樹形的(Card 裏有 Row,Row 裏有 Col,Col 裏有 Button),我們的渲染函數必須是遞歸的。

const renderNode = (node: any): any => {
  if (typeof node === 'string') return node;

  const Component = componentRegistry[node.component];
  if (!Component) return h('div', { style: 'color:red' }, `[未知組件: ${node.component}]`);

  // 核心:處理 props 和 children
  // 1. 透傳 AI 生成的屬性 (如 label, placeholder)
  const props = { ...node.props };

  // 2. 遞歸構建子節點
  const children = node.children 
    ? { default: () => node.children.map(renderNode) } // 插槽形式傳遞子節點
    : null;

  return h(Component, props, children);
};

這段代碼僅用十幾行,就實現了理論上無限嵌套的 UI 構建能力。

四、 價值閉環:不僅能看,還能“帶走”

如果只能在預覽裏看,那這個工具充其量只是個玩具。為了讓它真正產生價值,我們必須實現“從對話到源碼”的閉環。

試想一下,你讓 AI 生成了一個複雜的表單,覺得效果不錯。這時候,你肯定不想照着預覽圖再去手寫一遍代碼吧?

所以,我們開發了 useCodeGenerator.ts。它能把當前的 JSON DSL 逆向編譯成標準的 Vue SFC(單文件組件)代碼。

// playground/src/nl2ui-engine/composables/useCodeGenerator.tsconst generateVueCode = (dsl: UiDsl) => {
  // 1. 逆向生成 Templateconst templateBody = dsl.components
    .map(node => generateTemplateNode(node, 2))
    .join('\n');

  // 2. 智能分析依賴,生成 Scriptconst imports = analyzeImports(dsl.components);

  // 3. 拼接成完整的 Vue 文件字符串return `<template>
  <div class="generated-page">
${templateBody}
  </div>
</template>

<script setup>
import { ${imports.join(', ')} } from 'vue-devui';
</script>`;
};

在我們的 Demo 右側,專門做了一個“查看源碼”的 Tab。點擊它,你就能複製這段生成的代碼,直接粘貼到你的項目裏。這才是真正的提效

五、 場景演示:MateChat 的“雙面”能力

最後,我們看看這套系統在實際場景中的表現。我們設計了一個“左指令、右預覽”的佈局。

左邊是大家熟悉的 MateChat 聊天窗口,它作為交互的入口。用户在這裏輸入自然語言,比如“幫我生成一個銷售看板,要看總收入和活躍用户”。

MateChat 會顯示“正在構建組件樹...”,幾秒鐘後,右邊的預覽區就會實時渲染出一個包含數據卡片和圖表的 Dashboard。

如果你輸入“創建一個用户註冊表單,包含用户名和密碼”,右側瞬間就會變成一個帶有校驗規則的 DevUI 表單。

這種“即問即答、即答即現”的體驗,徹底改變了我們構建 UI 的方式。

智能交互新範式:拒絕“黑盒”,帶你用 MateChat 與 DSL 構建“高可靠”的 NL2UI 引擎_UI


六、 總結與展望

通過這個項目,我們驗證了一個核心觀點:受限的 DSL 反而是 AI 落地的最佳路徑

我們沒有追求讓 AI 直接寫出完美的代碼,而是利用 MateChat 做交互,利用 DSL 做約束,利用 DevUI 做渲染。這套組合拳打下來,既保證了系統的穩定性,又發揮了 AI 的靈活性。

未來,這套架構還有很大的想象空間。比如,我們可以把 DSL 餵給後端,直接生成數據庫模型;或者結合語音識別,實現“動動嘴做軟件”的科幻場景。

希望這個開源項目能給大家帶來一點啓發,也歡迎大家來 GitCode 提 PR,我們一起把這個 NL2UI 引擎打磨得更強大!

附官方鏈接:

DevUI

MateChat - 輕鬆構建你的AI應用