Skip to content

Commit 664c638

Browse files
committed
Add v1.navie.thread.(add|remove)MessageAttachment
This restores functionality with code snippets.
1 parent d9771b4 commit 664c638

File tree

15 files changed

+487
-240
lines changed

15 files changed

+487
-240
lines changed

packages/cli/src/cmds/index/rpc.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ import NavieService from '../../rpc/navie/services/navieService';
3939
import { ThreadIndexService } from '../../rpc/navie/services/threadIndexService';
4040
import { container } from 'tsyringe';
4141
import ThreadService from '../../rpc/navie/services/threadService';
42+
import {
43+
navieThreadAddMessageAttachmentHandler,
44+
navieThreadRemoveMessageAttachmentHandler,
45+
} from '../../rpc/navie/thread/handlers/messageAttachment';
4246

4347
export const command = 'rpc';
4448
export const describe = 'Run AppMap JSON-RPC server';
@@ -87,6 +91,8 @@ export function rpcMethods(navie: INavieProvider, codeEditor?: string): RpcHandl
8791
navieThreadPinItemHandler(threadService),
8892
navieThreadUnpinItemHandler(threadService),
8993
navieThreadQueryHandler(threadIndexService),
94+
navieThreadAddMessageAttachmentHandler(threadService),
95+
navieThreadRemoveMessageAttachmentHandler(threadService),
9096
];
9197
}
9298

packages/cli/src/rpc/explain/review.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { getDiffLog, getWorkingDiff } from '../../lib/git';
1010
export default async function handleReview(
1111
question: string,
1212
userContext?: UserContext.Context
13-
): Promise<{ applied: boolean; userContext?: UserContext.Context }> {
13+
): Promise<{ applied: boolean; userContext?: UserContext.ContextItem[] }> {
1414
const [mode] = question.split(/\s+/g);
1515
if (mode !== '@review') return { applied: false };
1616

packages/cli/src/rpc/navie/thread/events.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import { ConversationThread } from '@appland/client';
22
import { ContextV2, Help, ProjectInfo } from '@appland/navie';
33
import { NavieRpc } from '@appland/rpc';
44

5+
export type Timestamp = {
6+
time: number;
7+
};
8+
59
export type NavieThreadInitEvent = {
610
type: 'thread-init';
711
conversationThread: ConversationThread;
@@ -56,14 +60,23 @@ export type NavieBeginContextSearchEvent = {
5660
id: string;
5761
request?: Help.HelpRequest | ProjectInfo.ProjectInfoRequest | ContextV2.ContextRequest;
5862
};
63+
5964
export type NavieCompleteContextSearchEvent = {
6065
type: 'complete-context-search';
6166
id: string;
6267
result?: Help.HelpResponse | ProjectInfo.ProjectInfoResponse | ContextV2.ContextResponse;
6368
};
6469

65-
export type Timestamp = {
66-
time: number;
70+
export type NavieAddMessageAttachmentEvent = {
71+
type: 'add-message-attachment';
72+
attachmentId: string;
73+
uri?: string;
74+
content?: string;
75+
};
76+
77+
export type NavieRemoveMessageAttachmentEvent = {
78+
type: 'remove-message-attachment';
79+
attachmentId: string;
6780
};
6881

6982
export type NavieEvent =
@@ -77,7 +90,9 @@ export type NavieEvent =
7790
| NaviePromptSuggestionsEvent
7891
| NavieThreadInitEvent
7992
| NavieTokenEvent
80-
| NavieTokenMetadataEvent;
93+
| NavieTokenMetadataEvent
94+
| NavieAddMessageAttachmentEvent
95+
| NavieRemoveMessageAttachmentEvent;
8196

8297
export type TimestampNavieEvent = Timestamp & NavieEvent;
8398

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { NavieRpc } from '@appland/rpc';
2+
import ThreadService from '../../services/threadService';
3+
import { RpcError, RpcHandler } from '../../../rpc';
4+
5+
export function navieThreadAddMessageAttachmentHandler(
6+
threadService: ThreadService
7+
): RpcHandler<
8+
NavieRpc.V1.Thread.AddMessageAttachment.Params,
9+
NavieRpc.V1.Thread.AddMessageAttachment.Response
10+
> {
11+
return {
12+
name: NavieRpc.V1.Thread.AddMessageAttachment.Method,
13+
async handler({ threadId, uri, content }) {
14+
if (!uri && !content) throw new RpcError(400, 'must specify at least one of uri or content');
15+
16+
const thread = await threadService.getThread(threadId);
17+
const attachmentId = thread.addMessageAttachment(uri, content);
18+
return { attachmentId };
19+
},
20+
};
21+
}
22+
23+
export function navieThreadRemoveMessageAttachmentHandler(
24+
threadService: ThreadService
25+
): RpcHandler<
26+
NavieRpc.V1.Thread.RemoveMessageAttachment.Params,
27+
NavieRpc.V1.Thread.RemoveMessageAttachment.Response
28+
> {
29+
return {
30+
name: NavieRpc.V1.Thread.RemoveMessageAttachment.Method,
31+
async handler({ threadId, attachmentId }) {
32+
const thread = await threadService.getThread(threadId);
33+
thread.removeMessageAttachment(attachmentId);
34+
},
35+
};
36+
}

packages/cli/src/rpc/navie/thread/index.ts

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ import { homedir } from 'os';
88
import { mkdir, writeFile } from 'fs/promises';
99

1010
import NavieService from '../services/navieService';
11-
import { NavieEvent, NavieMessageEvent, PinnedItem, TimestampNavieEvent } from './events';
11+
import {
12+
NavieAddMessageAttachmentEvent,
13+
NavieEvent,
14+
NavieMessageEvent,
15+
PinnedItem,
16+
TimestampNavieEvent,
17+
} from './events';
1218
import { ThreadIndexService } from '../services/threadIndexService';
1319
import { container } from 'tsyringe';
1420
import { NavieRpc } from '@appland/rpc';
@@ -25,15 +31,26 @@ type EventListener = (...args: any[]) => void;
2531
*/
2632
function convertContext(
2733
context?: NavieRpc.V1.UserContext.ContextItem[]
28-
): undefined | UserContext.Context {
29-
return context?.map((item) => {
34+
): UserContext.ContextItem[] {
35+
if (!context) return [];
36+
return context.map((item) => {
3037
if (item.type === 'static') {
3138
return { type: 'code-snippet', content: item.content, location: item.id };
3239
}
3340
return { type: 'file', location: item.uri };
3441
});
3542
}
3643

44+
function convertMessageAttachmentToContextItem(
45+
attachment: NavieAddMessageAttachmentEvent
46+
): UserContext.ContextItem {
47+
return {
48+
type: 'code-snippet',
49+
content: attachment.content ?? '',
50+
location: attachment.uri,
51+
};
52+
}
53+
3754
export class Thread {
3855
private eventEmitter = new EventEmitter();
3956
private listeners = new Map<string, EventListener[]>();
@@ -236,10 +253,11 @@ export class Thread {
236253

237254
/**
238255
* Send a user message to the thread. This will emit a `message` event. This promise will resolve
239-
* once the message has been acknowledged by the backend, before the message is completed.
256+
* once the message has been acknowledged by the backend, before the message is completed. Message
257+
* attachments need NOT be included in the `userContext` parameter.
240258
*
241259
* @param message the message to send
242-
* @param codeSelection (optional) the code selection to send
260+
* @param codeSelection (optional) additional context to use, i.e., pinned items
243261
*/
244262
async sendMessage(
245263
message: string,
@@ -248,8 +266,10 @@ export class Thread {
248266
const [navie, contextEvents] = this.navieService.getNavie();
249267

250268
let context = convertContext(userContext);
269+
context.push(...this.getMessageAttachments().map(convertMessageAttachmentToContextItem));
270+
251271
const { applied, userContext: newUserContext } = await handleReview(message, context);
252-
if (applied) {
272+
if (applied && newUserContext) {
253273
context = newUserContext;
254274
}
255275

@@ -296,6 +316,44 @@ export class Thread {
296316
});
297317
}
298318

319+
addMessageAttachment(uri?: string, content?: string): string {
320+
const attachmentId = randomUUID();
321+
this.logEvent({
322+
type: 'add-message-attachment',
323+
attachmentId,
324+
uri,
325+
content,
326+
});
327+
return attachmentId;
328+
}
329+
330+
removeMessageAttachment(attachmentId: string) {
331+
this.logEvent({ type: 'remove-message-attachment', attachmentId });
332+
}
333+
334+
getMessageAttachments(): NavieAddMessageAttachmentEvent[] {
335+
// Array.lastIndexOf is not supported until es2023.
336+
let lastUserMessageIndex = 0;
337+
for (let i = lastUserMessageIndex; i < this.log.length; ++i) {
338+
const e = this.log[i];
339+
if (e.type === 'message' && e.role === 'user') {
340+
lastUserMessageIndex = i;
341+
}
342+
}
343+
344+
const attachments = new Map<string, NavieAddMessageAttachmentEvent>();
345+
for (let i = lastUserMessageIndex + 1; i < this.log.length; ++i) {
346+
const e = this.log[i];
347+
if (e.type === 'add-message-attachment') {
348+
attachments.set(e.attachmentId, e);
349+
} else if (e.type === 'remove-message-attachment') {
350+
attachments.delete(e.attachmentId);
351+
}
352+
}
353+
354+
return Array.from(attachments.values());
355+
}
356+
299357
/**
300358
* Gets the events since the given nonce. If no nonce is given, it will return all events.
301359
*

packages/components/src/components/chat/Chat.vue

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -315,13 +315,8 @@ export default {
315315
}
316316
},
317317
async onSend(message: string) {
318-
this.sendMessage(
319-
message,
320-
this.codeSelections.map((s) => s.code),
321-
this.appmaps
322-
);
323-
324-
this.codeSelections = [];
318+
this.sendMessage(message, [], this.appmaps);
319+
this.$set(this, 'codeSelections', []);
325320
},
326321
onStop() {
327322
this.$emit('stop');
@@ -383,6 +378,13 @@ export default {
383378
includeCodeSelection(codeSelection: CodeSelection) {
384379
this.codeSelections.push(codeSelection);
385380
},
381+
removeCodeSelection(attachmentId: string) {
382+
this.$set(
383+
this,
384+
'codeSelections',
385+
this.codeSelections.filter((c) => c.attachmentId !== attachmentId)
386+
);
387+
},
386388
includeAppMap(appmap: string) {
387389
this.appmaps.push(appmap);
388390
},

packages/components/src/components/chat/ChatInput.vue

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,9 @@
2323
data-cy="input-attachments"
2424
>
2525
<v-code-selection
26-
v-for="(selection, i) in codeSelections"
27-
:key="i"
28-
:path="selection.path"
29-
:line-start="selection.lineStart"
30-
:line-end="selection.lineEnd"
31-
:language="selection.language"
32-
:code="selection.code"
26+
v-for="selection in codeSelections"
27+
:key="selection.uri"
28+
v-bind="selection"
3329
class="attachment"
3430
/>
3531
</div>
@@ -282,7 +278,7 @@ $border-color: rgba(white, 0.333);
282278
display: flex;
283279
flex-direction: column;
284280
gap: 1rem;
285-
padding: 1.5rem;
281+
padding: 1rem 1.5rem 1.5rem 1.5rem;
286282
border-top: 1px solid $color-background-dark;
287283
box-shadow: 0 0 1rem 0rem $color-tile-shadow;
288284
border-radius: $border-radius $border-radius 0 0;

0 commit comments

Comments
 (0)