tabby-ai-assistant 1.0.11 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -55
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/services/chat/ai-sidebar.service.ts +1 -1
- package/src/services/core/config-provider.service.ts +4 -12
- package/src/services/core/toast.service.ts +70 -0
- package/src/services/providers/anthropic-provider.service.ts +0 -172
- package/src/services/providers/base-provider.service.ts +225 -14
- package/src/services/providers/glm-provider.service.ts +0 -195
- package/src/services/providers/minimax-provider.service.ts +0 -198
- package/src/services/providers/ollama-provider.service.ts +0 -160
- package/src/services/providers/openai-compatible.service.ts +1 -143
- package/src/services/providers/openai-provider.service.ts +1 -143
- package/src/services/providers/vllm-provider.service.ts +0 -160
- package/src/styles/ai-assistant.scss +23 -0
- package/src/types/provider.types.ts +206 -75
- package/src/index.ts.backup +0 -165
- package/src/services/chat/chat-history.service.ts.backup +0 -239
- package/webpack.config.js.backup +0 -57
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
import { Injectable } from '@angular/core';
|
|
2
|
-
import { BehaviorSubject, Observable } from 'rxjs';
|
|
3
|
-
import { ChatMessage } from '../../types/ai.types';
|
|
4
|
-
import { LoggerService } from '../core/logger.service';
|
|
5
|
-
|
|
6
|
-
export interface SavedSession {
|
|
7
|
-
sessionId: string;
|
|
8
|
-
title: string;
|
|
9
|
-
messages: ChatMessage[];
|
|
10
|
-
createdAt: Date;
|
|
11
|
-
updatedAt: Date;
|
|
12
|
-
messageCount: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const STORAGE_KEY = 'tabby-ai-assistant-chat-history';
|
|
16
|
-
const MAX_SESSIONS = 50;
|
|
17
|
-
const MAX_MESSAGES_PER_SESSION = 1000;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* 聊天历史服务
|
|
21
|
-
* 持久化存储和管理聊天会话历史
|
|
22
|
-
*/
|
|
23
|
-
@Injectable({
|
|
24
|
-
providedIn: 'root'
|
|
25
|
-
})
|
|
26
|
-
export class ChatHistoryService {
|
|
27
|
-
private sessionsSubject = new BehaviorSubject<SavedSession[]>([]);
|
|
28
|
-
public sessions$ = this.sessionsSubject.asObservable();
|
|
29
|
-
|
|
30
|
-
constructor(private logger: LoggerService) {
|
|
31
|
-
this.loadSessions();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* 保存会话
|
|
36
|
-
*/
|
|
37
|
-
saveSession(sessionId: string, messages: ChatMessage[], title?: string): void {
|
|
38
|
-
try {
|
|
39
|
-
const sessions = this.sessionsSubject.value;
|
|
40
|
-
const existingIndex = sessions.findIndex(s => s.sessionId === sessionId);
|
|
41
|
-
|
|
42
|
-
const sessionTitle = title || this.generateSessionTitle(messages);
|
|
43
|
-
const now = new Date();
|
|
44
|
-
|
|
45
|
-
const session: SavedSession = {
|
|
46
|
-
sessionId,
|
|
47
|
-
title: sessionTitle,
|
|
48
|
-
messages: this.trimMessages(messages),
|
|
49
|
-
createdAt: existingIndex >= 0 ? sessions[existingIndex].createdAt : now,
|
|
50
|
-
updatedAt: now,
|
|
51
|
-
messageCount: messages.length
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
if (existingIndex >= 0) {
|
|
55
|
-
sessions[existingIndex] = session;
|
|
56
|
-
} else {
|
|
57
|
-
sessions.unshift(session);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// 限制会话数量
|
|
61
|
-
const trimmedSessions = sessions.slice(0, MAX_SESSIONS);
|
|
62
|
-
this.sessionsSubject.next(trimmedSessions);
|
|
63
|
-
this.saveToStorage(trimmedSessions);
|
|
64
|
-
|
|
65
|
-
this.logger.info('Session saved', { sessionId, title: sessionTitle });
|
|
66
|
-
|
|
67
|
-
} catch (error) {
|
|
68
|
-
this.logger.error('Failed to save session', { error, sessionId });
|
|
69
|
-
throw error;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* 加载会话
|
|
75
|
-
*/
|
|
76
|
-
loadSession(sessionId: string): SavedSession | undefined {
|
|
77
|
-
const sessions = this.sessionsSubject.value;
|
|
78
|
-
return sessions.find(s => s.sessionId === sessionId);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* 删除会话
|
|
83
|
-
*/
|
|
84
|
-
deleteSession(sessionId: string): void {
|
|
85
|
-
const sessions = this.sessionsSubject.value;
|
|
86
|
-
const filteredSessions = sessions.filter(s => s.sessionId !== sessionId);
|
|
87
|
-
this.sessionsSubject.next(filteredSessions);
|
|
88
|
-
this.saveToStorage(filteredSessions);
|
|
89
|
-
this.logger.info('Session deleted', { sessionId });
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* 清空所有历史
|
|
94
|
-
*/
|
|
95
|
-
clearAllHistory(): void {
|
|
96
|
-
this.sessionsSubject.next([]);
|
|
97
|
-
this.saveToStorage([]);
|
|
98
|
-
this.logger.info('All chat history cleared');
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* 搜索会话
|
|
103
|
-
*/
|
|
104
|
-
searchSessions(query: string): SavedSession[] {
|
|
105
|
-
const sessions = this.sessionsSubject.value;
|
|
106
|
-
const lowercaseQuery = query.toLowerCase();
|
|
107
|
-
|
|
108
|
-
return sessions.filter(session =>
|
|
109
|
-
session.title.toLowerCase().includes(lowercaseQuery) ||
|
|
110
|
-
session.messages.some(msg =>
|
|
111
|
-
msg.content.toLowerCase().includes(lowercaseQuery)
|
|
112
|
-
)
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* 获取最近的会话
|
|
118
|
-
*/
|
|
119
|
-
getRecentSessions(count: number = 10): SavedSession[] {
|
|
120
|
-
const sessions = this.sessionsSubject.value;
|
|
121
|
-
return sessions.slice(0, count);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* 获取会话统计
|
|
126
|
-
*/
|
|
127
|
-
getStatistics(): {
|
|
128
|
-
totalSessions: number;
|
|
129
|
-
totalMessages: number;
|
|
130
|
-
averageMessagesPerSession: number;
|
|
131
|
-
oldestSession?: Date;
|
|
132
|
-
newestSession?: Date;
|
|
133
|
-
} {
|
|
134
|
-
const sessions = this.sessionsSubject.value;
|
|
135
|
-
const totalSessions = sessions.length;
|
|
136
|
-
const totalMessages = sessions.reduce((sum, s) => sum + s.messageCount, 0);
|
|
137
|
-
const averageMessagesPerSession = totalSessions > 0 ? totalMessages / totalSessions : 0;
|
|
138
|
-
|
|
139
|
-
const dates = sessions.map(s => s.createdAt.getTime());
|
|
140
|
-
const oldestSession = dates.length > 0 ? new Date(Math.min(...dates)) : undefined;
|
|
141
|
-
const newestSession = dates.length > 0 ? new Date(Math.max(...dates)) : undefined;
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
totalSessions,
|
|
145
|
-
totalMessages,
|
|
146
|
-
averageMessagesPerSession: Math.round(averageMessagesPerSession * 100) / 100,
|
|
147
|
-
oldestSession,
|
|
148
|
-
newestSession
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* 导出所有历史
|
|
154
|
-
*/
|
|
155
|
-
exportAllHistory(): string {
|
|
156
|
-
const sessions = this.sessionsSubject.value;
|
|
157
|
-
const exportData = {
|
|
158
|
-
exportDate: new Date().toISOString(),
|
|
159
|
-
version: '1.0',
|
|
160
|
-
sessions
|
|
161
|
-
};
|
|
162
|
-
return JSON.stringify(exportData, null, 2);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* 导入历史
|
|
167
|
-
*/
|
|
168
|
-
importHistory(data: string): void {
|
|
169
|
-
try {
|
|
170
|
-
const importData = JSON.parse(data);
|
|
171
|
-
|
|
172
|
-
if (!importData.sessions || !Array.isArray(importData.sessions)) {
|
|
173
|
-
throw new Error('Invalid history format');
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const sessions = importData.sessions.map((s: any) => ({
|
|
177
|
-
...s,
|
|
178
|
-
createdAt: new Date(s.createdAt),
|
|
179
|
-
updatedAt: new Date(s.updatedAt)
|
|
180
|
-
}));
|
|
181
|
-
|
|
182
|
-
this.sessionsSubject.next(sessions);
|
|
183
|
-
this.saveToStorage(sessions);
|
|
184
|
-
|
|
185
|
-
this.logger.info('History imported', {
|
|
186
|
-
sessionCount: sessions.length
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
} catch (error) {
|
|
190
|
-
this.logger.error('Failed to import history', error);
|
|
191
|
-
throw new Error('Invalid history file format');
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
private loadSessions(): void {
|
|
196
|
-
try {
|
|
197
|
-
const stored = localStorage.getItem(STORAGE_KEY);
|
|
198
|
-
if (stored) {
|
|
199
|
-
const sessions = JSON.parse(stored).map((s: any) => ({
|
|
200
|
-
...s,
|
|
201
|
-
createdAt: new Date(s.createdAt),
|
|
202
|
-
updatedAt: new Date(s.updatedAt)
|
|
203
|
-
}));
|
|
204
|
-
this.sessionsSubject.next(sessions);
|
|
205
|
-
this.logger.info('Loaded sessions from storage', { count: sessions.length });
|
|
206
|
-
}
|
|
207
|
-
} catch (error) {
|
|
208
|
-
this.logger.error('Failed to load sessions from storage', error);
|
|
209
|
-
this.sessionsSubject.next([]);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
private saveToStorage(sessions: SavedSession[]): void {
|
|
214
|
-
try {
|
|
215
|
-
localStorage.setItem(STORAGE_KEY, JSON.stringify(sessions));
|
|
216
|
-
} catch (error) {
|
|
217
|
-
this.logger.error('Failed to save sessions to storage', error);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
private generateSessionTitle(messages: ChatMessage[]): string {
|
|
222
|
-
const firstUserMessage = messages.find(m => m.role === 'user');
|
|
223
|
-
if (!firstUserMessage) {
|
|
224
|
-
return `会话 ${new Date().toLocaleString()}`;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const content = firstUserMessage.content;
|
|
228
|
-
return content.length > 50 ? content.substring(0, 50) + '...' : content;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
private trimMessages(messages: ChatMessage[]): ChatMessage[] {
|
|
232
|
-
if (messages.length <= MAX_MESSAGES_PER_SESSION) {
|
|
233
|
-
return messages;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// 保留最近的messages
|
|
237
|
-
return messages.slice(-MAX_MESSAGES_PER_SESSION);
|
|
238
|
-
}
|
|
239
|
-
}
|
package/webpack.config.js.backup
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
const webpack = require('webpack');
|
|
3
|
-
|
|
4
|
-
module.exports = {
|
|
5
|
-
mode: 'production',
|
|
6
|
-
entry: './src/index.ts',
|
|
7
|
-
output: {
|
|
8
|
-
path: path.resolve(__dirname, 'dist'),
|
|
9
|
-
filename: 'index.js',
|
|
10
|
-
libraryTarget: 'umd',
|
|
11
|
-
devtoolModuleFilenameTemplate: 'webpack-tabby-ai-assistant:///[resource-path]',
|
|
12
|
-
},
|
|
13
|
-
resolve: {
|
|
14
|
-
extensions: ['.ts', '.js'],
|
|
15
|
-
alias: {
|
|
16
|
-
'@': path.resolve(__dirname, 'src')
|
|
17
|
-
},
|
|
18
|
-
fallback: {
|
|
19
|
-
'os': false,
|
|
20
|
-
'path': false,
|
|
21
|
-
'crypto': false,
|
|
22
|
-
'stream': false,
|
|
23
|
-
'util': false
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
module: {
|
|
27
|
-
rules: [
|
|
28
|
-
{
|
|
29
|
-
test: /\.ts$/,
|
|
30
|
-
use: [
|
|
31
|
-
{ loader: 'ts-loader' },
|
|
32
|
-
{ loader: 'angular2-template-loader' }
|
|
33
|
-
],
|
|
34
|
-
exclude: /node_modules/
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
test: /\.(html|css|scss)$/,
|
|
38
|
-
use: ['raw-loader']
|
|
39
|
-
}
|
|
40
|
-
]
|
|
41
|
-
},
|
|
42
|
-
externals: [
|
|
43
|
-
'tabby-core',
|
|
44
|
-
'tabby-terminal',
|
|
45
|
-
'tabby-settings',
|
|
46
|
-
'@angular/core',
|
|
47
|
-
'@angular/common',
|
|
48
|
-
'@angular/forms',
|
|
49
|
-
'@ng-bootstrap/ng-bootstrap',
|
|
50
|
-
'rxjs'
|
|
51
|
-
],
|
|
52
|
-
plugins: [
|
|
53
|
-
new webpack.DefinePlugin({
|
|
54
|
-
'process.env.NODE_ENV': JSON.stringify('production')
|
|
55
|
-
})
|
|
56
|
-
]
|
|
57
|
-
};
|