Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Fix: integrations UI fixes #234

Open
wants to merge 5 commits into
base: alpha
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/Chat/Chat.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ const Template: React.FC<{
);
};

const meta = {
const meta: Meta<typeof Template> = {
title: "Chat",
component: Template,
parameters: {
Expand All @@ -145,7 +145,7 @@ const meta = {
},
},
argTypes: {},
} satisfies Meta<typeof Template>;
};

export default meta;

Expand Down
6 changes: 3 additions & 3 deletions src/components/ChatForm/ToolConfirmation.module.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.ToolConfirmationCard {
position: absolute;
width: calc(100% - 80px);
bottom: 40px;
width: calc(100% - 100px);
bottom: 50px;
left: 50%;
transform: translateX(-50%);

Expand All @@ -10,7 +10,7 @@
flex-direction: column;
gap: 8px;
border-radius: 8px;
background-color: var(--color-surface);
background-color: var(--gray-1);
min-height: fit-content;
& pre {
margin: 0;
Expand Down
48 changes: 31 additions & 17 deletions src/components/ChatForm/ToolConfirmation.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import React from "react";
import type { PauseReason } from "../../features/ToolConfirmation/confirmationSlice";
import { useEventsBusForIDE } from "../../hooks";
import {
useAppDispatch,
// useEventsBusForIDE
} from "../../hooks";
import { Card, Button, Text, Flex } from "@radix-ui/themes";
import { Markdown } from "../Markdown";
import { Link } from "../Link";
import styles from "./ToolConfirmation.module.css";
import { push } from "../../features/Pages/pagesSlice";

type ToolConfirmationProps = {
pauseReasons: PauseReason[];
Expand All @@ -18,29 +22,39 @@ const getConfirmationalMessage = (
confirmationalCommands: string[],
denialCommands: string[],
) => {
const ruleText = `${rules.join(", ")} ${rules.length > 1 ? "rules" : "rule"}`;
const ruleText = `${rules.join(", ")}`;
if (types.every((type) => type === "confirmation")) {
return `Following ${
commands.length > 1 ? "commands need" : "command needs"
} confirmation due to ${ruleText}.`;
return `${
commands.length > 1 ? "Commands need" : "Command needs"
} confirmation due to \`\`\`${ruleText}\`\`\` ${
rules.length > 1 ? "rules" : "rule"
}.`;
} else if (types.every((type) => type === "denial")) {
return `Following ${
commands.length > 1 ? "commands were" : "command was"
} denied due to ${ruleText}.`;
return `${
commands.length > 1 ? "Commands were" : "Command was"
} denied due to \`\`\`${ruleText}\`\`\` ${
rules.length > 1 ? "rules" : "rule"
}.`;
} else {
return `Following ${
confirmationalCommands.length > 1 ? "commands need" : "command needs"
return `${
confirmationalCommands.length > 1 ? "Commands need" : "Command needs"
} confirmation: ${confirmationalCommands.join(", ")}.\n\nFollowing ${
denialCommands.length > 1 ? "commands were" : "command was"
} denied: ${denialCommands.join(", ")}.\n\nAll due to ${ruleText}.`;
} denied: ${denialCommands.join(
", ",
)}.\n\nAll due to \`\`\`${ruleText}\`\`\` ${
rules.length > 1 ? "rules" : "rule"
}.`;
}
};

export const ToolConfirmation: React.FC<ToolConfirmationProps> = ({
pauseReasons,
onConfirm,
}) => {
const { openIntegrationsFile } = useEventsBusForIDE();
const dispatch = useAppDispatch();

// const { openIntegrationsFile } = useEventsBusForIDE();

const commands = pauseReasons.map((reason) => reason.command);
const rules = pauseReasons.map((reason) => reason.rule);
Expand Down Expand Up @@ -80,15 +94,15 @@ export const ToolConfirmation: React.FC<ToolConfirmationProps> = ({
>{`${"```bash\n"}${command}${"\n```"}`}</Markdown>
))}
<Text className={styles.ToolConfirmationText}>
{message.concat("\n\n")}
<Text className={styles.ToolConfirmationText}>
You can modify the ruleset in{" "}
<Markdown>{message.concat("\n\n")}</Markdown>
<Text className={styles.ToolConfirmationText} mt="3">
You can modify the ruleset on{" "}
<Link
onClick={() => {
void openIntegrationsFile();
dispatch(push({ name: "integrations page" }));
}}
>
integrations.yaml
Configuration Page
</Link>
</Text>
</Text>
Expand Down
52 changes: 19 additions & 33 deletions src/components/IntegrationsView/IntegrationForm/IntegrationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,6 @@ import { toPascalCase } from "../../../utils/toPascalCase";
import { debugIntegrations } from "../../../debugConfig";
import { iconMap } from "../icons/iconMap";

// TODO: should be extracted in the future
function jsonHasWhenIsolated(
json: unknown,
): json is Record<string, boolean> & { when_isolated: boolean } {
return (
typeof json === "object" &&
json !== null &&
"when_isolated" in json &&
typeof json.when_isolated === "boolean"
);
}

function areAllFieldsBoolean(json: unknown): json is Record<string, boolean> {
return (
typeof json === "object" &&
Expand Down Expand Up @@ -257,29 +245,27 @@ export const IntegrationForm: FC<IntegrationFormProps> = ({
</Flex>
</Flex>
)}
{integration.data.integr_schema.docker &&
jsonHasWhenIsolated(integration.data.integr_values.available) &&
integration.data.integr_values.available.when_isolated && (
<Flex mt="6" direction="column" align="start" gap="5">
<Flex gap="2" align="center" justify="center" width="100%">
<img
src={iconMap.docker}
className={styles.DockerIcon}
alt={integration.data.integr_name}
/>
<Heading as="h3" align="left">
{toPascalCase(integration.data.integr_name)} Containers
</Heading>
</Flex>
<IntegrationDocker
dockerData={integration.data.integr_schema.docker}
integrationName={integration.data.integr_name}
integrationProject={integration.data.project_path}
integrationPath={integration.data.integr_config_path}
handleSwitchIntegration={handleSwitchIntegration}
{integration.data.integr_schema.docker && (
<Flex mt="6" direction="column" align="start" gap="5">
<Flex gap="2" align="center" justify="center" width="100%">
<img
src={iconMap.docker}
className={styles.DockerIcon}
alt={integration.data.integr_name}
/>
<Heading as="h3" align="left">
{toPascalCase(integration.data.integr_name)} Containers
</Heading>
</Flex>
)}
<IntegrationDocker
dockerData={integration.data.integr_schema.docker}
integrationName={integration.data.integr_name}
integrationProject={integration.data.project_path}
integrationPath={integration.data.integr_config_path}
handleSwitchIntegration={handleSwitchIntegration}
/>
</Flex>
)}
</Flex>
);
};
77 changes: 57 additions & 20 deletions src/components/IntegrationsView/IntegrationsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
Integration,
integrationsApi,
IntegrationWithIconRecord,
IntegrationWithIconRecordAndAddress,
IntegrationWithIconResponse,
isDetailMessage,
isNotConfiguredIntegrationWithIconRecord,
Expand Down Expand Up @@ -80,42 +81,78 @@ export const IntegrationsView: FC<IntegrationViewProps> = ({
const maybeIntegration = useMemo(() => {
if (!currentThreadIntegration) return null;
if (!integrationsMap) return null;
debugIntegrations(
`[DEBUG]: currentThreadIntegration: `,
currentThreadIntegration,
);
// TODO: check for extra flag in currentThreadIntegration to return different find() call from notConfiguredGrouped integrations if it's set to true
return (
const integration =
integrationsMap.integrations.find(
(integration) =>
integration.integr_config_path ===
currentThreadIntegration.integrationPath,
) ?? null
);
currentThreadIntegration.integrationName?.startsWith("cmdline")
? integration.integr_name === "cmdline_TEMPLATE"
: integration.integr_name ===
currentThreadIntegration.integrationName,
// integration.integr_name === currentThreadIntegration.integrationName,
) ?? null;
if (!integration) return null;

const integrationWithFlag = {
...integration,
shouldIntermediatePageShowUp:
currentThreadIntegration.shouldIntermediatePageShowUp ?? false,
} as IntegrationWithIconRecordAndAddress;
return integrationWithFlag;
}, [currentThreadIntegration, integrationsMap]);

// TBD: what if they went home then came back to integrations?

const [currentIntegration, setCurrentIntegration] =
useState<IntegrationWithIconRecord | null>(maybeIntegration);
useState<IntegrationWithIconRecord | null>(
maybeIntegration?.shouldIntermediatePageShowUp ? null : maybeIntegration,
);

const [currentNotConfiguredIntegration, setCurrentNotConfiguredIntegration] =
useState<NotConfiguredIntegrationWithIconRecord | null>(null);

// TODO: uncomment when ready
// useEffect(() => {
// if (maybeIntegration) {
// if (maybeIntegration.shouldBeOpenedOnIntermediatePage) {
// setNotConfiguredIntegration(maybeIntegration);
// setCurrentIntegration(null);
// } else {
// setCurrentIntegration(maybeIntegration);
// setNotConfiguredIntegration(null);
// }
// }
// }, [maybeIntegration]);

useEffect(() => {
if (maybeIntegration) {
setCurrentIntegration(maybeIntegration);
if (maybeIntegration.shouldIntermediatePageShowUp) {
setCurrentNotConfiguredIntegration(() => {
const similarIntegrations = integrationsMap?.integrations.filter(
(integr) => integr.integr_name === maybeIntegration.integr_name,
);
if (!similarIntegrations) return null;
const uniqueConfigPaths = Array.from(
new Set(
similarIntegrations.map((integr) => integr.integr_config_path),
),
);
const uniqueProjectPaths = Array.from(
new Set(similarIntegrations.map((integr) => integr.project_path)),
);

uniqueProjectPaths.sort((a, _b) => (a === "" ? -1 : 1));
uniqueConfigPaths.sort((a, _b) => (a.includes(".config") ? -1 : 1));

const integrationToConfigure: NotConfiguredIntegrationWithIconRecord =
{
...maybeIntegration,
integr_config_path: uniqueConfigPaths,
project_path: uniqueProjectPaths,
integr_config_exists: false,
};

return integrationToConfigure;
});
setCurrentIntegration(null);
} else {
setCurrentIntegration(maybeIntegration);
setCurrentNotConfiguredIntegration(null);
}
}
}, [maybeIntegration]);
}, [maybeIntegration, integrationsMap?.integrations]);

const [currentIntegrationSchema, setCurrentIntegrationSchema] = useState<
Integration["integr_schema"] | null
Expand Down
2 changes: 1 addition & 1 deletion src/components/SmartLink/SmartLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const SmartLink: FC<{

const handleClick = useCallback(() => {
if (sl_goto) {
handleGoTo(sl_goto);
handleGoTo({ goto: sl_goto });
return;
}
if (sl_chat) {
Expand Down
1 change: 1 addition & 0 deletions src/features/Chat/Thread/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type IntegrationMeta = {
name?: string;
path?: string;
project?: string;
shouldIntermediatePageShowUp?: boolean;
};
export type ChatThread = {
id: string;
Expand Down
1 change: 1 addition & 0 deletions src/features/History/historySlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export const historySlice = createSlice({
: getFirstUserContentFromChat(action.payload.messages),
createdAt: action.payload.createdAt ?? now,
updatedAt: now,
integration: null,
isTitleGenerated: action.payload.isTitleGenerated,
};

Expand Down
1 change: 1 addition & 0 deletions src/features/Pages/pagesSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export interface IntegrationsSetupPage {
projectPath?: string;
integrationName?: string;
integrationPath?: string;
shouldIntermediatePageShowUp?: boolean;
}

export type Page =
Expand Down
26 changes: 19 additions & 7 deletions src/hooks/useGoToLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import { useAppDispatch } from "./useAppDispatch";
import { popBackTo } from "../features/Pages/pagesSlice";
import { useAppSelector } from "./useAppSelector";
import { selectIntegration } from "../features/Chat/Thread/selectors";
import { debugIntegrations } from "../debugConfig";

export function useGoToLink() {
const dispatch = useAppDispatch();
const { queryPathThenOpenFile } = useEventsBusForIDE();
const maybeIntegration = useAppSelector(selectIntegration);

const handleGoTo = useCallback(
(goto?: string) => {
({ goto }: { goto?: string }) => {
if (!goto) return;
// TODO: duplicated in smart links.
const [action, payload] = goto.split(":");
Expand All @@ -24,16 +25,25 @@ export function useGoToLink() {
}
case "settings": {
const isFile = isAbsolutePath(payload);
debugIntegrations(`[DEBUG]: maybeIntegration: `, maybeIntegration);
if (!maybeIntegration) {
debugIntegrations(`[DEBUG]: integration data is not available.`);
return;
}
dispatch(
popBackTo({
name: "integrations page",
// projectPath: isFile ? payload : "",
integrationName:
!isFile && payload !== "DEFAULT"
? payload
: maybeIntegration?.name,
integrationPath: isFile ? payload : maybeIntegration?.path,
projectPath: maybeIntegration?.project,
: maybeIntegration.name,
integrationPath: isFile ? payload : maybeIntegration.path,
projectPath: maybeIntegration.project,
shouldIntermediatePageShowUp:
payload !== "DEFAULT"
? maybeIntegration.shouldIntermediatePageShowUp
: false,
}),
);
// TODO: open in the integrations
Expand All @@ -48,9 +58,11 @@ export function useGoToLink() {
},
[
dispatch,
maybeIntegration?.name,
maybeIntegration?.path,
maybeIntegration?.project,
// maybeIntegration?.name,
// maybeIntegration?.path,
// maybeIntegration?.project,
// maybeIntegration?.shouldIntermediatePageShowUp,
maybeIntegration,
queryPathThenOpenFile,
],
);
Expand Down
Loading
Loading