都年底了,产品部门也不懂的消停,整了十几个原型图过来~~
今天我就打算用这些免费的模型给产品部门上点强度~~
大概思路如下,利用免费的智普大模型 glm-4v-flash 把产品部门给我的原型图直接转换为代码,先转成 HTML 代码演示下。
整个功能就是读取一个目录下的所有原型图片,然后点击左侧🫲文件列表中的图片,再右侧🫱点击 “AI 解读” 按钮来解析原型图片,解析完成后,API 接口返回生成的代码,代码可以在前端显示预览的效果:
另外,我们也可以点击 “切换源码/预览” 按钮,查看源代码,效果图如下:
整个效果生成如下,现在 glm-4v-flash 的性能还是挺快的:
再来一个任务列表的原型图,生成页面效果如下:
切换到源代码模式查看代码:
以上所有功能实现是基于免费的智普大模型 glm-4v-flash,不愧是被白宫严选的“大模型”,不用钱的用起来就是舒服。
智谱官方也直接硬刚了,即便遭遇制裁,智谱 AI 依然底气十足,表示掌握全链路大模型核心技术,制裁不会对其业务产生实质影响,并将继续参与全球人工智能竞争,推动技术发展。
接下来直接上源代码了,有兴趣的可以尝试下。
1、安装
先安装下智谱的 Python SDK 库,当然也可以其他的语言,官方文档都有提供详细说明,我这采用的是 Python:
pip install zhipuai
服务端采用 FastAPI 来实现文件读取和 API 的请求功能,安装 FastAPI 和 Uvicorn:
pip install fastapi uvicorn
前端采用的是 SSE 调用,所以还要安装一个专门用于处理 Server-Sent Events (SSE) 的库:
pip install sse-starlette
在代码中导入 EventSourceResponse:
from sse_starlette.sse import EventSourceResponse
2、源码
前端部分需要引入的第三方 CDN 库,如下所示:
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/themes/prism-tomorrow.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/prism.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
前端 HTML 代码使用 bootstrap5 来实现:
<div class="container-fluid p-0">
<!-- 头部 -->
<header class="bg-dark text-white text-center py-3" >
<h1>原型图转代码</h1>
</header>
<!-- 主体 -->
<main class="row no-gutters my-2 mx-4 border">
<!-- 左侧文件目录 -->
<aside class="col-md-2" id="file-list"> <!-- 左侧菜单宽度调整为 col-md-2 -->
<div class="card">
<div class="card-header">文件列表</div>
<div class="card-body">
<ul class="list-group">
<!-- 文件列表通过 JS 动态生成 -->
</ul>
</div>
</div>
</aside>
<!-- 右侧代码阅读器 -->
<section class="col-md-10"> <!-- 右侧内容宽度调整为 col-md-10 -->
<div class="card">
<div class="card-header">
<button id="parseButton" class="btn btn-warning btn-lg transition-all">
<span id="buttonText">AI 解析图片</span>
<span id="loadingSpinner" class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
</button>
<button id="toggleSourceButton" class="btn btn-secondary btn-lg transition-all">切换源码/预览</button>
</div>
<div class="card-body">
<!-- 图片和代码并列排列 -->
<div class="image-code-container">
<div id="imageContainer" class="image-container" style="display:none"></div>
<div class="code-output-container">
<div id="output" class="alert alert-secondary mt-2" role="alert" >...</div>
</div>
</div>
</div>
</div>
</section>
</main>
<!-- 底部 -->
<footer class="bg-secondary text-white text-center py-2">
<p>© 2024 原型转换工具 - WWW.RUNOOB</p>
</footer>
<!-- Toast 提示 -->
<div id="toast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<strong class="me-auto">提示</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body">
解析完成!
</div>
</div>
</div>
CSS 代码如下:
body {
background: linear-gradient(to bottom right, #f6e7fc, #e3f4fc);
}
#file-list {
height: 80vh;
overflow-y: auto;
border-right: 1px solid #ddd;
}
.code-container {
height: 60vh;
border-left: 1px solid #ddd;
}
.img-fluid {
max-width: 100%;
height: auto;
}
.transition-all {
transition: all 0.3s ease;
}
/* 图片和代码并列排列 */
.image-code-container {
display: flex;
gap: 1rem; /* 图片和代码之间的间距 */
}
.image-container {
flex: 1;
max-width: 50%; /* 图片占一半宽度 */
}
.code-output-container {
flex: 1;
max-width: 70%; /* 代码占一半宽度 */
}
/* Toast 样式 */
.toast {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
}
/* 源码模式样式 */
#output pre {
background: #2d2d2d;
color: #f8f9fa;
padding: 1rem;
border-radius: 5px;
overflow-x: auto; /* 水平滚动 */
overflow-y: auto; /* 垂直滚动 */
white-space: pre-wrap; /* 保留换行 */
max-height: 60vh; /* 限制高度 */
}
前端 JS 代码部分:
$(document).ready(function () {
const fileList = $("#file-list .list-group");
let filenamePath = '';
let isSourceMode = false; // 当前是否为源码模式
let currentMarkdown = ''; // 当前解析的 Markdown 内容
let currentHtml = ''; // 当前解析的 HTML 内容
// 从 FastAPI 后端获取文件列表
$.get('http://127.0.0.1:8000/files', function (data) {
const files = data.files;
files.forEach(file => {
const listItem = $(`<li class="list-group-item transition-all">${file}</li>`);
listItem.on("click", function() {
$('.list-group-item').removeClass('active');
$(this).addClass('active');
$("#output").html('');
loadFile(file);
});
fileList.append(listItem);
});
});
// 加载文件内容
function loadFile(filename) {
filename = `http://127.0.0.1:8000/file/${filename}`;
if (isImageFile(filename)) {
convertImageUrlToBase64(filename)
.then(base64String => {
const img = $('<img class="img-fluid">').attr('src', base64String);
$('#imageContainer').empty().append(img).show();
})
.catch(error => {
console.error('Error:', error);
});
} else {
$('#imageContainer').html(`${filename} 不是图片文件,无法显示。`).show();
}
}
// 点击 AI 解析按钮
$("#parseButton").click(function () {
const base64Code = $("#imageContainer img").attr("src");
if (!base64Code) {
alert("请设置图片!");
return;
}
// 显示加载效果
$("#loadingSpinner").show();
$("#buttonText").text("解析中...");
$("#parseButton").prop("disabled", true);
const eventSource = new EventSource(`http://127.0.0.1:8000/start-parsing?image=${encodeURIComponent(base64Code)}`);
$("#output").html('');
let _markdownContent = '';
eventSource.onmessage = function(event) {
const data = JSON.parse(event.data);
const content = data.content;
if (content === "[DONE]") {
eventSource.close();
// 隐藏加载效果
$("#loadingSpinner").hide();
$("#buttonText").text("AI 解读");
$("#parseButton").prop("disabled", false);
// 显示完成提示
const toast = new bootstrap.Toast(document.getElementById('toast'));
toast.show();
// 保存当前内容
currentMarkdown = _markdownContent;
// currentHtml = marked.parse(currentMarkdown); // 解析 Markdown 为 HTML
currentHtml = currentMarkdown;
console.log(currentHtml);
// 替换开头的 ```html
currentHtml = currentHtml.replace(/^```html/, '');
// 替换结尾的 ```
currentHtml = currentHtml.replace(/```$/, '');
updateOutput(); // 更新显示内容
console.log("Stream completed.");
} else {
_markdownContent = content;
}
};
eventSource.onerror = function (err) {
eventSource.close();
// 隐藏加载效果
$("#loadingSpinner").hide();
$("#buttonText").text("AI 解读");
$("#parseButton").prop("disabled", false);
console.error("Error:", err);
};
});
// 切换源码/预览模式
$("#toggleSourceButton").click(function () {
isSourceMode = !isSourceMode; // 切换模式
updateOutput(); // 更新显示内容
});
// 更新显示内容
function updateOutput() {
if (isSourceMode) {
// 源码模式:显示原始的 Markdown 内容
$("#output").html(`<pre><code class="language-html">${escapeHtml(currentMarkdown)}</code></pre>`);
Prism.highlightAll(); // 高亮代码
} else {
// 预览模式:显示解析后的 HTML 内容
$("#output").html(currentHtml);
}
}
// 转义 HTML 特殊字符
function escapeHtml(html) {
return html.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// 判断文件是否为图片的函数
function isImageFile(filename) {
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
const extension = filename.split('.').pop().toLowerCase();
return imageExtensions.includes(extension);
}
function convertImageUrlToBase64(url) {
return new Promise((resolve, reject) => {
fetch(url)
.then(response => response.blob())
.then(blob => {
const reader = new FileReader();
reader.onload = () => {
const base64Data = reader.result;
const pngBase64Data = base64Data.replace(/^data:.+;base64,/, 'data:image/png;base64,');
resolve(pngBase64Data);
};
reader.onerror = () => reject(new Error('Failed to read file'));
reader.readAsDataURL(blob);
})
.catch(error => reject(error));
});
}
});
后端部分源代码:
# 指定根目录下的 `files` 目录
base_dir = Path(__file__).parent / "files"
# 配置静态文件服务,用于提供前端页面
app.mount("/static", StaticFiles(directory="static"), name="static")
async def read_root():
return FileResponse("image.html", media_type="text/html")
async def get_files():
try:
if not base_dir.exists():
return {"error": "The files directory does not exist"}
files = get_files_recursive(base_dir)
return {"files": files}
except Exception as e:
return {"error": str(e)}
async def start_parsing(request: Request):
img_base = request.query_params.get('image')
client = ZhipuAI(api_key=apiKey)
"""
接收图片的 Base64 数据并解析
"""
response = client.chat.completions.create(
model="glm-4v-flash", # 填写需要调用的模型名称
messages=[
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {
"url": img_base
}
},
{
"type": "text",
"text": "你是一个开放人员,图片内容是一个原型设计稿,使用 bootstrap 生成一个跟他一摸一样网页代码,只需要输出代码即可。"
}
]
}
]
)
# 生成器:逐步输出流式数据
async def event_stream():
for chunk in response:
delta_content = response.choices[0].message.content
if delta_content:
yield f"{json.dumps({'content': delta_content})}"
#yield "data: [DONE]\n\n" # 结束标志
yield f"{json.dumps({'content': '[DONE]'})}"# 结束标志
return EventSourceResponse(event_stream())
语言模型 GLM-4-Flash 图像理解模型 GLM-4V-Flash 图像生成模型 CogView-3-Flash 视频生成模型 CogVideoX-Flash
另外,近期智谱还是上新了 GLM-Realtime API ,具备了实时视频通话,跨文本、音频、视频推理的能力,AI 通话时人还可以插嘴。
GLM-Realtime 还能用摄像头互动、读屏幕信息、理解对话环境,还创新实现清唱功能。
👇点击「阅读原文」,申请 APIKey!👇