mirror of https://github.com/buster-so/buster.git
commit
dd9126bbb8
|
@ -3060,11 +3060,35 @@ async fn apply_file_filtering_rules(
|
||||||
// Process current turn files
|
// Process current turn files
|
||||||
let new_filtered_assets =
|
let new_filtered_assets =
|
||||||
process_current_turn_files(&metrics_this_turn, &dashboards_this_turn)?;
|
process_current_turn_files(&metrics_this_turn, &dashboards_this_turn)?;
|
||||||
// Return context dashboard first, then the processed new assets
|
|
||||||
Ok(vec![context_dashboard_info]
|
// --- START REFACTOR ---
|
||||||
|
// Check if the context dashboard ID is already present in the assets processed this turn
|
||||||
|
let context_dashboard_modified_this_turn = new_filtered_assets
|
||||||
|
.iter()
|
||||||
|
.any(|asset| asset.id == context_dashboard_info.id);
|
||||||
|
|
||||||
|
if context_dashboard_modified_this_turn {
|
||||||
|
// If the context dashboard was modified THIS turn, return only the processed assets for this turn
|
||||||
|
// (which already includes the updated dashboard)
|
||||||
|
tracing::debug!("Context dashboard {} was modified this turn. Returning processed assets directly.", ctx_id);
|
||||||
|
Ok(new_filtered_assets)
|
||||||
|
} else {
|
||||||
|
// If the context dashboard was NOT modified this turn (only its metrics were),
|
||||||
|
// return the context dashboard (unmodified info) followed by the other processed assets.
|
||||||
|
tracing::debug!("Context dashboard {} was NOT modified this turn (only metrics). Prepending context info.", ctx_id);
|
||||||
|
Ok(vec![context_dashboard_info] // Use the fetched (unmodified) context info
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(new_filtered_assets.into_iter())
|
.chain(new_filtered_assets.into_iter())
|
||||||
.collect())
|
.collect())
|
||||||
|
}
|
||||||
|
// --- END REFACTOR ---
|
||||||
|
|
||||||
|
// OLD CODE - REMOVED:
|
||||||
|
// // Return context dashboard first, then the processed new assets
|
||||||
|
// Ok(vec![context_dashboard_info]
|
||||||
|
// .into_iter()
|
||||||
|
// .chain(new_filtered_assets.into_iter())
|
||||||
|
// .collect())
|
||||||
} else {
|
} else {
|
||||||
// No context metric modified, or context parsing failed. Process current turn only.
|
// No context metric modified, or context parsing failed. Process current turn only.
|
||||||
tracing::debug!("No context metric modified (or context parse failed). Processing current turn files only.");
|
tracing::debug!("No context metric modified (or context parse failed). Processing current turn files only.");
|
||||||
|
|
|
@ -99,98 +99,101 @@ fn compute_column_metadata(data: &[IndexMap<String, DataType>]) -> Vec<ColumnMet
|
||||||
|
|
||||||
columns.iter().map(|column_name| {
|
columns.iter().map(|column_name| {
|
||||||
let mut value_map = HashSet::new();
|
let mut value_map = HashSet::new();
|
||||||
let mut min_value = None;
|
let mut min_value_numeric: Option<f64> = None; // Use specific name
|
||||||
let mut max_value = None;
|
let mut max_value_numeric: Option<f64> = None; // Use specific name
|
||||||
let mut is_date_type = false;
|
|
||||||
let mut min_value_str: Option<String> = None;
|
let mut min_value_str: Option<String> = None;
|
||||||
let mut max_value_str: Option<String> = None;
|
let mut max_value_str: Option<String> = None;
|
||||||
|
let mut determined_type: Option<(SimpleType, ColumnType)> = None;
|
||||||
|
|
||||||
for row in data {
|
for row in data {
|
||||||
if let Some(value) = row.get(column_name) {
|
if let Some(value) = row.get(column_name) {
|
||||||
// Track unique values (up to a reasonable limit)
|
// Track unique values (up to a reasonable limit)
|
||||||
if value_map.len() < 100 {
|
if value_map.len() < 100 {
|
||||||
value_map.insert(format!("{:?}", value));
|
value_map.insert(format!("{:?}", value)); // format! handles nulls acceptably
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate min/max for appropriate types
|
// Determine type from first non-null value encountered
|
||||||
|
if determined_type.is_none() {
|
||||||
|
match value {
|
||||||
|
// Check for non-null variants using matches! for conciseness
|
||||||
|
DataType::Int2(Some(_)) | DataType::Int4(Some(_)) | DataType::Int8(Some(_)) |
|
||||||
|
DataType::Float4(Some(_)) | DataType::Float8(Some(_)) | DataType::Text(Some(_)) |
|
||||||
|
DataType::Bool(Some(_)) | DataType::Date(Some(_)) | DataType::Timestamp(Some(_)) |
|
||||||
|
DataType::Timestamptz(Some(_)) | DataType::Json(Some(_)) | DataType::Uuid(Some(_)) |
|
||||||
|
DataType::Decimal(Some(_)) | DataType::Time(Some(_)) => {
|
||||||
|
determined_type = Some(determine_types(value));
|
||||||
|
}
|
||||||
|
// If it's a Null variant or Unknown, keep looking
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate min/max based on value's actual type in this row
|
||||||
match value {
|
match value {
|
||||||
DataType::Int2(Some(v)) => {
|
DataType::Int2(Some(v)) => {
|
||||||
let n = *v as f64;
|
let n = *v as f64;
|
||||||
min_value = Some(min_value.map_or(n, |min: f64| min.min(n)));
|
min_value_numeric = Some(min_value_numeric.map_or(n, |min| min.min(n)));
|
||||||
max_value = Some(max_value.map_or(n, |max: f64| max.max(n)));
|
max_value_numeric = Some(max_value_numeric.map_or(n, |max| max.max(n)));
|
||||||
}
|
}
|
||||||
DataType::Int4(Some(v)) => {
|
DataType::Int4(Some(v)) => {
|
||||||
let n = *v as f64;
|
let n = *v as f64;
|
||||||
min_value = Some(min_value.map_or(n, |min: f64| min.min(n)));
|
min_value_numeric = Some(min_value_numeric.map_or(n, |min| min.min(n)));
|
||||||
max_value = Some(max_value.map_or(n, |max: f64| max.max(n)));
|
max_value_numeric = Some(max_value_numeric.map_or(n, |max| max.max(n)));
|
||||||
}
|
}
|
||||||
DataType::Int8(Some(v)) => {
|
DataType::Int8(Some(v)) => {
|
||||||
let n = *v as f64;
|
let n = *v as f64;
|
||||||
min_value = Some(min_value.map_or(n, |min: f64| min.min(n)));
|
min_value_numeric = Some(min_value_numeric.map_or(n, |min| min.min(n)));
|
||||||
max_value = Some(max_value.map_or(n, |max: f64| max.max(n)));
|
max_value_numeric = Some(max_value_numeric.map_or(n, |max| max.max(n)));
|
||||||
}
|
}
|
||||||
DataType::Float4(Some(v)) => {
|
DataType::Float4(Some(v)) => {
|
||||||
let n = *v as f64;
|
let n = *v as f64;
|
||||||
min_value = Some(min_value.map_or(n, |min: f64| min.min(n)));
|
min_value_numeric = Some(min_value_numeric.map_or(n, |min| min.min(n)));
|
||||||
max_value = Some(max_value.map_or(n, |max: f64| max.max(n)));
|
max_value_numeric = Some(max_value_numeric.map_or(n, |max| max.max(n)));
|
||||||
}
|
}
|
||||||
DataType::Float8(Some(v)) => {
|
DataType::Float8(Some(v)) => {
|
||||||
let n = *v as f64;
|
let n = *v as f64;
|
||||||
min_value = Some(min_value.map_or(n, |min: f64| min.min(n)));
|
min_value_numeric = Some(min_value_numeric.map_or(n, |min| min.min(n)));
|
||||||
max_value = Some(max_value.map_or(n, |max: f64| max.max(n)));
|
max_value_numeric = Some(max_value_numeric.map_or(n, |max| max.max(n)));
|
||||||
}
|
}
|
||||||
DataType::Date(Some(date)) => {
|
DataType::Date(Some(date)) => {
|
||||||
is_date_type = true;
|
update_date_min_max(&date.to_string(), &mut min_value_str, &mut max_value_str);
|
||||||
let date_str = date.to_string();
|
|
||||||
update_date_min_max(&date_str, &mut min_value_str, &mut max_value_str);
|
|
||||||
}
|
}
|
||||||
DataType::Timestamp(Some(ts)) => {
|
DataType::Timestamp(Some(ts)) => {
|
||||||
is_date_type = true;
|
update_date_min_max(&ts.to_string(), &mut min_value_str, &mut max_value_str);
|
||||||
let ts_str = ts.to_string();
|
|
||||||
update_date_min_max(&ts_str, &mut min_value_str, &mut max_value_str);
|
|
||||||
}
|
}
|
||||||
DataType::Timestamptz(Some(ts)) => {
|
DataType::Timestamptz(Some(ts)) => {
|
||||||
is_date_type = true;
|
update_date_min_max(&ts.to_string(), &mut min_value_str, &mut max_value_str);
|
||||||
let ts_str = ts.to_string();
|
|
||||||
update_date_min_max(&ts_str, &mut min_value_str, &mut max_value_str);
|
|
||||||
}
|
}
|
||||||
|
// Ignore nulls and non-comparable types for min/max calculation
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the column type and simple type
|
// Finalize types - default if no non-null value was found
|
||||||
let column_type = first_row.get(column_name).unwrap();
|
let (simple_type, column_type) = determined_type.unwrap_or((SimpleType::Other, ColumnType::Other));
|
||||||
let (simple_type, column_type) = determine_types(column_type);
|
|
||||||
|
|
||||||
// Format min/max values appropriately based on type
|
// Format min/max values appropriately based on determined simple_type
|
||||||
let (min_value, max_value) = if is_date_type {
|
let (min_value_json, max_value_json) = match simple_type {
|
||||||
(
|
SimpleType::Number => (
|
||||||
min_value_str.map_or(serde_json::Value::Null, |v| serde_json::Value::String(v)),
|
min_value_numeric.and_then(|v| serde_json::Number::from_f64(v).map(serde_json::Value::Number))
|
||||||
max_value_str.map_or(serde_json::Value::Null, |v| serde_json::Value::String(v)),
|
.unwrap_or(serde_json::Value::Null),
|
||||||
)
|
max_value_numeric.and_then(|v| serde_json::Number::from_f64(v).map(serde_json::Value::Number))
|
||||||
} else {
|
.unwrap_or(serde_json::Value::Null),
|
||||||
(
|
),
|
||||||
min_value.map_or(serde_json::Value::Null, |v| {
|
SimpleType::Date => (
|
||||||
match serde_json::Number::from_f64(v) {
|
min_value_str.map_or(serde_json::Value::Null, serde_json::Value::String),
|
||||||
Some(num) => serde_json::Value::Number(num),
|
max_value_str.map_or(serde_json::Value::Null, serde_json::Value::String),
|
||||||
None => serde_json::Value::Null,
|
),
|
||||||
}
|
// Don't provide min/max for other types
|
||||||
}),
|
_ => (serde_json::Value::Null, serde_json::Value::Null),
|
||||||
max_value.map_or(serde_json::Value::Null, |v| {
|
|
||||||
match serde_json::Number::from_f64(v) {
|
|
||||||
Some(num) => serde_json::Value::Number(num),
|
|
||||||
None => serde_json::Value::Null,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ColumnMetaData {
|
ColumnMetaData {
|
||||||
name: column_name.to_lowercase(),
|
name: column_name.to_lowercase(),
|
||||||
min_value,
|
min_value: min_value_json,
|
||||||
max_value,
|
max_value: max_value_json,
|
||||||
unique_values: value_map.len() as i32,
|
unique_values: value_map.len() as i32, // Count includes distinct null representations
|
||||||
simple_type,
|
simple_type,
|
||||||
column_type,
|
column_type,
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ export const prefetchGetMyUserInfo = async (
|
||||||
queryFn: () => initialData!,
|
queryFn: () => initialData!,
|
||||||
initialData
|
initialData
|
||||||
});
|
});
|
||||||
return { queryClient, initialData };
|
return queryClient;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetUser = (params: Parameters<typeof getUser>[0]) => {
|
export const useGetUser = (params: Parameters<typeof getUser>[0]) => {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { prefetchGetMyUserInfo } from '@/api/buster_rest';
|
||||||
import { getSupabaseUserContext } from '@/lib/supabase';
|
import { getSupabaseUserContext } from '@/lib/supabase';
|
||||||
import { AppProviders } from '@/context/AppProviders';
|
import { AppProviders } from '@/context/AppProviders';
|
||||||
import { headers } from 'next/headers';
|
import { headers } from 'next/headers';
|
||||||
|
import { queryKeys } from '@/api/query_keys';
|
||||||
|
|
||||||
export default async function Layout({
|
export default async function Layout({
|
||||||
children
|
children
|
||||||
|
@ -18,10 +19,19 @@ export default async function Layout({
|
||||||
const pathname = headersList.get('x-pathname');
|
const pathname = headersList.get('x-pathname');
|
||||||
const supabaseContext = await getSupabaseUserContext();
|
const supabaseContext = await getSupabaseUserContext();
|
||||||
const { accessToken } = supabaseContext;
|
const { accessToken } = supabaseContext;
|
||||||
const { initialData: userInfo, queryClient } = await prefetchGetMyUserInfo({
|
const queryClient = await prefetchGetMyUserInfo({
|
||||||
jwtToken: accessToken
|
jwtToken: accessToken
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const userInfoState = queryClient.getQueryState(queryKeys.userGetUserMyself.queryKey);
|
||||||
|
|
||||||
|
const is402Error = userInfoState?.status === 'error' && userInfoState?.error?.status === 402;
|
||||||
|
|
||||||
|
if (is402Error) {
|
||||||
|
return <ClientRedirect to={createBusterRoute({ route: BusterRoutes.INFO_GETTING_STARTED })} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userInfo = queryClient.getQueryData(queryKeys.userGetUserMyself.queryKey);
|
||||||
const newUserRoute = createBusterRoute({ route: BusterRoutes.NEW_USER });
|
const newUserRoute = createBusterRoute({ route: BusterRoutes.NEW_USER });
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { Paragraph, Text, Title } from '@/components/ui/typography';
|
||||||
|
import { Button } from '@/components/ui/buttons';
|
||||||
|
import { Card, CardContent, CardFooter } from '@/components/ui/card/CardBase';
|
||||||
|
import { BUSTER_GETTING_STARTED_URL } from '@/routes/externalRoutes';
|
||||||
|
import { BusterLogo } from '@/assets/svg/BusterLogo';
|
||||||
|
import { ArrowUpRight } from '@/components/ui/icons';
|
||||||
|
|
||||||
|
export default function GettingStartedPage() {
|
||||||
|
return (
|
||||||
|
<div className="container flex min-h-screen w-full min-w-screen items-center justify-center py-10">
|
||||||
|
<Card className="w-full max-w-lg">
|
||||||
|
<CardContent className="space-y-4 pt-6 text-center">
|
||||||
|
<>
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<BusterLogo className="h-10 w-10" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Title as={'h1'} variant="default">
|
||||||
|
Welcome to Buster!
|
||||||
|
</Title>
|
||||||
|
<Paragraph size={'md'}>
|
||||||
|
It looks like your organization hasn't been fully set up yet. To get started with
|
||||||
|
Buster, you'll need to complete the organization setup process or verify your
|
||||||
|
payment information.
|
||||||
|
</Paragraph>
|
||||||
|
</>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className="w-full pt-0">
|
||||||
|
<a
|
||||||
|
href={BUSTER_GETTING_STARTED_URL}
|
||||||
|
target="_blank"
|
||||||
|
className="w-full"
|
||||||
|
rel="noopener noreferrer">
|
||||||
|
<Button
|
||||||
|
size="tall"
|
||||||
|
variant={'black'}
|
||||||
|
block
|
||||||
|
suffix={
|
||||||
|
<span className="text-base">
|
||||||
|
<ArrowUpRight />
|
||||||
|
</span>
|
||||||
|
}>
|
||||||
|
Get Started
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { useDebounceFn, useMemoizedFn } from '@/hooks';
|
import { useMemoizedFn } from '@/hooks';
|
||||||
import { EditableTitle } from '@/components/ui/typography/EditableTitle';
|
import { EditableTitle } from '@/components/ui/typography/EditableTitle';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useUpdateDashboard } from '@/api/buster_rest/dashboards';
|
import { useUpdateDashboard } from '@/api/buster_rest/dashboards';
|
||||||
|
@ -50,7 +50,7 @@ export const DashboardEditTitles: React.FC<{
|
||||||
className={'py-0! pl-0!'}
|
className={'py-0! pl-0!'}
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
onChange={onChangeDashboardDescription}
|
onChange={onChangeDashboardDescription}
|
||||||
defaultValue={description}
|
value={description}
|
||||||
autoResize={descriptionAutoResize}
|
autoResize={descriptionAutoResize}
|
||||||
placeholder="Add description..."
|
placeholder="Add description..."
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -9,6 +9,8 @@ import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
import { useAutoScroll } from '@/hooks/useAutoScroll';
|
import { useAutoScroll } from '@/hooks/useAutoScroll';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
import { ReasoningScrollToBottom } from './ReasoningScrollToBottom';
|
import { ReasoningScrollToBottom } from './ReasoningScrollToBottom';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { queryKeys } from '@/api/query_keys';
|
||||||
|
|
||||||
interface ReasoningControllerProps {
|
interface ReasoningControllerProps {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
|
@ -23,6 +25,11 @@ export const ReasoningController: React.FC<ReasoningControllerProps> = ({ chatId
|
||||||
const { data: isCompletedStream } = useGetChatMessage(messageId, {
|
const { data: isCompletedStream } = useGetChatMessage(messageId, {
|
||||||
select: ({ isCompletedStream }) => isCompletedStream
|
select: ({ isCompletedStream }) => isCompletedStream
|
||||||
});
|
});
|
||||||
|
const blackBoxMessage = useQuery({
|
||||||
|
...queryKeys.chatsBlackBoxMessages(messageId),
|
||||||
|
notifyOnChangeProps: ['data']
|
||||||
|
}).data;
|
||||||
|
|
||||||
const viewportRef = useRef<HTMLDivElement>(null);
|
const viewportRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const { isAutoScrollEnabled, scrollToBottom, enableAutoScroll } = useAutoScroll(viewportRef, {
|
const { isAutoScrollEnabled, scrollToBottom, enableAutoScroll } = useAutoScroll(viewportRef, {
|
||||||
|
@ -49,11 +56,11 @@ export const ReasoningController: React.FC<ReasoningControllerProps> = ({ chatId
|
||||||
isCompletedStream={isCompletedStream ?? true}
|
isCompletedStream={isCompletedStream ?? true}
|
||||||
chatId={chatId}
|
chatId={chatId}
|
||||||
messageId={messageId}
|
messageId={messageId}
|
||||||
isLastMessage={messageIndex === reasoningMessageIds.length - 1}
|
isLastMessage={messageIndex === reasoningMessageIds.length - 1 && !blackBoxMessage}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<BlackBoxMessage messageId={messageId} />
|
<BlackBoxMessage blackBoxMessage={blackBoxMessage} />
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,8 @@ import React from 'react';
|
||||||
import { queryKeys } from '@/api/query_keys';
|
import { queryKeys } from '@/api/query_keys';
|
||||||
import { BarContainer } from './BarContainer';
|
import { BarContainer } from './BarContainer';
|
||||||
|
|
||||||
export const BlackBoxMessage: React.FC<{ messageId: string }> = React.memo(({ messageId }) => {
|
export const BlackBoxMessage: React.FC<{ blackBoxMessage: string | undefined | null }> = React.memo(
|
||||||
const blackBoxMessage = useQuery({
|
({ blackBoxMessage }) => {
|
||||||
...queryKeys.chatsBlackBoxMessages(messageId),
|
|
||||||
notifyOnChangeProps: ['data']
|
|
||||||
}).data;
|
|
||||||
|
|
||||||
if (blackBoxMessage) {
|
if (blackBoxMessage) {
|
||||||
return (
|
return (
|
||||||
<BarContainer
|
<BarContainer
|
||||||
|
@ -24,6 +20,7 @@ export const BlackBoxMessage: React.FC<{ messageId: string }> = React.memo(({ me
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
BlackBoxMessage.displayName = 'BlackBoxMessage';
|
BlackBoxMessage.displayName = 'BlackBoxMessage';
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
export enum BusterInfoRoutes {
|
||||||
|
INFO_GETTING_STARTED = '/info/getting-started'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BusterInfoRoutesWithArgs = {
|
||||||
|
[BusterInfoRoutes.INFO_GETTING_STARTED]: { route: BusterInfoRoutes.INFO_GETTING_STARTED };
|
||||||
|
};
|
|
@ -1,6 +1,7 @@
|
||||||
import { BusterAppRoutes, BusterAppRoutesWithArgs } from './busterAppRoutes';
|
import { BusterAppRoutes, BusterAppRoutesWithArgs } from './busterAppRoutes';
|
||||||
import { BusterAuthRoutes, BusterAuthRoutesWithArgs } from './busterAuthRoutes';
|
import { BusterAuthRoutes, BusterAuthRoutesWithArgs } from './busterAuthRoutes';
|
||||||
import { BusterEmbedRoutes, BusterEmbedRoutesWithArgs } from './busterEmbedRoutes';
|
import { BusterEmbedRoutes, BusterEmbedRoutesWithArgs } from './busterEmbedRoutes';
|
||||||
|
import { BusterInfoRoutes, BusterInfoRoutesWithArgs } from './busterInfoRoutes';
|
||||||
import { BusterSettingsRoutes, BusterSettingsRoutesWithArgs } from './busterSettingsRoutes';
|
import { BusterSettingsRoutes, BusterSettingsRoutesWithArgs } from './busterSettingsRoutes';
|
||||||
|
|
||||||
export enum BusterRootRoutes {
|
export enum BusterRootRoutes {
|
||||||
|
@ -16,6 +17,7 @@ export const BusterRoutes = {
|
||||||
...BusterAuthRoutes,
|
...BusterAuthRoutes,
|
||||||
...BusterSettingsRoutes,
|
...BusterSettingsRoutes,
|
||||||
...BusterEmbedRoutes,
|
...BusterEmbedRoutes,
|
||||||
|
...BusterInfoRoutes,
|
||||||
...BusterRootRoutes
|
...BusterRootRoutes
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,12 +26,14 @@ export type BusterRoutes =
|
||||||
| BusterAuthRoutes
|
| BusterAuthRoutes
|
||||||
| BusterSettingsRoutes
|
| BusterSettingsRoutes
|
||||||
| BusterEmbedRoutes
|
| BusterEmbedRoutes
|
||||||
|
| BusterInfoRoutes
|
||||||
| BusterRootRoutes;
|
| BusterRootRoutes;
|
||||||
|
|
||||||
export type BusterRoutesWithArgs = BusterRootRoutesWithArgs &
|
export type BusterRoutesWithArgs = BusterRootRoutesWithArgs &
|
||||||
BusterAuthRoutesWithArgs &
|
BusterAuthRoutesWithArgs &
|
||||||
BusterAppRoutesWithArgs &
|
BusterAppRoutesWithArgs &
|
||||||
BusterEmbedRoutesWithArgs &
|
BusterEmbedRoutesWithArgs &
|
||||||
BusterSettingsRoutesWithArgs;
|
BusterSettingsRoutesWithArgs &
|
||||||
|
BusterInfoRoutesWithArgs;
|
||||||
|
|
||||||
export type BusterRoutesWithArgsRoute = BusterRoutesWithArgs[BusterRoutes];
|
export type BusterRoutesWithArgsRoute = BusterRoutesWithArgs[BusterRoutes];
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
export const BUSTER_HOME_PAGE = 'https://buster.so';
|
export const BUSTER_HOME_PAGE = 'https://buster.so';
|
||||||
export const BUSTER_DOCS_URL = 'https://docs.buster.so';
|
export const BUSTER_DOCS_URL = 'https://docs.buster.so';
|
||||||
|
export const BUSTER_GETTING_STARTED_URL = 'https://www.buster.so/get-started';
|
||||||
|
|
Loading…
Reference in New Issue