tabby-ai-assistant 1.0.8 → 1.0.10

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.
Files changed (54) hide show
  1. package/dist/components/chat/ai-sidebar.component.d.ts +16 -3
  2. package/dist/components/chat/chat-input.component.d.ts +4 -0
  3. package/dist/components/chat/chat-interface.component.d.ts +22 -1
  4. package/dist/components/chat/chat-settings.component.d.ts +21 -11
  5. package/dist/components/settings/ai-settings-tab.component.d.ts +14 -4
  6. package/dist/components/settings/general-settings.component.d.ts +43 -12
  7. package/dist/components/settings/provider-config.component.d.ts +110 -5
  8. package/dist/components/settings/security-settings.component.d.ts +14 -4
  9. package/dist/i18n/index.d.ts +48 -0
  10. package/dist/i18n/translations/en-US.d.ts +5 -0
  11. package/dist/i18n/translations/ja-JP.d.ts +5 -0
  12. package/dist/i18n/translations/zh-CN.d.ts +5 -0
  13. package/dist/i18n/types.d.ts +198 -0
  14. package/dist/index.js +1 -1
  15. package/dist/services/chat/ai-sidebar.service.d.ts +23 -1
  16. package/dist/services/core/theme.service.d.ts +53 -0
  17. package/dist/services/core/toast.service.d.ts +15 -0
  18. package/package.json +1 -1
  19. package/src/components/chat/ai-sidebar.component.scss +468 -0
  20. package/src/components/chat/ai-sidebar.component.ts +47 -344
  21. package/src/components/chat/chat-input.component.scss +2 -2
  22. package/src/components/chat/chat-input.component.ts +16 -5
  23. package/src/components/chat/chat-interface.component.html +11 -11
  24. package/src/components/chat/chat-interface.component.scss +410 -4
  25. package/src/components/chat/chat-interface.component.ts +105 -14
  26. package/src/components/chat/chat-message.component.scss +3 -3
  27. package/src/components/chat/chat-message.component.ts +3 -2
  28. package/src/components/chat/chat-settings.component.html +95 -61
  29. package/src/components/chat/chat-settings.component.scss +224 -50
  30. package/src/components/chat/chat-settings.component.ts +56 -30
  31. package/src/components/security/risk-confirm-dialog.component.scss +7 -7
  32. package/src/components/settings/ai-settings-tab.component.html +27 -27
  33. package/src/components/settings/ai-settings-tab.component.scss +34 -20
  34. package/src/components/settings/ai-settings-tab.component.ts +59 -20
  35. package/src/components/settings/general-settings.component.html +69 -40
  36. package/src/components/settings/general-settings.component.scss +151 -58
  37. package/src/components/settings/general-settings.component.ts +168 -55
  38. package/src/components/settings/provider-config.component.html +183 -60
  39. package/src/components/settings/provider-config.component.scss +332 -153
  40. package/src/components/settings/provider-config.component.ts +268 -19
  41. package/src/components/settings/security-settings.component.html +70 -39
  42. package/src/components/settings/security-settings.component.scss +104 -8
  43. package/src/components/settings/security-settings.component.ts +48 -10
  44. package/src/i18n/index.ts +129 -0
  45. package/src/i18n/translations/en-US.ts +193 -0
  46. package/src/i18n/translations/ja-JP.ts +193 -0
  47. package/src/i18n/translations/zh-CN.ts +193 -0
  48. package/src/i18n/types.ts +224 -0
  49. package/src/index.ts +6 -0
  50. package/src/services/chat/ai-sidebar.service.ts +157 -5
  51. package/src/services/core/theme.service.ts +480 -0
  52. package/src/services/core/toast.service.ts +36 -0
  53. package/src/styles/ai-assistant.scss +8 -88
  54. package/src/styles/themes.scss +161 -0
@@ -1,37 +1,54 @@
1
1
  <div class="general-settings">
2
- <h3>基本设置</h3>
2
+ <h3>{{ t.general.title }}</h3>
3
3
 
4
4
  <!-- 启用状态 -->
5
5
  <div class="settings-section">
6
- <h4>功能状态</h4>
6
+ <h4>{{ t.security.accessControl }}</h4>
7
7
  <div class="form-check">
8
8
  <input type="checkbox" id="enabled" [(ngModel)]="isEnabled" (change)="updateEnabled(isEnabled)">
9
9
  <label for="enabled">
10
- <strong>启用AI助手</strong>
11
- <p class="form-description">启用或禁用整个AI助手功能</p>
10
+ <strong>{{ t.general.enableAssistant }}</strong>
11
+ <p class="form-description">{{ t.general.enableAssistantDesc }}</p>
12
12
  </label>
13
13
  </div>
14
14
  </div>
15
15
 
16
16
  <!-- 默认提供商 -->
17
17
  <div class="settings-section">
18
- <h4>默认AI提供商</h4>
18
+ <div class="section-header">
19
+ <h4>{{ t.general.defaultProvider }}</h4>
20
+ <span class="section-info">
21
+ {{ translate._('general.providerCount', { count: availableProviders.length }) }}
22
+ <strong>{{ config.getProviderConfig(selectedProvider)?.displayName || t.providerNames[selectedProvider] || selectedProvider }}</strong>
23
+ </span>
24
+ </div>
19
25
  <div class="provider-selector">
20
- <label>选择默认提供商</label>
21
26
  <div class="providers-grid">
22
27
  <div *ngFor="let provider of availableProviders" class="provider-card"
23
28
  [class.selected]="selectedProvider === provider.name"
24
29
  (click)="updateDefaultProvider(provider.name)">
25
30
  <div class="provider-header">
26
- <i class="fa fa-cloud"></i>
27
- <h5>{{ provider.displayName }}</h5>
31
+ <i class="fa" [ngClass]="provider.isLocal ? 'fa-server' : 'fa-cloud'"></i>
32
+ <h5>{{ t.providerNames[provider.name] || provider.displayName }}</h5>
33
+ <span class="selection-indicator" *ngIf="selectedProvider === provider.name">
34
+ <i class="fa fa-check-circle"></i>
35
+ </span>
28
36
  </div>
29
37
  <div class="provider-body">
30
- <p class="provider-desc">{{ provider.description || 'AI模型提供商' }}</p>
38
+ <p class="provider-desc">{{ provider.description || t.providers.notConfigured }}</p>
31
39
  <div class="provider-meta">
32
- <span class="status-badge" [style.color]="getProviderStatus(provider.name).color">
33
- {{ getProviderStatus(provider.name).text }}
34
- </span>
40
+ <ng-container *ngIf="provider.isLocal; else cloudStatus">
41
+ <span class="status-badge" [style.color]="getLocalProviderStatus(provider.name).color">
42
+ <i class="fa" [ngClass]="getLocalProviderStatus(provider.name).icon"></i>
43
+ {{ getLocalProviderStatus(provider.name).text }}
44
+ </span>
45
+ </ng-container>
46
+ <ng-template #cloudStatus>
47
+ <span class="status-badge" [style.color]="getProviderStatus(provider.name).color">
48
+ <i class="fa" [ngClass]="getProviderStatus(provider.name).icon"></i>
49
+ {{ getProviderStatus(provider.name).text }}
50
+ </span>
51
+ </ng-template>
35
52
  </div>
36
53
  </div>
37
54
  </div>
@@ -41,54 +58,66 @@
41
58
 
42
59
  <!-- 界面设置 -->
43
60
  <div class="settings-section">
44
- <h4>界面设置</h4>
61
+ <h4>{{ t.settings.title }}</h4>
45
62
 
46
- <div class="form-group">
47
- <label>语言</label>
48
- <select class="form-control" [(ngModel)]="language" (change)="updateLanguage(language)">
49
- <option *ngFor="let lang of languages" [value]="lang.value">
50
- {{ lang.label }}
51
- </option>
52
- </select>
53
- </div>
63
+ <div class="form-row">
64
+ <div class="form-group">
65
+ <label>{{ t.general.language }}</label>
66
+ <select class="form-control" [(ngModel)]="language" (change)="updateLanguage(language)">
67
+ <option *ngFor="let lang of languages" [value]="lang.value">
68
+ {{ lang.flag }} {{ lang.label }}
69
+ </option>
70
+ </select>
71
+ </div>
54
72
 
55
- <div class="form-group">
56
- <label>主题</label>
57
- <div class="theme-selector">
58
- <button *ngFor="let themeOption of themes" class="theme-btn"
59
- [class.active]="theme === themeOption.value" (click)="updateTheme(themeOption.value)" type="button">
60
- <div class="theme-preview" [attr.data-theme]="themeOption.value"></div>
61
- <span>{{ themeOption.label }}</span>
62
- </button>
73
+ <div class="form-group theme-group">
74
+ <label>{{ t.general.theme }}</label>
75
+ <div class="theme-selector">
76
+ <button *ngFor="let themeOption of themes" class="theme-btn"
77
+ [class.active]="theme === themeOption.value" (click)="updateTheme(themeOption.value)" type="button">
78
+ <div class="theme-preview" [attr.data-theme]="themeOption.value"></div>
79
+ <span>{{ themeOption.label }}</span>
80
+ </button>
81
+ </div>
63
82
  </div>
64
83
  </div>
65
84
  </div>
66
85
 
67
86
  <!-- 快捷键提示 -->
68
87
  <div class="settings-section">
69
- <h4>快捷键</h4>
88
+ <h4>{{ t.general.shortcuts }}</h4>
70
89
  <div class="hotkey-list">
71
90
  <div class="hotkey-item">
72
91
  <div class="hotkey-info">
73
- <strong>打开AI助手</strong>
74
- <p class="form-description">打开聊天界面</p>
92
+ <strong>{{ t.general.shortcutOpenChat }}</strong>
93
+ <p class="form-description">{{ t.general.shortcutOpenChatDesc }}</p>
94
+ </div>
95
+ <div class="hotkey-keys">
96
+ <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>A</kbd>
75
97
  </div>
76
- <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>A</kbd>
77
98
  </div>
78
99
  <div class="hotkey-item">
79
100
  <div class="hotkey-info">
80
- <strong>生成命令</strong>
81
- <p class="form-description">从选择生成命令</p>
101
+ <strong>{{ t.general.shortcutGenerate }}</strong>
102
+ <p class="form-description">{{ t.general.shortcutGenerateDesc }}</p>
103
+ </div>
104
+ <div class="hotkey-keys">
105
+ <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>G</kbd>
82
106
  </div>
83
- <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>G</kbd>
84
107
  </div>
85
108
  <div class="hotkey-item">
86
109
  <div class="hotkey-info">
87
- <strong>解释命令</strong>
88
- <p class="form-description">解释当前选择</p>
110
+ <strong>{{ t.general.shortcutExplain }}</strong>
111
+ <p class="form-description">{{ t.general.shortcutExplainDesc }}</p>
112
+ </div>
113
+ <div class="hotkey-keys">
114
+ <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>E</kbd>
89
115
  </div>
90
- <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>E</kbd>
91
116
  </div>
92
117
  </div>
118
+ <p class="hotkey-tip">
119
+ <i class="fa fa-info-circle"></i>
120
+ {{ t.general.shortcutTip }}
121
+ </p>
93
122
  </div>
94
- </div>
123
+ </div>
@@ -1,25 +1,47 @@
1
1
  .general-settings {
2
- max-width: 900px;
2
+ max-width: 1000px;
3
3
 
4
4
  h3 {
5
5
  margin: 0 0 1.5rem 0;
6
6
  font-size: 1.25rem;
7
7
  font-weight: 600;
8
- color: var(--ai-dark);
8
+ color: var(--ai-text-primary);
9
9
  }
10
10
 
11
11
  .settings-section {
12
12
  margin-bottom: 2rem;
13
13
  padding: 1.5rem;
14
14
  background-color: var(--ai-bg-secondary);
15
- border-radius: 0.5rem;
15
+ border-radius: 12px;
16
16
  border: 1px solid var(--ai-border);
17
17
 
18
18
  h4 {
19
19
  margin: 0 0 1rem 0;
20
20
  font-size: 1.125rem;
21
21
  font-weight: 600;
22
- color: var(--ai-dark);
22
+ color: var(--ai-text-primary);
23
+ }
24
+
25
+ .section-header {
26
+ display: flex;
27
+ align-items: center;
28
+ justify-content: space-between;
29
+ margin-bottom: 1rem;
30
+ flex-wrap: wrap;
31
+ gap: 8px;
32
+
33
+ h4 {
34
+ margin: 0;
35
+ }
36
+
37
+ .section-info {
38
+ font-size: 0.875rem;
39
+ color: var(--ai-text-secondary);
40
+
41
+ strong {
42
+ color: var(--ai-primary);
43
+ }
44
+ }
23
45
  }
24
46
 
25
47
  .form-check {
@@ -41,34 +63,43 @@
41
63
  strong {
42
64
  display: block;
43
65
  margin-bottom: 0.25rem;
44
- color: var(--ai-dark);
66
+ color: var(--ai-text-primary);
45
67
  }
46
68
 
47
69
  .form-description {
48
70
  margin: 0;
49
71
  font-size: 0.875rem;
50
- color: var(--ai-secondary);
72
+ color: var(--ai-text-secondary);
51
73
  }
52
74
  }
53
75
  }
54
76
 
77
+ .form-row {
78
+ display: grid;
79
+ grid-template-columns: 200px 1fr;
80
+ gap: 2rem;
81
+ align-items: start;
82
+ }
83
+
55
84
  .form-group {
56
- margin-bottom: 1rem;
85
+ margin-bottom: 0;
57
86
 
58
87
  label {
59
88
  display: block;
60
89
  font-weight: 500;
61
90
  margin-bottom: 0.5rem;
62
- color: var(--ai-dark);
91
+ color: var(--ai-text-primary);
92
+ font-size: 0.875rem;
63
93
  }
64
94
 
65
95
  .form-control {
66
- width: 250px;
67
- padding: 0.5rem 0.75rem;
96
+ width: 100%;
97
+ min-width: 200px;
98
+ padding: 0.625rem 0.875rem;
68
99
  border: 1px solid var(--ai-border);
69
- border-radius: 0.375rem;
100
+ border-radius: 8px;
70
101
  background-color: var(--ai-bg-primary);
71
- color: var(--ai-dark);
102
+ color: var(--ai-text-primary);
72
103
  font-size: 14px;
73
104
 
74
105
  &:focus {
@@ -80,35 +111,29 @@
80
111
  }
81
112
 
82
113
  .provider-selector {
83
- label {
84
- display: block;
85
- font-weight: 500;
86
- margin-bottom: 1rem;
87
- color: var(--ai-dark);
88
- }
89
-
90
114
  .providers-grid {
91
115
  display: grid;
92
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
116
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
93
117
  gap: 1rem;
94
118
 
95
119
  .provider-card {
96
120
  padding: 1rem;
97
121
  border: 2px solid var(--ai-border);
98
- border-radius: 0.5rem;
122
+ border-radius: 12px;
99
123
  cursor: pointer;
100
- transition: all 0.2s;
124
+ transition: all 0.2s ease;
101
125
  background-color: var(--ai-bg-primary);
102
126
 
103
127
  &:hover {
104
128
  border-color: var(--ai-primary);
105
129
  transform: translateY(-2px);
106
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
130
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
107
131
  }
108
132
 
109
133
  &.selected {
110
134
  border-color: var(--ai-primary);
111
- background-color: rgba(0, 123, 255, 0.05);
135
+ background-color: rgba(0, 123, 255, 0.08);
136
+ box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.15);
112
137
  }
113
138
 
114
139
  .provider-header {
@@ -116,6 +141,7 @@
116
141
  align-items: center;
117
142
  gap: 0.75rem;
118
143
  margin-bottom: 0.5rem;
144
+ position: relative;
119
145
 
120
146
  i {
121
147
  font-size: 1.5rem;
@@ -126,15 +152,22 @@
126
152
  margin: 0;
127
153
  font-size: 1rem;
128
154
  font-weight: 600;
129
- color: var(--ai-dark);
155
+ color: var(--ai-text-primary);
156
+ flex: 1;
157
+ }
158
+
159
+ .selection-indicator {
160
+ color: var(--ai-primary);
161
+ font-size: 1.25rem;
130
162
  }
131
163
  }
132
164
 
133
165
  .provider-body {
134
166
  .provider-desc {
135
167
  margin: 0 0 0.75rem 0;
136
- font-size: 0.875rem;
137
- color: var(--ai-secondary);
168
+ font-size: 0.8125rem;
169
+ color: var(--ai-text-secondary);
170
+ line-height: 1.4;
138
171
  }
139
172
 
140
173
  .provider-meta {
@@ -143,8 +176,14 @@
143
176
  align-items: center;
144
177
 
145
178
  .status-badge {
179
+ display: inline-flex;
180
+ align-items: center;
181
+ gap: 4px;
146
182
  font-size: 0.75rem;
147
183
  font-weight: 500;
184
+ padding: 3px 8px;
185
+ background: rgba(255, 255, 255, 0.08);
186
+ border-radius: 4px;
148
187
  }
149
188
  }
150
189
  }
@@ -154,16 +193,16 @@
154
193
 
155
194
  .theme-selector {
156
195
  display: flex;
157
- gap: 0.75rem;
196
+ gap: 1rem;
158
197
  flex-wrap: wrap;
159
198
 
160
199
  .theme-btn {
161
200
  padding: 0.75rem;
162
201
  border: 2px solid var(--ai-border);
163
- border-radius: 0.5rem;
202
+ border-radius: 12px;
164
203
  background-color: var(--ai-bg-primary);
165
204
  cursor: pointer;
166
- transition: all 0.2s;
205
+ transition: all 0.2s ease;
167
206
  display: flex;
168
207
  flex-direction: column;
169
208
  align-items: center;
@@ -172,35 +211,57 @@
172
211
 
173
212
  &:hover {
174
213
  border-color: var(--ai-primary);
214
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
215
+ transform: translateY(-2px);
175
216
  }
176
217
 
177
218
  &.active {
178
219
  border-color: var(--ai-primary);
179
- background-color: rgba(0, 123, 255, 0.05);
220
+ background-color: rgba(0, 123, 255, 0.08);
221
+ box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.2);
180
222
  }
181
223
 
182
224
  .theme-preview {
183
- width: 40px;
184
- height: 40px;
185
- border-radius: 0.375rem;
186
- border: 1px solid var(--ai-border);
225
+ width: 56px;
226
+ height: 56px;
227
+ border-radius: 8px;
228
+ border: 2px solid var(--ai-border);
229
+ transition: all 0.2s ease;
187
230
 
188
231
  &[data-theme="light"] {
189
- background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
232
+ background: linear-gradient(135deg, #ffffff 0%, #f0f0f0 100%);
190
233
  }
191
234
 
192
235
  &[data-theme="dark"] {
193
- background: linear-gradient(135deg, #2d3748 0%, #1a202c 100%);
236
+ background: linear-gradient(135deg, #3a3a4a 0%, #1a1a2a 100%);
194
237
  }
195
238
 
196
239
  &[data-theme="auto"] {
197
- background: linear-gradient(135deg, #ffffff 0%, #2d3748 100%);
240
+ background: linear-gradient(135deg, #ffffff 0%, #3a3a4a 50%, #1a1a2a 100%);
241
+ }
242
+
243
+ &[data-theme="pixel"] {
244
+ background: #0f380f;
245
+ border: 4px solid #9bbc0f;
246
+ border-radius: 0;
247
+ background-image:
248
+ linear-gradient(90deg, rgba(155, 188, 15, 0.1) 1px, transparent 1px),
249
+ linear-gradient(rgba(155, 188, 15, 0.1) 1px, transparent 1px);
250
+ background-size: 8px 8px;
251
+ }
252
+
253
+ &[data-theme="tech"] {
254
+ background: #0a0a0f;
255
+ border: 1px solid #00fff9;
256
+ border-radius: 4px;
257
+ box-shadow: 0 0 10px rgba(0, 255, 249, 0.4);
198
258
  }
199
259
  }
200
260
 
201
261
  span {
202
262
  font-size: 0.875rem;
203
- color: var(--ai-dark);
263
+ color: var(--ai-text-primary);
264
+ font-weight: 500;
204
265
  }
205
266
  }
206
267
  }
@@ -210,7 +271,7 @@
210
271
  display: flex;
211
272
  justify-content: space-between;
212
273
  align-items: center;
213
- padding: 0.75rem 0;
274
+ padding: 0.875rem 0;
214
275
  border-bottom: 1px solid var(--ai-border);
215
276
 
216
277
  &:last-child {
@@ -223,30 +284,53 @@
223
284
  strong {
224
285
  display: block;
225
286
  margin-bottom: 0.25rem;
226
- color: var(--ai-dark);
287
+ color: var(--ai-text-primary);
227
288
  }
228
289
 
229
290
  .form-description {
230
291
  margin: 0;
231
- font-size: 0.875rem;
232
- color: var(--ai-secondary);
292
+ font-size: 0.8125rem;
293
+ color: var(--ai-text-secondary);
233
294
  }
234
295
  }
235
296
 
236
- kbd {
237
- padding: 0.25rem 0.5rem;
238
- background-color: var(--ai-bg-primary, #2a2a40);
239
- border: 1px solid var(--ai-border, #444);
240
- border-radius: 0.25rem;
241
- font-size: 0.75rem;
242
- font-family: monospace;
243
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
244
- color: var(--ai-dark, #e0e0e0);
245
- display: inline-block;
246
- margin: 0 0.125rem;
297
+ .hotkey-keys {
298
+ display: flex;
299
+ align-items: center;
300
+ gap: 4px;
301
+
302
+ kbd {
303
+ padding: 0.375rem 0.625rem;
304
+ background-color: var(--ai-bg-primary);
305
+ border: 1px solid var(--ai-border);
306
+ border-radius: 6px;
307
+ font-size: 0.75rem;
308
+ font-family: 'SF Mono', 'Consolas', monospace;
309
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
310
+ color: var(--ai-text-primary);
311
+ font-weight: 500;
312
+ min-width: 28px;
313
+ text-align: center;
314
+ }
247
315
  }
248
316
  }
249
317
  }
318
+
319
+ .hotkey-tip {
320
+ display: flex;
321
+ align-items: center;
322
+ gap: 0.5rem;
323
+ margin: 1rem 0 0 0;
324
+ padding: 0.75rem 1rem;
325
+ background-color: rgba(0, 123, 255, 0.08);
326
+ border-radius: 8px;
327
+ font-size: 0.8125rem;
328
+ color: var(--ai-text-secondary);
329
+
330
+ i {
331
+ color: var(--ai-primary);
332
+ }
333
+ }
250
334
  }
251
335
  }
252
336
 
@@ -256,6 +340,11 @@
256
340
  .settings-section {
257
341
  padding: 1rem;
258
342
 
343
+ .form-row {
344
+ grid-template-columns: 1fr;
345
+ gap: 1.5rem;
346
+ }
347
+
259
348
  .providers-grid {
260
349
  grid-template-columns: 1fr;
261
350
  }
@@ -266,8 +355,8 @@
266
355
  padding: 0.5rem;
267
356
 
268
357
  .theme-preview {
269
- width: 32px;
270
- height: 32px;
358
+ width: 48px;
359
+ height: 48px;
271
360
  }
272
361
 
273
362
  span {
@@ -280,9 +369,13 @@
280
369
  .hotkey-item {
281
370
  flex-direction: column;
282
371
  align-items: flex-start;
283
- gap: 0.5rem;
372
+ gap: 0.75rem;
373
+
374
+ .hotkey-keys {
375
+ align-self: flex-end;
376
+ }
284
377
  }
285
378
  }
286
379
  }
287
380
  }
288
- }
381
+ }