Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion core/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ export interface ChatHistoryItem {
toolCallStates?: ToolCallState[];
isGatheringContext?: boolean;
reasoning?: Reasoning;
appliedRules?: RuleWithSource[];
appliedRules?: Omit<RuleWithSource, "rule">[];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can keep the AppliedRule type and just say type AppliedRule = Omit<RuleWithSource, "rule"> to prevent having Omits all over

conversationSummary?: string;
}

Expand Down
2 changes: 1 addition & 1 deletion gui/src/components/mainInput/ContinueInputBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface ContinueInputBoxProps {
) => void;
editorState?: JSONContent;
contextItems?: ContextItemWithId[];
appliedRules?: RuleWithSource[];
appliedRules?: Omit<RuleWithSource, "rule">[];
hidden?: boolean;
inputId: string; // used to keep track of things per input in redux
}
Expand Down
56 changes: 28 additions & 28 deletions gui/src/components/mainInput/Lump/sections/RulesSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,33 @@ interface RuleCardProps {
rule: RuleWithSource;
}

export const openRules = async (rule: Omit<RuleWithSource, "rule">) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could take RuleWithSource | AppliedRule

const ideMessenger = useContext(IdeMessengerContext);
Copy link
Author

@etherandrius etherandrius Aug 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ideMessenger this was define outside the function before. I assume the useContext(IdeMessengerContext) call is basically free and we can call it inside the function.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will need to pass ideMessenger for this one since can't use hooks outside of top level of component

if (rule.slug) {
void ideMessenger.request("controlPlane/openUrl", {
path: `${rule.slug}/new-version`,
orgSlug: undefined,
});
} else if (rule.ruleFile) {
ideMessenger.post("openFile", {
path: rule.ruleFile,
});
} else if (
rule.source === "default-chat" ||
rule.source === "default-plan" ||
rule.source === "default-agent"
) {
ideMessenger.post("openUrl", DEFAULT_SYSTEM_MESSAGES_URL);
} else {
ideMessenger.post("config/openProfile", {
profileId: undefined,
element: { sourceFile: (rule as any).sourceFile },
});
}
};

const RuleCard: React.FC<RuleCardProps> = ({ rule }) => {
const dispatch = useAppDispatch();
const ideMessenger = useContext(IdeMessengerContext);
const mode = useAppSelector((store) => store.session.mode);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused.

const policy = useAppSelector((state) =>
rule.name
? state.ui.ruleSettings[rule.name] || DEFAULT_RULE_SETTING
Expand All @@ -41,29 +64,6 @@ const RuleCard: React.FC<RuleCardProps> = ({ rule }) => {

const isDisabled = policy === "off";

const handleOpen = async () => {
if (rule.slug) {
void ideMessenger.request("controlPlane/openUrl", {
path: `${rule.slug}/new-version`,
orgSlug: undefined,
});
} else if (rule.ruleFile) {
ideMessenger.post("openFile", {
path: rule.ruleFile,
});
} else if (
rule.source === "default-chat" ||
rule.source === "default-plan" ||
rule.source === "default-agent"
) {
ideMessenger.post("openUrl", DEFAULT_SYSTEM_MESSAGES_URL);
} else {
ideMessenger.post("config/openProfile", {
profileId: undefined,
element: { sourceFile: (rule as any).sourceFile },
});
}
};

const handleTogglePolicy = () => {
if (rule.name) {
Expand Down Expand Up @@ -139,12 +139,12 @@ const RuleCard: React.FC<RuleCardProps> = ({ rule }) => {
<ArrowsPointingOutIcon className="h-3 w-3 text-gray-400" />
</HeaderButtonWithToolTip>{" "}
{rule.source === "default-chat" ||
rule.source === "default-agent" ? (
<HeaderButtonWithToolTip onClick={handleOpen} text="View">
rule.source === "default-agent" ? (
<HeaderButtonWithToolTip onClick={() => openRules(rule)} text="View">
<EyeIcon className="h-3 w-3 text-gray-400" />
</HeaderButtonWithToolTip>
) : (
<HeaderButtonWithToolTip onClick={handleOpen} text="Edit">
<HeaderButtonWithToolTip onClick={() => openRules(rule)} text="Edit">
<PencilIcon className="h-3 w-3 text-gray-400" />
</HeaderButtonWithToolTip>
)}
Expand Down
48 changes: 10 additions & 38 deletions gui/src/components/mainInput/belowMainInput/RulesPeek.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import { DocumentTextIcon, GlobeAltIcon } from "@heroicons/react/24/outline";
import { RuleWithSource } from "core";
import { getLastNPathParts } from "core/util/uri";
import { ComponentType, useMemo, useState } from "react";
import { ComponentType, useContext, useMemo } from "react";
import ToggleDiv from "../../ToggleDiv";
import { IdeMessengerContext } from "../../../context/IdeMessenger";
import { DEFAULT_SYSTEM_MESSAGES_URL } from "core/llm/defaultSystemMessages";
import { RuleWithSource } from "core";
import { openRules } from "../Lump/sections/RulesSection";

interface RulesPeekProps {
appliedRules?: RuleWithSource[];
appliedRules?: Omit<RuleWithSource, "rule">[];
icon?: ComponentType<React.SVGProps<SVGSVGElement>>;
}

interface RulesPeekItemProps {
rule: RuleWithSource;
rule: Omit<RuleWithSource, "rule">;
}

// Convert technical source to user-friendly text
const getSourceLabel = (rule: RuleWithSource): string => {
const getSourceLabel = (rule: Omit<RuleWithSource, "rule">): string => {
switch (rule.source) {
case "default-chat":
return "Default Chat";
Expand Down Expand Up @@ -45,29 +48,11 @@ const getSourceLabel = (rule: RuleWithSource): string => {

export function RulesPeekItem({ rule }: RulesPeekItemProps) {
const isGlobal = rule.alwaysApply ?? !rule.globs;
const [expanded, setExpanded] = useState(false);

// Define maximum length for rule text display
const maxRuleLength = 100;
const isRuleLong = rule.rule.length > maxRuleLength;

// Get the displayed rule text based on expanded state
const displayedRule =
isRuleLong && !expanded
? `${rule.rule.slice(0, maxRuleLength)}...`
: rule.rule;

const toggleExpand = () => {
if (isRuleLong) {
setExpanded(!expanded);
}
};

return (
<div
className={`group mr-2 flex flex-col overflow-hidden rounded px-1.5 py-1 text-xs hover:bg-white/10 ${isRuleLong ? "cursor-pointer hover:text-gray-200" : ""}`}
className={`group mr-2 flex flex-col overflow-hidden rounded px-1.5 py-1 text-xs hover:bg-white/10`}
data-testid="rules-peek-item"
onClick={toggleExpand}
onClick={() => openRules(rule)}
>
<div className="flex w-full items-center">
{isGlobal ? (
Expand All @@ -88,19 +73,6 @@ export function RulesPeekItem({ rule }: RulesPeekItemProps) {
</div>
</div>
</div>
<div
className={`mt-1 whitespace-pre-line pl-6 pr-2 text-xs italic text-gray-300`}
title={
isRuleLong ? (expanded ? "Click to collapse" : "Click to expand") : ""
}
>
{displayedRule}
{isRuleLong && (
<span className="text-description-muted ml-1 opacity-0 transition-opacity group-hover:opacity-100">
{expanded ? "(collapse)" : "(expand)"}
</span>
)}
</div>
<div className="mt-1 pl-6 pr-2 text-xs text-gray-500">
Source: {getSourceLabel(rule)}
</div>
Expand Down
4 changes: 2 additions & 2 deletions gui/src/redux/slices/sessionSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from "@reduxjs/toolkit";
import { JSONContent } from "@tiptap/react";
import {
AppliedRule,
ApplyState,
AssistantChatMessage,
ChatHistoryItem,
Expand All @@ -16,7 +17,6 @@ import {
FileSymbolMap,
MessageModes,
PromptLog,
RuleWithSource,
Session,
SessionMetadata,
ThinkingChatMessage,
Expand Down Expand Up @@ -503,7 +503,7 @@ export const sessionSlice = createSlice({
payload,
}: PayloadAction<{
index: number;
appliedRules: RuleWithSource[];
appliedRules: ChatHistoryItem["appliedRules"];
}>,
) => {
if (state.history[payload.index]) {
Expand Down
2 changes: 1 addition & 1 deletion gui/src/redux/thunks/streamNormalInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ export const streamNormalInput = createAsyncThunk<
dispatch(
setAppliedRulesAtIndex({
index: appliedRuleIndex,
appliedRules: appliedRules,
appliedRules: { ...appliedRules },
}),
);

Expand Down
Loading