Ostrakon-VL-8B与QT框架集成:开发桌面端餐饮管理智能插件
Ostrakon-VL-8B与QT框架集成开发桌面端餐饮管理智能插件最近在帮一个做餐饮软件的朋友琢磨点新东西。他们那个系统服务员点餐还得手动在电脑上敲菜名碰上菜单更新或者新员工不熟悉效率就下来了。我就想现在大模型看图说话这么厉害能不能让电脑自己“看”菜单图片然后把菜名、价格啥的自动填进去试了一圈发现Ostrakon-VL-8B这个模型挺合适。它是个多模态模型既能理解图片内容又能进行对话关键是对中文场景支持得不错识别个宫保鸡丁、清蒸鲈鱼应该不在话下。剩下的问题就是怎么把这个模型的能力“塞”进他们现有的那个用QT框架开发的桌面管理系统里做成一个即插即用的小插件。今天这篇文章我就来分享一下整个思路和实现过程。我们会用QT搭一个简单的悬浮窗程序实现拖拽图片、调用模型API、解析结果并模拟填充到系统表单的功能。即使你没怎么接触过QT或者模型集成跟着步骤走也能把这个小工具跑起来。1. 项目目标与环境准备我们最终要做的是一个可以独立运行也能方便集成到现有QT程序里的插件。它的核心工作流程很简单用户从手机相册或者电脑里把菜品图片拖拽到我们插件的小窗口里。插件把图片上传到部署好的Ostrakon-VL-8B模型服务。模型识别图片中的菜品并返回结构化的信息比如菜名、主要食材、可能的口味。插件解析这些信息并自动模拟键盘输入把“菜名”填写到餐饮管理系统的对应输入框中。这样一来服务员只需要拍个照或者找到图片一拖一放菜名就自动录入了能省不少事。1.1 核心工具与框架选择开发框架Qt 5.15或更高版本。选择QT是因为它在桌面端开发中非常成熟跨平台特性好界面库丰富而且很多现有的餐饮管理系统FSRS就是基于它开发的集成起来会相对顺畅。编程语言C。这是QT的原生语言配合起来性能最好也最稳定。模型服务Ostrakon-VL-8B。我们需要提前将这个模型部署成可提供API调用的服务。你可以使用诸如vLLM、TGI(Text Generation Inference) 等推理框架在本地或服务器上进行部署得到一个类似http://your-server-ip:port/v1/chat/completions的API端点。本文假设你已经有一个可用的API服务。网络请求库QT自带的QNetworkAccessManager就足够处理HTTP请求了。JSON解析库QT的QJsonDocument、QJsonObject等类用于解析模型返回的数据。1.2 开发环境搭建安装Qt从Qt官网下载Qt Creator安装包。建议选择开源版本或者商业试用版。安装时务必勾选Qt 5.15.x或Qt 6.x的桌面开发组件如MSVC 2019 64-bit和CMake工具。配置编译器如果你在Windows上安装时通常会自动配置好MSVC编译器。在Linux或macOS上需要确保已安装GCC或Clang。创建项目打开Qt Creator新建一个项目选择Qt Widgets Application。给项目起个名字比如DishRecognitionPlugin。在“选择套件”的步骤确保选中你安装的Qt版本和对应的编译器。项目结构预览创建完成后你会得到几个核心文件main.cpp程序入口。mainwindow.h/mainwindow.cpp/mainwindow.ui主窗口的头文件、源文件和界面设计文件。我们将主要在这里进行开发。环境准备好后我们的Qt Creator界面应该能正常打开并编译运行一个默认的空白窗口程序。接下来我们就开始设计这个插件的界面。2. 设计插件界面与核心交互这个插件的界面不需要太复杂核心是提供一个“接收图片”的入口和“展示结果”的区域。为了让它有插件的感觉我们把它设计成一个始终置顶、可拖拽的悬浮小窗口。2.1 使用Qt Designer设计主界面双击项目中的mainwindow.ui文件会打开Qt Designer可视化设计器。调整窗口属性在右侧属性编辑器中找到windowTitle改为“菜品识别插件”。找到geometry可以设置一个初始大小比如宽度400高度300。找到windowFlags这是一个关键设置。我们需要在其中添加Qt::WindowStaysOnTopHint属性让窗口始终置顶。不过这个在Designer里可能无法直接设置我们稍后在代码里设置会更灵活。你可以先勾选Qt::FramelessWindowHint来创建一个无边框窗口这样更像一个悬浮控件。拖拽控件从左侧“Widget Box”中拖拽一个Label标签到窗口中央。将其objectName属性改为labelDropAreatext属性改为“将菜品图片拖拽到此处”。将其alignment属性设置为居中对齐。这个标签将作为我们的图片拖拽区域。拖拽一个TextEdit文本编辑框到标签下方。将其objectName改为textEditResultreadOnly属性勾选上因为它是用来展示结果的。你可以适当调整大小。拖拽一个PushButton按钮到最下方。将其objectName改为pushButtonClosetext属性改为“关闭”。为了更友好可以再加一个“识别”按钮objectName为pushButtonRecognize但我们的核心交互是拖拽即识别这个按钮可以作为手动触发备用。布局管理选中主窗口的空白处不要选中任何控件右键点击选择“布局” - “垂直布局”。这样所有控件就会整齐地垂直排列并且随着窗口大小变化自动调整。设计好的界面大致如下图意念图所示[始终置顶的悬浮小窗口] --------------------------------- | [拖拽图片到此] | -- labelDropArea (QLabel) | | | ------------------------- | | | | | -- textEditResult (QTextEdit) | | 识别结果将显示在这里... | | | | | | | ------------------------- | | | | [识别] [关闭] | -- pushButton (QPushButton) ---------------------------------2.2 启用拖拽功能光有一个标签提示是不够的我们需要让窗口能够接收用户拖拽进来的图片文件。这需要处理Qt的拖放事件Drag and Drop。在mainwindow.h文件中我们需要声明重写拖放事件处理函数// mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include QMainWindow QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent nullptr); ~MainWindow(); protected: // 重写拖放事件处理函数 void dragEnterEvent(QDragEnterEvent *event) override; void dropEvent(QDropEvent *event) override; private slots: void on_pushButtonClose_clicked(); // 关闭按钮的槽函数 void on_pushButtonRecognize_clicked(); // 识别按钮的槽函数 private: Ui::MainWindow *ui; QString currentImagePath; // 用于存储当前拖拽进来的图片路径 }; #endif // MAINWINDOW_H接着在mainwindow.cpp中实现这些函数// mainwindow.cpp #include mainwindow.h #include ui_mainwindow.h #include QDragEnterEvent #include QDropEvent #include QMimeData #include QFileInfo #include QMessageBox #include QDebug // 用于调试输出 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui-setupUi(this); // 设置窗口标志无边框 始终置顶 setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); // 允许窗口接收拖放 setAcceptDrops(true); // 允许作为拖放放置目标 ui-labelDropArea-setAcceptDrops(false); // 标签本身不处理由主窗口处理 ui-labelDropArea-setScaledContents(true); // 让标签可以缩放图片 // 连接按钮的点击信号到槽函数 connect(ui-pushButtonClose, QPushButton::clicked, this, MainWindow::close); connect(ui-pushButtonRecognize, QPushButton::clicked, this, MainWindow::on_pushButtonRecognize_clicked); } MainWindow::~MainWindow() { delete ui; } void MainWindow::dragEnterEvent(QDragEnterEvent *event) { // 当有东西拖入窗口时触发 if (event-mimeData()-hasUrls()) { // 检查拖入的是否是文件很可能是图片 QListQUrl urls event-mimeData()-urls(); if (!urls.isEmpty()) { QString filePath urls.first().toLocalFile(); QFileInfo fileInfo(filePath); QString suffix fileInfo.suffix().toLower(); // 检查文件后缀是否为常见图片格式 if (suffix jpg || suffix jpeg || suffix png || suffix bmp) { event-acceptProposedAction(); // 接受这个拖放动作 ui-labelDropArea-setText(松开鼠标以识别图片...); ui-labelDropArea-setStyleSheet(border: 2px dashed #4CAF50; background-color: #f0f0f0;); // 视觉反馈 } } } } void MainWindow::dropEvent(QDropEvent *event) { // 当用户在窗口内松开鼠标完成拖放时触发 ui-labelDropArea-setStyleSheet(); // 恢复样式 if (event-mimeData()-hasUrls()) { QListQUrl urls event-mimeData()-urls(); if (!urls.isEmpty()) { currentImagePath urls.first().toLocalFile(); qDebug() 拖拽的图片路径: currentImagePath; // 在标签上显示缩略图可选 QPixmap pixmap(currentImagePath); if (!pixmap.isNull()) { pixmap pixmap.scaled(ui-labelDropArea-size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); ui-labelDropArea-setPixmap(pixmap); } // 清空上一次的结果 ui-textEditResult-clear(); ui-textEditResult-append(正在识别图片...); // 自动触发识别功能 on_pushButtonRecognize_clicked(); } } event-acceptProposedAction(); } void MainWindow::on_pushButtonClose_clicked() { this-close(); } void MainWindow::on_pushButtonRecognize_clicked() { if (currentImagePath.isEmpty()) { QMessageBox::information(this, 提示, 请先拖拽一张菜品图片到窗口内。); return; } ui-textEditResult-append(开始调用识别模型...); // 这里将调用模型识别函数我们下一步实现 // recognizeDish(currentImagePath); }现在编译并运行程序你应该能看到一个置顶的小窗口。尝试从文件管理器拖拽一张.jpg或.png格式的图片到窗口上你会看到标签的文字和样式发生变化并且控制台会输出图片的路径。这说明我们的拖拽接收功能已经成功了。3. 集成Ostrakon-VL-8B模型API界面能接收图片了接下来就是最核心的一步把图片传给Ostrakon-VL-8B模型让它告诉我们图片里是什么菜。3.1 封装模型API请求我们需要写一个函数负责将图片文件编码通常是Base64然后构造符合模型API要求的HTTP请求通常是JSON格式并发送出去。首先在mainwindow.h的private slots区域添加一个槽函数声明并在private区域添加一个网络管理器和请求函数// mainwindow.h (补充) private slots: // ... 已有的槽函数 ... void onNetworkReplyFinished(QNetworkReply *reply); // 处理网络请求回复 private: Ui::MainWindow *ui; QString currentImagePath; QNetworkAccessManager *networkManager; // 网络访问管理器 void recognizeDish(const QString imagePath); // 发起识别请求的函数 QString imageToBase64(const QString filePath); // 图片转Base64然后在mainwindow.cpp中实现这些函数// mainwindow.cpp (补充) #include QNetworkAccessManager #include QNetworkRequest #include QNetworkReply #include QHttpMultiPart #include QHttpPart #include QJsonDocument #include QJsonObject #include QJsonArray #include QBuffer #include QImageReader // 在构造函数中初始化 networkManager MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) , networkManager(new QNetworkAccessManager(this)) // 初始化 { // ... 之前的初始化代码 ... // 连接网络管理器的finished信号到我们的处理槽函数 connect(networkManager, QNetworkAccessManager::finished, this, MainWindow::onNetworkReplyFinished); } // 图片转Base64 QString MainWindow::imageToBase64(const QString filePath) { QFile file(filePath); if (!file.open(QIODevice::ReadOnly)) { qDebug() 无法打开图片文件: filePath; return QString(); } QByteArray imageData file.readAll(); file.close(); return QString(imageData.toBase64()); } // 发起识别请求 void MainWindow::recognizeDish(const QString imagePath) { // 1. 准备API地址 (请替换成你实际的模型服务地址) QString apiUrl http://your-model-server-ip:port/v1/chat/completions; // 2. 构建请求JSON数据 QJsonObject message; message[role] user; // 构建多模态内容数组 QJsonArray contentArray; // 文本部分 QJsonObject textPart; textPart[type] text; textPart[text] 请识别这张图片中的菜品。请用中文回答并尽可能详细地描述菜名、主要食材和可能的口味。请将结果以清晰的格式返回。; contentArray.append(textPart); // 图片部分 (Base64编码) QString base64Image imageToBase64(imagePath); if (base64Image.isEmpty()) { ui-textEditResult-append(图片读取失败。); return; } QJsonObject imagePart; imagePart[type] image_url; QJsonObject imageUrl; // 根据模型API要求可能需要不同的格式常见的是 data:image/jpeg;base64,{base64} imageUrl[url] QString(data:image/jpeg;base64,%1).arg(base64Image); imagePart[image_url] imageUrl; contentArray.append(imagePart); QJsonObject requestBody; requestBody[model] ostrakon-vl-8b; // 模型名称根据实际部署调整 QJsonArray messagesArray; QJsonObject msgObj; msgObj[content] contentArray; msgObj[role] user; messagesArray.append(msgObj); requestBody[messages] messagesArray; requestBody[max_tokens] 500; // 限制回复长度 QJsonDocument doc(requestBody); QByteArray postData doc.toJson(); // 3. 设置网络请求 QNetworkRequest request; request.setUrl(QUrl(apiUrl)); request.setHeader(QNetworkRequest::ContentTypeHeader, application/json); // 如果需要API密钥在这里添加 // request.setRawHeader(Authorization, Bearer your-api-key); // 4. 发送POST请求 QNetworkReply *reply networkManager-post(request, postData); // 可以在这里关联reply和当前请求的上下文例如图片路径用于后续处理 reply-setProperty(imagePath, imagePath); } // 处理网络请求回复 void MainWindow::onNetworkReplyFinished(QNetworkReply *reply) { // 读取回复数据 QByteArray responseData reply-readAll(); int statusCode reply-attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); QString imagePath reply-property(imagePath).toString(); ui-textEditResult-append(QString(请求完成状态码: %1).arg(statusCode)); if (reply-error() QNetworkReply::NoError statusCode 200) { // 解析JSON回复 QJsonDocument jsonDoc QJsonDocument::fromJson(responseData); if (!jsonDoc.isNull()) { QJsonObject jsonObj jsonDoc.object(); // 解析结构取决于模型API的具体返回格式以下是OpenAI兼容格式的示例 if (jsonObj.contains(choices) jsonObj[choices].isArray()) { QJsonArray choices jsonObj[choices].toArray(); if (!choices.isEmpty()) { QJsonObject firstChoice choices.first().toObject(); if (firstChoice.contains(message)) { QJsonObject message firstChoice[message].toObject(); if (message.contains(content)) { QString content message[content].toString(); ui-textEditResult-append(识别结果\n); ui-textEditResult-append(content); // 这里可以添加后续的自动填充逻辑 // autoFillOrder(content); return; } } } } // 如果格式不匹配直接输出原始回复的一部分用于调试 ui-textEditResult-append(回复格式解析失败原始内容\n QString(responseData.left(500))); } else { ui-textEditResult-append(回复不是有效的JSON。); } } else { ui-textEditResult-append(QString(请求失败: %1\n错误详情: %2) .arg(reply-errorString()) .arg(QString(responseData))); } reply-deleteLater(); // 清理回复对象 }最后记得在on_pushButtonRecognize_clicked()函数中调用recognizeDishvoid MainWindow::on_pushButtonRecognize_clicked() { if (currentImagePath.isEmpty()) { QMessageBox::information(this, 提示, 请先拖拽一张菜品图片到窗口内。); return; } ui-textEditResult-append(开始调用识别模型...); recognizeDish(currentImagePath); // 调用识别函数 }重要提示你需要将apiUrl替换为你实际部署的Ostrakon-VL-8B模型服务的地址和端口。模型的API返回格式也可能有所不同请根据你使用的推理框架如vLLM, TGI的API文档调整onNetworkReplyFinished函数中的JSON解析逻辑。3.2 解析与处理模型返回结果模型返回的通常是一段自然语言描述比如“图片中是一盘宫保鸡丁主要食材有鸡丁、花生、干辣椒口味是酸甜微辣属于川菜。” 我们需要从中提取出最关键的“菜名”信息用于后续的自动填充。我们可以写一个简单的函数来解析这段文本。这里用一个非常基础的规则查找特定关键词后的内容来演示在实际应用中你可能需要更复杂的自然语言处理NLP规则或者让模型直接返回结构化的JSON。在mainwindow.cpp中添加一个解析函数// 一个简单的示例解析函数实际应用需要更健壮的逻辑 QString MainWindow::extractDishName(const QString modelResponse) { QString response modelResponse.toLower(); // 转为小写方便查找 QStringList keywords {菜名是, 菜品是, 这是, 名为, , :}; QString dishName; for (const QString keyword : keywords) { int index response.indexOf(keyword); if (index ! -1) { // 找到关键词提取后面的部分到句号或换行为止 int startPos index keyword.length(); int endPos response.indexOf(QRegExp([。.\n]), startPos); if (endPos -1) endPos response.length(); dishName response.mid(startPos, endPos - startPos).trimmed(); // 如果提取的内容过长可能不是菜名尝试截取第一个逗号或空格前的内容 if (dishName.length() 20) { int shortEnd dishName.indexOf(QRegExp([, ])); if (shortEnd ! -1) { dishName dishName.left(shortEnd); } } if (!dishName.isEmpty()) { break; } } } // 如果没找到返回原文本的前几个词作为备选 if (dishName.isEmpty()) { dishName modelResponse.section( , 0, 2); // 取前三个词 } return dishName; }然后在onNetworkReplyFinished函数成功获取到content后调用这个解析函数// 在 onNetworkReplyFinished 函数中成功获取content后 QString content message[content].toString(); ui-textEditResult-append(识别结果\n); ui-textEditResult-append(content); QString extractedName extractDishName(content); ui-textEditResult-append(\n提取出的菜名: extractedName); // 触发自动填充 autoFillOrder(extractedName); // 我们接下来实现这个函数4. 模拟输入与集成到现有系统最后一步就是把提取出来的菜名“填”到目标餐饮管理系统的输入框里。由于我们无法直接修改别人的软件这里采用模拟键盘输入的方式这是一种通用的外挂式集成方法。4.1 使用Qt模拟键盘输入Qt提供了QApplication和QTest来模拟键盘事件但更直接的方式是使用平台相关的API。为了跨平台我们可以使用Qt的QGuiApplication配合QWindow。不过一个更简单且跨平台的方法是先让用户“告诉”插件目标输入框在哪里。我们可以先实现一个“瞄准”模式用户点击一个按钮然后点击目标餐饮管理系统窗口中的输入框我们的插件记录下这个输入框的位置或窗口句柄。这里为了简化我们假设目标输入框的窗口标题或类别名是已知的例如订单录入窗口的菜名输入框。我们使用Qt的QWindow来查找窗口并模拟输入。首先在mainwindow.h中添加必要的头文件和函数声明// mainwindow.h (补充) #include QWindow #include QGuiApplication private slots: // ... 已有的槽函数 ... void on_pushButtonSelectTarget_clicked(); // 选择目标输入框的按钮 private: // ... 已有私有成员 ... QWindow *targetWindow; // 目标窗口 QPoint targetInputPos; // 目标输入框的大致位置相对屏幕 void simulateKeyInput(const QString text);在mainwindow.cpp中实现// mainwindow.cpp (补充) #include QScreen #include QTimer // ... 构造函数中连接新按钮的信号槽 ... // connect(ui-pushButtonSelectTarget, QPushButton::clicked, this, MainWindow::on_pushButtonSelectTarget_clicked); void MainWindow::on_pushButtonSelectTarget_clicked() { ui-textEditResult-append(请在5秒内点击目标餐饮管理系统的菜名输入框...); this-hide(); // 隐藏插件窗口避免干扰 // 这里需要一个机制来捕获下一个鼠标点击的窗口。 // 由于涉及跨进程窗口捕获实现较为复杂通常需要平台特定代码如Windows的SetWindowsHookEx。 // 作为简化示例我们假设已知目标窗口的标题。 QString targetWindowTitle 餐饮管理系统 - 订单录入; // 请替换为实际窗口标题 QTimer::singleShot(5000, [this, targetWindowTitle]() { // 5秒后执行 QListQWindow * windows QGuiApplication::topLevelWindows(); for (QWindow *window : windows) { if (window-title().contains(targetWindowTitle, Qt::CaseInsensitive)) { targetWindow window; ui-textEditResult-append(已找到目标窗口: window-title()); // 这里我们简单地将目标位置设为窗口中心实际应更精确 targetInputPos window-geometry().center(); this-show(); return; } } ui-textEditResult-append(未找到目标窗口请确保其已打开。); this-show(); }); } void MainWindow::simulateKeyInput(const QString text) { if (!targetWindow || text.isEmpty()) { ui-textEditResult-append(未指定目标窗口或输入内容为空。); return; } // 激活目标窗口 if (targetWindow-visibility() ! QWindow::FullScreen) { targetWindow-requestActivate(); } // 给一点时间让窗口激活 QTimer::singleShot(100, [this, text]() { // 将鼠标移动到目标位置并点击模拟获得焦点 QCursor::setPos(targetInputPos); // 这里需要更精确的模拟点击使用平台API如mouse_event (Windows) 或 XTest (Linux) 更可靠。 // 作为示例我们跳过精确点击直接发送键盘事件到活动窗口。 // 模拟键盘输入逐个字符发送 for (const QChar ch : text) { // 创建并发送按键事件到目标窗口这是一个简化示例实际可能需要更复杂的窗口消息发送 // 更健壮的做法是使用像pyautoguiPython这样的库或Qt的QTest但QTest需要知道具体控件 // 此处仅示意逻辑 QKeyEvent keyPress(QEvent::KeyPress, ch.unicode(), Qt::NoModifier, QString(ch)); QKeyEvent keyRelease(QEvent::KeyRelease, ch.unicode(), Qt::NoModifier, QString(ch)); QCoreApplication::postEvent(targetWindow, keyPress); QCoreApplication::postEvent(targetWindow, keyRelease); QThread::msleep(50); // 稍微延迟模拟真人输入速度 } ui-textEditResult-append(已尝试模拟输入菜名: text); }); } // 修改 autoFillOrder 函数 void MainWindow::autoFillOrder(const QString dishName) { if (dishName.isEmpty()) { ui-textEditResult-append(未提取到有效菜名无法自动填充。); return; } simulateKeyInput(dishName); }请注意跨进程的窗口控制和模拟输入是一个复杂主题上述代码是一个高度简化的示例。在生产环境中你可能需要使用平台特定的API如Windows的SendInput或keybd_eventLinux的XTest来实现更可靠的模拟输入。使用更精确的方法来定位目标输入框例如通过可访问性API如UI Automation on Windows, AT-SPI on Linux获取控件的精确信息。考虑使用像AutoHotkey(Windows) 或xdotool(Linux) 这样的外部工具并通过Qt调用它们。4.2 插件化与集成建议为了让这个程序更好地作为“插件”集成到现有QT餐饮管理系统中有几种思路动态库DLL/SO将我们的识别和模拟输入功能封装成一个动态链接库。主系统通过接口调用该库的功能。进程间通信IPC我们的插件作为一个独立的EXE运行通过TCP/IP、命名管道或DBus等机制与主系统通信。主系统发送图片路径插件返回识别结果。直接代码集成这是最紧密的方式。将我们的代码模块拖拽处理、网络请求、结果解析直接作为源代码或库添加到主系统的QT项目中并在其UI中添加一个触发按钮或拖拽区域。对于快速原型和演示独立EXE配合模拟键盘输入的方式是最快、侵入性最小的。在实际项目集成时则需要和主系统的开发团队协商选择最合适的集成方式。5. 总结与展望走完这一趟一个基本的桌面端菜品识别插件原型就搭建起来了。从拖拽图片到调用视觉大模型API再到解析结果并尝试自动填充我们利用了QT强大的桌面开发能力和Ostrakon-VL-8B模型的多模态理解能力实现了一个能提升餐饮前台效率的小工具。实际用下来你会发现几个可以继续打磨的地方。首先是模型识别的准确率和速度这非常依赖于你部署的模型服务本身的质量和性能。如果识别错了菜那自动填充反而添乱。其次就是模拟输入那块目前的方法还比较粗糙在不同系统、不同窗口管理器下可能不稳定要想真正稳定好用得花不少功夫去研究各个平台的自动化接口。不过这个原型的价值在于验证了技术路线的可行性。你可以基于它去探索更复杂的场景比如识别后不仅填菜名还能根据历史数据推荐相似菜品、估算价格或者和库存管理系统联动实时更新食材存量。甚至可以把插件做得更智能让它学习不同餐厅的菜单越用越准。如果你正在为类似的桌面系统寻找智能化的增强方案希望这个实践能给你带来一些启发。从一个小功能点切入快速验证再逐步完善往往是技术落地最有效的方式。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2428795.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!