tabby-ai-assistant 1.0.8 → 1.0.9
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/dist/components/chat/ai-sidebar.component.d.ts +16 -3
- package/dist/components/chat/chat-input.component.d.ts +4 -0
- package/dist/components/chat/chat-interface.component.d.ts +22 -1
- package/dist/components/chat/chat-settings.component.d.ts +21 -11
- package/dist/components/settings/ai-settings-tab.component.d.ts +14 -4
- package/dist/components/settings/general-settings.component.d.ts +43 -12
- package/dist/components/settings/provider-config.component.d.ts +83 -5
- package/dist/components/settings/security-settings.component.d.ts +14 -4
- package/dist/i18n/index.d.ts +48 -0
- package/dist/i18n/translations/en-US.d.ts +5 -0
- package/dist/i18n/translations/ja-JP.d.ts +5 -0
- package/dist/i18n/translations/zh-CN.d.ts +5 -0
- package/dist/i18n/types.d.ts +198 -0
- package/dist/index.js +1 -1
- package/dist/services/chat/ai-sidebar.service.d.ts +23 -1
- package/dist/services/core/theme.service.d.ts +53 -0
- package/package.json +1 -1
- package/src/components/chat/ai-sidebar.component.scss +468 -0
- package/src/components/chat/ai-sidebar.component.ts +47 -344
- package/src/components/chat/chat-input.component.scss +2 -2
- package/src/components/chat/chat-input.component.ts +16 -5
- package/src/components/chat/chat-interface.component.html +11 -11
- package/src/components/chat/chat-interface.component.scss +410 -4
- package/src/components/chat/chat-interface.component.ts +105 -14
- package/src/components/chat/chat-message.component.scss +3 -3
- package/src/components/chat/chat-message.component.ts +3 -2
- package/src/components/chat/chat-settings.component.html +95 -61
- package/src/components/chat/chat-settings.component.scss +224 -50
- package/src/components/chat/chat-settings.component.ts +56 -30
- package/src/components/security/risk-confirm-dialog.component.scss +7 -7
- package/src/components/settings/ai-settings-tab.component.html +27 -27
- package/src/components/settings/ai-settings-tab.component.scss +34 -20
- package/src/components/settings/ai-settings-tab.component.ts +59 -20
- package/src/components/settings/general-settings.component.html +69 -40
- package/src/components/settings/general-settings.component.scss +151 -58
- package/src/components/settings/general-settings.component.ts +168 -55
- package/src/components/settings/provider-config.component.html +149 -60
- package/src/components/settings/provider-config.component.scss +273 -153
- package/src/components/settings/provider-config.component.ts +177 -19
- package/src/components/settings/security-settings.component.html +70 -39
- package/src/components/settings/security-settings.component.scss +104 -8
- package/src/components/settings/security-settings.component.ts +48 -10
- package/src/i18n/index.ts +129 -0
- package/src/i18n/translations/en-US.ts +193 -0
- package/src/i18n/translations/ja-JP.ts +193 -0
- package/src/i18n/translations/zh-CN.ts +193 -0
- package/src/i18n/types.ts +224 -0
- package/src/index.ts +6 -0
- package/src/services/chat/ai-sidebar.service.ts +157 -5
- package/src/services/core/theme.service.ts +480 -0
- package/src/styles/ai-assistant.scss +8 -88
- package/src/styles/themes.scss +161 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 中文翻译
|
|
3
|
+
*/
|
|
4
|
+
import { TranslationKeys } from '../types';
|
|
5
|
+
|
|
6
|
+
export const zhCN: TranslationKeys = {
|
|
7
|
+
common: {
|
|
8
|
+
save: '保存',
|
|
9
|
+
cancel: '取消',
|
|
10
|
+
delete: '删除',
|
|
11
|
+
confirm: '确定',
|
|
12
|
+
enabled: '已启用',
|
|
13
|
+
disabled: '已禁用',
|
|
14
|
+
online: '在线',
|
|
15
|
+
offline: '离线',
|
|
16
|
+
testing: '检测中...',
|
|
17
|
+
error: '错误',
|
|
18
|
+
success: '成功',
|
|
19
|
+
notConfigured: '未配置',
|
|
20
|
+
add: '添加',
|
|
21
|
+
remove: '删除',
|
|
22
|
+
close: '关闭',
|
|
23
|
+
yes: '是',
|
|
24
|
+
no: '否',
|
|
25
|
+
reset: '重置',
|
|
26
|
+
default: '默认',
|
|
27
|
+
today: '今天'
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
settings: {
|
|
31
|
+
title: '设置',
|
|
32
|
+
generalTab: '基本设置',
|
|
33
|
+
providersTab: 'AI提供商',
|
|
34
|
+
securityTab: '安全设置',
|
|
35
|
+
chatTab: '聊天设置',
|
|
36
|
+
advancedTab: '高级设置'
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
general: {
|
|
40
|
+
title: '基本设置',
|
|
41
|
+
enableAssistant: '启用AI助手',
|
|
42
|
+
enableAssistantDesc: '启用或禁用整个AI助手功能',
|
|
43
|
+
defaultProvider: '默认AI提供商',
|
|
44
|
+
providerCount: '已配置 {count} 个提供商,当前使用',
|
|
45
|
+
language: '语言',
|
|
46
|
+
theme: '主题',
|
|
47
|
+
themeAuto: '跟随系统',
|
|
48
|
+
themeLight: '浅色主题',
|
|
49
|
+
themeDark: '深色主题',
|
|
50
|
+
themePixel: '像素复古',
|
|
51
|
+
themeTech: '赛博科技',
|
|
52
|
+
shortcuts: '快捷键',
|
|
53
|
+
shortcutOpenChat: '打开AI助手',
|
|
54
|
+
shortcutOpenChatDesc: '打开聊天界面',
|
|
55
|
+
shortcutGenerate: '生成命令',
|
|
56
|
+
shortcutGenerateDesc: '从选择生成命令',
|
|
57
|
+
shortcutExplain: '解释命令',
|
|
58
|
+
shortcutExplainDesc: '解释当前选择',
|
|
59
|
+
shortcutTip: '快捷键可在 Tabby 设置中自定义'
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
chatSettings: {
|
|
63
|
+
title: '聊天设置',
|
|
64
|
+
appearance: '外观',
|
|
65
|
+
theme: '主题',
|
|
66
|
+
fontSize: '字体大小',
|
|
67
|
+
compactMode: '紧凑模式',
|
|
68
|
+
compactModeDesc: '减少消息间距,显示更多内容',
|
|
69
|
+
behavior: '聊天行为',
|
|
70
|
+
enterToSend: 'Enter键发送消息',
|
|
71
|
+
enterToSendDesc: 'Shift+Enter 换行',
|
|
72
|
+
showTimestamps: '显示时间戳',
|
|
73
|
+
showTimestampsDesc: '在每条消息下方显示发送时间',
|
|
74
|
+
showAvatars: '显示头像',
|
|
75
|
+
showAvatarsDesc: '显示用户和AI的头像图标',
|
|
76
|
+
soundEnabled: '启用提示音',
|
|
77
|
+
soundEnabledDesc: '收到AI回复时播放提示音',
|
|
78
|
+
history: '聊天历史',
|
|
79
|
+
enableHistory: '启用聊天历史',
|
|
80
|
+
enableHistoryDesc: '保存聊天记录以便下次使用',
|
|
81
|
+
maxHistory: '最大历史记录数',
|
|
82
|
+
maxHistoryUnit: '条',
|
|
83
|
+
autoSave: '自动保存聊天记录',
|
|
84
|
+
autoSaveDesc: '每次发送消息后自动保存',
|
|
85
|
+
exportSettings: '导出设置',
|
|
86
|
+
clearHistory: '清空聊天记录',
|
|
87
|
+
resetDefaults: '重置为默认',
|
|
88
|
+
clearHistoryConfirm: '确定要清空所有聊天记录吗?此操作不可恢复。',
|
|
89
|
+
resetConfirm: '确定要重置所有设置为默认值吗?'
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
security: {
|
|
93
|
+
title: '安全设置',
|
|
94
|
+
accessControl: '访问保护',
|
|
95
|
+
passwordProtection: '启用密码保护',
|
|
96
|
+
passwordProtectionDesc: '高风险命令需要密码确认',
|
|
97
|
+
setPassword: '设置密码',
|
|
98
|
+
passwordPlaceholder: '输入密码',
|
|
99
|
+
riskAssessment: '风险评估',
|
|
100
|
+
riskAssessmentDesc: '自动评估命令风险等级',
|
|
101
|
+
defaultRiskLevel: '默认风险等级',
|
|
102
|
+
riskLow: '低风险',
|
|
103
|
+
riskMedium: '中等风险',
|
|
104
|
+
riskHigh: '高风险',
|
|
105
|
+
userConsent: '用户授权',
|
|
106
|
+
rememberConsent: '记住用户授权',
|
|
107
|
+
rememberConsentDesc: '授权有效期',
|
|
108
|
+
consentExpiryDays: '天',
|
|
109
|
+
dangerousPatterns: '危险命令模式',
|
|
110
|
+
addPattern: '添加危险命令模式',
|
|
111
|
+
patternPlaceholder: '输入危险命令模式',
|
|
112
|
+
saveSettings: '保存设置',
|
|
113
|
+
resetDefaults: '恢复默认',
|
|
114
|
+
resetConfirm: '确定要重置安全设置为默认值吗?'
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
providers: {
|
|
118
|
+
title: 'AI提供商配置',
|
|
119
|
+
cloudProviders: '云端提供商',
|
|
120
|
+
cloudProvidersDesc: '需要 API Key 的在线服务',
|
|
121
|
+
localProviders: '本地提供商',
|
|
122
|
+
localProvidersDesc: '无需 API Key,本地运行',
|
|
123
|
+
configured: '已启用',
|
|
124
|
+
displayName: '显示名称',
|
|
125
|
+
status: '状态',
|
|
126
|
+
apiKey: 'API Key',
|
|
127
|
+
baseURL: 'Base URL',
|
|
128
|
+
model: 'Model',
|
|
129
|
+
saveConfig: '保存配置',
|
|
130
|
+
testConnection: '测试连接',
|
|
131
|
+
detectService: '检测服务',
|
|
132
|
+
delete: '删除',
|
|
133
|
+
deleteConfirm: '确定要删除该提供商配置吗?',
|
|
134
|
+
testSuccess: '连接测试成功!',
|
|
135
|
+
testFail: '连接测试失败',
|
|
136
|
+
testError: '无法连接到服务,请确保服务已启动',
|
|
137
|
+
configSaved: '配置已保存',
|
|
138
|
+
configDeleted: '配置已删除'
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
providerNames: {
|
|
142
|
+
openai: 'OpenAI',
|
|
143
|
+
anthropic: 'Anthropic Claude',
|
|
144
|
+
minimax: 'Minimax',
|
|
145
|
+
glm: 'GLM (ChatGLM)',
|
|
146
|
+
openaiCompatible: 'OpenAI Compatible',
|
|
147
|
+
ollama: 'Ollama (本地)',
|
|
148
|
+
vllm: 'vLLM (本地)'
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
chatInterface: {
|
|
152
|
+
title: 'AI助手',
|
|
153
|
+
welcomeMessage: '您好!我是AI助手',
|
|
154
|
+
inputPlaceholder: '输入您的问题或描述要执行的命令...',
|
|
155
|
+
thinking: 'AI正在思考...',
|
|
156
|
+
executingTool: '正在执行工具...',
|
|
157
|
+
toolComplete: '工具执行完成',
|
|
158
|
+
errorPrefix: '错误',
|
|
159
|
+
tipCommand: '提示:您可以描述想执行的命令,例如"查看当前目录的所有文件"',
|
|
160
|
+
tipShortcut: '快捷键:Ctrl+Shift+G 生成命令,Ctrl+Shift+E 解释命令',
|
|
161
|
+
clearChat: '清空聊天记录',
|
|
162
|
+
clearChatConfirm: '确定要清空聊天记录吗?',
|
|
163
|
+
exportChat: '导出聊天记录',
|
|
164
|
+
switchProvider: '切换AI提供商',
|
|
165
|
+
providerBadge: '提供商'
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
riskLevel: {
|
|
169
|
+
low: '低风险',
|
|
170
|
+
medium: '中等风险',
|
|
171
|
+
high: '高风险',
|
|
172
|
+
unknown: '未知'
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
advancedSettings: {
|
|
176
|
+
title: '高级设置',
|
|
177
|
+
configManagement: '配置管理',
|
|
178
|
+
validateConfig: '验证配置',
|
|
179
|
+
resetDefaults: '重置为默认',
|
|
180
|
+
logSettings: '日志设置',
|
|
181
|
+
logLevel: '日志级别',
|
|
182
|
+
logLevels: {
|
|
183
|
+
debug: '调试',
|
|
184
|
+
info: '信息',
|
|
185
|
+
warn: '警告',
|
|
186
|
+
error: '错误'
|
|
187
|
+
},
|
|
188
|
+
systemInfo: '系统信息',
|
|
189
|
+
pluginVersion: '插件版本',
|
|
190
|
+
supportedProviders: '支持的提供商',
|
|
191
|
+
currentProvider: '当前提供商'
|
|
192
|
+
}
|
|
193
|
+
};
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 翻译键类型定义
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// 通用
|
|
6
|
+
export interface CommonTranslations {
|
|
7
|
+
save: string;
|
|
8
|
+
cancel: string;
|
|
9
|
+
delete: string;
|
|
10
|
+
confirm: string;
|
|
11
|
+
enabled: string;
|
|
12
|
+
disabled: string;
|
|
13
|
+
online: string;
|
|
14
|
+
offline: string;
|
|
15
|
+
testing: string;
|
|
16
|
+
error: string;
|
|
17
|
+
success: string;
|
|
18
|
+
notConfigured: string;
|
|
19
|
+
add: string;
|
|
20
|
+
remove: string;
|
|
21
|
+
close: string;
|
|
22
|
+
yes: string;
|
|
23
|
+
no: string;
|
|
24
|
+
reset: string;
|
|
25
|
+
default: string;
|
|
26
|
+
today: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 设置页面
|
|
30
|
+
export interface SettingsTranslations {
|
|
31
|
+
title: string;
|
|
32
|
+
generalTab: string;
|
|
33
|
+
providersTab: string;
|
|
34
|
+
securityTab: string;
|
|
35
|
+
chatTab: string;
|
|
36
|
+
advancedTab: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 基本设置
|
|
40
|
+
export interface GeneralTranslations {
|
|
41
|
+
title: string;
|
|
42
|
+
enableAssistant: string;
|
|
43
|
+
enableAssistantDesc: string;
|
|
44
|
+
defaultProvider: string;
|
|
45
|
+
providerCount: string; // "已配置 {count} 个提供商,当前使用"
|
|
46
|
+
language: string;
|
|
47
|
+
theme: string;
|
|
48
|
+
themeAuto: string;
|
|
49
|
+
themeLight: string;
|
|
50
|
+
themeDark: string;
|
|
51
|
+
themePixel: string;
|
|
52
|
+
themeTech: string;
|
|
53
|
+
shortcuts: string;
|
|
54
|
+
shortcutOpenChat: string;
|
|
55
|
+
shortcutOpenChatDesc: string;
|
|
56
|
+
shortcutGenerate: string;
|
|
57
|
+
shortcutGenerateDesc: string;
|
|
58
|
+
shortcutExplain: string;
|
|
59
|
+
shortcutExplainDesc: string;
|
|
60
|
+
shortcutTip: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 聊天设置
|
|
64
|
+
export interface ChatSettingsTranslations {
|
|
65
|
+
title: string;
|
|
66
|
+
appearance: string;
|
|
67
|
+
theme: string;
|
|
68
|
+
fontSize: string;
|
|
69
|
+
compactMode: string;
|
|
70
|
+
compactModeDesc: string;
|
|
71
|
+
behavior: string;
|
|
72
|
+
enterToSend: string;
|
|
73
|
+
enterToSendDesc: string;
|
|
74
|
+
showTimestamps: string;
|
|
75
|
+
showTimestampsDesc: string;
|
|
76
|
+
showAvatars: string;
|
|
77
|
+
showAvatarsDesc: string;
|
|
78
|
+
soundEnabled: string;
|
|
79
|
+
soundEnabledDesc: string;
|
|
80
|
+
history: string;
|
|
81
|
+
enableHistory: string;
|
|
82
|
+
enableHistoryDesc: string;
|
|
83
|
+
maxHistory: string;
|
|
84
|
+
maxHistoryUnit: string;
|
|
85
|
+
autoSave: string;
|
|
86
|
+
autoSaveDesc: string;
|
|
87
|
+
exportSettings: string;
|
|
88
|
+
clearHistory: string;
|
|
89
|
+
resetDefaults: string;
|
|
90
|
+
clearHistoryConfirm: string;
|
|
91
|
+
resetConfirm: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 安全设置
|
|
95
|
+
export interface SecurityTranslations {
|
|
96
|
+
title: string;
|
|
97
|
+
accessControl: string;
|
|
98
|
+
passwordProtection: string;
|
|
99
|
+
passwordProtectionDesc: string;
|
|
100
|
+
setPassword: string;
|
|
101
|
+
passwordPlaceholder: string;
|
|
102
|
+
riskAssessment: string;
|
|
103
|
+
riskAssessmentDesc: string;
|
|
104
|
+
defaultRiskLevel: string;
|
|
105
|
+
riskLow: string;
|
|
106
|
+
riskMedium: string;
|
|
107
|
+
riskHigh: string;
|
|
108
|
+
userConsent: string;
|
|
109
|
+
rememberConsent: string;
|
|
110
|
+
rememberConsentDesc: string;
|
|
111
|
+
consentExpiryDays: string;
|
|
112
|
+
dangerousPatterns: string;
|
|
113
|
+
addPattern: string;
|
|
114
|
+
patternPlaceholder: string;
|
|
115
|
+
saveSettings: string;
|
|
116
|
+
resetDefaults: string;
|
|
117
|
+
resetConfirm: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 供应商配置
|
|
121
|
+
export interface ProvidersTranslations {
|
|
122
|
+
title: string;
|
|
123
|
+
cloudProviders: string;
|
|
124
|
+
cloudProvidersDesc: string;
|
|
125
|
+
localProviders: string;
|
|
126
|
+
localProvidersDesc: string;
|
|
127
|
+
configured: string;
|
|
128
|
+
displayName: string;
|
|
129
|
+
status: string;
|
|
130
|
+
apiKey: string;
|
|
131
|
+
baseURL: string;
|
|
132
|
+
model: string;
|
|
133
|
+
saveConfig: string;
|
|
134
|
+
testConnection: string;
|
|
135
|
+
detectService: string;
|
|
136
|
+
delete: string;
|
|
137
|
+
deleteConfirm: string;
|
|
138
|
+
testSuccess: string;
|
|
139
|
+
testFail: string;
|
|
140
|
+
testError: string;
|
|
141
|
+
configSaved: string;
|
|
142
|
+
configDeleted: string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 供应商
|
|
146
|
+
export interface ProviderNamesTranslations {
|
|
147
|
+
openai: string;
|
|
148
|
+
anthropic: string;
|
|
149
|
+
minimax: string;
|
|
150
|
+
glm: string;
|
|
151
|
+
openaiCompatible: string;
|
|
152
|
+
ollama: string;
|
|
153
|
+
vllm: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 聊天界面
|
|
157
|
+
export interface ChatInterfaceTranslations {
|
|
158
|
+
title: string;
|
|
159
|
+
welcomeMessage: string;
|
|
160
|
+
inputPlaceholder: string;
|
|
161
|
+
thinking: string;
|
|
162
|
+
executingTool: string;
|
|
163
|
+
toolComplete: string;
|
|
164
|
+
errorPrefix: string;
|
|
165
|
+
tipCommand: string;
|
|
166
|
+
tipShortcut: string;
|
|
167
|
+
clearChat: string;
|
|
168
|
+
clearChatConfirm: string;
|
|
169
|
+
exportChat: string;
|
|
170
|
+
switchProvider: string;
|
|
171
|
+
providerBadge: string;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 风险等级
|
|
175
|
+
export interface RiskLevelTranslations {
|
|
176
|
+
low: string;
|
|
177
|
+
medium: string;
|
|
178
|
+
high: string;
|
|
179
|
+
unknown: string;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// 高级设置
|
|
183
|
+
export interface AdvancedSettingsTranslations {
|
|
184
|
+
title: string;
|
|
185
|
+
configManagement: string;
|
|
186
|
+
validateConfig: string;
|
|
187
|
+
resetDefaults: string;
|
|
188
|
+
logSettings: string;
|
|
189
|
+
logLevel: string;
|
|
190
|
+
logLevels: {
|
|
191
|
+
debug: string;
|
|
192
|
+
info: string;
|
|
193
|
+
warn: string;
|
|
194
|
+
error: string;
|
|
195
|
+
};
|
|
196
|
+
systemInfo: string;
|
|
197
|
+
pluginVersion: string;
|
|
198
|
+
supportedProviders: string;
|
|
199
|
+
currentProvider: string;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 主翻译类型
|
|
203
|
+
export interface TranslationKeys {
|
|
204
|
+
common: CommonTranslations;
|
|
205
|
+
settings: SettingsTranslations;
|
|
206
|
+
general: GeneralTranslations;
|
|
207
|
+
chatSettings: ChatSettingsTranslations;
|
|
208
|
+
security: SecurityTranslations;
|
|
209
|
+
providers: ProvidersTranslations;
|
|
210
|
+
providerNames: ProviderNamesTranslations;
|
|
211
|
+
chatInterface: ChatInterfaceTranslations;
|
|
212
|
+
riskLevel: RiskLevelTranslations;
|
|
213
|
+
advancedSettings: AdvancedSettingsTranslations;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// 支持的语言类型
|
|
217
|
+
export type SupportedLanguage = 'zh-CN' | 'en-US' | 'ja-JP';
|
|
218
|
+
|
|
219
|
+
// 语言配置
|
|
220
|
+
export interface LanguageConfig {
|
|
221
|
+
code: SupportedLanguage;
|
|
222
|
+
label: string;
|
|
223
|
+
flag: string;
|
|
224
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,9 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
|
|
6
6
|
// 全局样式
|
|
7
7
|
import './styles/ai-assistant.scss';
|
|
8
8
|
|
|
9
|
+
// i18n Services
|
|
10
|
+
import { TranslateService } from './i18n';
|
|
11
|
+
|
|
9
12
|
// Tabby modules
|
|
10
13
|
import TabbyCoreModule, { AppService, ConfigService, ToolbarButtonProvider, ConfigProvider, HotkeyProvider, HotkeysService } from 'tabby-core';
|
|
11
14
|
import TabbyTerminalModule from 'tabby-terminal';
|
|
@@ -106,6 +109,9 @@ import { AiHotkeyProvider } from './providers/tabby/ai-hotkey.provider';
|
|
|
106
109
|
ConfigProviderService,
|
|
107
110
|
LoggerService,
|
|
108
111
|
|
|
112
|
+
// i18n Services
|
|
113
|
+
TranslateService,
|
|
114
|
+
|
|
109
115
|
// AI Providers
|
|
110
116
|
OpenAiProviderService,
|
|
111
117
|
AnthropicProviderService,
|
|
@@ -11,6 +11,7 @@ export interface AiSidebarConfig {
|
|
|
11
11
|
showInToolbar?: boolean;
|
|
12
12
|
sidebarVisible?: boolean;
|
|
13
13
|
sidebarCollapsed?: boolean;
|
|
14
|
+
sidebarWidth?: number;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -24,8 +25,15 @@ export class AiSidebarService {
|
|
|
24
25
|
private sidebarComponentRef: ComponentRef<AiSidebarComponent> | null = null;
|
|
25
26
|
private sidebarElement: HTMLElement | null = null;
|
|
26
27
|
private styleElement: HTMLStyleElement | null = null;
|
|
28
|
+
private resizeHandle: HTMLElement | null = null;
|
|
27
29
|
private _isVisible = false;
|
|
28
|
-
|
|
30
|
+
|
|
31
|
+
// Resize constants
|
|
32
|
+
private readonly MIN_WIDTH = 280;
|
|
33
|
+
private readonly MAX_WIDTH = 500;
|
|
34
|
+
private readonly DEFAULT_WIDTH = 320;
|
|
35
|
+
private currentWidth: number = this.DEFAULT_WIDTH;
|
|
36
|
+
private isResizing = false;
|
|
29
37
|
|
|
30
38
|
/**
|
|
31
39
|
* 侧边栏是否可见
|
|
@@ -122,24 +130,76 @@ export class AiSidebarService {
|
|
|
122
130
|
|
|
123
131
|
// 获取 DOM 元素
|
|
124
132
|
const domElem = (this.sidebarComponentRef.hostView as EmbeddedViewRef<any>).rootNodes[0] as HTMLElement;
|
|
133
|
+
// 直接设置组件 host 元素的样式 - 确保 flex 布局正确
|
|
134
|
+
domElem.style.display = 'flex';
|
|
135
|
+
domElem.style.flexDirection = 'column';
|
|
136
|
+
domElem.style.height = '100%';
|
|
137
|
+
domElem.style.width = '100%';
|
|
138
|
+
domElem.style.overflow = 'hidden';
|
|
125
139
|
|
|
126
140
|
// 创建 wrapper 元素 - 使用固定定位
|
|
127
141
|
const wrapper = document.createElement('div');
|
|
128
142
|
wrapper.className = 'ai-sidebar-wrapper';
|
|
143
|
+
|
|
144
|
+
// 加载保存的宽度或使用默认值
|
|
145
|
+
this.currentWidth = this.loadSidebarWidth();
|
|
146
|
+
|
|
147
|
+
// 获取视口高度 - 使用绝对像素值确保滚动容器正确计算
|
|
148
|
+
const viewportHeight = window.innerHeight;
|
|
129
149
|
wrapper.style.cssText = `
|
|
130
150
|
position: fixed;
|
|
131
151
|
left: 0;
|
|
132
152
|
top: 0;
|
|
133
|
-
width: ${this.
|
|
134
|
-
height:
|
|
153
|
+
width: ${this.currentWidth}px;
|
|
154
|
+
height: ${viewportHeight}px;
|
|
135
155
|
display: flex;
|
|
136
156
|
flex-direction: column;
|
|
137
157
|
background: var(--bs-body-bg, #1e1e1e);
|
|
138
158
|
border-right: 1px solid var(--bs-border-color, #333);
|
|
139
159
|
box-shadow: 2px 0 10px rgba(0,0,0,0.3);
|
|
140
160
|
z-index: 1000;
|
|
161
|
+
overflow: hidden;
|
|
162
|
+
`;
|
|
163
|
+
|
|
164
|
+
// 监听窗口大小变化,动态更新高度
|
|
165
|
+
const resizeHandler = () => {
|
|
166
|
+
wrapper.style.height = `${window.innerHeight}px`;
|
|
167
|
+
};
|
|
168
|
+
window.addEventListener('resize', resizeHandler);
|
|
169
|
+
// 存储 handler 以便在销毁时移除
|
|
170
|
+
(wrapper as any)._resizeHandler = resizeHandler;
|
|
171
|
+
|
|
172
|
+
// 创建 resize handle(拖动条)
|
|
173
|
+
const resizeHandle = document.createElement('div');
|
|
174
|
+
resizeHandle.className = 'ai-sidebar-resize-handle';
|
|
175
|
+
resizeHandle.style.cssText = `
|
|
176
|
+
position: absolute;
|
|
177
|
+
top: 0;
|
|
178
|
+
right: -4px;
|
|
179
|
+
width: 8px;
|
|
180
|
+
height: 100%;
|
|
181
|
+
cursor: ew-resize;
|
|
182
|
+
background: transparent;
|
|
183
|
+
z-index: 1001;
|
|
184
|
+
transition: background 0.2s;
|
|
141
185
|
`;
|
|
142
186
|
|
|
187
|
+
// 鼠标悬停时显示高亮
|
|
188
|
+
resizeHandle.addEventListener('mouseenter', () => {
|
|
189
|
+
resizeHandle.style.background = 'var(--ai-primary, #4dabf7)';
|
|
190
|
+
});
|
|
191
|
+
resizeHandle.addEventListener('mouseleave', () => {
|
|
192
|
+
if (!this.isResizing) {
|
|
193
|
+
resizeHandle.style.background = 'transparent';
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// 添加拖动逻辑
|
|
198
|
+
this.setupResizeHandler(resizeHandle, wrapper, viewportHeight);
|
|
199
|
+
|
|
200
|
+
wrapper.appendChild(resizeHandle);
|
|
201
|
+
this.resizeHandle = resizeHandle;
|
|
202
|
+
|
|
143
203
|
wrapper.appendChild(domElem);
|
|
144
204
|
|
|
145
205
|
// 插入到 body
|
|
@@ -164,6 +224,14 @@ export class AiSidebarService {
|
|
|
164
224
|
// 移除注入的 CSS
|
|
165
225
|
this.removeLayoutCSS();
|
|
166
226
|
|
|
227
|
+
// 移除 resize 监听器
|
|
228
|
+
if (this.sidebarElement) {
|
|
229
|
+
const handler = (this.sidebarElement as any)._resizeHandler;
|
|
230
|
+
if (handler) {
|
|
231
|
+
window.removeEventListener('resize', handler);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
167
235
|
if (this.sidebarComponentRef) {
|
|
168
236
|
this.appRef.detachView(this.sidebarComponentRef.hostView);
|
|
169
237
|
this.sidebarComponentRef.destroy();
|
|
@@ -211,7 +279,7 @@ export class AiSidebarService {
|
|
|
211
279
|
|
|
212
280
|
/**
|
|
213
281
|
* 注入布局 CSS - 使用 margin-left 把主内容推开
|
|
214
|
-
*
|
|
282
|
+
*
|
|
215
283
|
* 固定定位方案:侧边栏 fixed,主内容区 margin-left
|
|
216
284
|
*/
|
|
217
285
|
private injectLayoutCSS(): void {
|
|
@@ -220,7 +288,7 @@ export class AiSidebarService {
|
|
|
220
288
|
style.textContent = `
|
|
221
289
|
/* 用 margin-left 把 app-root 推开,为侧边栏腾出空间 */
|
|
222
290
|
app-root {
|
|
223
|
-
margin-left: ${this.
|
|
291
|
+
margin-left: ${this.currentWidth}px !important;
|
|
224
292
|
}
|
|
225
293
|
`;
|
|
226
294
|
|
|
@@ -238,6 +306,90 @@ export class AiSidebarService {
|
|
|
238
306
|
}
|
|
239
307
|
}
|
|
240
308
|
|
|
309
|
+
/**
|
|
310
|
+
* 设置 resize handle 拖动逻辑
|
|
311
|
+
*/
|
|
312
|
+
private setupResizeHandler(handle: HTMLElement, wrapper: HTMLElement, viewportHeight: number): void {
|
|
313
|
+
let startX: number;
|
|
314
|
+
let startWidth: number;
|
|
315
|
+
|
|
316
|
+
const onMouseDown = (e: MouseEvent) => {
|
|
317
|
+
e.preventDefault();
|
|
318
|
+
this.isResizing = true;
|
|
319
|
+
startX = e.clientX;
|
|
320
|
+
startWidth = this.currentWidth;
|
|
321
|
+
|
|
322
|
+
document.addEventListener('mousemove', onMouseMove);
|
|
323
|
+
document.addEventListener('mouseup', onMouseUp);
|
|
324
|
+
document.body.style.cursor = 'ew-resize';
|
|
325
|
+
document.body.style.userSelect = 'none';
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const onMouseMove = (e: MouseEvent) => {
|
|
329
|
+
if (!this.isResizing) return;
|
|
330
|
+
|
|
331
|
+
const delta = e.clientX - startX;
|
|
332
|
+
let newWidth = startWidth + delta;
|
|
333
|
+
|
|
334
|
+
// 限制宽度范围
|
|
335
|
+
newWidth = Math.max(this.MIN_WIDTH, Math.min(this.MAX_WIDTH, newWidth));
|
|
336
|
+
|
|
337
|
+
this.currentWidth = newWidth;
|
|
338
|
+
wrapper.style.width = `${newWidth}px`;
|
|
339
|
+
|
|
340
|
+
// 更新 app-root 的 margin-left
|
|
341
|
+
this.updateLayoutCSS(newWidth);
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const onMouseUp = () => {
|
|
345
|
+
this.isResizing = false;
|
|
346
|
+
document.removeEventListener('mousemove', onMouseMove);
|
|
347
|
+
document.removeEventListener('mouseup', onMouseUp);
|
|
348
|
+
document.body.style.cursor = '';
|
|
349
|
+
document.body.style.userSelect = '';
|
|
350
|
+
handle.style.background = 'transparent';
|
|
351
|
+
|
|
352
|
+
// 保存宽度到配置
|
|
353
|
+
this.saveSidebarWidth(this.currentWidth);
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
handle.addEventListener('mousedown', onMouseDown);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* 更新布局 CSS(resize 时调用)
|
|
361
|
+
*/
|
|
362
|
+
private updateLayoutCSS(width: number): void {
|
|
363
|
+
if (this.styleElement) {
|
|
364
|
+
this.styleElement.textContent = `
|
|
365
|
+
app-root {
|
|
366
|
+
margin-left: ${width}px !important;
|
|
367
|
+
}
|
|
368
|
+
`;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* 加载保存的侧边栏宽度
|
|
374
|
+
*/
|
|
375
|
+
private loadSidebarWidth(): number {
|
|
376
|
+
const pluginConfig = this.getPluginConfig();
|
|
377
|
+
const savedWidth = pluginConfig.sidebarWidth;
|
|
378
|
+
if (savedWidth && savedWidth >= this.MIN_WIDTH && savedWidth <= this.MAX_WIDTH) {
|
|
379
|
+
return savedWidth;
|
|
380
|
+
}
|
|
381
|
+
return this.DEFAULT_WIDTH;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* 保存侧边栏宽度到配置
|
|
386
|
+
*/
|
|
387
|
+
private saveSidebarWidth(width: number): void {
|
|
388
|
+
const pluginConfig = this.getPluginConfig();
|
|
389
|
+
pluginConfig.sidebarWidth = width;
|
|
390
|
+
this.savePluginConfig(pluginConfig);
|
|
391
|
+
}
|
|
392
|
+
|
|
241
393
|
/**
|
|
242
394
|
* 获取插件配置
|
|
243
395
|
*/
|