Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
cf3d273
feat:删除多余测试文件
Definition-f-Imaginative-Spring Jul 4, 2026
5b885fe
Merge branch 'develop' of github.com:rfhdw0102/YouDaoNoteLM into develop
Definition-f-Imaginative-Spring Jul 4, 2026
d42286a
feat:完善细节
rfhdw0102 Jul 11, 2026
2dc6171
Merge remote-tracking branch 'origin/develop' into feature/chat
rfhdw0102 Jul 11, 2026
f317341
feat(asr): 添加阿里云ASR凭证验证功能并优化前端音频导入错误处理
Flandern1211 Jul 11, 2026
be43ebd
feat:支持多文件及拖拽上传
rfhdw0102 Jul 11, 2026
3759ce3
Merge remote-tracking branch 'origin/develop' into feature/chat
rfhdw0102 Jul 11, 2026
fa1d6e3
fix(asr): 优化阿里云 ASR 服务凭证校验和错误处理
Flandern1211 Jul 11, 2026
10ce66d
fix(health): 修复健康检查中的资源泄露和配置解析错误
Flandern1211 Jul 11, 2026
bb9e025
fix(youdao): 解决有道云笔记API认证失败的错误处理
Flandern1211 Jul 11, 2026
567f81a
Merge remote-tracking branch 'origin/develop' into develop
Flandern1211 Jul 11, 2026
d24bb92
feat:实现切换会话、页面时SSE断开,停止生成
rfhdw0102 Jul 11, 2026
78f217a
Merge remote-tracking branch 'origin/develop' into feature/chat
rfhdw0102 Jul 11, 2026
95e4c9c
feat(admin): 限制前端只展示博查搜索provider
Flandern1211 Jul 11, 2026
95ba414
Merge branch 'feature/youdao' into develop
Flandern1211 Jul 11, 2026
bec7bcb
fix(admin): 修复用户禁用功能并补齐管理员接口角色校验
Flandern1211 Jul 12, 2026
52b452a
refactor(agent): 优化搜索代理实现并增强功能
Flandern1211 Jul 13, 2026
878d38d
refactor(agent): 优化代码结构和依赖管理
Flandern1211 Jul 13, 2026
2959932
feat(search): 添加搜索限流器和指标处理器
Flandern1211 Jul 13, 2026
52d4c3e
Merge pull request #15 from rfhdw0102/feature/youdao
Flandern1211 Jul 13, 2026
3700340
Merge remote-tracking branch 'origin/develop' into develop
Flandern1211 Jul 13, 2026
a184120
feat:增加向量模型配置时向量维度的校验
rfhdw0102 Jul 13, 2026
99c81a0
Merge remote-tracking branch 'origin/develop' into feature/chat
rfhdw0102 Jul 13, 2026
2d0bcf2
feat:增加向量模型配置时向量维度的校验
rfhdw0102 Jul 13, 2026
c2b331a
docs(auth): 添加 Token 吊销方案设计文档
Flandern1211 Jul 13, 2026
0ac6b2c
feat(token_blacklist): 扩展支持用户级批量吊销
Flandern1211 Jul 13, 2026
0e5dea5
fix:修复向量存储时一次太多导致失败,进行分批存储
rfhdw0102 Jul 13, 2026
a39d9fb
refactor:重构工具的注册与绑定
rfhdw0102 Jul 13, 2026
2d8330b
refactor:重构工具的注册与绑定
rfhdw0102 Jul 13, 2026
a54c918
feat:优化关键词的提炼
Definition-f-Imaginative-Spring Jul 15, 2026
9039b89
feat:合并
Definition-f-Imaginative-Spring Jul 15, 2026
12553e5
feat:添加对话Agent对LLM调用的观测
rfhdw0102 Jul 15, 2026
1b995aa
refactor(agent): 重构搜索和有道云笔记,生成Agent实现主从协同架构
Flandern1211 Jul 15, 2026
087d681
Merge remote-tracking branch 'origin/develop' into develop
Flandern1211 Jul 15, 2026
b7145c7
docs(token): 删除令牌吊销设计方案文档
Flandern1211 Jul 15, 2026
bf9af4a
merge: 合并 develop 到 feature/youdao,解决冲突
Flandern1211 Jul 15, 2026
0e00db0
merge: 合并 origin/develop 解决冲突
Flandern1211 Jul 15, 2026
6057dc4
Merge pull request #17 from rfhdw0102/feature/youdao
Flandern1211 Jul 15, 2026
1227eba
fix:修改向量入库每批最多10个文档
rfhdw0102 Jul 15, 2026
17d4e10
Merge remote-tracking branch 'origin/develop' into feature/chat
rfhdw0102 Jul 15, 2026
bb954c8
feat:新增Reranker模型配置,RAG检索可用Reranker精排
rfhdw0102 Jul 16, 2026
3aae6c9
feat:拆分 加任务队列
Definition-f-Imaginative-Spring Jul 15, 2026
212db4c
feat:拆分 加任务队列
Definition-f-Imaginative-Spring Jul 20, 2026
1d51e04
Merge origin/develop into develop
Definition-f-Imaginative-Spring Jul 20, 2026
ea21362
feat:拆分 加任务队列,修复测验效果差的问题
Definition-f-Imaginative-Spring Jul 21, 2026
55d4385
feat:对上下文新增用户基本信息和物理环境信息
rfhdw0102 Jul 24, 2026
53ae011
feat:拆分 加任务队列,修复测验效果差的问题
Definition-f-Imaginative-Spring Jul 25, 2026
af6099d
feat:合并
Definition-f-Imaginative-Spring Jul 25, 2026
e7b2759
Add timeout to deploy-staging job
Definition-f-Imaginative-Spring Jul 27, 2026
2090170
Update cd.yml
Definition-f-Imaginative-Spring Jul 27, 2026
9c0d2fd
ci: extend CD deploy timeout
Definition-f-Imaginative-Spring Jul 27, 2026
a1fb170
feat(memory): 实现用户显式管理的长期记忆 V1
Flandern1211 Aug 3, 2026
d661c46
Merge pull request #20 from rfhdw0102/feature/long-term-memory
Flandern1211 Aug 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ jobs:
deploy-staging:
name: Deploy to Staging
runs-on: ubuntu-latest
timeout-minutes: 60
needs: build-and-push
if: >
(github.event_name == 'push' && github.ref == 'refs/heads/develop')
Expand Down Expand Up @@ -132,6 +133,7 @@ jobs:
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: 22
command_timeout: 45m
script: |
DEPLOY_DIR="/home/flandern/youdaonotelm"

Expand Down Expand Up @@ -214,6 +216,7 @@ jobs:
deploy-production:
name: Deploy to Production
runs-on: ubuntu-latest
timeout-minutes: 60
needs: build-and-push
if: >
(github.event_name == 'push' && github.ref == 'refs/heads/main')
Expand Down Expand Up @@ -245,6 +248,7 @@ jobs:
username: ${{ secrets.PRODUCTION_SSH_USERNAME }}
key: ${{ secrets.PRODUCTION_SSH_PRIVATE_KEY }}
port: 22
command_timeout: 45m
script: |
DEPLOY_DIR="/home/flandern/youdaonotelm"

Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,5 @@ frontend/test-screenshots/
# Docker (仅排除构建产物,不排除源文件)
# configs/config.docker.yaml.example
/configs/docker_config.yaml
/chat_Agent.md
/rag.md
48 changes: 24 additions & 24 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
package main
import (
"flag"
"fmt"
"YoudaoNoteLm/internal/app"
)
func main() {
// 定义命令行参数
flag.Parse()
// 创建应用实例 创建应用实例
application := app.NewApp()
// 初始化应用
if err := application.Initialize(); err != nil {
panic(fmt.Sprintf("应用初始化失败: %v", err))
}
// 运行应用
application.Run()
}
package main

import (
"flag"
"fmt"

"YoudaoNoteLm/internal/app"
)

func main() {
// 定义命令行参数
flag.Parse()

// 创建应用实例 创建应用实例
application := app.NewApp()

// 初始化应用
if err := application.Initialize(); err != nil {
panic(fmt.Sprintf("应用初始化失败: %v", err))
}

// 运行应用
application.Run()
}
5 changes: 4 additions & 1 deletion frontend/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ server {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}

# 静态资源缓存
Expand Down
5 changes: 3 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
"lint": "eslint .",
"test:generation": "node --experimental-strip-types --test src/stores/generationTaskHelpers.test.ts",
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.17.0",
Expand Down
71 changes: 54 additions & 17 deletions frontend/src/api/chat.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import client, { doRefreshToken } from './client';
import type { ApiResponse } from './auth';
import type { SearchResultItem } from './search';
import { getChatErrorMessage } from '../utils/error';

// ============ Request/Response Types ============
Expand Down Expand Up @@ -31,9 +32,12 @@ export interface ReferenceData {
}

export interface StreamEvent {
type: 'token' | 'reference' | 'done' | 'error' | 'message' | 'title' | 'tool_call' | 'tool_result';
type:
| 'token' | 'reference' | 'done' | 'error' | 'message' | 'title' | 'tool_call' | 'tool_result'
| 'search_started' | 'search_results' | 'search_busy'
| 'generation_started' | 'generation_result';
content: string;
data: ReferenceData[] | number | null;
data: ReferenceData[] | number | string | null;
}

// ============ API Functions ============
Expand Down Expand Up @@ -117,13 +121,15 @@ async function isTokenErrorResponse(response: Response): Promise<boolean> {
return false;
}

// 7. Send message (streaming) - returns a ReadableStream
// 7. Send message (streaming) - returns Response and AbortController
export async function sendMessage(
conversationId: number,
content: string,
sourceIds?: number[],
llmConfigId?: number
): Promise<Response> {
): Promise<{ response: Response; abortController: AbortController }> {
const abortController = new AbortController();

const makeRequest = (token: string) =>
fetch(`/api/v1/chat/conversations/${conversationId}/messages`, {
method: 'POST',
Expand All @@ -136,6 +142,7 @@ export async function sendMessage(
source_ids: sourceIds || [],
llm_config_id: llmConfigId || 0,
}),
signal: abortController.signal,
});

let token = sessionStorage.getItem('access_token') || '';
Expand All @@ -149,7 +156,7 @@ export async function sendMessage(
}
}

return response;
return { response, abortController };
}

// 8. Stop generation
Expand All @@ -168,16 +175,20 @@ export function parseSSEStream(
onToken?: (content: string) => void;
onReference?: (references: ReferenceData[]) => void;
onTitle?: (title: string) => void;
onDone?: (content: string) => void;
onDone?: (data?: ReferenceData[] | string) => void;
onError?: (error: string) => void;
}
): AbortController {
const abortController = new AbortController();

onSearchStarted?: () => void;
onSearchResults?: (results: SearchResultItem[], summary: string) => void;
onSearchBusy?: (message: string) => void;
onGenerationStarted?: (type: string) => void;
onGenerationResult?: (type: string, content: string) => void;
},
abortController?: AbortController
): void {
const reader = response.body?.getReader();
if (!reader) {
callbacks.onError?.('无法读取响应流');
return abortController;
return;
}

const decoder = new TextDecoder();
Expand Down Expand Up @@ -243,7 +254,8 @@ export function parseSSEStream(
callbacks.onTitle?.(data.content);
break;
case 'done':
callbacks.onDone?.(data.content);
// 后端将引用附加到 done 事件的 data 字段(原子发送,消除竞态)
callbacks.onDone?.(Array.isArray(data.data) ? data.data as ReferenceData[] : data.content);
break;
case 'error':
callbacks.onError?.(data.content);
Expand All @@ -253,6 +265,30 @@ export function parseSSEStream(
// 工具调用中间事件,不需要展示给用户,静默忽略
console.log('Tool event:', eventType, data.content);
break;
case 'search_started':
callbacks.onSearchStarted?.();
break;
case 'search_results': {
const searchData = data.data as { results?: SearchResultItem[]; summary?: string } | null;
if (searchData?.results) {
callbacks.onSearchResults?.(searchData.results, searchData.summary || '');
}
break;
}
case 'search_busy':
callbacks.onSearchBusy?.(data.content || '请等待当前搜索任务完成');
break;
case 'generation_started':
console.log('[Generation] started:', data.data);
callbacks.onGenerationStarted?.((data.data as string) || 'note');
break;
case 'generation_result': {
const genData = data.data as { type?: string; content?: string } | null;
if (genData?.content) {
callbacks.onGenerationResult?.(genData.type || 'note', genData.content);
}
break;
}
default:
console.log('Unknown event type:', eventType);
// 未知事件类型,不作为 token 显示
Expand All @@ -273,17 +309,18 @@ export function parseSSEStream(
console.log('Stream ended, calling onDone');
callbacks.onDone?.('');
} catch (error) {
console.error('Stream parsing error:', error);
if (abortController.signal.aborted) {
// Stream was intentionally aborted (user clicked stop)
const isAborted = abortController?.signal.aborted ?? false;
console.log('[parseSSEStream] catch 触发, isAborted:', isAborted, 'error:', error);
if (isAborted) {
// Stream was intentionally aborted (user clicked stop or switched conversation)
// Call onDone to preserve the accumulated content
console.log('[parseSSEStream] 检测到 abort,调用 onDone 保存已累积内容');
callbacks.onDone?.('');
} else {
const rawMessage = error instanceof Error ? error.message : '流读取错误';
console.log('[parseSSEStream] 非 abort 错误,调用 onError:', rawMessage);
callbacks.onError?.(getChatErrorMessage(rawMessage));
}
}
})();

return abortController;
}
20 changes: 18 additions & 2 deletions frontend/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ function onTokenRefreshed(newToken: string) {
refreshSubscribers = [];
}

function clearAuth() {
function clearAuth(reason?: string) {
sessionStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
localStorage.removeItem('user');
window.location.href = '/login';
window.location.href = reason ? `/login?reason=${reason}` : '/login';
}

// Request: attach access_token
Expand All @@ -40,6 +40,11 @@ function isTokenError(data: any): boolean {
return data && (data.code === 1005 || data.code === 1006);
}

// Check if response indicates the user has been disabled (1004)
function isUserDisabled(data: any): boolean {
return data && data.code === 1004;
}

export async function doRefreshToken(): Promise<string | null> {
const refreshToken = localStorage.getItem('refresh_token');
if (!refreshToken) return null;
Expand All @@ -62,6 +67,12 @@ client.interceptors.response.use(
(response: AxiosResponse) => {
const data = response.data;

// 用户被禁用 → 立即强制退出,不尝试刷新 token
if (isUserDisabled(data)) {
clearAuth('disabled');
return Promise.reject(new Error('user_disabled'));
}

// Backend returns HTTP 200 but code 1005/1006 → token issue
if (isTokenError(data)) {
const originalRequest = response.config as InternalAxiosRequestConfig & { _retry?: boolean };
Expand Down Expand Up @@ -107,6 +118,11 @@ client.interceptors.response.use(
async (error) => {
// HTTP 4xx/5xx errors - check if it's a token issue in the response body
const data = error.response?.data;
// 用户被禁用 → 立即强制退出
if (isUserDisabled(data)) {
clearAuth('disabled');
return Promise.reject(error);
}
if (isTokenError(data)) {
const originalRequest = error.config;
if (originalRequest._retry) {
Expand Down
Loading
Loading