Merge branch 'staging' into cursor/replace-select-with-dropdown-component-39ce

This commit is contained in:
Nate Kelley 2025-07-14 11:40:57 -06:00
commit 90801c89c4
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
16 changed files with 149 additions and 48 deletions

View File

@ -1,23 +1,23 @@
{ {
"folders": [ "folders": [
{ "path": "./apps/api" }, { "path": "../apps/api" },
{ "path": "./apps/electric-server" }, { "path": "../apps/electric-server" },
{ "path": "./apps/server" }, { "path": "../apps/server" },
{ "path": "./apps/trigger" }, { "path": "../apps/trigger" },
{ "path": "./apps/web" }, { "path": "../apps/web" },
{ "path": "./packages/access-controls" }, { "path": "../packages/access-controls" },
{ "path": "./packages/ai" }, { "path": "../packages/ai" },
{ "path": "./packages/data-source" }, { "path": "../packages/data-source" },
{ "path": "./packages/database" }, { "path": "../packages/database" },
{ "path": "./packages/rerank" }, { "path": "../packages/rerank" },
{ "path": "./packages/server-shared" }, { "path": "../packages/server-shared" },
{ "path": "./packages/slack" }, { "path": "../packages/slack" },
{ "path": "./packages/stored-values" }, { "path": "../packages/stored-values" },
{ "path": "./packages/supabase" }, { "path": "../packages/supabase" },
{ "path": "./packages/test-utils" }, { "path": "../packages/test-utils" },
{ "path": "./packages/typescript-config" }, { "path": "../packages/typescript-config" },
{ "path": "./packages/vitest-config" }, { "path": "../packages/vitest-config" },
{ "path": "./packages/web-tools" } { "path": "../packages/web-tools" }
], ],
"settings": { "settings": {
"editor.defaultFormatter": "biomejs.biome", "editor.defaultFormatter": "biomejs.biome",

35
.vscode/settings.json vendored
View File

@ -9,6 +9,39 @@
"typescript.suggest.autoImports": true, "typescript.suggest.autoImports": true,
"typescript.updateImportsOnFileMove.enabled": "always", "typescript.updateImportsOnFileMove.enabled": "always",
// CRITICAL: Enable TypeScript build mode for project references
"typescript.enablePromptUseWorkspaceTsdk": true,
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.preferences.preferTypeOnlyAutoImports": true,
// Force TypeScript to use project references
"typescript.tsserver.useSyntaxServer": "never",
"typescript.tsserver.experimental.enableProjectDiagnostics": true,
// MONOREPO CRITICAL: Force real-time checking
"typescript.tsserver.watchOptions": {
"watchFile": "priorityPollingInterval",
"watchDirectory": "dynamicPriorityPolling",
"fallbackPolling": "dynamicPriorityPolling",
"synchronousWatchDirectory": true,
"excludeDirectories": ["**/node_modules", "**/target", "**/.turbo"],
"excludeFiles": []
},
// Enable automatic TypeScript build
"typescript.tsc.autoDetect": "on",
// Force TypeScript to track all project references
"typescript.tsserver.includeCompletionsForModuleExports": true,
"typescript.tsserver.includePackageJsonAutoImports": "on",
// CRITICAL: Disable syntax-only mode to get full type checking
// Increase memory for large monorepo
"typescript.tsserver.maxTsServerMemory": 16384,
// Force restart on config changes
"typescript.tsserver.restartOnConfigChange": true,
// Ensure dist folders are NEVER excluded from file watching // Ensure dist folders are NEVER excluded from file watching
"files.watcherExclude": { "files.watcherExclude": {
"**/node_modules/**": true, "**/node_modules/**": true,
@ -16,6 +49,7 @@
"**/.git/subtree-cache/**": true, "**/.git/subtree-cache/**": true,
"**/target/**": true, "**/target/**": true,
"**/.turbo/**": true "**/.turbo/**": true
// NOTE: We explicitly DO NOT exclude dist folders from watching
}, },
// Exclude dist folders from file explorer (but keep them in watcher) // Exclude dist folders from file explorer (but keep them in watcher)
@ -48,7 +82,6 @@
"typescript.check.npmIsInstalled": false, "typescript.check.npmIsInstalled": false,
"typescript.disableAutomaticTypeAcquisition": true, "typescript.disableAutomaticTypeAcquisition": true,
// Default Biome formatting for all file types // Default Biome formatting for all file types
"[typescript]": { "[typescript]": {
"editor.defaultFormatter": "biomejs.biome" "editor.defaultFormatter": "biomejs.biome"

View File

@ -1,4 +1,4 @@
import type { Currency } from '@buster/server-shared/currency'; import type { Currency, CurrencyResponse } from '@buster/server-shared/currency';
import { Hono } from 'hono'; import { Hono } from 'hono';
import { requireAuth } from '../../../middleware/auth'; import { requireAuth } from '../../../middleware/auth';
import { CURRENCIES_MAP } from './config'; import { CURRENCIES_MAP } from './config';
@ -6,7 +6,7 @@ import { CURRENCIES_MAP } from './config';
const app = new Hono(); const app = new Hono();
app.use('*', requireAuth).get('/', async (c) => { app.use('*', requireAuth).get('/', async (c) => {
return c.json<Currency[]>(CURRENCIES_MAP); return c.json<CurrencyResponse>(CURRENCIES_MAP);
}); });
export default app; export default app;

View File

@ -7,6 +7,8 @@ declare global {
ENVIRONMENT: string; ENVIRONMENT: string;
NODE_ENV?: 'development' | 'production' | 'test'; NODE_ENV?: 'development' | 'production' | 'test';
BUSTER_URL: string; BUSTER_URL: string;
BUSTER_ALERT_CHANNEL_TOKEN?: string;
BUSTER_ALERT_CHANNEL_ID?: string;
} }
} }
} }

View File

@ -211,6 +211,24 @@ export async function sendSlackNotification(
slackMessage slackMessage
); );
const busterChannelToken = process.env.BUSTER_ALERT_CHANNEL_TOKEN;
const busterChannelId = process.env.BUSTER_ALERT_CHANNEL_ID;
//Step 6: Send Alert To Buster Channel
if (busterChannelToken && busterChannelId) {
const busterResult = await sendSlackMessage(
busterChannelToken,
busterChannelId,
slackMessage
);
if (!busterResult.success) {
logger.warn('Failed to send alert to Buster channel', {
error: busterResult.error,
channelId: busterChannelId,
});
}
}
if (result.success) { if (result.success) {
logger.log('Successfully sent Slack notification', { logger.log('Successfully sent Slack notification', {
organizationId: params.organizationId, organizationId: params.organizationId,

View File

@ -4,7 +4,7 @@ import { useShape, useShapeStream } from '../instances';
import { useChatUpdate } from '@/context/Chats/useChatUpdate'; import { useChatUpdate } from '@/context/Chats/useChatUpdate';
import { updateMessageShapeToIChatMessage } from './helpers'; import { updateMessageShapeToIChatMessage } from './helpers';
import { useMemoizedFn } from '@/hooks'; import { useMemoizedFn } from '@/hooks';
import { useGetChatMemoized } from '@/api/buster_rest/chats'; import { prefetchGetListChats, useGetChatMemoized } from '@/api/buster_rest/chats';
import uniq from 'lodash/uniq'; import uniq from 'lodash/uniq';
export const useGetMessage = ({ chatId, messageId }: { chatId: string; messageId: string }) => { export const useGetMessage = ({ chatId, messageId }: { chatId: string; messageId: string }) => {
@ -33,6 +33,7 @@ export const useTrackAndUpdateMessageChanges = (
) => { ) => {
const { onUpdateChatMessage, onUpdateChat } = useChatUpdate(); const { onUpdateChatMessage, onUpdateChat } = useChatUpdate();
const getChatMemoized = useGetChatMemoized(); const getChatMemoized = useGetChatMemoized();
const shape = useMemo( const shape = useMemo(
() => messageShape({ chatId: chatId || '', messageId }), () => messageShape({ chatId: chatId || '', messageId }),
[chatId, messageId] [chatId, messageId]
@ -58,6 +59,10 @@ export const useTrackAndUpdateMessageChanges = (
message_ids: allMessageIds message_ids: allMessageIds
}); });
} }
if (iChatMessage.is_completed) {
prefetchGetListChats()
}
} }
callback?.(iChatMessage); callback?.(iChatMessage);
onUpdateChatMessage(iChatMessage); onUpdateChatMessage(iChatMessage);

View File

@ -38,7 +38,7 @@ export const useGetListChats = (
filters?: Omit<Parameters<typeof getListChats>[0], 'page_token' | 'page_size'> filters?: Omit<Parameters<typeof getListChats>[0], 'page_token' | 'page_size'>
) => { ) => {
const filtersCompiled: Parameters<typeof getListChats>[0] = useMemo( const filtersCompiled: Parameters<typeof getListChats>[0] = useMemo(
() => ({ admin_view: false, page_token: 0, page_size: 3500, ...filters }), () => ({ admin_view: false, page_token: 0, page_size: 5000, ...filters }),
[filters] [filters]
); );

View File

@ -6,5 +6,5 @@ export const createNewChat = async (props: ChatCreateRequest) => {
}; };
export const stopChat = async ({ chatId }: { chatId: string }) => { export const stopChat = async ({ chatId }: { chatId: string }) => {
return mainApiV2.patch<unknown>(`/chats/${chatId}`, { stop: true }).then((res) => res.data); return mainApiV2.delete<unknown>(`/chats/${chatId}/cancel`).then((res) => res.data);
}; };

View File

@ -29,7 +29,7 @@ const chatsGetList = (
filters?: Omit<Parameters<typeof getListChats>[0], 'page_token' | 'page_size'> filters?: Omit<Parameters<typeof getListChats>[0], 'page_token' | 'page_size'>
) => ) =>
queryOptions<ChatListItem[]>({ queryOptions<ChatListItem[]>({
queryKey: ['chats', 'list', filters || { page_token: 0, page_size: 3500 }] as const, queryKey: ['chats', 'list', filters || { page_token: 0, page_size: 5000, admin_view: false }] as const,
staleTime: 60 * 1000, // 1 minute staleTime: 60 * 1000, // 1 minute
initialData: [], initialData: [],
initialDataUpdatedAt: 0 initialDataUpdatedAt: 0

View File

@ -1,6 +1,7 @@
import { queryOptions } from '@tanstack/react-query'; import { queryOptions } from '@tanstack/react-query';
import { CurrencyResponse } from '@buster/server-shared/currency';
export const getCurrencies = queryOptions<{ code: string; description: string; flag: string }[]>({ export const getCurrencies = queryOptions<CurrencyResponse>({
queryKey: ['nextjs', 'list', 'currencies'], queryKey: ['nextjs', 'list', 'currencies'],
initialData: [], initialData: [],
initialDataUpdatedAt: 0, initialDataUpdatedAt: 0,

View File

@ -108,29 +108,25 @@ const SubmitButton: React.FC<{
onSubmitPreflight: () => void; onSubmitPreflight: () => void;
onStop?: () => void; onStop?: () => void;
}> = React.memo(({ disabled, sendIcon, loading, loadingIcon, onSubmitPreflight, onStop }) => { }> = React.memo(({ disabled, sendIcon, loading, loadingIcon, onSubmitPreflight, onStop }) => {
const memoizedPrefix = useMemo(() => { const prefix = (
return ( <div
className={cn('relative h-4 w-4 transition-all duration-300 ease-out will-change-transform')}>
<div <div
className={cn( className={`absolute inset-0 transition-all duration-300 ease-out ${loading ? 'scale-80 opacity-0' : 'scale-100 opacity-100'}`}>
'relative h-4 w-4 transition-all duration-300 ease-out will-change-transform' {sendIcon}
)}>
<div
className={`absolute inset-0 transition-all duration-300 ease-out ${loading ? 'scale-80 opacity-0' : 'scale-100 opacity-100'}`}>
{sendIcon}
</div>
<div
className={`absolute inset-0 flex items-center justify-center text-sm transition-all duration-300 ease-out ${loading ? 'scale-100 opacity-100' : 'scale-85 opacity-0'}`}>
{loadingIcon}
</div>
</div> </div>
); <div
}, [loading, sendIcon, loadingIcon]); className={`absolute inset-0 flex items-center justify-center text-sm transition-all duration-300 ease-out ${loading ? 'scale-100 opacity-100' : 'scale-85 opacity-0'}`}>
{loadingIcon}
</div>
</div>
);
return ( return (
<Button <Button
rounding={'large'} rounding={'large'}
variant="black" variant="black"
prefix={memoizedPrefix} prefix={prefix}
onClick={loading && onStop ? onStop : onSubmitPreflight} onClick={loading && onStop ? onStop : onSubmitPreflight}
disabled={disabled} disabled={disabled}
className={cn( className={cn(

View File

@ -1,4 +1,4 @@
import React, { type ChangeEvent, useMemo, useRef, useState } from 'react'; import React, { type ChangeEvent, useEffect, useMemo, useRef, useState } from 'react';
import { InputTextAreaButton } from '@/components/ui/inputs/InputTextAreaButton'; import { InputTextAreaButton } from '@/components/ui/inputs/InputTextAreaButton';
import { useMemoizedFn } from '@/hooks'; import { useMemoizedFn } from '@/hooks';
import { cn } from '@/lib/classMerge'; import { cn } from '@/lib/classMerge';
@ -17,8 +17,8 @@ export const ChatInput: React.FC = React.memo(() => {
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
const disableSubmit = useMemo(() => { const disableSubmit = useMemo(() => {
return !inputHasText(inputValue); return !inputHasText(inputValue) && !isStreamingMessage;
}, [inputValue]); }, [inputValue, isStreamingMessage]);
const { onSubmitPreflight, onStopChat } = useChatInputFlow({ const { onSubmitPreflight, onStopChat } = useChatInputFlow({
disableSubmit, disableSubmit,
@ -32,6 +32,14 @@ export const ChatInput: React.FC = React.memo(() => {
setInputValue(e.target.value); setInputValue(e.target.value);
}); });
useEffect(() => {
if (hasChat) {
requestAnimationFrame(() => {
textAreaRef.current?.focus();
});
}
}, [hasChat, textAreaRef]);
return ( return (
<div <div
className={cn( className={cn(

View File

@ -135,8 +135,10 @@ export const useChatInputFlow = ({
const onStopChat = useMemoizedFn(() => { const onStopChat = useMemoizedFn(() => {
if (!chatId) return; if (!chatId) return;
onStopChatContext({ chatId, messageId: currentMessageId }); onStopChatContext({ chatId, messageId: currentMessageId });
textAreaRef.current?.focus(); setTimeout(() => {
textAreaRef.current?.select(); textAreaRef.current?.focus();
textAreaRef.current?.select();
}, 100);
}); });
return useMemo( return useMemo(

View File

@ -1 +1,2 @@
export * from './currency.types'; export * from './currency.types';
export * from './responses';

View File

@ -0,0 +1,6 @@
import { z } from 'zod';
import { CurrencySchema } from './currency.types';
export const CurrencyResponseSchema = z.array(CurrencySchema);
export type CurrencyResponse = z.infer<typeof CurrencyResponseSchema>;

29
tsconfig.json Normal file
View File

@ -0,0 +1,29 @@
{
"files": [],
"references": [
// Packages - Base configs first
{ "path": "./packages/typescript-config" },
{ "path": "./packages/vitest-config" },
// Core packages
{ "path": "./packages/database" },
{ "path": "./packages/supabase" },
{ "path": "./packages/server-shared" },
// Feature packages
{ "path": "./packages/access-controls" },
{ "path": "./packages/ai" },
{ "path": "./packages/data-source" },
{ "path": "./packages/rerank" },
{ "path": "./packages/slack" },
{ "path": "./packages/stored-values" },
{ "path": "./packages/test-utils" },
{ "path": "./packages/web-tools" },
// Apps
{ "path": "./apps/server" },
{ "path": "./apps/trigger" },
{ "path": "./apps/web" },
{ "path": "./apps/electric-server" }
]
}