diff --git a/src/app/api/envConfig/route.ts b/src/app/api/envConfig/route.ts index fa78e6cb..4468de78 100644 --- a/src/app/api/envConfig/route.ts +++ b/src/app/api/envConfig/route.ts @@ -17,6 +17,8 @@ export async function GET() { DEPLOYMENT_TYPE: process.env.IL_UI_DEPLOYMENT || '', ENABLE_DEV_MODE: process.env.IL_ENABLE_DEV_MODE || 'false', ENABLE_DOC_CONVERSION: process.env.IL_ENABLE_DOC_CONVERSION || 'false', + ENABLE_SKILLS_FEATURES: process.env.IL_ENABLE_SKILLS_FEATURES || '', + ENABLE_PLAYGROUND_FEATURES: process.env.IL_ENABLE_PLAYGROUND_FEATURES || '', EXPERIMENTAL_FEATURES: process.env.NEXT_PUBLIC_EXPERIMENTAL_FEATURES || '', TAXONOMY_ROOT_DIR: process.env.NEXT_PUBLIC_TAXONOMY_ROOT_DIR || '', TAXONOMY_KNOWLEDGE_DOCUMENT_REPO: diff --git a/src/app/edit-submission/knowledge/github/[...slug]/page.tsx b/src/app/contribute/knowledge/github/[...slug]/page.tsx similarity index 89% rename from src/app/edit-submission/knowledge/github/[...slug]/page.tsx rename to src/app/contribute/knowledge/github/[...slug]/page.tsx index a16412f2..aea515c7 100644 --- a/src/app/edit-submission/knowledge/github/[...slug]/page.tsx +++ b/src/app/contribute/knowledge/github/[...slug]/page.tsx @@ -1,4 +1,4 @@ -// src/app/edit-submission/knowledge/github/[...slug]/page.tsx +// src/app/contribute/knowledge/github/[...slug]/page.tsx import * as React from 'react'; import { AppLayout } from '@/components/AppLayout'; import EditKnowledge from '@/components/Contribute/EditKnowledge/github/EditKnowledge'; diff --git a/src/app/contribute/knowledge/native/[...slug]/page.tsx b/src/app/contribute/knowledge/native/[...slug]/page.tsx new file mode 100644 index 00000000..225d06ac --- /dev/null +++ b/src/app/contribute/knowledge/native/[...slug]/page.tsx @@ -0,0 +1,20 @@ +// src/app/contribute/knowledge/github/[...slug]/page.tsx +import * as React from 'react'; +import { AppLayout } from '@/components/AppLayout'; +import EditKnowledge from '@/components/Contribute/EditKnowledge/native/EditKnowledge'; + +type PageProps = { + params: Promise<{ slug: string[] }>; +}; + +const EditKnowledgePage = async ({ params }: PageProps) => { + const resolvedParams = await params; + + return ( + + + + ); +}; + +export default EditKnowledgePage; diff --git a/src/app/contribute/knowledge/page.tsx b/src/app/contribute/knowledge/page.tsx index f86119a5..3ac277ac 100644 --- a/src/app/contribute/knowledge/page.tsx +++ b/src/app/contribute/knowledge/page.tsx @@ -1,39 +1,23 @@ // src/app/contribute/knowledge/page.tsx 'use client'; -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { Flex, Spinner } from '@patternfly/react-core'; import { t_global_spacer_xl as XlSpacerSize } from '@patternfly/react-tokens'; import { AppLayout } from '@/components/AppLayout'; import KnowledgeFormGithub from '@/components/Contribute/Knowledge/Github'; import KnowledgeFormNative from '@/components/Contribute/Knowledge/Native'; +import { useEnvConfig } from '@/context/EnvConfigContext'; const KnowledgeFormPage: React.FunctionComponent = () => { - const [deploymentType, setDeploymentType] = useState(); - const [loaded, setLoaded] = useState(); - - useEffect(() => { - let canceled = false; - - const getEnvVariables = async () => { - const res = await fetch('/api/envConfig'); - const envConfig = await res.json(); - if (!canceled) { - setDeploymentType(envConfig.DEPLOYMENT_TYPE); - setLoaded(true); - } - }; - - getEnvVariables(); - - return () => { - canceled = true; - }; - }, []); + const { + loaded, + envConfig: { isGithubMode } + } = useEnvConfig(); return ( {loaded ? ( - <>{deploymentType === 'native' ? : } + <>{!isGithubMode ? : } ) : ( diff --git a/src/app/contribute/skill/github/[...slug]/page.tsx b/src/app/contribute/skill/github/[...slug]/page.tsx new file mode 100644 index 00000000..c5170401 --- /dev/null +++ b/src/app/contribute/skill/github/[...slug]/page.tsx @@ -0,0 +1,20 @@ +// src/app/contribute/knowledge/github/[...slug]/page.tsx +import * as React from 'react'; +import { AppLayout } from '@/components/AppLayout'; +import EditSkill from '@/components/Contribute/EditSkill/github/EditSkill'; + +type PageProps = { + params: Promise<{ slug: string[] }>; +}; + +const EditKnowledgePage = async ({ params }: PageProps) => { + const resolvedParams = await params; + + return ( + + + + ); +}; + +export default EditKnowledgePage; diff --git a/src/app/contribute/skill/native/[...slug]/page.tsx b/src/app/contribute/skill/native/[...slug]/page.tsx new file mode 100644 index 00000000..a9c55be0 --- /dev/null +++ b/src/app/contribute/skill/native/[...slug]/page.tsx @@ -0,0 +1,20 @@ +// src/app/contribute/knowledge/github/[...slug]/page.tsx +import * as React from 'react'; +import { AppLayout } from '@/components/AppLayout'; +import EditSkill from '@/components/Contribute/EditSkill/native/EditSkill'; + +type PageProps = { + params: Promise<{ slug: string[] }>; +}; + +const EditKnowledgePage = async ({ params }: PageProps) => { + const resolvedParams = await params; + + return ( + + + + ); +}; + +export default EditKnowledgePage; diff --git a/src/app/contribute/skill/page.tsx b/src/app/contribute/skill/page.tsx index 4ea7a5dd..ee8aab9f 100644 --- a/src/app/contribute/skill/page.tsx +++ b/src/app/contribute/skill/page.tsx @@ -1,38 +1,27 @@ // src/app/contribute/skill/page.tsx 'use client'; +import React from 'react'; +import { Flex, Spinner } from '@patternfly/react-core'; +import { t_global_spacer_xl as XlSpacerSize } from '@patternfly/react-tokens/dist/esm/t_global_spacer_xl'; import { AppLayout } from '@/components/AppLayout'; import { SkillFormGithub } from '@/components/Contribute/Skill/Github/index'; import { SkillFormNative } from '@/components/Contribute/Skill/Native/index'; -import { t_global_spacer_xl as XlSpacerSize } from '@patternfly/react-tokens'; -import { Flex, Spinner } from '@patternfly/react-core'; -import { useEffect, useState } from 'react'; +import { useEnvConfig } from '@/context/EnvConfigContext'; const SkillFormPage: React.FunctionComponent = () => { - const [deploymentType, setDeploymentType] = useState(); - const [loaded, setLoaded] = useState(); - - useEffect(() => { - let canceled = false; - - const getEnvVariables = async () => { - const res = await fetch('/api/envConfig'); - const envConfig = await res.json(); - if (!canceled) { - setDeploymentType(envConfig.DEPLOYMENT_TYPE); - setLoaded(true); - } - }; - - getEnvVariables(); + const { + loaded, + envConfig: { isGithubMode } + } = useEnvConfig(); - return () => { - canceled = true; - }; - }, []); return ( {loaded ? ( - <>{deploymentType === 'native' ? : } + !isGithubMode ? ( + + ) : ( + + ) ) : ( diff --git a/src/app/dashboard/knowledge/github/[...slug]/page.tsx b/src/app/dashboard/knowledge/github/[...slug]/page.tsx new file mode 100644 index 00000000..c7014966 --- /dev/null +++ b/src/app/dashboard/knowledge/github/[...slug]/page.tsx @@ -0,0 +1,20 @@ +// src/app/dashboard/knowledge/github/[...slug]/page.tsx +import * as React from 'react'; +import { AppLayout } from '@/components/AppLayout'; +import EditKnowledge from '@/components/Contribute/EditKnowledge/github/EditKnowledge'; + +type PageProps = { + params: Promise<{ slug: string[] }>; +}; + +const EditKnowledgePage = async ({ params }: PageProps) => { + const resolvedParams = await params; + + return ( + + + + ); +}; + +export default EditKnowledgePage; diff --git a/src/app/edit-submission/knowledge/native/[...slug]/page.tsx b/src/app/dashboard/knowledge/native/[...slug]/page.tsx similarity index 90% rename from src/app/edit-submission/knowledge/native/[...slug]/page.tsx rename to src/app/dashboard/knowledge/native/[...slug]/page.tsx index 745dedb9..076809ce 100644 --- a/src/app/edit-submission/knowledge/native/[...slug]/page.tsx +++ b/src/app/dashboard/knowledge/native/[...slug]/page.tsx @@ -1,4 +1,4 @@ -// src/app/edit-submission/knowledge/native/[...slug]/page.tsx +// src/app/dashboard/knowledge/native/[...slug]/page.tsx import { AppLayout } from '@/components/AppLayout'; import EditKnowledgeNative from '@/components/Contribute/EditKnowledge/native/EditKnowledge'; import * as React from 'react'; diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index ee28b27a..b9cf4a6c 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -1,25 +1,36 @@ // src/app/dashboard/page.tsx 'use client'; +import React from 'react'; +import { Flex, Spinner } from '@patternfly/react-core'; +import { t_global_spacer_xl as XlSpacerSize } from '@patternfly/react-tokens/dist/esm/t_global_spacer_xl'; import '@patternfly/react-core/dist/styles/base.css'; import { AppLayout } from '@/components/AppLayout'; import { DashboardGithub } from '@/components/Dashboard/Github/dashboard'; import { DashboardNative } from '@/components/Dashboard/Native/dashboard'; -import { useEffect, useState } from 'react'; +import { useEnvConfig } from '@/context/EnvConfigContext'; const Home: React.FunctionComponent = () => { - const [deploymentType, setDeploymentType] = useState(); + const { + loaded, + envConfig: { isGithubMode } + } = useEnvConfig(); - useEffect(() => { - const getEnvVariables = async () => { - const res = await fetch('/api/envConfig'); - const envConfig = await res.json(); - setDeploymentType(envConfig.DEPLOYMENT_TYPE); - }; - getEnvVariables(); - }, []); - - return {deploymentType === 'native' ? : }; + return ( + + {loaded ? ( + !isGithubMode ? ( + + ) : ( + + ) + ) : ( + + + + )} + + ); }; export default Home; diff --git a/src/app/edit-submission/skill/github/[...slug]/page.tsx b/src/app/dashboard/skill/github/[...slug]/page.tsx similarity index 92% rename from src/app/edit-submission/skill/github/[...slug]/page.tsx rename to src/app/dashboard/skill/github/[...slug]/page.tsx index 015c38f7..78cf3a6b 100644 --- a/src/app/edit-submission/skill/github/[...slug]/page.tsx +++ b/src/app/dashboard/skill/github/[...slug]/page.tsx @@ -1,4 +1,4 @@ -// src/app/edit-submission/skill/[id]/page.tsx +// src/app/dashboard/skill/[id]/page.tsx import * as React from 'react'; import { AppLayout } from '@/components/AppLayout'; import EditSkill from '@/components/Contribute/EditSkill/github/EditSkill'; diff --git a/src/app/edit-submission/skill/native/[...slug]/page.tsx b/src/app/dashboard/skill/native/[...slug]/page.tsx similarity index 92% rename from src/app/edit-submission/skill/native/[...slug]/page.tsx rename to src/app/dashboard/skill/native/[...slug]/page.tsx index 1b79309e..a52d5ff4 100644 --- a/src/app/edit-submission/skill/native/[...slug]/page.tsx +++ b/src/app/dashboard/skill/native/[...slug]/page.tsx @@ -1,4 +1,4 @@ -// src/app/edit-submission/skill/[id]/page.tsx +// src/app/dashboard/skill/[id]/page.tsx import * as React from 'react'; import { AppLayout } from '@/components/AppLayout'; import EditSkillNative from '@/components/Contribute/EditSkill/native/EditSkill'; diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index f238c5eb..0fba3caa 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -1,41 +1,28 @@ // src/app/login/page.tsx 'use client'; -import React, { useState, useEffect } from 'react'; +import React from 'react'; import { Spinner } from '@patternfly/react-core'; import NativeLogin from '@/app/login/nativelogin'; import GithubLogin from '@/app/login/githublogin'; import './login-page.css'; +import { useEnvConfig } from '@/context/EnvConfigContext'; const Login: React.FunctionComponent = () => { - const [deploymentType, setDeploymentType] = useState(); - const [isLoading, setIsLoading] = useState(true); - - useEffect(() => { - const chooseLoginPage = async () => { - try { - const res = await fetch('/api/envConfig'); - const envConfig = await res.json(); - setDeploymentType(envConfig.DEPLOYMENT_TYPE); - } catch (error) { - console.error('Error fetching environment config:', error); - setDeploymentType('github'); - } finally { - setIsLoading(false); - } - }; - chooseLoginPage(); - }, []); + const { + loaded, + envConfig: { isGithubMode } + } = useEnvConfig(); return (
- {isLoading ? ( + {!loaded ? (
Loading...
) : ( -
{deploymentType === 'native' ? : }
+
{!isGithubMode ? : }
)}
); diff --git a/src/app/page.tsx b/src/app/page.tsx index 8324beef..6588d295 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,23 +1,21 @@ // src/app/page.tsx 'use client'; -import { DashboardGithub } from '@/components/Dashboard/Github/dashboard'; -import { GithubAccessPopup } from '@/components/GithubAccessPopup'; import * as React from 'react'; -import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { GithubAccessPopup } from '@/components/GithubAccessPopup'; import { AppLayout } from '../components/AppLayout'; const HomePage: React.FC = () => { - const [isWarningConditionAccepted, setIsWarningConditionAccepted] = useState(false); + const router = useRouter(); - const handleWarningConditionAccepted = React.useCallback(() => { - setIsWarningConditionAccepted(true); - }, []); + const handleWarningConditionAccepted = () => { + router.push('/dashboard'); + }; return ( - {isWarningConditionAccepted && } ); }; diff --git a/src/app/playground/chat/page.tsx b/src/app/playground/chat/page.tsx index 99315bdf..695d169f 100644 --- a/src/app/playground/chat/page.tsx +++ b/src/app/playground/chat/page.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { useRouter } from 'next/navigation'; -import { Breadcrumb, BreadcrumbItem, Button, PageBreadcrumb, PageSection, Content } from '@patternfly/react-core/'; +import { Button, PageSection, Content } from '@patternfly/react-core/'; import { t_global_spacer_sm as SmSpacerSize } from '@patternfly/react-tokens'; import { AppLayout } from '@/components/AppLayout'; import ModelsContextProvider from '../../../components/Chat/ModelsContext'; @@ -16,14 +16,8 @@ const ChatPage: React.FC = () => { return ( - - - Dashboard - Chat - - - Chat with a Model + Chat with a model {`Before you start adding new skills and knowledge to your model, you can check its baseline performance by chatting with a language model that's hosted on your cloud. Choose a supported model diff --git a/src/components/AppLayout.tsx b/src/components/AppLayout.tsx index 6a7c6f17..831317db 100644 --- a/src/components/AppLayout.tsx +++ b/src/components/AppLayout.tsx @@ -3,11 +3,8 @@ import * as React from 'react'; import { usePathname, useRouter } from 'next/navigation'; -import HelpDropdown from './HelpDropdown/HelpDropdown'; import Link from 'next/link'; -import UserMenu from './UserMenu/UserMenu'; import { useSession } from 'next-auth/react'; -import { useState } from 'react'; import { Bullseye, Spinner, @@ -31,7 +28,11 @@ import { } from '@patternfly/react-core'; import { BarsIcon } from '@patternfly/react-icons'; import ThemePreference from '@/components/ThemePreference/ThemePreference'; +import HelpDropdown from './HelpDropdown/HelpDropdown'; +import UserMenu from './UserMenu/UserMenu'; + import '../styles/globals.scss'; +import { useFeatureFlags } from '@/context/FeatureFlagsContext'; interface IAppLayout { children: React.ReactNode; @@ -46,21 +47,14 @@ type Route = { const AppLayout: React.FunctionComponent = ({ children, className }) => { const { data: session, status } = useSession(); - const [isExperimentalEnabled, setExperimental] = useState(false); + const { + loaded, + featureFlags: { skillFeaturesEnabled, playgroundFeaturesEnabled, experimentalFeaturesEnabled } + } = useFeatureFlags(); const router = useRouter(); const pathname = usePathname(); - React.useEffect(() => { - // Fetch the experimental feature flag - const fetchExperimentalFeature = async () => { - const res = await fetch('/api/envConfig'); - const envConfig = await res.json(); - setExperimental(envConfig.EXPERIMENTAL_FEATURES === 'true'); - }; - fetchExperimentalFeature(); - }, []); - React.useEffect(() => { if (status === 'loading') return; // Do nothing while loading if (!session && pathname !== '/login') { @@ -68,7 +62,7 @@ const AppLayout: React.FunctionComponent = ({ children, className }) } }, [session, status, pathname, router]); - if (status === 'loading') { + if (!loaded || status === 'loading') { return ( @@ -80,40 +74,34 @@ const AppLayout: React.FunctionComponent = ({ children, className }) return null; // Return nothing if not authenticated to avoid flicker } - //const isExperimentalEnabled = process.env.NEXT_PUBLIC_EXPERIMENTAL_FEATURES === 'true'; - - // Only log if experimental features are enabled - if (isExperimentalEnabled) { - console.log('Is Experimental Enabled:', isExperimentalEnabled); - console.log('Environment Variable:', process.env.NEXT_PUBLIC_EXPERIMENTAL_FEATURES); - } - const routes = [ { path: '/dashboard', label: 'Dashboard' }, - { - path: '/contribute', - label: 'Contribute', - children: [ - { path: '/contribute/skill', label: 'Skill' }, - { path: '/contribute/knowledge', label: 'Knowledge' } - ] - }, - { - path: '/playground', - label: 'Playground', - children: [ - { path: '/playground/chat', label: 'Chat' }, - { path: '/playground/endpoints', label: 'Custom model endpoints' } - ] - }, - isExperimentalEnabled && { - path: '/experimental', - label: 'Experimental features', - children: [ - { path: '/experimental/fine-tune/', label: 'Fine-tuning' }, - { path: '/experimental/chat-eval/', label: 'Model chat eval' } - ] - } + { path: '/contribute/knowledge', label: 'Contribute knowledge' }, + ...(skillFeaturesEnabled ? [{ path: '/contribute/skill', label: 'Contribute skills' }] : []), + ...(playgroundFeaturesEnabled + ? [ + { + path: '/playground', + label: 'Playground', + children: [ + { path: '/playground/chat', label: 'Chat with a model' }, + { path: '/playground/endpoints', label: 'Custom model endpoints' } + ] + } + ] + : []), + ...(experimentalFeaturesEnabled + ? [ + { + path: '/experimental', + label: 'Experimental features', + children: [ + { path: '/experimental/fine-tune/', label: 'Fine-tuning' }, + { path: '/experimental/chat-eval/', label: 'Model chat eval' } + ] + } + ] + : []) ].filter(Boolean) as Route[]; const Header = ( @@ -151,7 +139,7 @@ const AppLayout: React.FunctionComponent = ({ children, className }) ); const renderNavItem = (route: Route, index: number) => ( - + {route.label} ); @@ -160,7 +148,7 @@ const AppLayout: React.FunctionComponent = ({ children, className }) child.path === pathname)} + isActive={route.path.startsWith(pathname) || route.children?.some((child) => pathname.startsWith(child.path))} isExpanded > {route.children?.map((child, idx) => renderNavItem(child, idx))} diff --git a/src/components/Chat/ModelsContext.tsx b/src/components/Chat/ModelsContext.tsx index 63f2faa0..cfd9c4fe 100644 --- a/src/components/Chat/ModelsContext.tsx +++ b/src/components/Chat/ModelsContext.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; import { Endpoint, Model } from '@/types'; +import { useEnvConfig } from '@/context/EnvConfigContext'; const isValidModel = (model: Model) => model.name && model.apiURL && model.modelName; @@ -20,17 +21,25 @@ type ModelsProviderProps = { const ModelsContextProvider: React.FC = ({ children }) => { const [models, setModels] = React.useState([]); + const { loaded, envConfig } = useEnvConfig(); React.useEffect(() => { - let canceled = false; - - const fetchModels = async (): Promise => { - const response = await fetch('/api/envConfig'); - const envConfig = await response.json(); - + if (loaded) { const defaultModels: Model[] = [ - { isDefault: true, name: 'Granite-7b', apiURL: envConfig.GRANITE_API, modelName: envConfig.GRANITE_MODEL_NAME, enabled: true }, - { isDefault: true, name: 'Merlinite-7b', apiURL: envConfig.MERLINITE_API, modelName: envConfig.MERLINITE_MODEL_NAME, enabled: true } + { + isDefault: true, + name: 'Granite-7b', + apiURL: envConfig.graniteApi, + modelName: envConfig.graniteModelName, + enabled: true + }, + { + isDefault: true, + name: 'Merlinite-7b', + apiURL: envConfig.merliniteApi, + modelName: envConfig.merliniteModelName, + enabled: true + } ]; const storedEndpoints = localStorage.getItem('endpoints'); @@ -45,19 +54,9 @@ const ModelsContextProvider: React.FC = ({ children }) => { })) : []; - return [...defaultModels.filter(isValidModel), ...customModels.filter(isValidModel)]; - }; - - fetchModels().then((models) => { - if (!canceled) { - setModels(models); - } - }); - - return () => { - canceled = true; - }; - }, []); + setModels([...defaultModels.filter(isValidModel), ...customModels.filter(isValidModel)]); + } + }, [loaded, envConfig]); const contextValue = React.useMemo( () => ({ diff --git a/src/components/ClientProviders.tsx b/src/components/ClientProviders.tsx index e548aeae..0f321756 100644 --- a/src/components/ClientProviders.tsx +++ b/src/components/ClientProviders.tsx @@ -3,7 +3,9 @@ import { ReactNode } from 'react'; import { SessionProvider } from 'next-auth/react'; -import { ThemeProvider } from '../context/ThemeContext'; +import { ThemeProvider } from '@/context/ThemeContext'; +import { EnvConfigProvider } from '@/context/EnvConfigContext'; +import { FeatureFlagsProvider } from '@/context/FeatureFlagsContext'; interface ClientProviderProps { children: ReactNode; @@ -12,7 +14,11 @@ interface ClientProviderProps { const ClientProvider = ({ children }: ClientProviderProps) => { return ( - {children} + + + {children} + + ); }; diff --git a/src/components/Contribute/AutoFill.ts b/src/components/Contribute/AutoFill.ts index 1e39acf3..66388d6f 100644 --- a/src/components/Contribute/AutoFill.ts +++ b/src/components/Contribute/AutoFill.ts @@ -353,7 +353,7 @@ const skillsSeedExamples: SkillSeedExample[] = [ ]; export const autoFillSkillsFields: SkillFormData = { - branchName: `knowledge-contribution-${Date.now()}`, + branchName: `skill-contribution-${Date.now()}`, email: 'helloworld@instructlab.com', name: 'juliadenham', submissionSummary: 'Teaching a model to rhyme.', diff --git a/src/components/Contribute/ContributionWizard/ContributionWizard.tsx b/src/components/Contribute/ContributionWizard/ContributionWizard.tsx index 35bc34ce..c5c515fe 100644 --- a/src/components/Contribute/ContributionWizard/ContributionWizard.tsx +++ b/src/components/Contribute/ContributionWizard/ContributionWizard.tsx @@ -2,26 +2,14 @@ 'use client'; import React from 'react'; import { useSession } from 'next-auth/react'; +import { Button, Content, Flex, FlexItem, PageGroup, PageSection, Title, Wizard, WizardStep } from '@patternfly/react-core'; import { ContributionFormData, EditFormData } from '@/types'; import { useRouter } from 'next/navigation'; import { autoFillKnowledgeFields, autoFillSkillsFields } from '@/components/Contribute/AutoFill'; -import { - Breadcrumb, - BreadcrumbItem, - Button, - Content, - Flex, - FlexItem, - PageBreadcrumb, - PageGroup, - PageSection, - Title, - Wizard, - WizardStep -} from '@patternfly/react-core'; import { getGitHubUserInfo } from '@/utils/github'; import ContributionWizardFooter from '@/components/Contribute/ContributionWizard/ContributionWizardFooter'; import { deleteDraftData } from '@/components/Contribute/Utils/autoSaveUtils'; +import { useEnvConfig } from '@/context/EnvConfigContext'; import './contribute-page.scss'; @@ -47,6 +35,7 @@ export interface StepType { export interface Props { title: React.ReactNode; description: React.ReactNode; + breadcrumbs?: React.ReactNode; editFormData?: EditFormData; formData: ContributionFormData; setFormData: React.Dispatch>; @@ -60,6 +49,7 @@ export interface Props { export const ContributionWizard: React.FunctionComponent = ({ title, description, + breadcrumbs = null, editFormData, formData, setFormData, @@ -69,7 +59,9 @@ export const ContributionWizard: React.FunctionComponent = ({ convertToYaml, onSubmit }) => { - const [devModeEnabled, setDevModeEnabled] = React.useState(); + const { + envConfig: { isDevMode } + } = useEnvConfig(); const { data: session } = useSession(); const [githubUsername, setGithubUsername] = React.useState(''); const [submitEnabled, setSubmitEnabled] = React.useState(false); // **New State Added** @@ -90,15 +82,6 @@ export const ContributionWizard: React.FunctionComponent = ({ ); const getStepIndex = (stepId: string) => stepIds.indexOf(stepId); - React.useEffect(() => { - const getEnvVariables = async () => { - const res = await fetch('/api/envConfig'); - const envConfig = await res.json(); - setDevModeEnabled(envConfig.ENABLE_DEV_MODE === 'true'); - }; - getEnvVariables(); - }, []); - React.useEffect(() => { let canceled = false; @@ -156,12 +139,7 @@ export const ContributionWizard: React.FunctionComponent = ({ return ( - - - Contribute - {title} - - + {breadcrumbs} @@ -172,7 +150,7 @@ export const ContributionWizard: React.FunctionComponent = ({ - {devModeEnabled && ( + {isDevMode && ( diff --git a/src/components/Contribute/EditKnowledge/github/EditKnowledge.tsx b/src/components/Contribute/EditKnowledge/github/EditKnowledge.tsx index 5b3fa9f7..b688fb7a 100644 --- a/src/components/Contribute/EditKnowledge/github/EditKnowledge.tsx +++ b/src/components/Contribute/EditKnowledge/github/EditKnowledge.tsx @@ -156,7 +156,7 @@ const EditKnowledge: React.FC = ({ prNumber, } }; fetchPRData(); - }, [session?.accessToken, prNumber]); + }, [session?.accessToken, prNumber, isDraft]); const parseAttributionContent = (content: string): AttributionData => { const lines = content.split('\n'); diff --git a/src/components/Contribute/EditKnowledge/native/EditKnowledge.tsx b/src/components/Contribute/EditKnowledge/native/EditKnowledge.tsx index 9c6fe3c2..3f250b8a 100644 --- a/src/components/Contribute/EditKnowledge/native/EditKnowledge.tsx +++ b/src/components/Contribute/EditKnowledge/native/EditKnowledge.tsx @@ -161,7 +161,7 @@ const EditKnowledgeNative: React.FC = ({ bran } }; fetchBranchChanges(); - }, [branchName, session?.user?.email, session?.user?.name]); + }, [branchName, isDraft, session?.user?.email, session?.user?.name]); const parseAttributionContent = (content: string): AttributionData => { const lines = content.split('\n'); diff --git a/src/components/Contribute/EditSkill/github/EditSkill.tsx b/src/components/Contribute/EditSkill/github/EditSkill.tsx index 8b2582a6..a4364a3a 100644 --- a/src/components/Contribute/EditSkill/github/EditSkill.tsx +++ b/src/components/Contribute/EditSkill/github/EditSkill.tsx @@ -130,7 +130,7 @@ const EditSkill: React.FC = ({ prNumber, isDraft } }; fetchPRData(); - }, [session?.accessToken, prNumber]); + }, [session?.accessToken, prNumber, isDraft]); const parseAttributionContent = (content: string): AttributionData => { const lines = content.split('\n'); diff --git a/src/components/Contribute/EditSkill/native/EditSkill.tsx b/src/components/Contribute/EditSkill/native/EditSkill.tsx index 2ec311af..844bec12 100644 --- a/src/components/Contribute/EditSkill/native/EditSkill.tsx +++ b/src/components/Contribute/EditSkill/native/EditSkill.tsx @@ -134,7 +134,7 @@ const EditSkillNative: React.FC = ({ branchName, } }; fetchBranchChanges(); - }, [branchName, session?.user?.email, session?.user?.name]); + }, [branchName, isDraft, session?.user?.email, session?.user?.name]); const parseAttributionContent = (content: string): AttributionData => { const lines = content.split('\n'); diff --git a/src/components/Contribute/Knowledge/KnowledgeWizard/KnowledgeWizard.tsx b/src/components/Contribute/Knowledge/KnowledgeWizard/KnowledgeWizard.tsx index 4dc1dbf6..1955f8e1 100644 --- a/src/components/Contribute/Knowledge/KnowledgeWizard/KnowledgeWizard.tsx +++ b/src/components/Contribute/Knowledge/KnowledgeWizard/KnowledgeWizard.tsx @@ -7,7 +7,7 @@ import DocumentInformation from '@/components/Contribute/Knowledge/DocumentInfor import KnowledgeSeedExamples from '@/components/Contribute/Knowledge/KnowledgeSeedExamples/KnowledgeSeedExamples'; import { ContributionFormData, KnowledgeEditFormData, KnowledgeFormData, KnowledgeSeedExample, KnowledgeYamlData } from '@/types'; import { useRouter } from 'next/navigation'; -import { Button, ValidatedOptions } from '@patternfly/react-core'; +import { Breadcrumb, BreadcrumbItem, Button, PageBreadcrumb, ValidatedOptions } from '@patternfly/react-core'; import { ActionGroupAlertContent } from '@/components/Contribute/types'; import { UploadKnowledgeDocuments } from '@/components/Contribute/Utils/documentUtils'; import { @@ -86,39 +86,35 @@ export const KnowledgeWizard: React.FunctionComponent = ({ k const setFilePath = React.useCallback((filePath: string) => setKnowledgeFormData((prev) => ({ ...prev, filePath })), []); - async function saveKnowledgeDraft() { - // If no change in the form data and there is no existing draft present, skip storing the draft. - if (!doSaveDraft(knowledgeFormData) && !isDraftDataExist(knowledgeFormData.branchName)) return; - - await Promise.all( - knowledgeFormData.filesToUpload.map(async (file) => { - await storeDraftKnowledgeFile(knowledgeFormData.branchName, file); - }) - ); - - const draftContributionStr = JSON.stringify(knowledgeFormData, (key, value) => { - if (key === 'filesToUpload' && Array.isArray(value)) { - const files = value as File[]; - return files.map((v: File) => { - return { name: v.name }; - }); - } - return value; - }); - storeDraftData( - knowledgeFormData.branchName, - draftContributionStr, - !!knowledgeEditFormData?.isSubmitted, - knowledgeEditFormData?.oldFilesPath || '' - ); - } - useEffect(() => { const storeDraft = async () => { - await saveKnowledgeDraft(); + // If no change in the form data and there is no existing draft present, skip storing the draft. + if (!doSaveDraft(knowledgeFormData) && !isDraftDataExist(knowledgeFormData.branchName)) return; + + await Promise.all( + knowledgeFormData.filesToUpload.map(async (file) => { + await storeDraftKnowledgeFile(knowledgeFormData.branchName, file); + }) + ); + + const draftContributionStr = JSON.stringify(knowledgeFormData, (key, value) => { + if (key === 'filesToUpload' && Array.isArray(value)) { + const files = value as File[]; + return files.map((v: File) => { + return { name: v.name }; + }); + } + return value; + }); + storeDraftData( + knowledgeFormData.branchName, + draftContributionStr, + !!knowledgeEditFormData?.isSubmitted, + knowledgeEditFormData?.oldFilesPath || '' + ); }; storeDraft(); - }, [knowledgeFormData]); + }, [knowledgeEditFormData?.isSubmitted, knowledgeEditFormData?.oldFilesPath, knowledgeFormData]); const steps: StepType[] = React.useMemo(() => { const documentInformationStep = { @@ -340,7 +336,27 @@ export const KnowledgeWizard: React.FunctionComponent = ({ k return ( <> + + { + e.preventDefault(); + router.push('/contribute/knowledge'); + }} + > + Contribute knowledge + + {`Edit${knowledgeEditFormData?.isDraft ? ' draft' : ''} knowledge contribution`} + + + ) : null + } + title={ + knowledgeEditFormData?.formData ? `Edit${knowledgeEditFormData?.isDraft ? ' draft' : ''} knowledge contribution` : 'Knowledge contribution' + } description={ <> Knowledge contributions improve a model’s ability to answer questions accurately. They consist of questions and answers, and documents diff --git a/src/components/Contribute/Knowledge/UploadFile.tsx b/src/components/Contribute/Knowledge/UploadFile.tsx index af21a70e..f2ec678b 100644 --- a/src/components/Contribute/Knowledge/UploadFile.tsx +++ b/src/components/Contribute/Knowledge/UploadFile.tsx @@ -23,6 +23,7 @@ import { KnowledgeFile } from '@/types'; import UploadFromGitModal from '@/components/Contribute/Knowledge/UploadFromGitModal'; import MultiFileUploadArea from '@/components/Contribute/Knowledge/MultFileUploadArea'; import FileConversionModal from '@/components/Contribute/Knowledge/FileConversionModal'; +import { useFeatureFlags } from '@/context/FeatureFlagsContext'; interface ReadFile { fileName: string; @@ -39,13 +40,15 @@ interface UploadFileProps { } export const UploadFile: React.FunctionComponent = ({ existingFiles, setExistingFiles, filesToUpload, setFilesToUpload }) => { + const { + featureFlags: { docConversionEnabled } + } = useFeatureFlags(); const [showUploadFromGitModal, setShowUploadFromGitModal] = React.useState(); const [readFileData, setReadFileData] = useState([]); const [showNewFilesStatus, setShowNewFilesStatus] = useState(false); const [showExistingFilesStatus, setExistingFilesStatus] = useState(false); const [showOverwriteModal, setShowOverwriteModal] = useState(false); const [showFileDeleteModal, setShowFileDeleteModal] = useState(false); - const [enableDocConversion, setEnableDocConversion] = useState(false); const [fileToDelete, setFileToDelete] = useState([]); const [filesToOverwrite, setFilesToOverwrite] = useState([]); const [droppedFiles, setDroppedFiles] = React.useState(); @@ -53,15 +56,6 @@ export const UploadFile: React.FunctionComponent = ({ existingF const [modalText, setModalText] = useState(''); React.useContext(MultipleFileUploadContext); - useEffect(() => { - const getEnvVariables = async () => { - const res = await fetch('/api/envConfig'); - const envConfig = await res.json(); - setEnableDocConversion(envConfig.ENABLE_DOC_CONVERSION === 'true'); - }; - getEnvVariables(); - }, []); - useEffect(() => { if (filesToUpload.length > 0) { setShowNewFilesStatus(true); @@ -122,7 +116,7 @@ export const UploadFile: React.FunctionComponent = ({ existingF }; // Define allowed file types. If doc conversion is not enabled, only allow markdown files. - const allowedFileTypes: { [mime: string]: string[] } = enableDocConversion + const allowedFileTypes: { [mime: string]: string[] } = docConversionEnabled ? { 'application/pdf': ['.pdf'], 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx'], @@ -275,7 +269,7 @@ export const UploadFile: React.FunctionComponent = ({ existingF titleIcon={} titleText="Drag and drop files here or upload" infoText={ - enableDocConversion + docConversionEnabled ? 'Accepted file types include PDF, DOCX, PPTX, XLSX, HTML, AsciiDoc, Markdown, and images. All files will be converted to Markdown.' : 'Accepted file type: Markdown' } diff --git a/src/components/Contribute/Skill/SkillWizard/SkillWizard.tsx b/src/components/Contribute/Skill/SkillWizard/SkillWizard.tsx index 1371d9cc..e050f45c 100644 --- a/src/components/Contribute/Skill/SkillWizard/SkillWizard.tsx +++ b/src/components/Contribute/Skill/SkillWizard/SkillWizard.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react'; import { useSession } from 'next-auth/react'; import { useRouter } from 'next/navigation'; -import { ValidatedOptions, Button } from '@patternfly/react-core'; +import { ValidatedOptions, Button, PageBreadcrumb, Breadcrumb, BreadcrumbItem } from '@patternfly/react-core'; import { SkillSchemaVersion } from '@/types/const'; import { ContributionFormData, SkillEditFormData, SkillFormData, SkillSeedExample, SkillYamlData } from '@/types'; import { ActionGroupAlertContent } from '@/components/Contribute/types'; @@ -23,11 +23,11 @@ import ContributeAlertGroup from '@/components/Contribute/ContributeAlertGroup'; import ReviewSubmission from '@/components/Contribute/ReviewSubmission/ReviewSubmission'; import SkillSeedExamples from '@/components/Contribute/Skill/SkillSeedExamples/SkillSeedExamples'; import SkillSeedExamplesReviewSection from '@/components/Contribute/Skill/SkillSeedExamples/SkillSeedExamplesReviewSection'; - -import './skills.css'; import DetailsPage from '@/components/Contribute/DetailsPage/DetailsPage'; import { storeDraftData, deleteDraftData, doSaveDraft, isDraftDataExist } from '@/components/Contribute/Utils/autoSaveUtils'; +import './skills.css'; + export interface Props { skillEditFormData?: SkillEditFormData; isGithubMode: boolean; @@ -77,19 +77,13 @@ export const SkillWizard: React.FunctionComponent = ({ skillEditFormData, } }; - async function saveSkillDraft() { - // If no change in the form data and there is no existing draft present, skill storing the draft. - if (!doSaveDraft(skillFormData) && !isDraftDataExist(skillFormData.branchName)) return; + useEffect(() => { + if (!doSaveDraft(skillFormData) && !isDraftDataExist(skillFormData.branchName)) { + return; + } storeDraftData(skillFormData.branchName, JSON.stringify(skillFormData), !!skillEditFormData?.isSubmitted, skillEditFormData?.oldFilesPath || ''); - } - - useEffect(() => { - const storeDraft = async () => { - await saveSkillDraft(); - }; - storeDraft(); - }, [skillFormData]); + }, [skillEditFormData?.isSubmitted, skillEditFormData?.oldFilesPath, skillFormData]); const steps: StepType[] = React.useMemo( () => [ @@ -229,7 +223,25 @@ export const SkillWizard: React.FunctionComponent = ({ skillEditFormData, return ( <> + + { + e.preventDefault(); + router.push('/contribute/skill'); + }} + > + Contribute skills + + {`Edit${skillEditFormData?.isDraft ? ' draft' : ''} skills contribution`} + + + ) : null + } + title={skillEditFormData?.formData ? `Edit${skillEditFormData?.isDraft ? ' draft' : ''} skills contribution` : 'Skills contribution'} description={ <> {`Skill contributions improve a model’s ability to perform tasks. They consist of seed data which provide instructions for completing a task. To autofill this form from a document, `} diff --git a/src/components/Contribute/Utils/autoSaveUtils.ts b/src/components/Contribute/Utils/autoSaveUtils.ts index 0845ba1e..d5829394 100644 --- a/src/components/Contribute/Utils/autoSaveUtils.ts +++ b/src/components/Contribute/Utils/autoSaveUtils.ts @@ -221,6 +221,7 @@ export function fetchDraftKnowledgeChanges({ branchName, setIsLoading, setLoadin const knowledgeEditFormData: KnowledgeEditFormData = { isEditForm: true, isSubmitted: !!draftInfo?.isSubmitted, + isDraft: true, version: KnowledgeSchemaVersion, formData: knowledgeExistingFormData, pullRequestNumber: 0, @@ -246,6 +247,7 @@ export function fetchDraftSkillChanges({ branchName, setIsLoading, setLoadingMsg const skillEditFormData: SkillEditFormData = { isEditForm: true, isSubmitted: draftInfo ? draftInfo.isSubmitted : false, + isDraft: true, version: SkillSchemaVersion, pullRequestNumber: 0, formData: skillExistingFormData, diff --git a/src/components/Contribute/Utils/submitUtils.ts b/src/components/Contribute/Utils/submitUtils.ts index 5cff591c..973997b9 100644 --- a/src/components/Contribute/Utils/submitUtils.ts +++ b/src/components/Contribute/Utils/submitUtils.ts @@ -11,6 +11,7 @@ import { KnowledgeSchemaVersion, SkillSchemaVersion } from '@/types/const'; import { dumpYaml } from '@/utils/yamlConfig'; import { ActionGroupAlertContent } from '@/components/Contribute/types'; import { amendCommit, getGitHubUsername, updatePullRequest } from '@/utils/github'; +import { fetchEnvConfig } from '@/utils/envConfigService'; import { Session } from 'next-auth'; import { validateKnowledgeFormFields, validateSkillFormFields } from '@/components/Contribute/Utils/validation'; @@ -374,13 +375,12 @@ Creator names: ${attributionData.creator_names} }; setActionGroupAlertContent(waitForSubmissionAlert); - const res = await fetch('/api/envConfig'); - const envConfig = await res.json(); + const { upstreamRepoName, upstreamRepoOwner } = await fetchEnvConfig(); const amendedCommitResponse = await amendCommit( session.accessToken, githubUsername, - envConfig.UPSTREAM_REPO_NAME, + upstreamRepoName, oldFilePath, newFilePath, yamlString, @@ -390,7 +390,7 @@ Creator names: ${attributionData.creator_names} ); console.log('Amended commit response:', amendedCommitResponse); - const prLink = `https://github.com/${envConfig.UPSTREAM_REPO_OWNER}/${envConfig.UPSTREAM_REPO_NAME}/pull/${knowledgeEditFormData.pullRequestNumber}`; + const prLink = `https://github.com/${upstreamRepoOwner}/${upstreamRepoName}/pull/${knowledgeEditFormData.pullRequestNumber}`; const actionGroupAlertContent: ActionGroupAlertContent = { title: 'Knowledge contribution updated successfully!', message: `Thank you for your contribution!`, @@ -439,6 +439,10 @@ export const submitNativeSkillData = async ( })) }; + console.log(`========== Submitting Skill ==============`); + console.log(`YamlData: `, skillYamlData); + console.log(`FormData: `, skillFormData); + const yamlString = dumpYaml(skillYamlData); const waitForSubmissionAlert: ActionGroupAlertContent = { @@ -717,8 +721,7 @@ Creator names: ${attributionData.creator_names} attribution: finalAttributionPath }; - const res = await fetch('/api/envConfig'); - const envConfig = await res.json(); + const { upstreamRepoName, upstreamRepoOwner } = await fetchEnvConfig(); const waitForSubmissionAlert: ActionGroupAlertContent = { title: 'Skill contribution update is in progress.!', @@ -732,7 +735,7 @@ Creator names: ${attributionData.creator_names} const amendedCommitResponse = await amendCommit( session.accessToken, githubUsername, - envConfig.UPSTREAM_REPO_NAME, + upstreamRepoName, oldFilePath, newFilePath, yamlString, @@ -742,7 +745,7 @@ Creator names: ${attributionData.creator_names} ); console.log('Amended commit response:', amendedCommitResponse); - const prLink = `https://github.com/${envConfig.UPSTREAM_REPO_OWNER}/${envConfig.UPSTREAM_REPO_NAME}/pull/${pullRequestNumber}`; + const prLink = `https://github.com/${upstreamRepoOwner}/${upstreamRepoName}/pull/${pullRequestNumber}`; const actionGroupAlertContent: ActionGroupAlertContent = { title: 'Skill contribution updated successfully!', message: `Thank you for your contribution!`, diff --git a/src/components/Dashboard/Github/dashboard.tsx b/src/components/Dashboard/Github/dashboard.tsx index 81155e52..bc0671b5 100644 --- a/src/components/Dashboard/Github/dashboard.tsx +++ b/src/components/Dashboard/Github/dashboard.tsx @@ -6,9 +6,6 @@ import { useRouter } from 'next/navigation'; import { DraftEditFormInfo, PullRequest } from '@/types'; import { useState } from 'react'; import { - PageBreadcrumb, - Breadcrumb, - BreadcrumbItem, PageSection, Title, Content, @@ -48,42 +45,45 @@ const DashboardGithub: React.FunctionComponent = () => { const { data: session } = useSession(); const [pullRequests, setPullRequests] = React.useState([]); const [draftContributions, setDraftContributions] = React.useState([]); - const [isFirstPullDone, setIsFirstPullDone] = React.useState(false); const [isDownloadDone, setIsDownloadDone] = React.useState(true); const [isLoading, setIsLoading] = useState(true); //const [error, setError] = React.useState(null); const [isActionMenuOpen, setIsActionMenuOpen] = React.useState<{ [key: number | string]: boolean }>({}); const router = useRouter(); - const fetchAndSetPullRequests = React.useCallback(async () => { - if (session?.accessToken) { - try { - const header = { - Authorization: `Bearer ${session.accessToken}`, - Accept: 'application/vnd.github.v3+json' - }; - const fetchedUsername = await getGitHubUsername(header); - const data = await fetchPullRequests(session.accessToken); - const filteredPRs = data.filter( - (pr: PullRequest) => pr.user.login === fetchedUsername && pr.labels.some((label) => label.name === 'skill' || label.name === 'knowledge') - ); + React.useEffect(() => { + const fetchAndSetPullRequests = async () => { + if (session?.accessToken) { + try { + const header = { + Authorization: `Bearer ${session.accessToken}`, + Accept: 'application/vnd.github.v3+json' + }; + const fetchedUsername = await getGitHubUsername(header); + const data = await fetchPullRequests(session.accessToken); + const filteredPRs = data.filter( + (pr: PullRequest) => pr.user.login === fetchedUsername && pr.labels.some((label) => label.name === 'skill' || label.name === 'knowledge') + ); - // Sort by date (newest first) - const sortedPRs = filteredPRs.sort((a: PullRequest, b: PullRequest) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); - setPullRequests(sortedPRs); - } catch (error) { - console.error('Failed to fetch pull requests.' + error); + // Sort by date (newest first) + const sortedPRs = filteredPRs.sort((a: PullRequest, b: PullRequest) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); + setPullRequests(sortedPRs); + } catch (error) { + console.error('Failed to fetch pull requests.' + error); + } } - setIsFirstPullDone(true); - setIsLoading(false); - } else { + }; + + fetchAndSetPullRequests().then(() => { setIsLoading(false); - } + }); + + const intervalId = setInterval(fetchAndSetPullRequests, 60000); + + return () => clearInterval(intervalId); }, [session?.accessToken]); React.useEffect(() => { - fetchAndSetPullRequests(); - // Fetch all the draft contributions and mark them submitted if present in the pull requests const drafts = fetchDraftContributions().map((draft: DraftEditFormInfo) => ({ ...draft, @@ -91,10 +91,7 @@ const DashboardGithub: React.FunctionComponent = () => { })); setDraftContributions(drafts); - - const intervalId = setInterval(fetchAndSetPullRequests, 60000); - return () => clearInterval(intervalId); - }, [session, fetchAndSetPullRequests]); + }, [pullRequests]); const handleDeleteDraftContribution = async (branchName: string) => { deleteDraftData(branchName); @@ -105,9 +102,9 @@ const DashboardGithub: React.FunctionComponent = () => { const handleEditDraftContribution = (branchName: string) => { // Check if branchName contains string "knowledge" if (branchName.includes('knowledge')) { - router.push(`/edit-submission/knowledge/github/${branchName}/isDraft`); + router.push(`/contribute/knowledge/github/${branchName}/isDraft`); } else { - router.push(`/edit-submission/skill/github/${branchName}/isDraft`); + router.push(`/contribute/skill/github/${branchName}/isDraft`); } }; @@ -119,15 +116,15 @@ const DashboardGithub: React.FunctionComponent = () => { // If user is editing the submitted contribution, use the latest data from draft, if available. // Pass the pr number as well, it's required to pull the data from PR. if (hasKnowledgeLabel) { - router.push(`/edit-submission/knowledge/github/${pr.head.ref}/isDraft`); + router.push(`/contribute/knowledge/github/${pr.head.ref}/isDraft`); } else { - router.push(`/edit-submission/skill/github/${pr.head.ref}/isDraft`); + router.push(`/contribute/skill/github/${pr.head.ref}/isDraft`); } } else { if (hasKnowledgeLabel) { - router.push(`/edit-submission/knowledge/github/${pr.number}`); + router.push(`/contribute/knowledge/github/${pr.number}`); } else if (hasSkillLabel) { - router.push(`/edit-submission/skill/github/${pr.number}`); + router.push(`/contribute/skill/github/${pr.number}`); } } }; @@ -155,12 +152,7 @@ const DashboardGithub: React.FunctionComponent = () => { }; return ( -
- - - Dashboard - - + <> My Submissions @@ -187,8 +179,8 @@ const DashboardGithub: React.FunctionComponent = () => { </PageSection> <PageSection hasBodyWrapper={false}> <div style={{ marginBottom: '20px' }} /> - {!isFirstPullDone && ( - <Modal variant={ModalVariant.small} title="Retrieving your submissions" isOpen={isLoading} onClose={() => handleOnClose()}> + {isLoading && ( + <Modal variant={ModalVariant.small} title="Retrieving your submissions" isOpen onClose={() => handleOnClose()}> <ModalBody> <div> <Spinner size="md" /> @@ -207,7 +199,7 @@ const DashboardGithub: React.FunctionComponent = () => { </ModalBody> </Modal> )} - {isFirstPullDone && pullRequests.length === 0 && draftContributions.length === 0 ? ( + {!isLoading && pullRequests.length === 0 && draftContributions.length === 0 ? ( <EmptyState titleText="Welcome to InstructLab" headingLevel="h4" icon={InstructLabLogo}> <EmptyStateBody> <div style={{ maxWidth: '60ch' }}> @@ -403,7 +395,7 @@ const DashboardGithub: React.FunctionComponent = () => { </Gallery> )} </PageSection> - </div> + </> ); }; diff --git a/src/components/Dashboard/Native/dashboard.tsx b/src/components/Dashboard/Native/dashboard.tsx index 0bad08a1..1e95b724 100644 --- a/src/components/Dashboard/Native/dashboard.tsx +++ b/src/components/Dashboard/Native/dashboard.tsx @@ -2,12 +2,8 @@ import * as React from 'react'; import Image from 'next/image'; import { useRouter } from 'next/navigation'; -import path from 'path'; import { AlertProps, - PageBreadcrumb, - Breadcrumb, - BreadcrumbItem, PageSection, Title, Content, @@ -48,6 +44,7 @@ import { v4 as uuidv4 } from 'uuid'; import { DraftEditFormInfo } from '@/types'; import { deleteDraftData, fetchDraftContributions } from '@/components/Contribute/Utils/autoSaveUtils'; import { handleTaxonomyDownload } from '@/utils/taxonomy'; +import { useEnvConfig } from '@/context/EnvConfigContext'; const InstructLabLogo: React.FC = () => <Image src="/InstructLab-LogoFile-RGB-FullColor.svg" alt="InstructLab Logo" width={256} height={256} />; @@ -64,10 +61,34 @@ interface AlertItem { key: React.Key; } +const cloneNativeTaxonomyRepo = async (): Promise<boolean> => { + try { + const response = await fetch('/api/native/clone-repo', { + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }); + + const result = await response.json(); + if (response.ok) { + console.log(result.message); + return true; + } else { + console.error(result.message); + return false; + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + console.error('Error cloning repo:', errorMessage); + return false; + } +}; + const DashboardNative: React.FunctionComponent = () => { + const { + envConfig: { taxonomyRootDir } + } = useEnvConfig(); const [branches, setBranches] = React.useState<{ name: string; creationDate: number; message: string; author: string }[]>([]); const [draftContributions, setDraftContributions] = React.useState<DraftEditFormInfo[]>([]); - const [taxonomyRepoDir, setTaxonomyRepoDir] = React.useState<string>(''); const [isLoading, setIsLoading] = React.useState<boolean>(true); const [mergeStatus] = React.useState<{ branch: string; message: string; success: boolean } | null>(null); const [diffData, setDiffData] = React.useState<{ branch: string; changes: ChangeData[] } | null>(null); @@ -102,64 +123,47 @@ const DashboardNative: React.FunctionComponent = () => { addAlert(message, 'danger'); }, []); - const fetchBranches = React.useCallback(async () => { - try { - const response = await fetch('/api/native/git/branches'); - const result = await response.json(); - if (response.ok) { - // Filter out 'main' branch - const filteredBranches = result.branches.filter((branch: { name: string }) => branch.name !== 'main'); - setBranches(filteredBranches); - } else { - console.error('Failed to fetch branches:', result.error); - addDangerAlert(result.error || 'Failed to fetch branches.'); - } - } catch (error) { - console.error('Error fetching branches:', error); - addDangerAlert('Error fetching branches.'); - } finally { - setIsLoading(false); - } - }, [addDangerAlert]); - - async function cloneNativeTaxonomyRepo(): Promise<boolean> { - try { - const response = await fetch('/api/native/clone-repo', { - method: 'POST', - headers: { 'Content-Type': 'application/json' } - }); - - const result = await response.json(); - if (response.ok) { - console.log(result.message); - return true; - } else { - console.error(result.message); - return false; - } - } catch (error) { - const errorMessage = error instanceof Error ? error.message : 'Unknown error'; - console.error('Error cloning repo:', errorMessage); - return false; - } - } - // Fetch branches from the API route React.useEffect(() => { - const getEnvVariables = async () => { - const res = await fetch('/api/envConfig'); - const envConfig = await res.json(); - const taxonomyRepoDir = path.join(envConfig.TAXONOMY_ROOT_DIR + '/taxonomy'); - setTaxonomyRepoDir(taxonomyRepoDir); + let refreshIntervalId: NodeJS.Timeout; + + const fetchBranches = async () => { + const success = await cloneNativeTaxonomyRepo(); + if (success) { + try { + const response = await fetch('/api/native/git/branches'); + const result = await response.json(); + if (response.ok) { + // Filter out 'main' branch + const filteredBranches = result.branches.filter((branch: { name: string }) => branch.name !== 'main'); + setBranches(filteredBranches); + } else { + console.error('Failed to fetch branches:', result.error); + addDangerAlert(result.error || 'Failed to fetch branches.'); + } + } catch (error) { + console.error('Error fetching branches:', error); + addDangerAlert('Error fetching branches.'); + } + } }; - getEnvVariables(); cloneNativeTaxonomyRepo().then((success) => { if (success) { - fetchBranches(); + fetchBranches().then(() => { + setIsLoading(false); + }); + refreshIntervalId = setInterval(fetchBranches, 60000); + } else { + addDangerAlert('Failed to fetch branches.'); + setIsLoading(false); } }); + return () => clearInterval(refreshIntervalId); + }, [addDangerAlert]); + + React.useEffect(() => { // Fetch all the draft contributions and mark them submitted if present in the branches const drafts = fetchDraftContributions().map((draft: DraftEditFormInfo) => ({ ...draft, @@ -167,7 +171,7 @@ const DashboardNative: React.FunctionComponent = () => { })); setDraftContributions(drafts); - }, [fetchBranches]); + }, [branches]); const formatDateTime = (timestamp: number) => { const date = new Date(timestamp); @@ -261,9 +265,9 @@ const DashboardNative: React.FunctionComponent = () => { setSelectedDraftContribution(branchName); // Check if branchName contains string "knowledge" if (branchName.includes('knowledge')) { - router.push(`/edit-submission/knowledge/native/${branchName}/isDraft`); + router.push(`/contribute/knowledge/native/${branchName}/isDraft`); } else { - router.push(`/edit-submission/skill/native/${branchName}/isDraft`); + router.push(`/contribute/skill/native/${branchName}/isDraft`); } }; @@ -273,16 +277,16 @@ const DashboardNative: React.FunctionComponent = () => { if (draftContributions.find((draft) => draft.branchName == branchName)) { // If user is editing the submitted contribution, use the latest data from draft. if (branchName.includes('knowledge')) { - router.push(`/edit-submission/knowledge/native/${branchName}/isDraft`); + router.push(`/contribute/knowledge/native/${branchName}/isDraft`); } else { - router.push(`/edit-submission/skill/native/${branchName}/isDraft`); + router.push(`/contribute/skill/native/${branchName}/isDraft`); } } else { // Check if branchName contains string "knowledge" if (branchName.includes('knowledge')) { - router.push(`/edit-submission/knowledge/native/${branchName}`); + router.push(`/contribute/knowledge/native/${branchName}`); } else { - router.push(`/edit-submission/skill/native/${branchName}`); + router.push(`/contribute/skill/native/${branchName}`); } } }; @@ -351,12 +355,7 @@ const DashboardNative: React.FunctionComponent = () => { }; return ( - <div> - <PageBreadcrumb hasBodyWrapper={false}> - <Breadcrumb> - <BreadcrumbItem to="/"> Dashboard </BreadcrumbItem> - </Breadcrumb> - </PageBreadcrumb> + <> <PageSection hasBodyWrapper={false}> <Title headingLevel="h1" size="lg"> My Submissions @@ -671,7 +670,7 @@ const DashboardNative: React.FunctionComponent = () => { > <ModalHeader title="Deleting Contribution" labelId="delete-contribution-modal-title" titleIconVariant="warning" /> <ModalBody id="delete-contribution-body-variant"> - <p>are you sure you want to delete this contribution?</p> + <p>Are you sure you want to delete this contribution?</p> </ModalBody> <ModalFooter> <Button key="confirm" variant="primary" onClick={() => handleDeleteContributionConfirm()}> @@ -693,7 +692,7 @@ const DashboardNative: React.FunctionComponent = () => { > <ModalHeader title="Publishing Contribution" labelId="publish-contribution-modal-title" titleIconVariant="warning" /> <ModalBody id="publish-contribution-body-variant"> - <p>are you sure you want to publish contribution to remote taxonomy repository present at : {taxonomyRepoDir}?</p> + <p>Are you sure you want to publish contribution to remote taxonomy repository present at : {taxonomyRootDir}?</p> </ModalBody> <ModalFooter> <Button key="confirm" variant="primary" onClick={() => handlePublishContributionConfirm()}> @@ -706,7 +705,7 @@ const DashboardNative: React.FunctionComponent = () => { </ModalFooter> </Modal> </PageSection> - </div> + </> ); }; diff --git a/src/components/Experimental/ChatEval/ChatEval.tsx b/src/components/Experimental/ChatEval/ChatEval.tsx index 63fd2836..bfd6767d 100644 --- a/src/components/Experimental/ChatEval/ChatEval.tsx +++ b/src/components/Experimental/ChatEval/ChatEval.tsx @@ -42,10 +42,14 @@ import { import logo from '../../../../public/bot-icon-chat-32x32.svg'; import userLogo from '../../../../public/default-avatar.svg'; import ModelStatusIndicator from '@/components/Experimental/ModelServeStatus/ModelServeStatus'; +import { useEnvConfig } from '@/context/EnvConfigContext'; import './ChatEval.css'; const ChatModelEval: React.FC = () => { + const { + envConfig: { apiServer } + } = useEnvConfig(); const [isUnifiedInput, setIsUnifiedInput] = useState(false); const [modelServerURL, setModelServerURL] = useState<string>(''); @@ -118,10 +122,7 @@ const ChatModelEval: React.FC = () => { // Fetch models on component mount useEffect(() => { const fetchDefaultModels = async () => { - const response = await fetch('/api/envConfig'); - const envConfig = await response.json(); - - const modelServerURL = envConfig.API_SERVER.replace(/:\d+/, ''); + const modelServerURL = apiServer.replace(/:\d+/, ''); console.log('Model server url is set to :', modelServerURL); setModelServerURL(modelServerURL); @@ -139,7 +140,7 @@ const ChatModelEval: React.FC = () => { }; fetchDefaultModels(); - }, []); + }, [apiServer]); /** * Helper function to map internal model identifiers to chat model names. diff --git a/src/components/GithubAccessPopup/index.tsx b/src/components/GithubAccessPopup/index.tsx index 5eca472b..04e2af3f 100644 --- a/src/components/GithubAccessPopup/index.tsx +++ b/src/components/GithubAccessPopup/index.tsx @@ -4,37 +4,41 @@ import * as React from 'react'; import { signOut } from 'next-auth/react'; import { Modal, ModalVariant, Button, ModalBody, ModalFooter, ModalHeader } from '@patternfly/react-core'; +import { useEnvConfig } from '@/context/EnvConfigContext'; interface Props { onAccept: () => void; } const GithubAccessPopup: React.FunctionComponent<Props> = ({ onAccept }) => { + const { + envConfig: { isGithubMode, isDevMode }, + loaded + } = useEnvConfig(); const [isOpen, setIsOpen] = React.useState(false); React.useEffect(() => { - const showPopupWarning = async () => { - const res = await fetch('/api/envConfig'); - const envConfig = await res.json(); - if (envConfig.DEPLOYMENT_TYPE === 'native' || envConfig.ENABLE_DEV_MODE === 'true') { - setIsOpen(false); - onAccept(); - } else { - setIsOpen(true); - } - }; - showPopupWarning(); - }, [onAccept]); + if (loaded && (!isGithubMode || isDevMode)) { + setIsOpen(false); + onAccept(); + } else { + setIsOpen(true); + } + }, [isDevMode, isGithubMode, loaded, onAccept]); const setDecisionAndClose = () => { setIsOpen(false); onAccept(); }; + if (!isOpen) { + return null; + } + return ( <Modal variant={ModalVariant.medium} title="GitHub Access Permissions" - isOpen={isOpen} + isOpen onClose={() => setIsOpen(false)} aria-labelledby="github-access-warn-modal-title" aria-describedby="github-access-warn-body-variant" diff --git a/src/context/EnvConfigContext.tsx b/src/context/EnvConfigContext.tsx new file mode 100644 index 00000000..288dda0a --- /dev/null +++ b/src/context/EnvConfigContext.tsx @@ -0,0 +1,50 @@ +// src/context/ThemeContext.tsx +'use client'; + +import React from 'react'; +import { devLog } from '@/utils/devlog'; +import { EnvConfigType } from '@/types'; +import { fetchEnvConfig } from '@/utils/envConfigService'; + +const DefaultEnvConfig = { + graniteApi: '', + graniteModelName: '', + merliniteApi: '', + merliniteModelName: '', + upstreamRepoOwner: '', + upstreamRepoName: '', + taxonomyRootDir: '', + taxonomyKnowledgeDocumentRepo: '', + apiServer: '', + isGithubMode: false, + isDevMode: false +}; + +const EnvConfigContext = React.createContext<{ loaded: boolean; envConfig: EnvConfigType }>({ loaded: false, envConfig: DefaultEnvConfig }); + +export const EnvConfigProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [currentState, setCurrentState] = React.useState<{ loaded: boolean; envConfig: EnvConfigType }>({ + loaded: false, + envConfig: { ...DefaultEnvConfig } + }); + + React.useEffect(() => { + fetchEnvConfig().then((newConfig) => { + devLog(`======== Env Config ============`); + devLog(` isGithubMode: `, newConfig.isGithubMode); + devLog(` isDevMode: `, newConfig.isDevMode); + + setCurrentState({ loaded: true, envConfig: newConfig }); + }); + }, []); + + return <EnvConfigContext.Provider value={{ ...currentState }}>{children}</EnvConfigContext.Provider>; +}; + +export const useEnvConfig = (): { loaded: boolean; envConfig: EnvConfigType } => { + const context = React.useContext(EnvConfigContext); + if (!context) { + throw new Error('useEnvConfig must be used within a EnvConfigProvider'); + } + return context; +}; diff --git a/src/context/FeatureFlagsContext.tsx b/src/context/FeatureFlagsContext.tsx new file mode 100644 index 00000000..aaaf6ca2 --- /dev/null +++ b/src/context/FeatureFlagsContext.tsx @@ -0,0 +1,47 @@ +// src/context/ThemeContext.tsx +'use client'; + +import React from 'react'; +import { devLog } from '@/utils/devlog'; +import { FeatureFlagsType } from '@/types'; +import { fetchFeatureFlags } from '@/utils/featureFlagsService'; + +const DefaultFeatureFlags = { + docConversionEnabled: false, + skillFeaturesEnabled: false, + playgroundFeaturesEnabled: false, + experimentalFeaturesEnabled: false +}; + +const FeatureFlagsContext = React.createContext<{ loaded: boolean; featureFlags: FeatureFlagsType }>({ + loaded: false, + featureFlags: DefaultFeatureFlags +}); + +export const FeatureFlagsProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [currentState, setCurrentState] = React.useState<{ loaded: boolean; featureFlags: FeatureFlagsType }>({ + loaded: false, + featureFlags: { ...DefaultFeatureFlags } + }); + + React.useEffect(() => { + fetchFeatureFlags().then((newConfig) => { + devLog(`======== Feature Flags ============`); + devLog(` docConversionEnabled: `, newConfig.docConversionEnabled); + devLog(` skillFeaturesEnabled: `, newConfig.skillFeaturesEnabled); + devLog(` playgroundFeaturesEnabled: `, newConfig.playgroundFeaturesEnabled); + devLog(` experimentalFeaturesEnabled: `, newConfig.experimentalFeaturesEnabled); + setCurrentState({ loaded: true, featureFlags: newConfig }); + }); + }, []); + + return <FeatureFlagsContext.Provider value={{ ...currentState }}>{children}</FeatureFlagsContext.Provider>; +}; + +export const useFeatureFlags = (): { loaded: boolean; featureFlags: FeatureFlagsType } => { + const context = React.useContext(FeatureFlagsContext); + if (!context) { + throw new Error('useEnvConfig must be used within a EnvConfigProvider'); + } + return context; +}; diff --git a/src/types/index.ts b/src/types/index.ts index 5307c116..a5c9b940 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -168,6 +168,7 @@ export interface KnowledgeFormData extends ContributionFormData { export interface EditFormData<T extends ContributionFormData = SkillFormData> { isEditForm: boolean; isSubmitted: boolean; + isDraft?: boolean; version: number; pullRequestNumber: number; oldFilesPath: string; @@ -177,3 +178,24 @@ export interface EditFormData<T extends ContributionFormData = SkillFormData> { export type SkillEditFormData = EditFormData; export type KnowledgeEditFormData = EditFormData<KnowledgeFormData>; + +export interface EnvConfigType { + graniteApi: string; + graniteModelName: string; + merliniteApi: string; + merliniteModelName: string; + upstreamRepoOwner: string; + upstreamRepoName: string; + taxonomyRootDir: string; + taxonomyKnowledgeDocumentRepo: string; + apiServer: string; + isGithubMode: boolean; + isDevMode: boolean; +} + +export interface FeatureFlagsType { + docConversionEnabled: boolean; + skillFeaturesEnabled: boolean; + playgroundFeaturesEnabled: boolean; + experimentalFeaturesEnabled: boolean; +} diff --git a/src/utils/envConfigService.ts b/src/utils/envConfigService.ts new file mode 100644 index 00000000..711a2190 --- /dev/null +++ b/src/utils/envConfigService.ts @@ -0,0 +1,37 @@ +import { EnvConfigType } from '@/types'; + +export const fetchEnvConfig = async (): Promise<EnvConfigType> => { + try { + const res = await fetch('/api/envConfig'); + const envConfig = await res.json(); + + return { + graniteApi: envConfig.GRANITE_API, + graniteModelName: envConfig.GRANITE_MODEL_NAME, + merliniteApi: envConfig.MERLINITE_API, + merliniteModelName: envConfig.MERLINITE_MODEL_NAME, + upstreamRepoOwner: envConfig.UPSTREAM_REPO_OWNER, + upstreamRepoName: envConfig.UPSTREAM_REPO_NAME, + taxonomyRootDir: envConfig.TAXONOMY_ROOT_DIR, + taxonomyKnowledgeDocumentRepo: envConfig.TAXONOMY_KNOWLEDGE_DOCUMENT_REPO, + apiServer: envConfig.API_SERVER, + isGithubMode: envConfig.DEPLOYMENT_TYPE !== 'native', + isDevMode: envConfig.ENABLE_DEV_MODE === 'true' + }; + } catch (error) { + console.error(`Error fetching ENV config: `, error); + return { + graniteApi: '', + graniteModelName: '', + merliniteApi: '', + merliniteModelName: '', + upstreamRepoOwner: '', + upstreamRepoName: '', + taxonomyRootDir: '', + taxonomyKnowledgeDocumentRepo: '', + apiServer: '', + isGithubMode: false, + isDevMode: false + }; + } +}; diff --git a/src/utils/featureFlagsService.ts b/src/utils/featureFlagsService.ts new file mode 100644 index 00000000..d7003b13 --- /dev/null +++ b/src/utils/featureFlagsService.ts @@ -0,0 +1,22 @@ +import { FeatureFlagsType } from '@/types'; + +export const fetchFeatureFlags = async (): Promise<FeatureFlagsType> => { + try { + const res = await fetch('/api/envConfig'); + const envConfig = await res.json(); + return { + docConversionEnabled: envConfig.ENABLE_DOC_CONVERSION === 'true', + skillFeaturesEnabled: envConfig.ENABLE_SKILLS_FEATURES === 'true', + playgroundFeaturesEnabled: envConfig.ENABLE_PLAYGROUND_FEATURES === 'true', + experimentalFeaturesEnabled: envConfig.EXPERIMENTAL_FEATURES === 'true' + }; + } catch (error) { + console.error(`Error fetching ENV config: `, error); + return { + docConversionEnabled: false, + skillFeaturesEnabled: false, + playgroundFeaturesEnabled: false, + experimentalFeaturesEnabled: false + }; + } +}; diff --git a/src/utils/github.ts b/src/utils/github.ts index af0c88f1..5a66b499 100644 --- a/src/utils/github.ts +++ b/src/utils/github.ts @@ -2,6 +2,7 @@ import axios from 'axios'; import { PullRequestUpdateData } from '@/types'; import { BASE_BRANCH, FORK_CLONE_CHECK_RETRY_COUNT, FORK_CLONE_CHECK_RETRY_TIMEOUT, GITHUB_API_URL } from '@/types/const'; +import { fetchEnvConfig } from '@/utils/envConfigService'; type GithubUserInfo = { login: string; @@ -11,19 +12,14 @@ type GithubUserInfo = { export async function fetchPullRequests(token: string) { try { - console.log('Refreshing PR Listing'); - const res = await fetch('/api/envConfig'); - const envConfig = await res.json(); + const { upstreamRepoName, upstreamRepoOwner } = await fetchEnvConfig(); - const response = await axios.get( - `https://api.github.com/repos/${envConfig.UPSTREAM_REPO_OWNER}/${envConfig.UPSTREAM_REPO_NAME}/pulls?state=all`, - { - headers: { - Authorization: `Bearer ${token}`, - Accept: 'application/vnd.github+json' - } + const response = await axios.get(`https://api.github.com/repos/${upstreamRepoOwner}/${upstreamRepoName}/pulls?state=all`, { + headers: { + Authorization: `Bearer ${token}`, + Accept: 'application/vnd.github+json' } - ); + }); return response.data; } catch (error) { if (axios.isAxiosError(error)) { @@ -37,17 +33,13 @@ export async function fetchPullRequests(token: string) { export const fetchPullRequest = async (token: string, prNumber: number) => { try { - const res = await fetch('/api/envConfig'); - const envConfig = await res.json(); - const response = await axios.get( - `https://api.github.com/repos/${envConfig.UPSTREAM_REPO_OWNER}/${envConfig.UPSTREAM_REPO_NAME}/pulls/${prNumber}`, - { - headers: { - Authorization: `Bearer ${token}`, - Accept: 'application/vnd.github+json' - } + const { upstreamRepoName, upstreamRepoOwner } = await fetchEnvConfig(); + const response = await axios.get(`https://api.github.com/repos/${upstreamRepoOwner}/${upstreamRepoName}/pulls/${prNumber}`, { + headers: { + Authorization: `Bearer ${token}`, + Accept: 'application/vnd.github+json' } - ); + }); if (response.status === 404) { throw new Error(`Pull request with number ${prNumber} not found.`); } @@ -65,17 +57,13 @@ export const fetchPullRequest = async (token: string, prNumber: number) => { export const fetchPullRequestFiles = async (token: string, prNumber: number) => { try { - const res = await fetch('/api/envConfig'); - const envConfig = await res.json(); - const response = await axios.get( - `https://api.github.com/repos/${envConfig.UPSTREAM_REPO_OWNER}/${envConfig.UPSTREAM_REPO_NAME}/pulls/${prNumber}/files`, - { - headers: { - Authorization: `Bearer ${token}`, - Accept: 'application/vnd.github+json' - } + const { upstreamRepoName, upstreamRepoOwner } = await fetchEnvConfig(); + const response = await axios.get(`https://api.github.com/repos/${upstreamRepoOwner}/${upstreamRepoName}/pulls/${prNumber}/files`, { + headers: { + Authorization: `Bearer ${token}`, + Accept: 'application/vnd.github+json' } - ); + }); return response.data; } catch (error) { if (axios.isAxiosError(error)) { @@ -89,17 +77,13 @@ export const fetchPullRequestFiles = async (token: string, prNumber: number) => export const fetchFileContent = async (token: string, filePath: string, ref: string) => { try { - const res = await fetch('/api/envConfig'); - const envConfig = await res.json(); - const response = await axios.get( - `https://api.github.com/repos/${envConfig.UPSTREAM_REPO_OWNER}/${envConfig.UPSTREAM_REPO_NAME}/contents/${filePath}?ref=${ref}`, - { - headers: { - Authorization: `Bearer ${token}`, - Accept: 'application/vnd.github.v3.raw' - } + const { upstreamRepoName, upstreamRepoOwner } = await fetchEnvConfig(); + const response = await axios.get(`https://api.github.com/repos/${upstreamRepoOwner}/${upstreamRepoName}/contents/${filePath}?ref=${ref}`, { + headers: { + Authorization: `Bearer ${token}`, + Accept: 'application/vnd.github.v3.raw' } - ); + }); return response.data; } catch (error) { if (axios.isAxiosError(error)) { @@ -114,18 +98,13 @@ export const fetchFileContent = async (token: string, filePath: string, ref: str export const updatePullRequest = async (token: string, prNumber: number, data: PullRequestUpdateData) => { try { console.log(`Updating PR Number: ${prNumber} with data:`, data); - const res = await fetch('/api/envConfig'); - const envConfig = await res.json(); - const response = await axios.patch( - `https://api.github.com/repos/${envConfig.UPSTREAM_REPO_OWNER}/${envConfig.UPSTREAM_REPO_NAME}/pulls/${prNumber}`, - data, - { - headers: { - Authorization: `Bearer ${token}`, - Accept: 'application/vnd.github+json' - } + const { upstreamRepoName, upstreamRepoOwner } = await fetchEnvConfig(); + const response = await axios.patch(`https://api.github.com/repos/${upstreamRepoOwner}/${upstreamRepoName}/pulls/${prNumber}`, data, { + headers: { + Authorization: `Bearer ${token}`, + Accept: 'application/vnd.github+json' } - ); + }); console.log(`Updated PR ${prNumber}:`, response.data); return response.data; } catch (error) {