{props.stylesheetPath &&
}
diff --git a/src/components/presentational/index.ts b/src/components/presentational/index.ts
index aa5e1b92..9d12d021 100644
--- a/src/components/presentational/index.ts
+++ b/src/components/presentational/index.ts
@@ -6,6 +6,7 @@ export { default as Calendar } from "./Calendar"
export { default as CalendarDay, CalendarDayStateConfiguration } from "./CalendarDay"
export { default as Card } from "./Card"
export { default as CardTitle } from "./CardTitle"
+export { default as Chat } from "./Chat"
export { default as DateRangeCoordinator, DateRangeContext } from "./DateRangeCoordinator"
export { default as DateRangeNavigator } from "./DateRangeNavigator"
export { default as DateRangeTitle } from "./DateRangeTitle"
diff --git a/src/components/view/BlankView/BlankView.tsx b/src/components/view/BlankView/BlankView.tsx
index 237efe72..41aaf321 100644
--- a/src/components/view/BlankView/BlankView.tsx
+++ b/src/components/view/BlankView/BlankView.tsx
@@ -14,11 +14,12 @@ export interface BlankViewProps {
titleColor?: ColorDefinition;
subtitleColor?: ColorDefinition;
navigationBarButtonColor?: ColorDefinition;
+ flexLayout?: boolean;
}
export default function (props: BlankViewProps) {
return (
-
+
{(props.showBackButton || props.showCloseButton) &&
-
+ {!props.flexLayout && }
>
}
diff --git a/src/helpers/AIAssistant/AIAssistant.ts b/src/helpers/AIAssistant/AIAssistant.ts
new file mode 100644
index 00000000..d12f72b3
--- /dev/null
+++ b/src/helpers/AIAssistant/AIAssistant.ts
@@ -0,0 +1,180 @@
+import { AIMessage, BaseMessage, HumanMessage } from "@langchain/core/messages";
+import { ChatOpenAI } from "@langchain/openai";
+import { END, StateGraph, StateGraphArgs, START, MemorySaver, CompiledStateGraph, messagesStateReducer } from "@langchain/langgraph/web";
+import { ChatPromptTemplate, MessagesPlaceholder, SystemMessagePromptTemplate } from "@langchain/core/prompts";
+import { ToolNode } from "@langchain/langgraph/prebuilt";
+import { RunnableConfig } from "@langchain/core/runnables";
+import { StructuredTool } from "@langchain/core/tools";
+
+import MyDataHelps, { Guid, ParticipantInfo, ProjectInfo } from "@careevolution/mydatahelps-js";
+
+import {
+ PersistParticipantInfoTool,
+ QueryAppleHealthActivitySummariesTool,
+ QueryAppleHealthWorkoutsTool,
+ QueryDailySleepTool,
+ QueryDeviceDataV2AggregateTool,
+ QueryDeviceDataV2Tool,
+ QueryNotificationsTool,
+ QuerySurveyAnswersTool,
+ QueryDailyDataTool,
+ GetAllDailyDataTypesTool,
+ GetEhrNewsFeedPageTool,
+ GetDeviceDataV2AllDataTypesTool
+} from "./Tools";
+
+
+export interface AIAssistantState {
+ messages: BaseMessage[];
+ participantInfo: string;
+ projectInfo: string;
+}
+
+export class MyDataHelpsAIAssistant {
+
+ constructor(baseUrl: string = "", additionalInstructions: string = "", tools: StructuredTool[] = [], appendTools: boolean = true) {
+ this.baseUrl = baseUrl || "https://xwk5dezh5vnf4in2avp6dxswym0tmgxg.lambda-url.us-east-1.on.aws/";
+ this.additionalInstructions = additionalInstructions;
+ this.tools = tools.length ? (appendTools ? this.defaultTools.concat(tools) : tools) : this.defaultTools;
+ }
+
+ async ask(userMessage: string, onEvent: (event: any) => void) {
+
+ if (!this.initialized) {
+ let participantInfo = await MyDataHelps.getParticipantInfo();
+ let projectInfo = await MyDataHelps.getProjectInfo();
+ this.initialize(participantInfo, projectInfo);
+ }
+
+ let config = { configurable: { thread_id: this.participantId } };
+ let inputs = {
+ messages: [
+ new HumanMessage(userMessage)
+ ]
+ };
+ for await (
+ const event of await this.graph.streamEvents(inputs, {
+ ...config,
+ streamMode: "values",
+ version: "v1"
+ })
+ ) {
+ onEvent(event);
+ }
+ }
+
+ private initialize(participantInfo: ParticipantInfo, projectInfo: ProjectInfo) {
+
+ const toolNode = new ToolNode<{ messages: BaseMessage[] }>(this.tools);
+
+ const graphState: StateGraphArgs["channels"] = {
+ messages: {
+ reducer: messagesStateReducer
+ },
+ participantInfo: {
+ value: (x: string, y: string) => y ? y : x,
+ default: () => "{}"
+ },
+ projectInfo: {
+ value: (x: string, y: string) => y ? y : x,
+ default: () => "{}"
+ }
+ };
+
+ const boundModel = new ChatOpenAI({
+ model: "gpt-4o",
+ temperature: 0,
+ apiKey: MyDataHelps.token.access_token
+ }, {
+ baseURL: this.baseUrl
+ }).bindTools(this.tools);
+
+ const promptTemplate = ChatPromptTemplate.fromMessages([
+ SystemMessagePromptTemplate.fromTemplate(`
+ You are a health and wellness data assistant. Your purpose is to help users understand their health and wearable data,
+ which includes providing simple summaries and highlighting insights and connections between data. The tone should
+ be clear and friendly. You are not a coach, so should not give advice. You should not be disparaging or discouraging,
+ just objectively share information.
+
+ You can encourage the user to ask more questions and even suggest additional follow-up questions that might be relevant.
+
+ If the user asks for some data, and you query it with a particular tool, but the returned data does not sufficiently
+ answer the user's question, for example, if the user asks for their last 3 LDL values, and you query the getEhrNewsFeedPage
+ tool and it returns only the last 2 LDL values and a nextPageID, then query the same tool again while passing the nextPageID as the
+ pageID parameter to fetch additional data. Continue this process until you have all the data that the user has asked for, up to 5 iterations.
+
+ User information: {participantInfo}
+
+ Project information: {projectInfo}
+
+ The time right now is ${new Date().toISOString()}.
+
+ ${this.additionalInstructions}
+ `),
+ new MessagesPlaceholder("messages")
+ ]);
+
+ const routeMessage = (state: AIAssistantState) => {
+ const { messages } = state;
+ const lastMessage = messages[messages.length - 1] as AIMessage;
+ if (!lastMessage.tool_calls?.length) {
+ return END;
+ }
+ return "tools";
+ };
+
+ const callModel = async (state: AIAssistantState, config?: RunnableConfig) => {
+ const { messages, participantInfo, projectInfo } = state;
+ const chain = promptTemplate.pipe(boundModel);
+ const response = await chain.invoke({ messages, participantInfo, projectInfo }, config);
+ return { messages: [response] };
+ };
+
+ const setContextInfo = async () => {
+ return {
+ participantInfo: JSON.stringify(participantInfo),
+ projectInfo: JSON.stringify(projectInfo)
+ };
+ };
+
+ const workflow = new StateGraph({
+ channels: graphState
+ })
+ .addNode("setContextInfo", setContextInfo)
+ .addNode("agent", callModel)
+ .addNode("tools", toolNode)
+ .addEdge(START, "setContextInfo")
+ .addEdge("setContextInfo", "agent")
+ .addConditionalEdges("agent", routeMessage)
+ .addEdge("tools", "agent");
+
+ const memory = new MemorySaver();
+
+ this.graph = workflow.compile({ checkpointer: memory });
+
+ this.initialized = true;
+ this.participantId = participantInfo.participantID;
+ }
+
+ private initialized = false;
+ private graph!: CompiledStateGraph, "agent" | "tools" | "setContextInfo" | typeof START>;
+ private baseUrl: string;
+ private participantId!: Guid;
+ private additionalInstructions: string;
+ private tools: StructuredTool[];
+
+ private defaultTools: StructuredTool[] = [
+ new QueryDailySleepTool(),
+ new PersistParticipantInfoTool(),
+ new QueryDeviceDataV2Tool(),
+ new QueryDeviceDataV2AggregateTool(),
+ new QueryNotificationsTool(),
+ new QueryAppleHealthWorkoutsTool(),
+ new QueryAppleHealthActivitySummariesTool(),
+ new QuerySurveyAnswersTool(),
+ new QueryDailyDataTool(),
+ new GetAllDailyDataTypesTool(),
+ new GetEhrNewsFeedPageTool(),
+ new GetDeviceDataV2AllDataTypesTool()
+ ];
+}
diff --git a/src/helpers/AIAssistant/Tools.ts b/src/helpers/AIAssistant/Tools.ts
new file mode 100644
index 00000000..8e078f18
--- /dev/null
+++ b/src/helpers/AIAssistant/Tools.ts
@@ -0,0 +1,280 @@
+import { StructuredTool } from "@langchain/core/tools";
+import { z } from "zod";
+import MyDataHelps, { DeviceDataV2AggregateQuery, DeviceDataV2Query, ParticipantDemographics, StringMap } from "@careevolution/mydatahelps-js";
+import { queryDailyData, getAllDailyDataTypes } from "../query-daily-data";
+import { getNewsFeedPage } from "../news-feed/data";
+
+const deviceDataV2QuerySchema = z.object({
+ namespace: z.enum(["Fitbit", "AppleHealth", "Garmin", "Dexcom", "HealthConnect"])
+ .describe("The namespace of the device data, representing the manufacturer of the devices used to collect the data."),
+
+ type: z.string().describe("The device data type is specific to the namespace. Example data types for AppleHealth are Steps, Heart Rate."),
+
+ observedAfter: z.string().optional()
+ .describe(`The start of the date range for the query. This is a datetime in the participant's local timezone, passed without the timezone offset.
+ This is exclusive. For example, if you want to query data for 11/26/2024, you would pass in "observedAfter": "2024-11-26T00:00:00". An event
+ that happened at 11/26/2024 00:00:00 would not be included in the results.`),
+
+ observedBefore: z.string().optional()
+ .describe(`The end of the date range for the query. This is a datetime in the participant's local timezone, passed without the timezone offset.
+ This is exclusive. For example, if you want to query data for up to 11/26/2024, you would pass in "observedBefore": "2024-11-26T00:00:00". An event
+ that happened at 11/26/2024 00:00:00 would not be included in the results.`),
+
+ dataSource: z.record(z.string(), z.string()).optional()
+ .describe(`These can be used to restrict the returned results to data coming from a specific device only. For example, if I
+ wanted to only query data from my iPhone, I could pass in "dataSource": { "sourceName": "Mike's iPhone" }. If I wanted to only query data
+ from my Oura ring, I could pass in "dataSource": { "sourceName": "Oura" }. You can prompt the user for the device name if it is unclear what that
+ is from the user's question.`),
+
+ properties: z.record(z.string(), z.string()).optional()
+ .describe('Filters to apply to the properties of the data points.')
+});
+
+export class PersistParticipantInfoTool extends StructuredTool {
+ schema = z.object({
+ demographics: z.object({
+ email: z.string().optional().describe("The email address of the participant."),
+ mobilePhone: z.string().optional().describe("The mobile phone number of the participant."),
+ firstName: z.string().optional().describe("The first name of the participant."),
+ middleName: z.string().optional().describe("The middle name of the participant."),
+ lastName: z.string().optional().describe("The last name of the participant."),
+ dateOfBirth: z.string().optional().describe("The date of birth of the participant."),
+ gender: z.string().optional().describe("The gender of the participant."),
+ preferredLanguage: z.string().optional().describe("The preferred language of the participant."),
+ street1: z.string().optional().describe("The first line of the participant's street address."),
+ street2: z.string().optional().describe("The second line of the participant's street address."),
+ city: z.string().optional().describe("The city of the participant."),
+ state: z.string().optional().describe("The state of the participant."),
+ postalCode: z.string().optional().describe("The postal code of the participant."),
+ unsubscribedFromEmails: z.string().optional().describe("Whether the participant is unsubscribed from email messages."),
+ unsubscribedFromSms: z.string().optional().describe("Whether the participant is unsubscribed from SMS messages.")
+ }).optional().describe("Demographic information about the participant."),
+ customFields: z.record(z.string(), z.string())
+ .optional().describe("Custom fields where dynamic information can be saved for the participant.")
+ });
+
+ name = "persistParticipantInfo";
+
+ description = "Can be used to save data about the current participant, including dynamic information in the participant's custom fields.";
+
+ async _call(input: z.infer) {
+ let response = await MyDataHelps.persistParticipantInfo(input.demographics as Partial, input.customFields as StringMap);
+
+ return JSON.stringify(response);
+ }
+}
+
+const appleHealthQuerySchema = z.object({
+ endDate: z.string().optional().describe("The end of the date range for the query. This is a datetime in the participant's local timezone."),
+ startDate: z.string().optional().describe("The start of the date range for the query. This is a datetime in the participant's local timezone."),
+ pageSize: z.number().optional().describe("The number of items to return."),
+ pageID: z.string().optional().describe("The page ID to continue from.")
+});
+
+export class QueryAppleHealthWorkoutsTool extends StructuredTool {
+ schema = appleHealthQuerySchema;
+
+ name = "queryAppleHealthWorkouts";
+
+ description = "Query the participant's workouts from Apple Health.";
+
+ async _call(input: z.infer) {
+ let response = await MyDataHelps.queryAppleHealthWorkouts(input);
+
+ return JSON.stringify(response);
+ }
+}
+
+export class QueryAppleHealthActivitySummariesTool extends StructuredTool {
+ schema = appleHealthQuerySchema;
+
+ name = "queryAppleHealthActivitySummaries";
+
+ description = `Query the participant's Apple Health activity summaries. These include Active Energy Burned,
+ Active Energy Burned Goal, Apple Exercise Time, Apple Exercise Time Goal, Apple Stand Hours and Apple Stand Hours Goal.`;
+
+ async _call(input: z.infer) {
+ let response = await MyDataHelps.queryAppleHealthActivitySummaries(input);
+
+ return JSON.stringify(response);
+ }
+}
+
+export class QueryNotificationsTool extends StructuredTool {
+ schema = z.object({
+ type: z.enum(["Sms", "Push", "Email"]).optional().describe("The type of notification to query."),
+ statusCode: z.enum(["Succeeded", "Unsubscribed", "MissingContactInfo", "NoRegisteredMobileDevice", "NoAssociatedUser", "ServiceError"])
+ .optional().describe("The status code of the notification."),
+ sentBefore: z.string().optional().describe("The end of the date range for the query. This is a datetime in the participant's local timezone."),
+ sentAfter: z.string().optional().describe("The start of the date range for the query. This is a datetime in the participant's local timezone."),
+ identifier: z.string().optional().describe("The identifier of the notification."),
+ });
+
+ name = "queryNotifications";
+
+ description = "Query notifications sent to the participant.";
+
+ async _call(input: z.infer) {
+ let response = await MyDataHelps.queryNotifications(input);
+
+ return JSON.stringify(response);
+ }
+}
+
+export class QuerySurveyAnswersTool extends StructuredTool {
+ schema = z.object({
+ surveyResultID: z.string().optional().describe("The ID of the survey result."),
+ surveyID: z.string().optional().describe("The ID of the survey."),
+ surveyName: z.string().optional().describe("The name of the survey."),
+ after: z.string().optional()
+ .describe(`The start of the date range for the query. Survey answers submitted after this date will be retrieved.
+ This is a datetime in the participant's local timezone.`),
+ before: z.string().optional()
+ .describe(`The end of the date range for the query. Survey answers submitted before this date will be retrieved.
+ This is a datetime in the participant's local timezone.`),
+ stepIdentifier: z.string().optional().describe("The step identifier."),
+ resultIdentifier: z.string().optional().describe("The result identifier."),
+ answer: z.string().optional().describe("A particular answer."),
+ pageID: z.string().optional().describe("The page ID to continue from.")
+ });
+
+ name = "querySurveyAnswers";
+
+ description = `Query answers to surveys the participant has completed.`;
+
+ async _call(input: z.infer) {
+ let response = await MyDataHelps.querySurveyAnswers(input);
+
+ return JSON.stringify(response);
+ }
+}
+
+export class QueryDeviceDataV2Tool extends StructuredTool {
+ schema = deviceDataV2QuerySchema;
+
+ name = "queryDeviceDataV2";
+
+ description = `Can query a participant's device data. This represents raw individual fine grained data points, not aggregated in any way.
+
+ Before using this tool call the getDeviceDataV2AllDataTypes tool to see which data types are available. If a particular data type is not available
+ use the queryDailyData tool instead.
+
+ For sleep this is the function you would query to determine the time a participant went to bed or woke up. For sleep a day consists of from 6 pm the
+ previous day to 6 pm the day of (For example 11/26/2024 sleep is from 11/25/2024 at 6 pm to 11/26/2024 at 6 pm). To determine the time a participant
+ went to bed look at the startDate of the first not awake and not in bed sleep stage for that day. To determine wake up times look at the observationDate
+ of the last not awake and not in bed sleep stage for that day.`;
+
+ async _call(input: z.infer) {
+ let response = await MyDataHelps.queryDeviceDataV2(input as DeviceDataV2Query);
+
+ return JSON.stringify(response.deviceDataPoints.map(({ value, startDate, startDateOffset, observationDate, observationDateOffset, units, dataSource }) => ({
+ value,
+ startDate,
+ startDateOffset,
+ observationDate,
+ observationDateOffset,
+ units,
+ dataSource: dataSource?.sourceName
+ })));
+ }
+}
+
+export class QueryDeviceDataV2AggregateTool extends StructuredTool {
+ schema = deviceDataV2QuerySchema.extend({
+ intervalAmount: z.number().describe("The number of periods to aggregate over. Together with intervalType this can be 1 Days or 3 Minutes."),
+ intervalType: z.enum(["Minutes", "Hours", "Days", "Weeks", "Months"])
+ .describe("The type of interval to aggregate over. Together with intervalAmount this can be 1 Days or 3 Minutes."),
+ aggregateFunctions: z.array(z.enum(["sum", "avg", "count", "min", "max"])).describe("The aggregations functions to apply to the granular data."),
+ });
+
+ name = "queryDeviceDataV2Aggregate";
+
+ description = `Can query a participant's aggregated device data. This can be used to get for instance a participant's hourly steps
+ count or their min max heart rate every day.`;
+
+ async _call(input: z.infer) {
+ let response = await MyDataHelps.queryDeviceDataV2Aggregate(input as DeviceDataV2AggregateQuery);
+
+ return JSON.stringify(response.intervals.map(({ date, statistics }) => ({ date, statistics })));
+ }
+}
+
+export class QueryDailySleepTool extends StructuredTool {
+ schema = deviceDataV2QuerySchema.extend({
+ type: z.string()
+ .describe("The device data type is specific to the namespace. For Apple Health this is called Sleep Analysis, for Fitbit it is called Sleep."),
+ });
+
+ name = "queryDeviceDataV2DailySleep";
+
+ description = `Can query daily aggregated sleep data. This will include sleep broken down by sleep stage.
+ Use this function when you need to do sleep aggregate queries instead of deviceDataV2Aggregate.`;
+
+ async _call(input: z.infer) {
+ let response = await MyDataHelps.queryDeviceDataV2DailySleep(input as DeviceDataV2AggregateQuery);
+
+ return JSON.stringify(response.sleepStageSummaries.map(({ date, duration, value }) => ({ date, duration, value })));
+ }
+}
+
+export class QueryDailyDataTool extends StructuredTool {
+ schema = z.object({
+ type: z.string().describe("The type of daily data to query."),
+ startDate: z.string().describe("The start of the date range for the query. This is a datetime in the participant's local timezone."),
+ endDate: z.string().describe("The end of the date range for the query. This is a datetime in the participant's local timezone.")
+ });
+
+ name = "queryDailyData";
+
+ description = "Query daily data for a participant. Before using this tool call the getAllDailyDataTypes tool to see which data types are available.";
+
+ async _call(input: z.infer) {
+ let response = await queryDailyData(input.type, new Date(input.startDate), new Date(input.endDate), false);
+
+ return JSON.stringify(response);
+ }
+}
+
+export class GetDeviceDataV2AllDataTypesTool extends StructuredTool {
+ schema = z.object({});
+
+ name = "getDeviceDataV2AllDataTypes";
+
+ description = "Get all the device data types that can be queried with the queryDeviceDataV2 tool. Only types that are enabled can be queried.";
+
+ async _call() {
+ let response = await MyDataHelps.getDeviceDataV2AllDataTypes();
+
+ return JSON.stringify(response);
+ }
+}
+
+export class GetAllDailyDataTypesTool extends StructuredTool {
+ schema = z.object({});
+
+ name = "getAllDailyDataTypes";
+
+ description = "Get all the daily data types that can be queried with the queryDailyData tool.";
+
+ async _call() {
+ return JSON.stringify(getAllDailyDataTypes());
+ }
+}
+
+export class GetEhrNewsFeedPageTool extends StructuredTool {
+ schema = z.object({
+ feed: z.enum(["Immunizations", "LabReports", "Procedures", "Reports"]).describe("The type of feed to query."),
+ pageID: z.string().optional().describe("The page ID to continue from if you need to fetch more results."),
+ pageDate: z.string().optional().describe("The date of the page to continue from if you are doing a time based query.")
+ });
+
+ name = "getEhrNewsFeedPage"
+
+ description = `Get electronic health record (EHR) data for the participant.`;
+
+ async _call(input: z.infer) {
+ let response = await getNewsFeedPage(input.feed, input.pageID, input.pageDate);
+
+ return JSON.stringify(response);
+ }
+}
diff --git a/src/helpers/AIAssistant/index.ts b/src/helpers/AIAssistant/index.ts
new file mode 100644
index 00000000..c571fa05
--- /dev/null
+++ b/src/helpers/AIAssistant/index.ts
@@ -0,0 +1 @@
+export * as MyDataHelpsTools from './Tools';
diff --git a/src/helpers/globalCss.ts b/src/helpers/globalCss.ts
index b5033ea5..ec670372 100644
--- a/src/helpers/globalCss.ts
+++ b/src/helpers/globalCss.ts
@@ -16,6 +16,8 @@ export const core = css`
--mdhui-padding-sm: 12px;
--mdhui-padding-md: 16px;
--mdhui-padding-lg: 24px;
+
+ --mdhui-touch: 44px;
}
@media (prefers-reduced-motion) {
@@ -112,6 +114,7 @@ a {
html {
font-size: 17px;
+ height: 100%;
}
@supports (font: -apple-system-body) {
@@ -129,4 +132,5 @@ body {
-moz-osx-font-smoothing: grayscale;
line-height: 1.3;
font-size: 17px;
+ height: 100%;
}`;
\ No newline at end of file
diff --git a/src/helpers/index.ts b/src/helpers/index.ts
index 9e731fdf..e43b7db6 100644
--- a/src/helpers/index.ts
+++ b/src/helpers/index.ts
@@ -16,4 +16,5 @@ export { default as dailyDataTypeDefinitions } from './daily-data-types/all';
export * from './chartOptions';
export * from './chartHelpers';
export * from './heart-rate-data-providers';
-export * from './glucose-and-meals';
\ No newline at end of file
+export * from './glucose-and-meals';
+export * from './AIAssistant';
diff --git a/src/helpers/strings-de.ts b/src/helpers/strings-de.ts
index fbaaa638..a250bf00 100644
--- a/src/helpers/strings-de.ts
+++ b/src/helpers/strings-de.ts
@@ -408,7 +408,8 @@ let strings: { [key: string]: string } = {
"meal-log-no-data": "Keine Mahlzeiten protokolliert",
"meal-editor-time-input-label": "Zeit",
"meal-editor-duplicate-timestamp-error": "Zwei Mahlzeiten können nicht die gleiche Uhrzeit haben.",
- "glucose-view-title": "Blutzuckerüberwachung"
+ "glucose-view-title": "Blutzuckerüberwachung",
+ "ai-assistant-loading": "Interaktion mit Ihren Daten..."
};
export default strings;
\ No newline at end of file
diff --git a/src/helpers/strings-en.ts b/src/helpers/strings-en.ts
index 229f43cd..d00670ad 100644
--- a/src/helpers/strings-en.ts
+++ b/src/helpers/strings-en.ts
@@ -408,7 +408,8 @@
"meal-log-no-data": "No meals logged",
"meal-editor-time-input-label": "Time",
"meal-editor-duplicate-timestamp-error": "Two meals cannot have the same timestamp.",
- "glucose-view-title": "Glucose Monitoring"
+ "glucose-view-title": "Glucose Monitoring",
+ "ai-assistant-loading": "Interacting with your data..."
};
export default strings;
\ No newline at end of file
diff --git a/src/helpers/strings-es.ts b/src/helpers/strings-es.ts
index 04dfee95..02118064 100644
--- a/src/helpers/strings-es.ts
+++ b/src/helpers/strings-es.ts
@@ -408,7 +408,8 @@
"meal-log-no-data": "No hay comidas registradas",
"meal-editor-time-input-label": "Hora",
"meal-editor-duplicate-timestamp-error": "Dos comidas no pueden tener la misma hora.",
- "glucose-view-title": "Monitorización de glucosa"
+ "glucose-view-title": "Monitorización de glucosa",
+ "ai-assistant-loading": "Interactuando con tus datos..."
};
export default strings;
\ No newline at end of file
diff --git a/src/helpers/strings-fr.ts b/src/helpers/strings-fr.ts
index de1e06b0..a5b945c3 100644
--- a/src/helpers/strings-fr.ts
+++ b/src/helpers/strings-fr.ts
@@ -408,7 +408,8 @@ let strings: { [key: string]: string } = {
"meal-log-no-data": "Aucun repas enregistré",
"meal-editor-time-input-label": "Heure",
"meal-editor-duplicate-timestamp-error": "Deux repas ne peuvent pas avoir la même heure.",
- "glucose-view-title": "Surveillance de la glycémie"
+ "glucose-view-title": "Surveillance de la glycémie",
+ "ai-assistant-loading": "Interagir avec vos données..."
};
export default strings;
\ No newline at end of file
diff --git a/src/helpers/strings-it.ts b/src/helpers/strings-it.ts
index cbd0849d..e7be46f0 100644
--- a/src/helpers/strings-it.ts
+++ b/src/helpers/strings-it.ts
@@ -408,7 +408,8 @@ let strings: { [key: string]: string } = {
"meal-log-no-data": "Nessun pasto registrato",
"meal-editor-time-input-label": "Ora",
"meal-editor-duplicate-timestamp-error": "Due pasti non possono avere la stessa ora.",
- "glucose-view-title": "Monitoraggio della glicemia"
+ "glucose-view-title": "Monitoraggio della glicemia",
+ "ai-assistant-loading": "Interagire con i tuoi dati..."
};
export default strings;
\ No newline at end of file
diff --git a/src/helpers/strings-nl.ts b/src/helpers/strings-nl.ts
index b298c336..798800b1 100644
--- a/src/helpers/strings-nl.ts
+++ b/src/helpers/strings-nl.ts
@@ -408,7 +408,8 @@ let strings: { [key: string]: string } = {
"meal-log-no-data": "Geen maaltijden geregistreerd",
"meal-editor-time-input-label": "Tijd",
"meal-editor-duplicate-timestamp-error": "Twee maaltijden kunnen niet dezelfde tijd hebben.",
- "glucose-view-title": "Glucosemonitoring"
+ "glucose-view-title": "Glucosemonitoring",
+ "ai-assistant-loading": "Interageren met uw gegevens..."
};
export default strings;
\ No newline at end of file
diff --git a/src/helpers/strings-pl.ts b/src/helpers/strings-pl.ts
index 4376729d..bffab1de 100644
--- a/src/helpers/strings-pl.ts
+++ b/src/helpers/strings-pl.ts
@@ -408,7 +408,8 @@ let strings: { [key: string]: string } = {
"meal-log-no-data": "Brak zarejestrowanych posiłków",
"meal-editor-time-input-label": "Czas",
"meal-editor-duplicate-timestamp-error": "Dwa posiłki nie mogą mieć tej samej godziny.",
- "glucose-view-title": "Monitorowanie glukozy"
+ "glucose-view-title": "Monitorowanie glukozy",
+ "ai-assistant-loading": "Interakcja z Twoimi danymi..."
};
export default strings;
\ No newline at end of file
diff --git a/src/helpers/strings-pt.ts b/src/helpers/strings-pt.ts
index f0bf5c15..7774733c 100644
--- a/src/helpers/strings-pt.ts
+++ b/src/helpers/strings-pt.ts
@@ -408,7 +408,8 @@ let strings: { [key: string]: string } = {
"meal-log-no-data": "Nenhuma refeição registrada",
"meal-editor-time-input-label": "Hora",
"meal-editor-duplicate-timestamp-error": "Duas refeições não podem ter o mesmo horário.",
- "glucose-view-title": "Monitoramento de glicose"
+ "glucose-view-title": "Monitoramento de glicose",
+ "ai-assistant-loading": "Interagindo com seus dados..."
};
export default strings;
\ No newline at end of file