mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-05-17 21:44:43 +02:00
Add files via upload
This commit is contained in:
@@ -205,7 +205,7 @@ func (db *DB) AddConversationToGroup(conversationID, groupID string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("删除对话旧分组关联失败: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// 然后插入新的分组关联
|
||||
id := uuid.New().String()
|
||||
_, err = db.Exec(
|
||||
@@ -282,6 +282,78 @@ func (db *DB) GetConversationsByGroup(groupID string) ([]*Conversation, error) {
|
||||
return conversations, nil
|
||||
}
|
||||
|
||||
// SearchConversationsByGroup 搜索分组中的对话(按标题和消息内容模糊匹配)
|
||||
func (db *DB) SearchConversationsByGroup(groupID string, searchQuery string) ([]*Conversation, error) {
|
||||
// 构建SQL查询,支持按标题和消息内容搜索
|
||||
// 使用 DISTINCT 避免因为一个对话有多条匹配消息而重复
|
||||
query := `SELECT DISTINCT c.id, c.title, COALESCE(c.pinned, 0), c.created_at, c.updated_at, COALESCE(cgm.pinned, 0) as group_pinned
|
||||
FROM conversations c
|
||||
INNER JOIN conversation_group_mappings cgm ON c.id = cgm.conversation_id
|
||||
WHERE cgm.group_id = ?`
|
||||
|
||||
args := []interface{}{groupID}
|
||||
|
||||
// 如果有搜索关键词,添加标题和消息内容搜索条件
|
||||
if searchQuery != "" {
|
||||
searchPattern := "%" + searchQuery + "%"
|
||||
// 搜索标题或消息内容
|
||||
// 使用 LEFT JOIN 连接消息表,这样即使没有消息的对话也能被搜索到(通过标题)
|
||||
query += ` AND (
|
||||
LOWER(c.title) LIKE LOWER(?)
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM messages m
|
||||
WHERE m.conversation_id = c.id
|
||||
AND LOWER(m.content) LIKE LOWER(?)
|
||||
)
|
||||
)`
|
||||
args = append(args, searchPattern, searchPattern)
|
||||
}
|
||||
|
||||
query += " ORDER BY COALESCE(cgm.pinned, 0) DESC, c.updated_at DESC"
|
||||
|
||||
rows, err := db.Query(query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("搜索分组对话失败: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var conversations []*Conversation
|
||||
for rows.Next() {
|
||||
var conv Conversation
|
||||
var createdAt, updatedAt string
|
||||
var pinned int
|
||||
var groupPinned int
|
||||
|
||||
if err := rows.Scan(&conv.ID, &conv.Title, &pinned, &createdAt, &updatedAt, &groupPinned); err != nil {
|
||||
return nil, fmt.Errorf("扫描对话失败: %w", err)
|
||||
}
|
||||
|
||||
// 尝试多种时间格式解析
|
||||
var err1, err2 error
|
||||
conv.CreatedAt, err1 = time.Parse("2006-01-02 15:04:05.999999999-07:00", createdAt)
|
||||
if err1 != nil {
|
||||
conv.CreatedAt, err1 = time.Parse("2006-01-02 15:04:05", createdAt)
|
||||
}
|
||||
if err1 != nil {
|
||||
conv.CreatedAt, _ = time.Parse(time.RFC3339, createdAt)
|
||||
}
|
||||
|
||||
conv.UpdatedAt, err2 = time.Parse("2006-01-02 15:04:05.999999999-07:00", updatedAt)
|
||||
if err2 != nil {
|
||||
conv.UpdatedAt, err2 = time.Parse("2006-01-02 15:04:05", updatedAt)
|
||||
}
|
||||
if err2 != nil {
|
||||
conv.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt)
|
||||
}
|
||||
|
||||
conv.Pinned = pinned != 0
|
||||
|
||||
conversations = append(conversations, &conv)
|
||||
}
|
||||
|
||||
return conversations, nil
|
||||
}
|
||||
|
||||
// GetGroupByConversation 获取对话所属的分组
|
||||
func (db *DB) GetGroupByConversation(conversationID string) (string, error) {
|
||||
var groupID string
|
||||
|
||||
@@ -189,8 +189,18 @@ type GroupConversation struct {
|
||||
// GetGroupConversations 获取分组中的所有对话
|
||||
func (h *GroupHandler) GetGroupConversations(c *gin.Context) {
|
||||
groupID := c.Param("id")
|
||||
searchQuery := c.Query("search") // 获取搜索参数
|
||||
|
||||
var conversations []*database.Conversation
|
||||
var err error
|
||||
|
||||
// 如果有搜索关键词,使用搜索方法;否则使用普通方法
|
||||
if searchQuery != "" {
|
||||
conversations, err = h.db.SearchConversationsByGroup(groupID, searchQuery)
|
||||
} else {
|
||||
conversations, err = h.db.GetConversationsByGroup(groupID)
|
||||
}
|
||||
|
||||
conversations, err := h.db.GetConversationsByGroup(groupID)
|
||||
if err != nil {
|
||||
h.logger.Error("获取分组对话失败", zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
|
||||
@@ -5780,6 +5780,63 @@ header {
|
||||
border-color: var(--error-color);
|
||||
}
|
||||
|
||||
.group-search-container {
|
||||
padding: 12px 24px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
background: var(--bg-primary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.group-search-input-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.group-search-input {
|
||||
width: 100%;
|
||||
padding: 8px 36px 8px 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-secondary);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.group-search-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-color);
|
||||
background: var(--bg-primary);
|
||||
box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.1);
|
||||
}
|
||||
|
||||
.group-search-input::placeholder {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.group-search-clear-btn {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.group-search-clear-btn:hover {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.group-detail-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
|
||||
+117
-9
@@ -5148,7 +5148,8 @@ async function enterGroupDetail(groupId) {
|
||||
// 刷新分组列表,确保当前分组高亮显示
|
||||
await loadGroups();
|
||||
|
||||
loadGroupConversations(groupId);
|
||||
// 加载分组对话(如果有搜索查询则使用搜索查询)
|
||||
loadGroupConversations(groupId, currentGroupSearchQuery);
|
||||
} catch (error) {
|
||||
console.error('加载分组失败:', error);
|
||||
currentGroupId = null;
|
||||
@@ -5158,6 +5159,14 @@ async function enterGroupDetail(groupId) {
|
||||
// 退出分组详情
|
||||
function exitGroupDetail() {
|
||||
currentGroupId = null;
|
||||
currentGroupSearchQuery = ''; // 清除搜索状态
|
||||
|
||||
// 隐藏搜索框并清除搜索内容
|
||||
const searchContainer = document.getElementById('group-search-container');
|
||||
const searchInput = document.getElementById('group-search-input');
|
||||
if (searchContainer) searchContainer.style.display = 'none';
|
||||
if (searchInput) searchInput.value = '';
|
||||
|
||||
const sidebar = document.querySelector('.conversation-sidebar');
|
||||
const groupDetailPage = document.getElementById('group-detail-page');
|
||||
const chatContainer = document.querySelector('.chat-container');
|
||||
@@ -5172,7 +5181,7 @@ function exitGroupDetail() {
|
||||
}
|
||||
|
||||
// 加载分组中的对话
|
||||
async function loadGroupConversations(groupId) {
|
||||
async function loadGroupConversations(groupId, searchQuery = '') {
|
||||
try {
|
||||
if (!groupId) {
|
||||
console.error('loadGroupConversations: groupId is null or undefined');
|
||||
@@ -5190,10 +5199,20 @@ async function loadGroupConversations(groupId) {
|
||||
console.error('group-conversations-list element not found');
|
||||
return;
|
||||
}
|
||||
list.innerHTML = '<div style="padding: 40px; text-align: center; color: var(--text-muted);">加载中...</div>';
|
||||
|
||||
// 显示加载状态
|
||||
if (searchQuery) {
|
||||
list.innerHTML = '<div style="padding: 40px; text-align: center; color: var(--text-muted);">搜索中...</div>';
|
||||
} else {
|
||||
list.innerHTML = '<div style="padding: 40px; text-align: center; color: var(--text-muted);">加载中...</div>';
|
||||
}
|
||||
|
||||
// 确保使用正确的 groupId
|
||||
const url = `/api/groups/${groupId}/conversations`;
|
||||
// 构建URL,如果有搜索关键词则添加search参数
|
||||
let url = `/api/groups/${groupId}/conversations`;
|
||||
if (searchQuery && searchQuery.trim()) {
|
||||
url += '?search=' + encodeURIComponent(searchQuery.trim());
|
||||
}
|
||||
|
||||
const response = await apiFetch(url);
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to load conversations for group ${groupId}:`, response.statusText);
|
||||
@@ -5235,7 +5254,11 @@ async function loadGroupConversations(groupId) {
|
||||
list.innerHTML = '';
|
||||
|
||||
if (groupConvs.length === 0) {
|
||||
list.innerHTML = '<div style="padding: 40px; text-align: center; color: var(--text-muted);">该分组暂无对话</div>';
|
||||
if (searchQuery && searchQuery.trim()) {
|
||||
list.innerHTML = '<div style="padding: 40px; text-align: center; color: var(--text-muted);">未找到匹配的对话</div>';
|
||||
} else {
|
||||
list.innerHTML = '<div style="padding: 40px; text-align: center; color: var(--text-muted);">该分组暂无对话</div>';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5631,9 +5654,94 @@ function closeGroupContextMenu() {
|
||||
}
|
||||
|
||||
|
||||
// 在分组中搜索(占位函数)
|
||||
function searchInGroup() {
|
||||
alert('搜索功能待实现');
|
||||
// 分组搜索相关变量
|
||||
let groupSearchTimer = null;
|
||||
let currentGroupSearchQuery = '';
|
||||
|
||||
// 切换分组搜索框显示/隐藏
|
||||
function toggleGroupSearch() {
|
||||
const searchContainer = document.getElementById('group-search-container');
|
||||
const searchInput = document.getElementById('group-search-input');
|
||||
|
||||
if (!searchContainer || !searchInput) return;
|
||||
|
||||
if (searchContainer.style.display === 'none') {
|
||||
searchContainer.style.display = 'block';
|
||||
searchInput.focus();
|
||||
} else {
|
||||
searchContainer.style.display = 'none';
|
||||
clearGroupSearch();
|
||||
}
|
||||
}
|
||||
|
||||
// 处理分组搜索输入
|
||||
function handleGroupSearchInput(event) {
|
||||
// 支持回车键搜索
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
performGroupSearch();
|
||||
return;
|
||||
}
|
||||
|
||||
// 支持ESC键关闭搜索
|
||||
if (event.key === 'Escape') {
|
||||
clearGroupSearch();
|
||||
toggleGroupSearch();
|
||||
return;
|
||||
}
|
||||
|
||||
const searchInput = document.getElementById('group-search-input');
|
||||
const clearBtn = document.getElementById('group-search-clear-btn');
|
||||
|
||||
if (!searchInput) return;
|
||||
|
||||
const query = searchInput.value.trim();
|
||||
|
||||
// 显示/隐藏清除按钮
|
||||
if (clearBtn) {
|
||||
clearBtn.style.display = query ? 'block' : 'none';
|
||||
}
|
||||
|
||||
// 防抖搜索
|
||||
if (groupSearchTimer) {
|
||||
clearTimeout(groupSearchTimer);
|
||||
}
|
||||
|
||||
groupSearchTimer = setTimeout(() => {
|
||||
performGroupSearch();
|
||||
}, 300); // 300ms 防抖
|
||||
}
|
||||
|
||||
// 执行分组搜索
|
||||
async function performGroupSearch() {
|
||||
const searchInput = document.getElementById('group-search-input');
|
||||
if (!searchInput || !currentGroupId) return;
|
||||
|
||||
const query = searchInput.value.trim();
|
||||
currentGroupSearchQuery = query;
|
||||
|
||||
// 加载搜索结果
|
||||
await loadGroupConversations(currentGroupId, query);
|
||||
}
|
||||
|
||||
// 清除分组搜索
|
||||
function clearGroupSearch() {
|
||||
const searchInput = document.getElementById('group-search-input');
|
||||
const clearBtn = document.getElementById('group-search-clear-btn');
|
||||
|
||||
if (searchInput) {
|
||||
searchInput.value = '';
|
||||
}
|
||||
if (clearBtn) {
|
||||
clearBtn.style.display = 'none';
|
||||
}
|
||||
|
||||
currentGroupSearchQuery = '';
|
||||
|
||||
// 重新加载分组对话(不搜索)
|
||||
if (currentGroupId) {
|
||||
loadGroupConversations(currentGroupId, '');
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化时加载分组
|
||||
|
||||
@@ -218,7 +218,7 @@
|
||||
</button>
|
||||
<h2 id="group-detail-title" class="group-detail-title"></h2>
|
||||
<div class="group-detail-actions">
|
||||
<button class="group-action-btn" onclick="searchInGroup()" title="搜索">
|
||||
<button class="group-action-btn" onclick="toggleGroupSearch()" title="搜索" id="group-search-toggle-btn">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="11" cy="11" r="8" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="m21 21-4.35-4.35" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
@@ -237,6 +237,17 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="group-search-container" class="group-search-container" style="display: none;">
|
||||
<div class="group-search-input-wrapper">
|
||||
<input type="text" id="group-search-input" class="group-search-input" placeholder="搜索分组中的对话..." onkeyup="handleGroupSearchInput(event)" oninput="handleGroupSearchInput(event)">
|
||||
<button class="group-search-clear-btn" onclick="clearGroupSearch()" title="清除搜索" id="group-search-clear-btn" style="display: none;">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="m8 8 8 8M16 8l-8 8" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="group-detail-content">
|
||||
<div id="group-conversations-list" class="group-conversations-list"></div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user