@theia/ai-chat-ui 1.59.0-next.72 → 1.60.0-next.43

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 (28) hide show
  1. package/lib/browser/ai-chat-ui-contribution.d.ts +0 -1
  2. package/lib/browser/ai-chat-ui-contribution.d.ts.map +1 -1
  3. package/lib/browser/ai-chat-ui-contribution.js +20 -5
  4. package/lib/browser/ai-chat-ui-contribution.js.map +1 -1
  5. package/lib/browser/chat-input-widget.d.ts.map +1 -1
  6. package/lib/browser/chat-input-widget.js +3 -1
  7. package/lib/browser/chat-input-widget.js.map +1 -1
  8. package/lib/browser/chat-response-renderer/code-part-renderer.d.ts.map +1 -1
  9. package/lib/browser/chat-response-renderer/code-part-renderer.js +3 -2
  10. package/lib/browser/chat-response-renderer/code-part-renderer.js.map +1 -1
  11. package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts.map +1 -1
  12. package/lib/browser/chat-tree-view/chat-view-tree-widget.js +2 -1
  13. package/lib/browser/chat-tree-view/chat-view-tree-widget.js.map +1 -1
  14. package/lib/browser/chat-view-contribution.d.ts.map +1 -1
  15. package/lib/browser/chat-view-contribution.js +17 -2
  16. package/lib/browser/chat-view-contribution.js.map +1 -1
  17. package/lib/browser/chat-view-language-contribution.d.ts +14 -4
  18. package/lib/browser/chat-view-language-contribution.d.ts.map +1 -1
  19. package/lib/browser/chat-view-language-contribution.js +96 -38
  20. package/lib/browser/chat-view-language-contribution.js.map +1 -1
  21. package/package.json +12 -11
  22. package/src/browser/ai-chat-ui-contribution.ts +20 -6
  23. package/src/browser/chat-input-widget.tsx +3 -1
  24. package/src/browser/chat-response-renderer/code-part-renderer.tsx +3 -2
  25. package/src/browser/chat-tree-view/chat-view-tree-widget.tsx +2 -1
  26. package/src/browser/chat-view-contribution.ts +18 -2
  27. package/src/browser/chat-view-language-contribution.ts +134 -77
  28. package/src/browser/style/index.css +11 -3
@@ -31,6 +31,16 @@ const VARIABLE_RESOLUTION_CONTEXT = { context: 'chat-input-autocomplete' };
31
31
  const VARIABLE_ARGUMENT_PICKER_COMMAND = 'trigger-variable-argument-picker';
32
32
  const VARIABLE_ADD_CONTEXT_COMMAND = 'add-context-variable';
33
33
 
34
+ interface CompletionSource<T> {
35
+ triggerCharacter: string;
36
+ getItems: () => T[];
37
+ kind: monaco.languages.CompletionItemKind;
38
+ getId: (item: T) => string;
39
+ getName: (item: T) => string;
40
+ getDescription: (item: T) => string;
41
+ command?: monaco.languages.Command;
42
+ }
43
+
34
44
  @injectable()
35
45
  export class ChatViewLanguageContribution implements FrontendApplicationContribution {
36
46
 
@@ -49,39 +59,84 @@ export class ChatViewLanguageContribution implements FrontendApplicationContribu
49
59
  onStart(_app: FrontendApplication): MaybePromise<void> {
50
60
  monaco.languages.register({ id: CHAT_VIEW_LANGUAGE_ID, extensions: [CHAT_VIEW_LANGUAGE_EXTENSION] });
51
61
 
52
- monaco.languages.registerCompletionItemProvider(CHAT_VIEW_LANGUAGE_ID, {
53
- triggerCharacters: [PromptText.AGENT_CHAR],
54
- provideCompletionItems: (model, position, _context, _token): ProviderResult<monaco.languages.CompletionList> => this.provideAgentCompletions(model, position),
62
+ this.registerCompletionProviders();
63
+
64
+ monaco.editor.registerCommand(VARIABLE_ARGUMENT_PICKER_COMMAND, this.triggerVariableArgumentPicker.bind(this));
65
+ monaco.editor.registerCommand(VARIABLE_ADD_CONTEXT_COMMAND, (_, ...args) => args.length > 1 ? this.addContextVariable(args[0], args[1]) : undefined);
66
+ }
67
+
68
+ protected registerCompletionProviders(): void {
69
+ this.registerStandardCompletionProvider({
70
+ triggerCharacter: PromptText.AGENT_CHAR,
71
+ getItems: () => this.agentService.getAgents(),
72
+ kind: monaco.languages.CompletionItemKind.Value,
73
+ getId: agent => `${agent.id} `,
74
+ getName: agent => agent.name,
75
+ getDescription: agent => agent.description
55
76
  });
56
- monaco.languages.registerCompletionItemProvider(CHAT_VIEW_LANGUAGE_ID, {
57
- triggerCharacters: [PromptText.VARIABLE_CHAR],
58
- provideCompletionItems: (model, position, _context, _token): ProviderResult<monaco.languages.CompletionList> => this.provideVariableCompletions(model, position),
77
+
78
+ this.registerStandardCompletionProvider({
79
+ triggerCharacter: PromptText.VARIABLE_CHAR,
80
+ getItems: () => this.variableService.getVariables(),
81
+ kind: monaco.languages.CompletionItemKind.Variable,
82
+ getId: variable => variable.args?.some(arg => !arg.isOptional) ? variable.name + PromptText.VARIABLE_SEPARATOR_CHAR : `${variable.name} `,
83
+ getName: variable => variable.name,
84
+ getDescription: variable => variable.description,
85
+ command: {
86
+ title: nls.localize('theia/ai/chat-ui/selectVariableArguments', 'Select variable arguments'),
87
+ id: VARIABLE_ARGUMENT_PICKER_COMMAND,
88
+ }
89
+ });
90
+
91
+ this.registerStandardCompletionProvider({
92
+ triggerCharacter: PromptText.FUNCTION_CHAR,
93
+ getItems: () => this.toolInvocationRegistry.getAllFunctions(),
94
+ kind: monaco.languages.CompletionItemKind.Function,
95
+ getId: tool => `${tool.id} `,
96
+ getName: tool => tool.name,
97
+ getDescription: tool => tool.description ?? ''
59
98
  });
99
+
100
+ // Register the variable argument completion provider (special case)
60
101
  monaco.languages.registerCompletionItemProvider(CHAT_VIEW_LANGUAGE_ID, {
61
102
  triggerCharacters: [PromptText.VARIABLE_CHAR, PromptText.VARIABLE_SEPARATOR_CHAR],
62
- provideCompletionItems: (model, position, _context, _token): ProviderResult<monaco.languages.CompletionList> => this.provideVariableWithArgCompletions(model, position),
103
+ provideCompletionItems: (model, position, _context, _token): ProviderResult<monaco.languages.CompletionList> =>
104
+ this.provideVariableWithArgCompletions(model, position),
63
105
  });
106
+ }
107
+
108
+ protected registerStandardCompletionProvider<T>(source: CompletionSource<T>): void {
64
109
  monaco.languages.registerCompletionItemProvider(CHAT_VIEW_LANGUAGE_ID, {
65
- triggerCharacters: [PromptText.FUNCTION_CHAR],
66
- provideCompletionItems: (model, position, _context, _token): ProviderResult<monaco.languages.CompletionList> => this.provideToolCompletions(model, position),
110
+ triggerCharacters: [source.triggerCharacter],
111
+ provideCompletionItems: (model, position, _context, _token): ProviderResult<monaco.languages.CompletionList> =>
112
+ this.provideCompletions(model, position, source),
67
113
  });
68
-
69
- monaco.editor.registerCommand(VARIABLE_ARGUMENT_PICKER_COMMAND, this.triggerVariableArgumentPicker.bind(this));
70
- monaco.editor.registerCommand(VARIABLE_ADD_CONTEXT_COMMAND, (_, ...args) => args.length > 1 ? this.addContextVariable(args[0], args[1]) : undefined);
71
114
  }
72
115
 
73
116
  getCompletionRange(model: monaco.editor.ITextModel, position: monaco.Position, triggerCharacter: string): monaco.Range | undefined {
74
- // Check if the character before the current position is the trigger character
117
+ const wordInfo = model.getWordUntilPosition(position);
75
118
  const lineContent = model.getLineContent(position.lineNumber);
76
- const characterBefore = lineContent[position.column - 2]; // Get the character before the current position
119
+ // one to the left, and -1 for 0-based index
120
+ const characterBeforeCurrentWord = lineContent[wordInfo.startColumn - 1 - 1];
77
121
 
78
- if (characterBefore !== triggerCharacter) {
79
- // Do not return agent suggestions if the user didn't just type the trigger character
122
+ if (characterBeforeCurrentWord !== triggerCharacter) {
80
123
  return undefined;
81
124
  }
82
125
 
83
- // Calculate the range from the position of the trigger character
84
- const wordInfo = model.getWordUntilPosition(position);
126
+ // we are not at the beginning of the line
127
+ if (wordInfo.startColumn > 2) {
128
+ const charBeforeTrigger = model.getValueInRange({
129
+ startLineNumber: position.lineNumber,
130
+ startColumn: wordInfo.startColumn - 2,
131
+ endLineNumber: position.lineNumber,
132
+ endColumn: wordInfo.startColumn - 1
133
+ });
134
+ // If the character before the trigger is not whitespace, don't provide completions
135
+ if (!/\s/.test(charBeforeTrigger)) {
136
+ return undefined;
137
+ }
138
+ }
139
+
85
140
  return new monaco.Range(
86
141
  position.lineNumber,
87
142
  wordInfo.startColumn,
@@ -90,73 +145,65 @@ export class ChatViewLanguageContribution implements FrontendApplicationContribu
90
145
  );
91
146
  }
92
147
 
93
- private getSuggestions<T>(
148
+ protected provideCompletions<T>(
94
149
  model: monaco.editor.ITextModel,
95
150
  position: monaco.Position,
96
- triggerChar: string,
97
- items: T[],
98
- kind: monaco.languages.CompletionItemKind,
99
- getId: (item: T) => string,
100
- getName: (item: T) => string,
101
- getDescription: (item: T) => string,
102
- command?: monaco.languages.Command
151
+ source: CompletionSource<T>
103
152
  ): ProviderResult<monaco.languages.CompletionList> {
104
- const completionRange = this.getCompletionRange(model, position, triggerChar);
153
+ const completionRange = this.getCompletionRange(model, position, source.triggerCharacter);
105
154
  if (completionRange === undefined) {
106
155
  return { suggestions: [] };
107
156
  }
157
+
158
+ const items = source.getItems();
108
159
  const suggestions = items.map(item => ({
109
- insertText: getId(item),
110
- kind: kind,
111
- label: getName(item),
160
+ insertText: source.getId(item),
161
+ kind: source.kind,
162
+ label: source.getName(item),
112
163
  range: completionRange,
113
- detail: getDescription(item),
114
- command
164
+ detail: source.getDescription(item),
165
+ command: source.command
115
166
  }));
167
+
116
168
  return { suggestions };
117
169
  }
118
170
 
119
- provideAgentCompletions(model: monaco.editor.ITextModel, position: monaco.Position): ProviderResult<monaco.languages.CompletionList> {
120
- return this.getSuggestions(
121
- model,
122
- position,
123
- PromptText.AGENT_CHAR,
124
- this.agentService.getAgents(),
125
- monaco.languages.CompletionItemKind.Value,
126
- agent => agent.id,
127
- agent => agent.name,
128
- agent => agent.description
129
- );
130
- }
171
+ async provideVariableWithArgCompletions(model: monaco.editor.ITextModel, position: monaco.Position): Promise<monaco.languages.CompletionList> {
172
+ // Get the text of the current line up to the cursor position
173
+ const textUntilPosition = model.getValueInRange({
174
+ startLineNumber: position.lineNumber,
175
+ startColumn: 1,
176
+ endLineNumber: position.lineNumber,
177
+ endColumn: position.column,
178
+ });
131
179
 
132
- provideVariableCompletions(model: monaco.editor.ITextModel, position: monaco.Position): ProviderResult<monaco.languages.CompletionList> {
133
- return this.getSuggestions(
134
- model,
135
- position,
136
- PromptText.VARIABLE_CHAR,
137
- this.variableService.getVariables(),
138
- monaco.languages.CompletionItemKind.Variable,
139
- variable => variable.name,
140
- variable => variable.name,
141
- variable => variable.description,
142
- {
143
- title: nls.localize('theia/ai/chat-ui/selectVariableArguments', 'Select variable arguments'),
144
- id: VARIABLE_ARGUMENT_PICKER_COMMAND,
145
- }
146
- );
147
- }
180
+ // Regex that captures the variable name in contexts like "#varname" or "#var-name:args"
181
+ // Matches only when # is at the beginning of the string or after whitespace
182
+ const variableRegex = /(?:^|\s)#([\w-]*)/;
183
+ const match = textUntilPosition.match(variableRegex);
184
+
185
+ if (!match) {
186
+ return { suggestions: [] };
187
+ }
188
+
189
+ const currentVariableName = match[1];
190
+ const hasColonSeparator = textUntilPosition.includes(`${currentVariableName}:`);
148
191
 
149
- async provideVariableWithArgCompletions(model: monaco.editor.ITextModel, position: monaco.Position): Promise<monaco.languages.CompletionList> {
150
192
  const variables = this.variableService.getVariables();
151
193
  const suggestions: monaco.languages.CompletionItem[] = [];
194
+
152
195
  for (const variable of variables) {
196
+ // If we have a variable:arg pattern, only process the matching variable
197
+ if (hasColonSeparator && variable.name !== currentVariableName) {
198
+ continue;
199
+ }
200
+
153
201
  const provider = await this.variableService.getArgumentCompletionProvider(variable.name);
154
202
  if (provider) {
155
203
  const items = await provider(model, position);
156
204
  if (items) {
157
205
  suggestions.push(...items.map(item => ({
158
206
  ...item,
159
- // trigger command to check if we should add a context variable
160
207
  command: {
161
208
  title: nls.localize('theia/ai/chat-ui/addContextVariable', 'Add context variable'),
162
209
  id: VARIABLE_ADD_CONTEXT_COMMAND,
@@ -166,20 +213,8 @@ export class ChatViewLanguageContribution implements FrontendApplicationContribu
166
213
  }
167
214
  }
168
215
  }
169
- return { suggestions };
170
- }
171
216
 
172
- provideToolCompletions(model: monaco.editor.ITextModel, position: monaco.Position): ProviderResult<monaco.languages.CompletionList> {
173
- return this.getSuggestions(
174
- model,
175
- position,
176
- PromptText.FUNCTION_CHAR,
177
- this.toolInvocationRegistry.getAllFunctions(),
178
- monaco.languages.CompletionItemKind.Function,
179
- tool => tool.id,
180
- tool => tool.name,
181
- tool => tool.description ?? ''
182
- );
217
+ return { suggestions };
183
218
  }
184
219
 
185
220
  protected async triggerVariableArgumentPicker(): Promise<void> {
@@ -187,35 +222,57 @@ export class ChatViewLanguageContribution implements FrontendApplicationContribu
187
222
  if (!inputEditor) {
188
223
  return;
189
224
  }
225
+
190
226
  const model = inputEditor.getModel();
191
227
  const position = inputEditor.getPosition();
192
228
  if (!model || !position) {
193
229
  return;
194
230
  }
195
- const variableName = model.getWordAtPosition(position)?.word;
231
+
232
+ // // Get the word at cursor
233
+ const wordInfo = model.getWordUntilPosition(position);
234
+
235
+ // account for the variable separator character if present
236
+ let endOfWordPosition = position.column;
237
+ if (wordInfo.word === '' && this.getCharacterBeforePosition(model, position) === PromptText.VARIABLE_SEPARATOR_CHAR) {
238
+ endOfWordPosition = position.column - 1;
239
+ } else {
240
+ return;
241
+ }
242
+
243
+ const variableName = model.getWordAtPosition({ ...position, column: endOfWordPosition })?.word;
196
244
  if (!variableName) {
197
245
  return;
198
246
  }
247
+
199
248
  const provider = await this.variableService.getArgumentPicker(variableName, VARIABLE_RESOLUTION_CONTEXT);
200
249
  if (!provider) {
201
250
  return;
202
251
  }
252
+
203
253
  const arg = await provider(VARIABLE_RESOLUTION_CONTEXT);
204
254
  if (!arg) {
205
255
  return;
206
256
  }
257
+
207
258
  inputEditor.executeEdits('variable-argument-picker', [{
208
259
  range: new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column),
209
- text: PromptText.VARIABLE_SEPARATOR_CHAR + arg
260
+ text: arg
210
261
  }]);
262
+
211
263
  await this.addContextVariable(variableName, arg);
212
264
  }
213
265
 
266
+ protected getCharacterBeforePosition(model: monaco.editor.ITextModel, position: monaco.Position): string {
267
+ return model.getLineContent(position.lineNumber)[position.column - 1 - 1];
268
+ }
269
+
214
270
  protected async addContextVariable(variableName: string, arg: string | undefined): Promise<void> {
215
271
  const variable = this.variableService.getVariable(variableName);
216
272
  if (!variable || !AIContextVariable.is(variable)) {
217
273
  return;
218
274
  }
275
+
219
276
  const widget = this.shell.getWidgetById(ChatViewWidget.ID);
220
277
  if (widget instanceof ChatViewWidget) {
221
278
  widget.addContext({ variable, arg });
@@ -405,6 +405,10 @@ div:last-child > .theia-ChatNode {
405
405
  border-color: var(--theia-focusBorder);
406
406
  }
407
407
 
408
+ .theia-ChatInput-Editor-Box .monaco-editor {
409
+ outline-color: var(--theia-editor-background);
410
+ }
411
+
408
412
  .theia-ChatInput-Editor {
409
413
  width: 100%;
410
414
  height: auto;
@@ -449,7 +453,7 @@ div:last-child > .theia-ChatNode {
449
453
  .theia-ChatInputOptions {
450
454
  width: 100%;
451
455
  height: 25px;
452
- padding-left: 6px;
456
+ padding-left: 3px;
453
457
  padding-right: 6px;
454
458
  display: flex;
455
459
  justify-content: space-between;
@@ -461,7 +465,7 @@ div:last-child > .theia-ChatNode {
461
465
  }
462
466
 
463
467
  .theia-ChatInputOptions .theia-ChatInputOptions-right {
464
- margin-right: 12px;
468
+ margin-right: 8px;
465
469
  }
466
470
 
467
471
  .theia-ChatInputOptions .option {
@@ -493,7 +497,7 @@ div:last-child > .theia-ChatNode {
493
497
  }
494
498
 
495
499
  .theia-ChatInputOptions .reverse {
496
- flex-direction: row-reverse;
500
+ flex-direction: row-reverse;
497
501
  }
498
502
 
499
503
  .theia-CodePartRenderer-root {
@@ -504,6 +508,10 @@ div:last-child > .theia-ChatNode {
504
508
  border-radius: 4px;
505
509
  }
506
510
 
511
+ .theia-CodePartRenderer-root .monaco-editor {
512
+ outline-color: var(--theia-editor-background);
513
+ }
514
+
507
515
  .theia-CodePartRenderer-left {
508
516
  flex-grow: 1;
509
517
  }