- 第三栏内容工厂:按拖入类型展示动作,动作按钮本身可接收 drop - 链接分类:http(s) 按扩展名判定图片/音视频/文档/压缩包/网页 - http 链接动作:打开、下载、另存为、复制、Markdown、复制文件名、待办、问小埃 - 非标准 scheme:尝试打开、复制;thunder 解析 base64 真实地址,magnet 解析 dn - 文字动作:ICU 分词、正则提取链接、朗读(spd-say)、复制、待办 - 文件/图片动作:打开、提取文字(占位)、生成描述、保存图片、复制路径、待办 - Downloader:异步下载到 XDG 下载目录 / 另存为,面板 toast 提示 - TodoModel:持久化到 todo.json,供「添加到待办」使用 - 无头自测 gxde-ai-see-selftest:链接分类/分词/提取链接/动作匹配 - 合成器 portal 自测脚本 tests/portal(虚拟指针 + GTK 源/目标)
129 lines
3.4 KiB
C++
129 lines
3.4 KiB
C++
// SPDX-FileCopyrightText: 2026 GXDE OS Team
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
#include "contentclassifier.h"
|
|
|
|
#include <QFileInfo>
|
|
#include <QImageReader>
|
|
#include <QMimeData>
|
|
#include <QUrl>
|
|
|
|
namespace {
|
|
|
|
QString extensionOfFile(const QString &path)
|
|
{
|
|
return QFileInfo(path).suffix().toLower();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
QString DropContent::summary() const
|
|
{
|
|
switch (kind) {
|
|
case Files: {
|
|
if (filePaths.size() == 1) {
|
|
const QFileInfo info(filePaths.first());
|
|
return QStringLiteral("文件 · %1").arg(info.fileName());
|
|
}
|
|
return QStringLiteral("%1 个文件").arg(filePaths.size());
|
|
}
|
|
case Image:
|
|
return QStringLiteral("图片");
|
|
case Text:
|
|
return QStringLiteral("文字 · %1 字").arg(text.size());
|
|
case Link:
|
|
case CustomLink: {
|
|
const QString label = link.fileType != QStringLiteral("page") && !link.fileType.isEmpty()
|
|
? link.fileType
|
|
: link.kindLabel();
|
|
return link.displayName.isEmpty() ? label
|
|
: QStringLiteral("%1 · %2").arg(label, link.displayName);
|
|
}
|
|
default:
|
|
return QString();
|
|
}
|
|
}
|
|
|
|
namespace ContentClassifier {
|
|
|
|
DropContent classifyText(const QString &text)
|
|
{
|
|
DropContent content;
|
|
const QString trimmed = text.trimmed();
|
|
if (trimmed.isEmpty()) {
|
|
return content;
|
|
}
|
|
|
|
const LinkInfo info = LinkTools::parse(trimmed);
|
|
if (info.isValid()) {
|
|
content.kind = info.kind == LinkInfo::Http ? DropContent::Link : DropContent::CustomLink;
|
|
content.link = info;
|
|
content.text = trimmed;
|
|
return content;
|
|
}
|
|
|
|
content.kind = DropContent::Text;
|
|
content.text = text;
|
|
return content;
|
|
}
|
|
|
|
DropContent classify(const QMimeData *mime)
|
|
{
|
|
DropContent content;
|
|
if (!mime) {
|
|
return content;
|
|
}
|
|
|
|
content.mimes = mime->formats();
|
|
|
|
if (mime->hasUrls()) {
|
|
QStringList localFiles;
|
|
LinkInfo link;
|
|
for (const QUrl &url : mime->urls()) {
|
|
if (url.isLocalFile()) {
|
|
localFiles.append(url.toLocalFile());
|
|
} else if (!link.isValid()) {
|
|
link = LinkTools::parseUrl(url);
|
|
}
|
|
}
|
|
|
|
if (!localFiles.isEmpty()) {
|
|
content.kind = DropContent::Files;
|
|
content.filePaths = localFiles;
|
|
if (localFiles.size() == 1) {
|
|
const QString suffix = extensionOfFile(localFiles.first());
|
|
if (LinkTools::fileTypeForExtension(suffix) == QStringLiteral("image") &&
|
|
QImageReader(localFiles.first()).canRead()) {
|
|
content.kind = DropContent::Image;
|
|
content.image = QImage(localFiles.first());
|
|
}
|
|
}
|
|
return content;
|
|
}
|
|
|
|
if (link.isValid()) {
|
|
content.kind =
|
|
link.kind == LinkInfo::Http ? DropContent::Link : DropContent::CustomLink;
|
|
content.link = link;
|
|
content.text = link.url.toString();
|
|
return content;
|
|
}
|
|
}
|
|
|
|
if (mime->hasImage()) {
|
|
content.image = qvariant_cast<QImage>(mime->imageData());
|
|
if (!content.image.isNull()) {
|
|
content.kind = DropContent::Image;
|
|
return content;
|
|
}
|
|
}
|
|
|
|
if (mime->hasText()) {
|
|
return classifyText(mime->text());
|
|
}
|
|
|
|
return content;
|
|
}
|
|
|
|
} // namespace ContentClassifier
|