Web开发全栈实践:构建展示SmallThinker-3B-Preview能力的响应式网站

发布时间:2026/6/8 18:59:12

Web开发全栈实践:构建展示SmallThinker-3B-Preview能力的响应式网站 Web开发全栈实践构建展示SmallThinker-3B-Preview能力的响应式网站最近在捣鼓一些AI模型发现SmallThinker-3B-Preview这个小家伙挺有意思功能不少但怎么把它展示给别人看呢总不能每次都让人家对着命令行敲代码吧。于是我就琢磨着能不能用咱们最熟悉的Web开发技术给它搭个“线上展厅”。这个想法很简单做一个网站前端做得漂漂亮亮、用起来顺顺手手把模型的各种能力比如文本对话、图片理解、创意生成都用直观的方式展示出来。用户点一点、输点文字、传张图片马上就能看到效果。后端呢就负责把模型“伺候”好提供一个稳定、安全的接口给前端调用。这其实就是一个非常典型的全栈项目。从用户看到的界面到背后处理请求的逻辑再到最终模型的响应一条龙全包了。今天我就把自己搭建这个“AI能力展厅”的过程和思考分享出来如果你也想把某个AI能力产品化、服务化或许能给你一些参考。1. 项目蓝图我们要做一个什么样的网站在动手写代码之前得先想清楚这个网站到底要干嘛长什么样。这就像盖房子先画图纸能避免后面返工。1.1 核心目标与功能规划我们这个网站的核心目标就一个让任何访客哪怕不懂技术也能轻松体验和了解SmallThinker-3B-Preview模型的能力。围绕这个目标我规划了几个关键功能模块能力概览首页一进来就看到最精华的部分。用几个大的“展示窗”或者卡片分别呈现文本生成、图文对话等核心功能的惊艳案例配上简单的说明第一时间抓住用户的眼球。交互式体验区这是网站的灵魂。不能光让用户看得让他们玩。文本聊天框用户输入问题或指令网站实时返回模型生成的文本回复。图片上传与分析用户上传一张图片网站不仅能描述图片内容还能回答用户关于这张图片的提问。创意生成工坊提供一些预设的场景比如“写一首关于春天的诗”、“生成一段产品广告文案”用户点击就能看到模型的创意成果。案例画廊专门一个页面用来陈列模型生成的各种高质量作品。比如优秀的文章段落、有趣的对话、对复杂图片的精准描述等按类别分好供用户浏览。简单的后台管理虽然主要是展示但后台也需要一个简单的界面用来查看API的调用情况、监控服务状态或者在需要时更新展示的案例。1.2 技术栈选型用合适的工具做合适的事技术选型没有绝对的好坏只有合不合适。我基于“快速开发、易于维护、社区活跃”的原则选了下面这套组合前端Vue.js 3 TypeScript Pinia Element Plus。为什么选Vue它的渐进式设计和响应式系统上手快对于构建这种交互复杂但结构清晰的单页面应用非常友好。TypeScript能提供更好的代码提示和类型安全减少低级错误。Pinia是状态管理Element Plus提供了丰富的现成UI组件能极大加快开发速度。后端Node.js (Express框架) Python (FastAPI框架)。这里有点特别我用了“混合”模式。Node.js的Express框架负责主Web服务器、用户请求路由、静态文件服务和业务逻辑。而真正调用SmallThinker-3B-Preview模型的任务我交给了用Python FastAPI写的一个独立服务。因为很多AI模型的原生生态在Python里更成熟这样拆分开Node.js做它擅长的Web服务Python做它擅长的AI推理彼此通过HTTP API通信职责清晰也方便各自升级。部署与运维Docker Docker Compose Nginx。Docker把前端、Node后端、Python模型服务分别打包成镜像确保在任何环境里运行一致。Docker Compose用一个配置文件就能把这三个服务一键启动管理起来特别方便。Nginx作为反向代理服务器放在最前面负责转发请求到正确的后端服务还能做负载均衡、缓存静态文件提升网站性能和安全。其他工具Git代码版本管理必不可少。ESLint Prettier保证代码风格统一和格式整洁。2. 前端展厅打造直观友好的用户体验前端是门面直接决定了用户愿不愿意用、好不好用。我们的设计原则是清晰、响应快、交互自然。2.1 项目搭建与核心组件设计首先我们用Vue CLI或者更现代的Vite来初始化项目。# 使用Vite创建项目 npm create vuelatest smallthinker-showcase # 按照提示选择TypeScript, Pinia, ESLint等 cd smallthinker-showcase npm install # 安装UI库和HTTP客户端 npm install element-plus axios npm install pinia pinia/nuxt # 如果使用Pinia接下来规划几个核心的页面组件HomeView.vue首页展示能力概览。PlaygroundView.vue交互体验区集成文本聊天和图片上传功能。GalleryView.vue案例画廊。Layout.vue整体布局组件包含导航栏和页脚。以PlaygroundView.vue中的文本聊天组件为例我们来看看怎么设计template div classplayground-container h2与SmallThinker对话/h2 div classchat-box !-- 消息展示区域 -- div classmessages refmessagesRef div v-for(msg, index) in messages :keyindex :class[message, msg.role] div classavatar{{ msg.role user ? 你 : AI }}/div div classcontent{{ msg.content }}/div /div div v-ifisLoading classmessage assistant div classavatarAI/div div classcontent typing正在思考.../div /div /div !-- 输入区域 -- div classinput-area el-input v-modeluserInput typetextarea :rows3 placeholder输入你想聊的内容... keyup.enter.exactsendMessage / el-button typeprimary :loadingisLoading clicksendMessage 发送 /el-button /div /div /div /template script setup langts import { ref, nextTick } from vue import { ElMessage } from element-plus import axios from axios interface ChatMessage { role: user | assistant content: string } const userInput ref() const messages refChatMessage[]([ { role: assistant, content: 你好我是SmallThinker有什么可以帮你的吗 } ]) const isLoading ref(false) const messagesRef refHTMLElement() const sendMessage async () { const input userInput.value.trim() if (!input) { ElMessage.warning(请输入内容) return } if (isLoading.value) return // 添加用户消息 messages.value.push({ role: user, content: input }) userInput.value isLoading.value true // 滚动到底部 scrollToBottom() try { // 调用后端API const response await axios.post(/api/chat, { message: input, history: messages.value.slice(-5) // 只发送最近5条作为上下文 }) // 添加AI回复 messages.value.push({ role: assistant, content: response.data.reply }) } catch (error) { console.error(对话失败:, error) ElMessage.error(对话请求失败请稍后重试) messages.value.push({ role: assistant, content: 抱歉我好像出了点问题。 }) } finally { isLoading.value false nextTick(() scrollToBottom()) } } const scrollToBottom () { nextTick(() { if (messagesRef.value) { messagesRef.value.scrollTop messagesRef.value.scrollHeight } }) } /script style scoped .chat-box { border: 1px solid #dcdfe6; border-radius: 8px; overflow: hidden; } .messages { height: 400px; padding: 20px; overflow-y: auto; background-color: #fafafa; } .message { display: flex; margin-bottom: 16px; } .message.user { flex-direction: row-reverse; } .message.user .content { background-color: #409eff; color: white; } .avatar { width: 36px; height: 36px; border-radius: 50%; background-color: #e4e7ed; display: flex; align-items: center; justify-content: center; margin: 0 12px; font-weight: bold; } .content { max-width: 70%; padding: 12px 16px; border-radius: 18px; background-color: white; box-shadow: 0 1px 2px rgba(0,0,0,0.1); } .input-area { padding: 20px; background-color: white; display: flex; gap: 12px; } .typing::after { content: ...; animation: typing 1.5s infinite; } keyframes typing { 0%, 100% { opacity: 0.3; } 50% { opacity: 1; } } /style这个组件实现了基本的对话界面包括消息展示、滚动控制、加载状态和错误处理。它通过axios向后端定义的/api/chat接口发送请求。2.2 状态管理与API集成随着功能增多我们需要一个地方集中管理应用的状态比如用户身份、全局配置等。这里我们用Pinia来创建一个管理API调用的store。// stores/api.ts import { defineStore } from pinia import axios, { AxiosInstance } from axios export const useApiStore defineStore(api, () { // 创建配置好的axios实例 const apiClient: AxiosInstance axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || /api, timeout: 30000, // 30秒超时AI推理可能较慢 headers: { Content-Type: application/json } }) // 统一的请求拦截器可以在这里添加token等 apiClient.interceptors.request.use( (config) { // 例如从localStorage读取token // const token localStorage.getItem(token) // if (token) { // config.headers.Authorization Bearer ${token} // } return config }, (error) Promise.reject(error) ) // 统一的响应拦截器处理通用错误 apiClient.interceptors.response.use( (response) response.data, // 直接返回data部分 (error) { console.error(API请求错误:, error) // 可以根据状态码统一处理 if (error.response?.status 401) { // 处理未授权 } else if (error.response?.status 429) { // 处理请求过多 } return Promise.reject(error) } ) // 定义具体的API方法 const chat (payload: { message: string; history?: any[] }) { return apiClient.post(/chat, payload) } const analyzeImage (formData: FormData) { return apiClient.post(/analyze-image, formData, { headers: { Content-Type: multipart/form-data } }) } const getGalleryItems () { return apiClient.get(/gallery) } return { chat, analyzeImage, getGalleryItems } })这样在任何一个Vue组件中我们都可以通过useApiStore()来调用这些封装好的API方法代码更整洁也便于统一修改。3. 后端引擎构建稳健高效的模型服务层前端做得再花哨如果后端服务不稳定、不安全一切都是空谈。我们的后端要扮演好“桥梁”和“守护者”的角色。3.1 Node.js主服务路由与业务逻辑Node.js服务用Express作为主入口处理所有Web请求。// server.js (Node.js Express) const express require(express) const cors require(cors) const rateLimit require(express-rate-limit) const helmet require(helmet) require(dotenv).config() const app express() const PORT process.env.PORT || 3000 // 1. 安全中间件 app.use(helmet()) // 设置安全的HTTP头 app.use(cors({ origin: process.env.FRONTEND_URL || http://localhost:5173, // 允许的前端地址 credentials: true })) // 2. 限流中间件 - 防止滥用 const apiLimiter rateLimit({ windowMs: 15 * 60 * 1000, // 15分钟 max: 100, // 每个IP最多100次请求 message: 请求过于频繁请稍后再试。 }) app.use(/api/, apiLimiter) // 3. 解析请求体 app.use(express.json({ limit: 10mb })) // 支持JSON限制大小 app.use(express.urlencoded({ extended: true })) // 4. 静态文件服务如果前端打包后放在这里 app.use(express.static(public)) // 5. 代理请求到Python模型服务 const { createProxyMiddleware } require(http-proxy-middleware) const modelServiceUrl process.env.MODEL_SERVICE_URL || http://localhost:8000 // 只将特定路径的请求转发给Python服务 app.use(/api/model, createProxyMiddleware({ target: modelServiceUrl, changeOrigin: true, pathRewrite: { ^/api/model: }, // 重写路径 onError: (err, req, res) { console.error(代理到模型服务出错:, err) res.status(502).json({ error: 模型服务暂时不可用 }) } })) // 6. 自定义的业务API路由如果需要聚合或处理数据 const apiRouter express.Router() apiRouter.post(/chat, async (req, res) { // 这里可以添加业务逻辑比如记录日志、检查内容安全 // 然后将请求转发给/model/chat或者直接调用Python服务 // 为了简化我们直接代理复杂的业务可以在这里处理 res.json({ reply: 此功能由模型服务直接处理请求已转发。 }) }) apiRouter.get(/gallery, async (req, res) { // 从数据库或文件中读取展示案例 const galleryItems [ { id: 1, title: 创意诗歌, content: 模型生成的一首关于星空的诗..., type: text }, { id: 2, title: 图片描述示例, description: 模型对一张风景图的精准描述..., type: image } ] res.json(galleryItems) }) app.use(/api, apiRouter) // 7. 全局错误处理 app.use((err, req, res, next) { console.error(err.stack) res.status(500).json({ error: 服务器内部错误 }) }) app.listen(PORT, () { console.log(Node.js主服务运行在 http://localhost:${PORT}) })这个Node服务主要做了几件事安全加固、请求限流、静态资源服务、将AI相关的请求代理给专门的Python服务以及处理一些简单的自定义业务逻辑。3.2 Python模型服务专注AI推理Python服务用FastAPI是专门干“体力活”的负责加载和运行SmallThinker-3B-Preview模型。# main.py (Python FastAPI) from fastapi import FastAPI, HTTPException, UploadFile, File, Depends from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel from typing import List, Optional import logging import asyncio from your_model_loader import SmallThinkerModel # 假设的模型加载模块 # 配置日志 logging.basicConfig(levellogging.INFO) logger logging.getLogger(__name__) app FastAPI(titleSmallThinker-3B-Preview Model Service) # 允许跨域 app.add_middleware( CORSMiddleware, allow_origins[*], # 生产环境应指定具体前端地址 allow_credentialsTrue, allow_methods[*], allow_headers[*], ) # 全局模型实例简单示例生产环境需考虑更复杂的生命周期管理 model None class ChatRequest(BaseModel): message: str history: Optional[List[dict]] None class ChatResponse(BaseModel): reply: str processing_time: float app.on_event(startup) async def startup_event(): 启动时加载模型 global model logger.info(正在加载SmallThinker-3B-Preview模型...) try: # 这里初始化你的模型 # model SmallThinkerModel(model_path./models/smallthinker-3b-preview) model 模拟模型 # 占位 logger.info(模型加载完成。) except Exception as e: logger.error(f模型加载失败: {e}) raise app.post(/chat, response_modelChatResponse) async def chat_with_model(request: ChatRequest): 处理文本对话请求 if model is None: raise HTTPException(status_code503, detail模型未就绪) logger.info(f收到对话请求: {request.message[:50]}...) try: # 模拟处理耗时 await asyncio.sleep(0.5) # 实际调用模型 # reply model.generate(request.message, historyrequest.history) reply f这是模型对『{request.message}』的模拟回复。在实际部署中这里会调用真正的模型推理接口。 return ChatResponse(replyreply, processing_time0.5) except Exception as e: logger.error(f模型推理出错: {e}) raise HTTPException(status_code500, detail模型推理过程发生错误) app.post(/analyze-image) async def analyze_image(file: UploadFile File(...), question: Optional[str] None): 处理图片上传与分析请求 if model is None: raise HTTPException(status_code503, detail模型未就绪) logger.info(f收到图片分析请求文件名: {file.filename}) # 检查文件类型和大小 if not file.content_type.startswith(image/): raise HTTPException(status_code400, detail请上传图片文件) contents await file.read() if len(contents) 10 * 1024 * 1024: # 10MB限制 raise HTTPException(status_code400, detail图片大小不能超过10MB) # 这里将图片内容传递给模型进行理解 # analysis model.understand_image(contents, question) analysis f已收到图片『{file.filename}』。模拟分析结果图片中包含物体A、B、C。 if question: analysis f\n对于问题『{question}』的模拟回答是... return {analysis: analysis} if __name__ __main__: import uvicorn uvicorn.run(app, host0.0.0.0, port8000)这个Python服务定义了两个核心接口/chat用于文本对话/analyze-image用于图片分析。它独立运行通过HTTP与Node.js主服务通信。在实际部署中你需要将your_model_loader替换为实际加载和调用SmallThinker模型的代码。4. 部署上线让网站稳定运行开发完成只是第一步如何让网站在服务器上稳定、高效、安全地跑起来是另一个关键。4.1 容器化使用Docker打包为每个服务创建Dockerfile确保环境一致。前端Dockerfile示例# 前端 Dockerfile FROM node:18-alpine as builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM nginx:alpine COPY --frombuilder /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/nginx.conf EXPOSE 80 CMD [nginx, -g, daemon off;]Node.js后端Dockerfile示例# Node.js后端 Dockerfile FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --onlyproduction COPY . . EXPOSE 3000 USER node CMD [node, server.js]Python模型服务Dockerfile示例# Python模型服务 Dockerfile FROM python:3.10-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 # 注意这里可能需要安装额外的系统依赖或CUDA驱动具体取决于模型 CMD [uvicorn, main:app, --host, 0.0.0.0, --port, 8000]4.2 编排与发布使用Docker Compose和Nginx用docker-compose.yml把三个服务组织起来并通过Nginx统一对外。# docker-compose.yml version: 3.8 services: nginx-proxy: build: context: ./nginx ports: - 80:80 - 443:443 # 如果配置了HTTPS depends_on: - frontend - backend-node networks: - app-network frontend: build: context: ./frontend environment: - VITE_API_BASE_URL/api networks: - app-network backend-node: build: context: ./backend-node environment: - PORT3000 - MODEL_SERVICE_URLhttp://model-service:8000 depends_on: - model-service networks: - app-network model-service: build: context: ./model-service environment: - MODEL_PATH/app/models # 如果模型很大可以考虑使用 volumes 挂载 # volumes: # - ./model-data:/app/models networks: - app-network # 可能需要更多资源限制 deploy: resources: limits: memory: 8G reservations: memory: 4G networks: app-network: driver: bridgeNginx配置 (nginx.conf)是关键它负责路由# nginx.conf events { worker_connections 1024; } http { upstream backend { server backend-node:3000; } upstream model_service { server model-service:8000; } server { listen 80; server_name your-domain.com; # 替换为你的域名 # 前端静态文件 location / { proxy_pass http://frontend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 代理到Node.js主后端API location /api/ { # 将/model开头的请求转发给Python服务 location /api/model/ { proxy_pass http://model_service/; rewrite ^/api/model/(.*) /$1 break; } # 其他API请求转发给Node服务 proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 静态资源缓存 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 1y; add_header Cache-Control public, immutable; } } }最后在服务器上只需要运行docker-compose up -d整个应用栈就会启动。通过配置域名和SSL证书可以使用Let‘s Encrypt你的SmallThinker能力展示网站就正式上线了。5. 总结走完这一整套流程从构思、设计、前后端开发到最后的容器化部署一个用于展示AI模型能力的全栈网站就搭建起来了。这个过程里我觉得最重要的不是用了多炫酷的技术而是如何把复杂的技术能力通过一个简单、可靠的Web服务包装起来提供给最终用户。前端要足够友好把模型的“黑盒子”变成可视化的交互后端要足够稳健做好安全、限流和错误处理保护模型服务也保护自己部署要足够自动化用Docker和Nginx把运维复杂度降到最低。当然这个示例项目还有很多可以深化的地方比如加入用户系统、实现更复杂的对话上下文管理、对模型输出进行内容安全过滤、做更细致的性能监控等等。但无论如何这提供了一个可行的起点。技术最终是为了解决问题、创造价值。希望这个“Web开发全栈实践”的思路能帮你把你手中的AI能力更快、更好地呈现给世界。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。

相关新闻