Add intent dispatch scaffold
This commit is contained in:
@@ -28,6 +28,11 @@ qt_add_executable(QtDesktopPet
|
|||||||
src/ai/GoogleGeminiProvider.cpp
|
src/ai/GoogleGeminiProvider.cpp
|
||||||
src/ai/OpenAICompatibleProvider.h
|
src/ai/OpenAICompatibleProvider.h
|
||||||
src/ai/OpenAICompatibleProvider.cpp
|
src/ai/OpenAICompatibleProvider.cpp
|
||||||
|
src/assistant/CommandDispatcher.h
|
||||||
|
src/assistant/CommandDispatcher.cpp
|
||||||
|
src/assistant/IntentRouter.h
|
||||||
|
src/assistant/IntentRouter.cpp
|
||||||
|
src/assistant/UserIntent.h
|
||||||
src/character/AnimationClip.h
|
src/character/AnimationClip.h
|
||||||
src/character/AnimationClip.cpp
|
src/character/AnimationClip.cpp
|
||||||
src/character/CharacterPackage.h
|
src/character/CharacterPackage.h
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,50 @@
|
|||||||
|
#include "CommandDispatcher.h"
|
||||||
|
|
||||||
|
QString userIntentTypeName(UserIntentType type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case UserIntentType::Chat:
|
||||||
|
return QStringLiteral("Chat");
|
||||||
|
case UserIntentType::Reminder:
|
||||||
|
return QStringLiteral("Reminder");
|
||||||
|
case UserIntentType::Weather:
|
||||||
|
return QStringLiteral("Weather");
|
||||||
|
case UserIntentType::FileOperation:
|
||||||
|
return QStringLiteral("FileOperation");
|
||||||
|
case UserIntentType::Search:
|
||||||
|
return QStringLiteral("Search");
|
||||||
|
}
|
||||||
|
|
||||||
|
return QStringLiteral("Unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandDispatchResult CommandDispatcher::dispatch(const QString &text) const
|
||||||
|
{
|
||||||
|
const UserIntent intent = m_intentRouter.route(text);
|
||||||
|
if (intent.type == UserIntentType::Chat)
|
||||||
|
{
|
||||||
|
return {CommandDispatchAction::Chat, intent, intent.text};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {CommandDispatchAction::UnsupportedTool, intent, unsupportedToolMessage(intent.type)};
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CommandDispatcher::unsupportedToolMessage(UserIntentType type) const
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case UserIntentType::Reminder:
|
||||||
|
return QStringLiteral("提醒功能尚未接入,我现在还不能创建本地提醒。");
|
||||||
|
case UserIntentType::Weather:
|
||||||
|
return QStringLiteral("天气查询功能尚未接入,我现在还不能查询实时天气。");
|
||||||
|
case UserIntentType::FileOperation:
|
||||||
|
return QStringLiteral("本地文件操作功能尚未接入。为避免风险,我现在不会读写或删除文件。");
|
||||||
|
case UserIntentType::Search:
|
||||||
|
return QStringLiteral("联网搜索功能尚未接入,我现在不会发起搜索请求。");
|
||||||
|
case UserIntentType::Chat:
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return QStringLiteral("该工具功能尚未接入。");
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "IntentRouter.h"
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
enum class CommandDispatchAction
|
||||||
|
{
|
||||||
|
Chat,
|
||||||
|
UnsupportedTool,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CommandDispatchResult
|
||||||
|
{
|
||||||
|
CommandDispatchAction action = CommandDispatchAction::Chat;
|
||||||
|
UserIntent intent;
|
||||||
|
QString message;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CommandDispatcher
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CommandDispatchResult dispatch(const QString &text) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString unsupportedToolMessage(UserIntentType type) const;
|
||||||
|
|
||||||
|
IntentRouter m_intentRouter;
|
||||||
|
};
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
#include "IntentRouter.h"
|
||||||
|
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
bool containsAny(const QString &text, const QStringList &keywords)
|
||||||
|
{
|
||||||
|
for (const QString &keyword : keywords)
|
||||||
|
{
|
||||||
|
if (text.contains(keyword, Qt::CaseInsensitive))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isReminderIntent(const QString &text)
|
||||||
|
{
|
||||||
|
static const QStringList keywords = {
|
||||||
|
QStringLiteral("提醒"),
|
||||||
|
QStringLiteral("提醒我"),
|
||||||
|
QStringLiteral("叫我"),
|
||||||
|
QStringLiteral("闹钟"),
|
||||||
|
QStringLiteral("到点"),
|
||||||
|
};
|
||||||
|
return containsAny(text, keywords);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isFileOperationIntent(const QString &text)
|
||||||
|
{
|
||||||
|
static const QStringList keywords = {
|
||||||
|
QStringLiteral("文件"),
|
||||||
|
QStringLiteral("文件夹"),
|
||||||
|
QStringLiteral("目录"),
|
||||||
|
QStringLiteral("保存到"),
|
||||||
|
QStringLiteral("截图"),
|
||||||
|
QStringLiteral("打包"),
|
||||||
|
QStringLiteral("压缩"),
|
||||||
|
QStringLiteral("复制"),
|
||||||
|
QStringLiteral("备份"),
|
||||||
|
QStringLiteral("重命名"),
|
||||||
|
QStringLiteral("删除"),
|
||||||
|
QStringLiteral("移动"),
|
||||||
|
QStringLiteral("读取"),
|
||||||
|
QStringLiteral(".txt"),
|
||||||
|
QStringLiteral(".md"),
|
||||||
|
QStringLiteral("日志"),
|
||||||
|
};
|
||||||
|
return containsAny(text, keywords);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isWeatherIntent(const QString &text)
|
||||||
|
{
|
||||||
|
static const QStringList keywords = {
|
||||||
|
QStringLiteral("天气"),
|
||||||
|
QStringLiteral("气温"),
|
||||||
|
QStringLiteral("温度"),
|
||||||
|
QStringLiteral("冷不冷"),
|
||||||
|
QStringLiteral("热不热"),
|
||||||
|
QStringLiteral("下雨"),
|
||||||
|
QStringLiteral("降雨"),
|
||||||
|
QStringLiteral("带伞"),
|
||||||
|
QStringLiteral("刮风"),
|
||||||
|
QStringLiteral("风力"),
|
||||||
|
QStringLiteral("湿度"),
|
||||||
|
QStringLiteral("空气质量"),
|
||||||
|
QStringLiteral("雾霾"),
|
||||||
|
QStringLiteral("适合出门"),
|
||||||
|
QStringLiteral("穿什么"),
|
||||||
|
QStringLiteral("外面怎么样"),
|
||||||
|
};
|
||||||
|
return containsAny(text, keywords);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isSearchIntent(const QString &text)
|
||||||
|
{
|
||||||
|
static const QStringList keywords = {
|
||||||
|
QStringLiteral("搜索"),
|
||||||
|
QStringLiteral("搜一下"),
|
||||||
|
QStringLiteral("查一下"),
|
||||||
|
QStringLiteral("查找"),
|
||||||
|
QStringLiteral("联网"),
|
||||||
|
QStringLiteral("最新"),
|
||||||
|
QStringLiteral("官网"),
|
||||||
|
QStringLiteral("新闻"),
|
||||||
|
QStringLiteral("版本"),
|
||||||
|
QStringLiteral("文档"),
|
||||||
|
QStringLiteral("报错"),
|
||||||
|
};
|
||||||
|
return containsAny(text, keywords);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UserIntent IntentRouter::route(const QString &text) const
|
||||||
|
{
|
||||||
|
const QString trimmedText = text.trimmed();
|
||||||
|
|
||||||
|
if (isReminderIntent(trimmedText))
|
||||||
|
{
|
||||||
|
return {UserIntentType::Reminder, trimmedText};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFileOperationIntent(trimmedText))
|
||||||
|
{
|
||||||
|
return {UserIntentType::FileOperation, trimmedText};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isWeatherIntent(trimmedText))
|
||||||
|
{
|
||||||
|
return {UserIntentType::Weather, trimmedText};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSearchIntent(trimmedText))
|
||||||
|
{
|
||||||
|
return {UserIntentType::Search, trimmedText};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {UserIntentType::Chat, trimmedText};
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "UserIntent.h"
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
class IntentRouter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
UserIntent route(const QString &text) const;
|
||||||
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
enum class UserIntentType
|
||||||
|
{
|
||||||
|
Chat,
|
||||||
|
Reminder,
|
||||||
|
Weather,
|
||||||
|
FileOperation,
|
||||||
|
Search,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UserIntent
|
||||||
|
{
|
||||||
|
UserIntentType type = UserIntentType::Chat;
|
||||||
|
QString text;
|
||||||
|
};
|
||||||
|
|
||||||
|
QString userIntentTypeName(UserIntentType type);
|
||||||
+31
-4
@@ -3,6 +3,7 @@
|
|||||||
#include "../ai/AIProviderFactory.h"
|
#include "../ai/AIProviderFactory.h"
|
||||||
#include "../ai/ConversationManager.h"
|
#include "../ai/ConversationManager.h"
|
||||||
#include "../ai/ConversationStore.h"
|
#include "../ai/ConversationStore.h"
|
||||||
|
#include "../assistant/CommandDispatcher.h"
|
||||||
#include "../character/CharacterPackageLoader.h"
|
#include "../character/CharacterPackageLoader.h"
|
||||||
#include "../character/CharacterPackageRepository.h"
|
#include "../character/CharacterPackageRepository.h"
|
||||||
#include "../config/ConfigManager.h"
|
#include "../config/ConfigManager.h"
|
||||||
@@ -460,19 +461,45 @@ bool PetWindow::submitChatMessage(const QString &message)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString trimmedMessage = message.trimmed();
|
const QString normalizedMessage = message.trimmed();
|
||||||
if (trimmedMessage.isEmpty())
|
if (normalizedMessage.isEmpty())
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trimmedMessage.size() > MaxUserMessageLength)
|
if (normalizedMessage.size() > MaxUserMessageLength)
|
||||||
{
|
{
|
||||||
playState(QStringLiteral("error"), false);
|
playState(QStringLiteral("error"), false);
|
||||||
showBubbleMessage(QStringLiteral("消息太长,请控制在 4000 字以内。"));
|
showBubbleMessage(QStringLiteral("消息太长,请控制在 4000 字以内。"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CommandDispatcher dispatcher;
|
||||||
|
const CommandDispatchResult result = dispatcher.dispatch(normalizedMessage);
|
||||||
|
if (result.action == CommandDispatchAction::UnsupportedTool)
|
||||||
|
{
|
||||||
|
playState(QStringLiteral("talk"), false);
|
||||||
|
showBubbleMessage(result.message);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return submitAiChatMessage(result.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PetWindow::submitAiChatMessage(const QString &message)
|
||||||
|
{
|
||||||
|
if (!m_conversationManager || m_conversationManager->isBusy())
|
||||||
|
{
|
||||||
|
showBubbleMessage(QStringLiteral("AI 回复正在进行。"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString chatMessage = message.trimmed();
|
||||||
|
if (chatMessage.isEmpty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
ConfigManager configManager;
|
ConfigManager configManager;
|
||||||
AIConfig config = configManager.loadAIConfig();
|
AIConfig config = configManager.loadAIConfig();
|
||||||
QString errorMessage;
|
QString errorMessage;
|
||||||
@@ -507,7 +534,7 @@ bool PetWindow::submitChatMessage(const QString &message)
|
|||||||
|
|
||||||
QPointer<PetWindow> window(this);
|
QPointer<PetWindow> window(this);
|
||||||
m_conversationManager->sendUserMessageStreaming(
|
m_conversationManager->sendUserMessageStreaming(
|
||||||
trimmedMessage,
|
chatMessage,
|
||||||
[window](const QString &delta) {
|
[window](const QString &delta) {
|
||||||
if (!window.isNull())
|
if (!window.isNull())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ private:
|
|||||||
void addStateTestActions(QMenu *menu);
|
void addStateTestActions(QMenu *menu);
|
||||||
void startChat();
|
void startChat();
|
||||||
bool submitChatMessage(const QString &message);
|
bool submitChatMessage(const QString &message);
|
||||||
|
bool submitAiChatMessage(const QString &message);
|
||||||
void clearConversation();
|
void clearConversation();
|
||||||
void cancelActiveAIRequest();
|
void cancelActiveAIRequest();
|
||||||
void showConversationHistory();
|
void showConversationHistory();
|
||||||
|
|||||||
Reference in New Issue
Block a user