Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AUTO] Increment version to 2.16.0.0 #228

Closed
wants to merge 8 commits into from
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- Change implementation of basic_input_output to built-in parser ([#10](https://github.com/opensearch-project/dashboards-assistant/pull/10))
- Add interactions into ChatState and pass specific interaction into message_bubble ([#12](https://github.com/opensearch-project/dashboards-assistant/pull/12))
- Refactor the code to get root agent id by calling the API in ml-commons plugin ([#128](https://github.com/opensearch-project/dashboards-assistant/pull/128))
- build(deps): bump braces from 3.0.2 to 3.0.3 ([#213](https://github.com/opensearch-project/dashboards-assistant/pull/213))
5 changes: 5 additions & 0 deletions common/constants/llm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export const ASSISTANT_API = {
ACCOUNT: `${API_BASE}/account`,
} as const;

export const TEXT2VIZ_API = {
TEXT2PPL: `${API_BASE}/text2ppl`,
TEXT2VEGA: `${API_BASE}/text2vega`,
};

export const NOTEBOOK_API = {
CREATE_NOTEBOOK: `${NOTEBOOK_PREFIX}/note`,
SET_PARAGRAPH: `${NOTEBOOK_PREFIX}/set_paragraphs/`,
Expand Down
3 changes: 3 additions & 0 deletions common/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export const configSchema = schema.object({
incontextInsight: schema.object({
enabled: schema.boolean({ defaultValue: true }),
}),
next: schema.object({
enabled: schema.boolean({ defaultValue: false }),
}),
});

export type ConfigSchema = TypeOf<typeof configSchema>;
6 changes: 5 additions & 1 deletion opensearch_dashboards.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@
"server": true,
"ui": true,
"requiredPlugins": [
"data",
"dashboard",
"embeddable",
"opensearchDashboardsReact",
"opensearchDashboardsUtils"
"opensearchDashboardsUtils",
"visualizations",
"savedObjects"
],
"optionalPlugins": [
"dataSource",
"dataSourceManagement"
],
"requiredBundles": [],
"configPath": [
"assistant"
]
Expand Down
102 changes: 102 additions & 0 deletions public/components/visualization/source_selector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useCallback, useMemo, useState, useEffect } from 'react';
import { i18n } from '@osd/i18n';

import { useOpenSearchDashboards } from '../../../../../src/plugins/opensearch_dashboards_react/public';
import {
DataSource,
DataSourceGroup,
DataSourceSelectable,
DataSourceOption,
} from '../../../../../src/plugins/data/public';
import { StartServices } from '../../types';

export const SourceSelector = ({
selectedSourceId,
onChange,
}: {
selectedSourceId: string;
onChange: (ds: DataSourceOption) => void;
}) => {
const {
services: {
data: { dataSources },
notifications: { toasts },
},
} = useOpenSearchDashboards<StartServices>();
const [currentDataSources, setCurrentDataSources] = useState<DataSource[]>([]);
const [dataSourceOptions, setDataSourceOptions] = useState<DataSourceGroup[]>([]);

const selectedSources = useMemo(() => {
if (selectedSourceId) {
for (const group of dataSourceOptions) {
for (const item of group.options) {
if (item.value === selectedSourceId) {
return [item];
}
}
}
}
return [];
}, [selectedSourceId, dataSourceOptions]);

useEffect(() => {
if (
!selectedSourceId &&
dataSourceOptions.length > 0 &&
dataSourceOptions[0].options.length > 0
) {
onChange(dataSourceOptions[0].options[0]);
}
}, [selectedSourceId, dataSourceOptions]);

useEffect(() => {
const subscription = dataSources.dataSourceService.getDataSources$().subscribe((ds) => {
setCurrentDataSources(Object.values(ds));
});

return () => {
subscription.unsubscribe();
};
}, [dataSources]);

const onDataSourceSelect = useCallback(
(selectedDataSources: DataSourceOption[]) => {
onChange(selectedDataSources[0]);
},
[onChange]
);

const handleGetDataSetError = useCallback(
() => (error: Error) => {
toasts.addError(error, {
title:
i18n.translate('visualize.vega.failedToGetDataSetErrorDescription', {
defaultMessage: 'Failed to get data set: ',
}) + (error.message || error.name),
});
},
[toasts]
);

const memorizedReload = useCallback(() => {
dataSources.dataSourceService.reload();
}, [dataSources.dataSourceService]);

return (
<DataSourceSelectable
dataSources={currentDataSources}
dataSourceOptionList={dataSourceOptions}
setDataSourceOptionList={setDataSourceOptions}
onDataSourceSelect={onDataSourceSelect}
selectedSources={selectedSources}
onGetDataSetError={handleGetDataSetError}
onRefresh={memorizedReload}
fullWidth
/>
);
};
163 changes: 163 additions & 0 deletions public/components/visualization/text2vega.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { BehaviorSubject, Observable, of } from 'rxjs';
import { debounceTime, switchMap, tap, filter, catchError } from 'rxjs/operators';
import { TEXT2VIZ_API } from '.../../../common/constants/llm';
import { HttpSetup } from '../../../../../src/core/public';
import { DataPublicPluginStart } from '../../../../../src/plugins/data/public';

const DATA_SOURCE_DELIMITER = '::';

const topN = (ppl: string, n: number) => `${ppl} | head ${n}`;

const getDataSourceAndIndexFromLabel = (label: string) => {
if (label.includes(DATA_SOURCE_DELIMITER)) {
return [
label.slice(0, label.indexOf(DATA_SOURCE_DELIMITER)),
label.slice(label.indexOf(DATA_SOURCE_DELIMITER) + DATA_SOURCE_DELIMITER.length),
] as const;
}
return [, label] as const;
};

interface Input {
prompt: string;
index: string;
dataSourceId?: string;
}

export class Text2Vega {
input$ = new BehaviorSubject<Input>({ prompt: '', index: '' });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
result$: Observable<Record<string, any> | { error: any }>;
status$ = new BehaviorSubject<'RUNNING' | 'STOPPED'>('STOPPED');
http: HttpSetup;
searchClient: DataPublicPluginStart['search'];

constructor(http: HttpSetup, searchClient: DataPublicPluginStart['search']) {
this.http = http;
this.searchClient = searchClient;
this.result$ = this.input$
.pipe(
filter((v) => v.prompt.length > 0),
debounceTime(200),
tap(() => this.status$.next('RUNNING'))
)
.pipe(
switchMap((v) =>
of(v).pipe(
// text to ppl
switchMap(async (value) => {
const [, indexName] = getDataSourceAndIndexFromLabel(value.index);
const pplQuestion = value.prompt.split('//')[0];
const ppl = await this.text2ppl(pplQuestion, indexName, value.dataSourceId);
return {
...value,
ppl,
};
}),
// query sample data with ppl
switchMap(async (value) => {
const ppl = topN(value.ppl, 2);
const res = await this.searchClient
.search(
{ params: { body: { query: ppl } }, dataSourceId: value.dataSourceId },
{ strategy: 'pplraw' }
)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.toPromise<any>();
return { ...value, sample: res.rawResponse };
}),
// call llm to generate vega
switchMap(async (value) => {
const result = await this.text2vega({
input: value.prompt,
ppl: value.ppl,
sampleData: JSON.stringify(value.sample.jsonData),
dataSchema: JSON.stringify(value.sample.schema),
dataSourceId: value.dataSourceId,
});
const [dataSourceName] = getDataSourceAndIndexFromLabel(value.index);
result.data = {
url: {
'%type%': 'ppl',
body: { query: value.ppl },
data_source_name: dataSourceName,
},
};
return result;
}),
catchError((e) => of({ error: e }))
)
)
)
.pipe(tap(() => this.status$.next('STOPPED')));
}

async text2vega({
input,
ppl,
sampleData,
dataSchema,
dataSourceId,
}: {
input: string;
ppl: string;
sampleData: string;
dataSchema: string;
dataSourceId?: string;
}) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const escapeField = (json: any, field: string) => {
if (json[field]) {
if (typeof json[field] === 'string') {
json[field] = json[field].replace(/\./g, '\\.');
}
if (typeof json[field] === 'object') {
Object.keys(json[field]).forEach((p) => {
escapeField(json[field], p);
});
}
}
};
const res = await this.http.post(TEXT2VIZ_API.TEXT2VEGA, {
body: JSON.stringify({
input,
ppl,
sampleData: JSON.stringify(sampleData),
dataSchema: JSON.stringify(dataSchema),
}),
query: { dataSourceId },
});

// need to escape field: geo.city -> field: geo\\.city
escapeField(res, 'encoding');
return res;
}

async text2ppl(query: string, index: string, dataSourceId?: string) {
const pplResponse = await this.http.post(TEXT2VIZ_API.TEXT2PPL, {
body: JSON.stringify({
question: query,
index,
}),
query: { dataSourceId },
});
return pplResponse.ppl;
}

invoke(value: Input) {
this.input$.next(value);
}

getStatus$() {
return this.status$;
}

getResult$() {
return this.result$;
}
}
10 changes: 10 additions & 0 deletions public/components/visualization/text2viz.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.text2viz__page {
.visualize {
height: 400px;
}

.text2viz__right {
padding-top: 15px;
padding-left: 30px;
}
}
Loading
Loading