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 && ( Autofill 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 = () => { - {!isFirstPullDone && ( - handleOnClose()}> + {isLoading && ( + handleOnClose()}> @@ -207,7 +199,7 @@ const DashboardGithub: React.FunctionComponent = () => { )} - {isFirstPullDone && pullRequests.length === 0 && draftContributions.length === 0 ? ( + {!isLoading && pullRequests.length === 0 && draftContributions.length === 0 ? ( @@ -403,7 +395,7 @@ const DashboardGithub: React.FunctionComponent = () => { )} - + > ); }; 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 = () => ; @@ -64,10 +61,34 @@ interface AlertItem { key: React.Key; } +const cloneNativeTaxonomyRepo = async (): Promise => { + 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([]); - const [taxonomyRepoDir, setTaxonomyRepoDir] = React.useState(''); const [isLoading, setIsLoading] = React.useState(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 { - 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 ( - - - - Dashboard - - + <> My Submissions @@ -671,7 +670,7 @@ const DashboardNative: React.FunctionComponent = () => { > - are you sure you want to delete this contribution? + Are you sure you want to delete this contribution? handleDeleteContributionConfirm()}> @@ -693,7 +692,7 @@ const DashboardNative: React.FunctionComponent = () => { > - are you sure you want to publish contribution to remote taxonomy repository present at : {taxonomyRepoDir}? + Are you sure you want to publish contribution to remote taxonomy repository present at : {taxonomyRootDir}? handlePublishContributionConfirm()}> @@ -706,7 +705,7 @@ const DashboardNative: React.FunctionComponent = () => { - + > ); }; 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(''); @@ -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 = ({ 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 ( 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 {children}; +}; + +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 {children}; +}; + +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 { isEditForm: boolean; isSubmitted: boolean; + isDraft?: boolean; version: number; pullRequestNumber: number; oldFilesPath: string; @@ -177,3 +178,24 @@ export interface EditFormData { export type SkillEditFormData = EditFormData; export type KnowledgeEditFormData = EditFormData; + +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 => { + 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 => { + 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) {
are you sure you want to delete this contribution?
Are you sure you want to delete this contribution?
are you sure you want to publish contribution to remote taxonomy repository present at : {taxonomyRepoDir}?
Are you sure you want to publish contribution to remote taxonomy repository present at : {taxonomyRootDir}?