mirror of
https://github.com/kuaifan/dootask.git
synced 2026-03-27 15:50:43 +00:00
feat: 新增对话ID参数支持,优化搜索功能以支持对话过滤
This commit is contained in:
parent
a52dc14369
commit
908171a977
@ -47,7 +47,7 @@ class SearchController extends AbstractController
|
|||||||
|
|
||||||
$key = trim(Request::input('key'));
|
$key = trim(Request::input('key'));
|
||||||
$searchType = Request::input('search_type', 'hybrid');
|
$searchType = Request::input('search_type', 'hybrid');
|
||||||
$take = min(50, max(1, intval(Request::input('take', 20))));
|
$take = Base::getPaginate(50, 20, 'take');
|
||||||
|
|
||||||
if (empty($key)) {
|
if (empty($key)) {
|
||||||
return Base::retSuccess('success', []);
|
return Base::retSuccess('success', []);
|
||||||
@ -103,7 +103,7 @@ class SearchController extends AbstractController
|
|||||||
|
|
||||||
$key = trim(Request::input('key'));
|
$key = trim(Request::input('key'));
|
||||||
$searchType = Request::input('search_type', 'hybrid');
|
$searchType = Request::input('search_type', 'hybrid');
|
||||||
$take = min(50, max(1, intval(Request::input('take', 20))));
|
$take = Base::getPaginate(50, 20, 'take');
|
||||||
|
|
||||||
if (empty($key)) {
|
if (empty($key)) {
|
||||||
return Base::retSuccess('success', []);
|
return Base::retSuccess('success', []);
|
||||||
@ -158,7 +158,7 @@ class SearchController extends AbstractController
|
|||||||
|
|
||||||
$key = trim(Request::input('key'));
|
$key = trim(Request::input('key'));
|
||||||
$searchType = Request::input('search_type', 'hybrid');
|
$searchType = Request::input('search_type', 'hybrid');
|
||||||
$take = min(50, max(1, intval(Request::input('take', 20))));
|
$take = Base::getPaginate(50, 20, 'take');
|
||||||
|
|
||||||
if (empty($key)) {
|
if (empty($key)) {
|
||||||
return Base::retSuccess('success', []);
|
return Base::retSuccess('success', []);
|
||||||
@ -214,7 +214,7 @@ class SearchController extends AbstractController
|
|||||||
|
|
||||||
$key = trim(Request::input('key'));
|
$key = trim(Request::input('key'));
|
||||||
$searchType = Request::input('search_type', 'hybrid');
|
$searchType = Request::input('search_type', 'hybrid');
|
||||||
$take = min(50, max(1, intval(Request::input('take', 20))));
|
$take = Base::getPaginate(50, 20, 'take');
|
||||||
|
|
||||||
if (empty($key)) {
|
if (empty($key)) {
|
||||||
return Base::retSuccess('success', []);
|
return Base::retSuccess('success', []);
|
||||||
@ -256,6 +256,11 @@ class SearchController extends AbstractController
|
|||||||
* @apiParam {String} key 搜索关键词
|
* @apiParam {String} key 搜索关键词
|
||||||
* @apiParam {String} [search_type] 搜索类型(text/vector/hybrid,默认:hybrid)
|
* @apiParam {String} [search_type] 搜索类型(text/vector/hybrid,默认:hybrid)
|
||||||
* @apiParam {Number} [take] 获取数量(默认:20,最大:50)
|
* @apiParam {Number} [take] 获取数量(默认:20,最大:50)
|
||||||
|
* @apiParam {String} [mode] 返回模式(message/position/dialog,默认:message)
|
||||||
|
* - message: 返回消息详细信息
|
||||||
|
* - position: 只返回消息ID
|
||||||
|
* - dialog: 返回对话级数据
|
||||||
|
* @apiParam {Number} [dialog_id] 对话ID(筛选指定对话内的消息)
|
||||||
*
|
*
|
||||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||||
@ -271,46 +276,89 @@ class SearchController extends AbstractController
|
|||||||
|
|
||||||
$key = trim(Request::input('key'));
|
$key = trim(Request::input('key'));
|
||||||
$searchType = Request::input('search_type', 'hybrid');
|
$searchType = Request::input('search_type', 'hybrid');
|
||||||
$take = min(50, max(1, intval(Request::input('take', 20))));
|
$take = Base::getPaginate(50, 20, 'take');
|
||||||
|
$mode = Request::input('mode', 'message');
|
||||||
|
$dialogId = intval(Request::input('dialog_id', 0));
|
||||||
|
|
||||||
|
// 验证 mode 参数
|
||||||
|
if (!in_array($mode, ['message', 'position', 'dialog'])) {
|
||||||
|
$mode = 'message';
|
||||||
|
}
|
||||||
|
|
||||||
if (empty($key)) {
|
if (empty($key)) {
|
||||||
return Base::retSuccess('success', []);
|
return Base::retSuccess('success', []);
|
||||||
}
|
}
|
||||||
|
|
||||||
$results = ManticoreMsg::search($user->userid, $key, $searchType, 0, $take);
|
// 如果指定了 dialog_id,需要验证用户有权限访问该对话
|
||||||
|
if ($dialogId > 0) {
|
||||||
// 补充消息完整信息
|
\App\Models\WebSocketDialog::checkDialog($dialogId);
|
||||||
$msgIds = array_column($results, 'msg_id');
|
|
||||||
if (!empty($msgIds)) {
|
|
||||||
$msgs = WebSocketDialogMsg::whereIn('id', $msgIds)
|
|
||||||
->with(['user' => function ($query) {
|
|
||||||
$query->select(User::$basicField);
|
|
||||||
}])
|
|
||||||
->get()
|
|
||||||
->keyBy('id');
|
|
||||||
|
|
||||||
$formattedResults = [];
|
|
||||||
foreach ($results as $item) {
|
|
||||||
$msgData = $msgs->get($item['msg_id']);
|
|
||||||
if ($msgData) {
|
|
||||||
$formattedResults[] = [
|
|
||||||
'id' => $msgData->id,
|
|
||||||
'msg_id' => $msgData->id,
|
|
||||||
'dialog_id' => $msgData->dialog_id,
|
|
||||||
'userid' => $msgData->userid,
|
|
||||||
'type' => $msgData->type,
|
|
||||||
'msg' => $msgData->msg,
|
|
||||||
'created_at' => $msgData->created_at,
|
|
||||||
'user' => $msgData->user,
|
|
||||||
'relevance' => $item['relevance'] ?? 0,
|
|
||||||
'content_preview' => $item['content_preview'] ?? null,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Base::retSuccess('success', $formattedResults);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Base::retSuccess('success', []);
|
$results = ManticoreMsg::search($user->userid, $key, $searchType, 0, $take, $dialogId);
|
||||||
|
|
||||||
|
// 根据 mode 返回不同格式的数据
|
||||||
|
switch ($mode) {
|
||||||
|
case 'position':
|
||||||
|
// 只返回消息ID
|
||||||
|
$data = array_column($results, 'msg_id');
|
||||||
|
return Base::retSuccess('success', compact('data'));
|
||||||
|
|
||||||
|
case 'dialog':
|
||||||
|
// 返回对话级数据
|
||||||
|
$list = [];
|
||||||
|
$seenDialogs = [];
|
||||||
|
foreach ($results as $item) {
|
||||||
|
$dialogIdFromResult = $item['dialog_id'];
|
||||||
|
// 每个对话只返回一次
|
||||||
|
if (isset($seenDialogs[$dialogIdFromResult])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$seenDialogs[$dialogIdFromResult] = true;
|
||||||
|
|
||||||
|
if ($dialog = \App\Models\WebSocketDialog::find($dialogIdFromResult)) {
|
||||||
|
$dialogData = array_merge($dialog->toArray(), [
|
||||||
|
'search_msg_id' => $item['msg_id'],
|
||||||
|
]);
|
||||||
|
$list[] = \App\Models\WebSocketDialog::synthesizeData($dialogData, $user->userid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Base::retSuccess('success', ['data' => $list]);
|
||||||
|
|
||||||
|
case 'message':
|
||||||
|
default:
|
||||||
|
// 返回消息详细信息(默认行为)
|
||||||
|
$msgIds = array_column($results, 'msg_id');
|
||||||
|
if (!empty($msgIds)) {
|
||||||
|
$msgs = WebSocketDialogMsg::whereIn('id', $msgIds)
|
||||||
|
->with(['user' => function ($query) {
|
||||||
|
$query->select(User::$basicField);
|
||||||
|
}])
|
||||||
|
->get()
|
||||||
|
->keyBy('id');
|
||||||
|
|
||||||
|
$formattedResults = [];
|
||||||
|
foreach ($results as $item) {
|
||||||
|
$msgData = $msgs->get($item['msg_id']);
|
||||||
|
if ($msgData) {
|
||||||
|
$formattedResults[] = [
|
||||||
|
'id' => $msgData->id,
|
||||||
|
'msg_id' => $msgData->id,
|
||||||
|
'dialog_id' => $msgData->dialog_id,
|
||||||
|
'userid' => $msgData->userid,
|
||||||
|
'type' => $msgData->type,
|
||||||
|
'msg' => $msgData->msg,
|
||||||
|
'created_at' => $msgData->created_at,
|
||||||
|
'user' => $msgData->user,
|
||||||
|
'relevance' => $item['relevance'] ?? 0,
|
||||||
|
'content_preview' => $item['content_preview'] ?? null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Base::retSuccess('success', $formattedResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Base::retSuccess('success', []);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1519,9 +1519,10 @@ class ManticoreBase
|
|||||||
* @param int $userid 用户ID(权限过滤)
|
* @param int $userid 用户ID(权限过滤)
|
||||||
* @param int $limit 返回数量
|
* @param int $limit 返回数量
|
||||||
* @param int $offset 偏移量
|
* @param int $offset 偏移量
|
||||||
|
* @param int $dialogId 对话ID(0表示不限制)
|
||||||
* @return array 搜索结果
|
* @return array 搜索结果
|
||||||
*/
|
*/
|
||||||
public static function msgFullTextSearch(string $keyword, int $userid = 0, int $limit = 20, int $offset = 0): array
|
public static function msgFullTextSearch(string $keyword, int $userid = 0, int $limit = 20, int $offset = 0, int $dialogId = 0): array
|
||||||
{
|
{
|
||||||
if (empty($keyword)) {
|
if (empty($keyword)) {
|
||||||
return [];
|
return [];
|
||||||
@ -1530,39 +1531,30 @@ class ManticoreBase
|
|||||||
$instance = new self();
|
$instance = new self();
|
||||||
$escapedKeyword = self::escapeMatch($keyword);
|
$escapedKeyword = self::escapeMatch($keyword);
|
||||||
|
|
||||||
|
// 构建过滤条件
|
||||||
|
$conditions = ["MATCH('@content {$escapedKeyword}')"];
|
||||||
if ($userid > 0) {
|
if ($userid > 0) {
|
||||||
// 使用 MVA 权限过滤
|
$conditions[] = "allowed_users = " . (int)$userid;
|
||||||
$sql = "
|
|
||||||
SELECT
|
|
||||||
id,
|
|
||||||
msg_id,
|
|
||||||
dialog_id,
|
|
||||||
userid,
|
|
||||||
msg_type,
|
|
||||||
content,
|
|
||||||
created_at,
|
|
||||||
WEIGHT() as relevance
|
|
||||||
FROM msg_vectors
|
|
||||||
WHERE MATCH('@content {$escapedKeyword}')
|
|
||||||
AND allowed_users = " . (int)$userid . "
|
|
||||||
ORDER BY relevance DESC
|
|
||||||
LIMIT " . (int)$limit . " OFFSET " . (int)$offset;
|
|
||||||
} else {
|
|
||||||
$sql = "
|
|
||||||
SELECT
|
|
||||||
id,
|
|
||||||
msg_id,
|
|
||||||
dialog_id,
|
|
||||||
userid,
|
|
||||||
msg_type,
|
|
||||||
content,
|
|
||||||
created_at,
|
|
||||||
WEIGHT() as relevance
|
|
||||||
FROM msg_vectors
|
|
||||||
WHERE MATCH('@content {$escapedKeyword}')
|
|
||||||
ORDER BY relevance DESC
|
|
||||||
LIMIT " . (int)$limit . " OFFSET " . (int)$offset;
|
|
||||||
}
|
}
|
||||||
|
if ($dialogId > 0) {
|
||||||
|
$conditions[] = "dialog_id = " . (int)$dialogId;
|
||||||
|
}
|
||||||
|
$whereClause = implode(' AND ', $conditions);
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
msg_id,
|
||||||
|
dialog_id,
|
||||||
|
userid,
|
||||||
|
msg_type,
|
||||||
|
content,
|
||||||
|
created_at,
|
||||||
|
WEIGHT() as relevance
|
||||||
|
FROM msg_vectors
|
||||||
|
WHERE {$whereClause}
|
||||||
|
ORDER BY relevance DESC
|
||||||
|
LIMIT " . (int)$limit . " OFFSET " . (int)$offset;
|
||||||
|
|
||||||
return $instance->query($sql);
|
return $instance->query($sql);
|
||||||
}
|
}
|
||||||
@ -1573,9 +1565,10 @@ class ManticoreBase
|
|||||||
* @param array $queryVector 查询向量
|
* @param array $queryVector 查询向量
|
||||||
* @param int $userid 用户ID(权限过滤)
|
* @param int $userid 用户ID(权限过滤)
|
||||||
* @param int $limit 返回数量
|
* @param int $limit 返回数量
|
||||||
|
* @param int $dialogId 对话ID(0表示不限制)
|
||||||
* @return array 搜索结果
|
* @return array 搜索结果
|
||||||
*/
|
*/
|
||||||
public static function msgVectorSearch(array $queryVector, int $userid = 0, int $limit = 20): array
|
public static function msgVectorSearch(array $queryVector, int $userid = 0, int $limit = 20, int $dialogId = 0): array
|
||||||
{
|
{
|
||||||
if (empty($queryVector)) {
|
if (empty($queryVector)) {
|
||||||
return [];
|
return [];
|
||||||
@ -1584,8 +1577,9 @@ class ManticoreBase
|
|||||||
$instance = new self();
|
$instance = new self();
|
||||||
$vectorStr = '(' . implode(',', $queryVector) . ')';
|
$vectorStr = '(' . implode(',', $queryVector) . ')';
|
||||||
|
|
||||||
// KNN 搜索需要先获取更多结果,再在应用层过滤权限
|
// KNN 搜索需要先获取更多结果,再在应用层过滤权限和对话
|
||||||
$fetchLimit = $userid > 0 ? $limit * 5 : $limit;
|
$needFilter = $userid > 0 || $dialogId > 0;
|
||||||
|
$fetchLimit = $needFilter ? $limit * 5 : $limit;
|
||||||
|
|
||||||
$sql = "
|
$sql = "
|
||||||
SELECT
|
SELECT
|
||||||
@ -1622,6 +1616,14 @@ class ManticoreBase
|
|||||||
$results = array_values($results);
|
$results = array_values($results);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 对话过滤
|
||||||
|
if ($dialogId > 0 && !empty($results)) {
|
||||||
|
$results = array_filter($results, function ($item) use ($dialogId) {
|
||||||
|
return $item['dialog_id'] == $dialogId;
|
||||||
|
});
|
||||||
|
$results = array_values($results);
|
||||||
|
}
|
||||||
|
|
||||||
return array_slice($results, 0, $limit);
|
return array_slice($results, 0, $limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1632,12 +1634,13 @@ class ManticoreBase
|
|||||||
* @param array $queryVector 查询向量
|
* @param array $queryVector 查询向量
|
||||||
* @param int $userid 用户ID(权限过滤)
|
* @param int $userid 用户ID(权限过滤)
|
||||||
* @param int $limit 返回数量
|
* @param int $limit 返回数量
|
||||||
|
* @param int $dialogId 对话ID(0表示不限制)
|
||||||
* @return array 搜索结果
|
* @return array 搜索结果
|
||||||
*/
|
*/
|
||||||
public static function msgHybridSearch(string $keyword, array $queryVector, int $userid = 0, int $limit = 20): array
|
public static function msgHybridSearch(string $keyword, array $queryVector, int $userid = 0, int $limit = 20, int $dialogId = 0): array
|
||||||
{
|
{
|
||||||
$textResults = self::msgFullTextSearch($keyword, $userid, 50, 0);
|
$textResults = self::msgFullTextSearch($keyword, $userid, 50, 0, $dialogId);
|
||||||
$vectorResults = !empty($queryVector) ? self::msgVectorSearch($queryVector, $userid, 50) : [];
|
$vectorResults = !empty($queryVector) ? self::msgVectorSearch($queryVector, $userid, 50, $dialogId) : [];
|
||||||
|
|
||||||
$scores = [];
|
$scores = [];
|
||||||
$items = [];
|
$items = [];
|
||||||
|
|||||||
@ -77,9 +77,10 @@ class ManticoreMsg
|
|||||||
* @param string $searchType 搜索类型: text/vector/hybrid
|
* @param string $searchType 搜索类型: text/vector/hybrid
|
||||||
* @param int $from 起始位置
|
* @param int $from 起始位置
|
||||||
* @param int $size 返回数量
|
* @param int $size 返回数量
|
||||||
|
* @param int $dialogId 对话ID(0表示不限制)
|
||||||
* @return array 搜索结果
|
* @return array 搜索结果
|
||||||
*/
|
*/
|
||||||
public static function search(int $userid, string $keyword, string $searchType = 'hybrid', int $from = 0, int $size = 20): array
|
public static function search(int $userid, string $keyword, string $searchType = 'hybrid', int $from = 0, int $size = 20, int $dialogId = 0): array
|
||||||
{
|
{
|
||||||
if (empty($keyword)) {
|
if (empty($keyword)) {
|
||||||
return [];
|
return [];
|
||||||
@ -94,7 +95,7 @@ class ManticoreMsg
|
|||||||
case 'text':
|
case 'text':
|
||||||
// 纯全文搜索
|
// 纯全文搜索
|
||||||
return self::formatSearchResults(
|
return self::formatSearchResults(
|
||||||
ManticoreBase::msgFullTextSearch($keyword, $userid, $size, $from)
|
ManticoreBase::msgFullTextSearch($keyword, $userid, $size, $from, $dialogId)
|
||||||
);
|
);
|
||||||
|
|
||||||
case 'vector':
|
case 'vector':
|
||||||
@ -103,11 +104,11 @@ class ManticoreMsg
|
|||||||
if (empty($embedding)) {
|
if (empty($embedding)) {
|
||||||
// embedding 获取失败,降级到全文搜索
|
// embedding 获取失败,降级到全文搜索
|
||||||
return self::formatSearchResults(
|
return self::formatSearchResults(
|
||||||
ManticoreBase::msgFullTextSearch($keyword, $userid, $size, $from)
|
ManticoreBase::msgFullTextSearch($keyword, $userid, $size, $from, $dialogId)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return self::formatSearchResults(
|
return self::formatSearchResults(
|
||||||
ManticoreBase::msgVectorSearch($embedding, $userid, $size)
|
ManticoreBase::msgVectorSearch($embedding, $userid, $size, $dialogId)
|
||||||
);
|
);
|
||||||
|
|
||||||
case 'hybrid':
|
case 'hybrid':
|
||||||
@ -115,7 +116,7 @@ class ManticoreMsg
|
|||||||
// 混合搜索
|
// 混合搜索
|
||||||
$embedding = self::getEmbedding($keyword);
|
$embedding = self::getEmbedding($keyword);
|
||||||
return self::formatSearchResults(
|
return self::formatSearchResults(
|
||||||
ManticoreBase::msgHybridSearch($keyword, $embedding, $userid, $size)
|
ManticoreBase::msgHybridSearch($keyword, $embedding, $userid, $size, $dialogId)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user