Skip to content

Commit ad7453c

Browse files
committed
feat: link data to graph image for description (DAVAI-38)
[DAVAI-38](https://concord-consortium.atlassian.net/browse/DAVAI-38)
1 parent f027e1a commit ad7453c

File tree

2 files changed

+82
-12
lines changed

2 files changed

+82
-12
lines changed

src/app-config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"accessibility": {
33
"keyboardShortcut": "ctrl+?"
44
},
5-
"assistantId": "asst_xmAX5oxByssXrkBymMbcsVEm",
5+
"assistantId": "asst_2sNDbextdsj0E3E0YugE08QH",
66
"dimensions": {
77
"height": 680,
88
"width": 380

src/models/assistant-model.ts

Lines changed: 81 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,25 +26,35 @@ const OpenAIType = types.custom({
2626
},
2727
});
2828

29+
interface IGraphAttrData {
30+
legendAttribute?: Record<string, any>;
31+
rightSplitAttribute?: Record<string, any>;
32+
topSplitAttribute?: Record<string, any>;
33+
xAttribute?: Record<string, any>;
34+
yAttribute?: Record<string, any>;
35+
y2Attribute?: Record<string, any>;
36+
}
37+
2938
/**
3039
* AssistantModel encapsulates the AI assistant and its interactions with the user.
3140
* It includes properties and methods for configuring the assistant, handling chat interactions, and maintaining the assistant's
3241
* thread and transcript.
3342
*
43+
* @property {Object} apiConnection - The API connection object for interacting with the assistant
3444
* @property {Object|null} assistant - The assistant object, or `null` if not initialized.
3545
* @property {string} assistantId - The unique ID of the assistant being used, or `null` if not initialized.
36-
* @property {Object} apiConnection - The API connection object for interacting with the assistant
37-
* @property {Object|null} thread - The assistant's thread used for the current chat, or `null` if no thread is active.
38-
* @property {ChatTranscriptModel} transcriptStore - The assistant's chat transcript store for recording and managing chat messages.
39-
* @property {boolean} isLoadingResponse - Flag indicating whether the assistant is currently processing a response.
46+
* @property {Object} assistantList - A map of available assistants, where the key is the assistant ID and the value is the assistant name.
4047
* @property {string[]} codapNotificationQueue - Queue of messages to be sent to the assistant. Used if CODAP generates notifications while assistant is processing a response.
41-
* @property {string[]} messageQueue - Queue of messages to be sent to the assistant. Used if user sends messages while assistant is processing a response.
42-
* @property {boolean} showLoadingIndicator - Flag indicating whether to show a loading indicator to the user; this is decoupled from the assistant's internal loading state to allow for more control over UI elements.
48+
* @property {string} dataUri - The data URI of the file to be uploaded.
49+
* @property {string} dataContextForGraph - The data context for the graph being processed by the assistant.
4350
* @property {boolean} isCancelling - Flag indicating whether the assistant is currently cancelling a request.
51+
* @property {boolean} isLoadingResponse - Flag indicating whether the assistant is currently processing a response.
4452
* @property {boolean} isResetting - Flag indicating whether the assistant is currently resetting the chat.
53+
* @property {string[]} messageQueue - Queue of messages to be sent to the assistant. Used if user sends messages while assistant is processing a response.
54+
* @property {boolean} showLoadingIndicator - Flag indicating whether to show a loading indicator to the user; this is decoupled from the assistant's internal loading state to allow for more control over UI elements.
55+
* @property {Object|null} thread - The assistant's thread used for the current chat, or `null` if no thread is active.
56+
* @property {ChatTranscriptModel} transcriptStore - The assistant's chat transcript store for recording and managing chat messages.
4557
* @property {boolean} uploadFileAfterRun - Flag indicating whether to upload a file after the assistant completes a run.
46-
* @property {string} dataUri - The data URI of the file to be uploaded.
47-
* @property {string} graphToSonify - The name of the graph to sonify, if applicable.
4858
*/
4959
export const AssistantModel = types
5060
.model("AssistantModel", {
@@ -60,9 +70,12 @@ export const AssistantModel = types
6070
showLoadingIndicator: types.optional(types.boolean, false),
6171
thread: types.maybe(types.frozen()),
6272
transcriptStore: ChatTranscriptModel,
63-
uploadFileAfterRun: false,
64-
dataUri: ""
73+
uploadFileAfterRun: false
6574
})
75+
.volatile(() => ({
76+
dataUri: "",
77+
dataContextForGraph: null as IGraphAttrData | null
78+
}))
6679
.actions((self) => ({
6780
addDavaiMsg(msg: string) {
6881
self.transcriptStore.addMessage(DAVAI_SPEAKER, { content: msg });
@@ -130,7 +143,6 @@ export const AssistantModel = types
130143
}
131144
});
132145

133-
134146
const sendDataCtxChangeInfo = flow(function* (msg: string) {
135147
try {
136148
if (self.isLoadingResponse || self.isCancelling || self.isResetting) {
@@ -237,6 +249,7 @@ export const AssistantModel = types
237249
yield sendFileMessage(fileId);
238250
self.uploadFileAfterRun = false;
239251
self.dataUri = "";
252+
self.dataContextForGraph = null;
240253
startRun();
241254
} else {
242255
const messages = yield self.apiConnection.beta.threads.messages.list(self.thread.id);
@@ -274,6 +287,54 @@ export const AssistantModel = types
274287
}
275288
});
276289

290+
const getAttributeData = flow(function* (graphID: string, attrID: string | null) {
291+
if (!attrID) return null;
292+
const response = yield Promise.resolve(codapInterface.sendRequest({
293+
action: "get",
294+
resource: `component[${graphID}].attribute[${attrID}]`
295+
}));
296+
297+
return response;
298+
});
299+
300+
const getGraphAttrData = flow(function* (graphID) {
301+
try {
302+
const graph = yield getGraphByID(graphID);
303+
if (graph) {
304+
const xAttrData = yield getAttributeData(graphID, graph.xAttributeID);
305+
const yAttrData = yield getAttributeData(graphID, graph.yAttributeID);
306+
const rightAttrData = yield getAttributeData(graphID, graph.rightSplitAttributeID);
307+
const topAttrData = yield getAttributeData(graphID, graph.topSplitAttributeID);
308+
const y2AttrData = yield getAttributeData(graphID, graph.yAttributeIDs ? graph.yAttributeIDs[1] : null);
309+
const legendAttrData = yield getAttributeData(graphID, graph.legendAttributeID);
310+
311+
const graphAttrData = {
312+
legendAttribute: legendAttrData,
313+
rightSplitAttribute: rightAttrData,
314+
topSplitAttribute: topAttrData,
315+
xAttribute: xAttrData,
316+
yAttribute: yAttrData,
317+
y2Attribute: y2AttrData
318+
};
319+
320+
if (graphAttrData) {
321+
self.addDbgMsg("Data context for graph", formatJsonMessage(graphAttrData));
322+
return graphAttrData;
323+
} else {
324+
self.addDbgMsg("No data context found for graph", formatJsonMessage(graph));
325+
return null;
326+
}
327+
} else {
328+
self.addDbgMsg("No graph found with ID", graphID);
329+
return null;
330+
}
331+
} catch (err) {
332+
console.error("Failed to get graph attribute data:", err);
333+
self.addDbgMsg("Failed to get graph attribute data", formatJsonMessage(err));
334+
return null;
335+
}
336+
});
337+
277338
const sendFileMessage = flow(function* (fileId) {
278339
try {
279340
const res = yield self.apiConnection.beta.threads.messages.create(self.thread.id, {
@@ -288,6 +349,10 @@ export const AssistantModel = types
288349
image_file: {
289350
file_id: fileId
290351
}
352+
},
353+
{
354+
type: "text",
355+
text: `The following JSON data describes key aspects of the graph in the image. Use this context to improve your interpretation and explanation of the graph. ${JSON.stringify(self.dataContextForGraph)}`
291356
}
292357
]
293358
});
@@ -320,6 +385,11 @@ export const AssistantModel = types
320385
if (isImageSnapshotRequest) {
321386
self.uploadFileAfterRun = true;
322387
self.dataUri = res.values.exportDataUri;
388+
const graphID = resource.match(/\[(\d+)\]/)?.[1];
389+
// We'll also send data for the attributes on the graph for additional context
390+
self.dataContextForGraph = yield getGraphAttrData(graphID);
391+
// TODO: Remove this console log when done with testing
392+
console.log("Attribute data for graph", self.dataContextForGraph);
323393
}
324394
// remove any exportDataUri value that exists since it can be large and we don't need to send it to the assistant
325395
res = isImageSnapshotRequest

0 commit comments

Comments
 (0)