Skip to content
Closed
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
8 changes: 8 additions & 0 deletions app/components/editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { extractPageContent } from '@/app/utils/contentExtractor';
import { formatEngineResults, ResultsMap } from '@/app/utils/resultFormatter';
import { casbinLinter, createPolicyLinter, requestLinter } from '@/app/utils/casbinLinter';
import { isInsideIframe } from '@/app/utils/iframeDetector';
import { buildPolicyDesignPrompt } from '@/app/utils/policyDesignPrompt';
import { useLang } from '@/app/context/LangContext';
import { useAutoCarousel } from '@/app/context/AutoCarouselContext';
import type { EngineType } from '@/app/config/engineConfig';
Expand Down Expand Up @@ -90,6 +91,11 @@ export const EditorScreen = () => {
const { message } = extractPageContent(boxType, t, lang, customConfig);
return message;
}, [t, lang, customConfig]);
const handlePolicyDesign = useCallback(() => {
// Core logic: build prompt from current context and open the AI side panel
const prompt = buildPolicyDesignPrompt({ t, lang, customConfig });
openDrawerWithMessage(prompt);
}, [t, lang, customConfig, openDrawerWithMessage]);

// Wrapper functions that disable auto carousel before updating editor content
const handleModelTextChange = useCallback((value: string) => {
Expand Down Expand Up @@ -387,6 +393,7 @@ export const EditorScreen = () => {
handleEngineChange={handleEngineChange}
versions={versions}
engineGithubLinks={engineGithubLinks}
onDesignPolicy={handlePolicyDesign}
/>
</div>
<div className="flex-grow overflow-auto h-full rounded-lg border border-border shadow-sm bg-white dark:bg-slate-800">
Expand Down Expand Up @@ -604,6 +611,7 @@ export const EditorScreen = () => {
handleEngineChange={handleEngineChange}
versions={versions}
engineGithubLinks={engineGithubLinks}
onDesignPolicy={handlePolicyDesign}
/>
</div>
<div className="flex-grow overflow-auto h-full rounded-lg border border-border shadow-sm bg-white dark:bg-slate-800">
Expand Down
20 changes: 20 additions & 0 deletions app/components/editor/panels/PolicyToolbar.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { useEffect, useRef, useState } from 'react';
import { clsx } from 'clsx';
import { FileUploadButton } from '@/app/components/editor/common/FileUploadButton';
import { EngineSelector } from '@/app/components/editor/common/EngineSelector';
import { EndpointSelector } from '@/app/components/editor/common/EndpointSelector';
import type { EngineType } from '@/app/config/engineConfig';
import type { VersionInfo } from '@/app/components/hooks/useRemoteEnforcer';
import { useLang } from '@/app/context/LangContext';

interface PolicyToolbarProps {
setPolicyPersistent: (content: string) => void;
Expand All @@ -12,6 +14,7 @@ interface PolicyToolbarProps {
handleEngineChange: (newPrimary: EngineType, newComparison: EngineType[]) => void;
versions: Record<EngineType, VersionInfo>;
engineGithubLinks: Record<EngineType, string>;
onDesignPolicy: () => void;
}

export const PolicyToolbar: React.FC<PolicyToolbarProps> = ({
Expand All @@ -21,9 +24,18 @@ export const PolicyToolbar: React.FC<PolicyToolbarProps> = ({
handleEngineChange,
versions,
engineGithubLinks,
onDesignPolicy,
}) => {
const toolbarRef = useRef<HTMLDivElement | null>(null);
const [compactMode, setCompactMode] = useState(false);
const { t } = useLang();
const designButtonClassName = clsx(
'px-3 py-1.5 rounded-lg',
'border border-primary text-primary bg-secondary',
'hover:bg-primary hover:text-primary-foreground',
'transition-all duration-200 shadow-sm hover:shadow-md',
'font-medium text-sm whitespace-nowrap',
);

// Responsive behavior - use compact mode when space is tight
useEffect(() => {
Expand Down Expand Up @@ -51,6 +63,14 @@ export const PolicyToolbar: React.FC<PolicyToolbarProps> = ({
<div className="font-normal text-base">
<FileUploadButton onFileContent={setPolicyPersistent} accept=".csv" />
</div>
{/* Primary entry: trigger AI policy design (parent builds prompt and opens the side panel) */}
<button
type="button"
onClick={onDesignPolicy}
className={designButtonClassName}
>
{t('AI Policy Design')}
</button>
<EndpointSelector />
<EngineSelector
selectedEngine={selectedEngine}
Expand Down
16 changes: 9 additions & 7 deletions app/components/hooks/useIndex.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { isValidElement, ReactNode, useEffect, useRef, useState } from 'react';
import React, { isValidElement, ReactNode, useCallback, useEffect, useRef, useState } from 'react';
import { defaultCustomConfig, defaultEnforceContext, example } from '@/app/components/editor/casbin-mode/example';
import { ShareFormat } from '@/app/components/hooks/useShareInfo';
import { defaultEnforceContextData } from '@/app/components/hooks/useSetupEnforceContext';
Expand All @@ -23,10 +23,11 @@ export default function useIndex() {
loadedFromUrl?: boolean;
}>({});

const setSelectedEngine = (engine: EngineType) => {
// Persist selected engine for future sessions.
const setSelectedEngine = useCallback((engine: EngineType) => {
setSelectedEngineState(engine);
localStorage.setItem('selectedEngine', engine);
};
}, []);

function setPolicyPersistent(text: string): void {
setPolicy(text);
Expand All @@ -49,7 +50,8 @@ export default function useIndex() {
setEnforceContextData(new Map(map));
}

const updateAllStates = (newModelKind: string, shared?: ShareFormat) => {
// Helper: apply model/policy/request/custom config state in one place.
const updateAllStates = useCallback((newModelKind: string, shared?: ShareFormat) => {
const modelKindToUse = shared?.modelKind && shared.modelKind in example ? shared.modelKind : newModelKind;

setModelKind(modelKindToUse);
Expand All @@ -67,7 +69,7 @@ export default function useIndex() {
if (shared?.comparisonEngines) {
setComparisonEngines(shared.comparisonEngines as EngineType[]);
}
};
}, [setSelectedEngine]);

useEffect(() => {
// Check for URL query parameter for model selection
Expand Down Expand Up @@ -98,13 +100,13 @@ export default function useIndex() {
return setEcho(<div className="text-red-500">Failed to load: {error}</div>);
});
}
}, []);
}, [updateAllStates]);

useEffect(() => {
if (!modelText && !policy && !request && !loadState.current.loadedFromUrl) {
updateAllStates(modelKind);
}
}, [modelKind, modelText, policy, request]);
}, [modelKind, modelText, policy, request, updateAllStates]);

function handleShare(v: ReactNode | string) {
if (isValidElement(v)) {
Expand Down
8 changes: 6 additions & 2 deletions app/utils/contentExtractor.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
const cleanContent = (content: string, t?: (key: string) => string) => {
let result = content.replace(/^\d+\s+/gm, '');

// Remove translated "Ask AI" and "Explain it" if translation function is provided
// Remove translated button labels to avoid polluting extracted content
if (t) {
const askAI = t('Ask AI');
const explainIt = t('Explain it');
result = result.replace(new RegExp(askAI, 'g'), '').replace(new RegExp(explainIt, 'g'), '');
const designPolicy = t('AI Policy Design');
result = result
.replace(new RegExp(askAI, 'g'), '')
.replace(new RegExp(explainIt, 'g'), '')
.replace(new RegExp(designPolicy, 'g'), '');
}

return result.trim();
Expand Down
35 changes: 35 additions & 0 deletions app/utils/policyDesignPrompt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { extractPageContent } from '@/app/utils/contentExtractor';

type BuildPolicyDesignPromptParams = {
t: (key: string) => string;
lang: string;
customConfig?: string;
};

const casbinPolicyGuide = `
Key Casbin notes:
- Policy rules are CSV lines like "p, sub, obj, act" (ACL example).
- RBAC role inheritance uses "g, user, role" (or g2/g3 if defined in model).
- Follow the model's [policy_definition] and [role_definition] fields strictly.
- Keep the rule set minimal and consistent with the request definition.
`;

// Key function: build the prompt for AI policy design using extracted page context.
export const buildPolicyDesignPrompt = ({ t, lang, customConfig }: BuildPolicyDesignPromptParams) => {
// Core logic: reuse content extraction to assemble context and avoid duplicate parsing.
const { extractedContent } = extractPageContent('policy', t, lang, customConfig);

// Edge case: if context is missing (e.g. "No ... found"), treat it as empty and be conservative.
return [
`Please answer in ${lang} language.`,
`You are a Casbin policy expert.`,
`Task: Design Casbin policy rules based on the context and produce a short explanation.`,
`Output format:`,
`1) Policy Rules (CSV, one rule per line, only p/g/g2/g3 lines).`,
`2) Explanation (brief, 3-6 sentences).`,
casbinPolicyGuide.trim(),
`Context:`,
extractedContent,
`Notes: If any section says "No ... found", treat it as empty and infer safely.`,
].join('\n');
};
2 changes: 2 additions & 0 deletions messages/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"No policy relationships found": "No policy relationships found",
"(empty)": "(فارغ)",
"Ask AI": "اسأل الذكاء الاصطناعي",
"AI Policy Design": "AI Policy Design",
"Auto": "تلقائي",
"Gallery description": "استكشف واختر من مجموعة نماذج التحكم في الوصول لدينا",
"Copyright": "حقوق النشر © {year} مساهمو Casbin.",
Expand Down Expand Up @@ -134,3 +135,4 @@
"Search models": "البحث عن النماذج...",
"Search placeholder": "البحث حسب الاسم أو الوصف أو الفئة..."
}

2 changes: 2 additions & 0 deletions messages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"No policy relationships found": "No policy relationships found",
"(empty)": "(leer)",
"Ask AI": "KI fragen",
"AI Policy Design": "AI Policy Design",
"Auto": "Auto",
"Gallery description": "Durchsuchen und wählen Sie aus unserer Sammlung von Zugriffskontrollmodellen",
"Copyright": "Copyright © {year} Casbin-Mitwirkende.",
Expand Down Expand Up @@ -134,3 +135,4 @@
"Search models": "Modelle suchen...",
"Search placeholder": "Nach Name, Beschreibung oder Kategorie suchen..."
}

2 changes: 2 additions & 0 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"Configuration downloaded successfully": "Configuration downloaded successfully",
"Failed to download configuration": "Failed to download configuration",
"Ask AI": "Ask AI",
"AI Policy Design": "AI Policy Design",
"Auto": "Auto",
"Gallery description": "Explore and select from our collection of access control models",
"Copyright": "Copyright © {year} Casbin contributors.",
Expand Down Expand Up @@ -140,3 +141,4 @@
"Custom Configuration": "Custom Configuration",
"Open in editor to see enforcement results for the example request": "Open in editor to see enforcement results for the example request"
}

2 changes: 2 additions & 0 deletions messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"No policy relationships found": "No policy relationships found",
"(empty)": "(vacío)",
"Ask AI": "Preguntar a IA",
"AI Policy Design": "AI Policy Design",
"Auto": "Auto",
"Gallery description": "Explore y seleccione de nuestra colección de modelos de control de acceso",
"Copyright": "Copyright © {year} Colaboradores de Casbin.",
Expand Down Expand Up @@ -134,3 +135,4 @@
"Search models": "Buscar modelos...",
"Search placeholder": "Buscar por nombre, descripción o categoría..."
}

2 changes: 2 additions & 0 deletions messages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"No policy relationships found": "No policy relationships found",
"(empty)": "(vide)",
"Ask AI": "Demander à l'IA",
"AI Policy Design": "AI Policy Design",
"Auto": "Auto",
"Gallery description": "Explorez et sélectionnez parmi notre collection de modèles de contrôle d'accès",
"Copyright": "Copyright © {year} Contributeurs Casbin.",
Expand Down Expand Up @@ -134,3 +135,4 @@
"Search models": "Rechercher des modèles...",
"Search placeholder": "Rechercher par nom, description ou catégorie..."
}

2 changes: 2 additions & 0 deletions messages/id.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"No policy relationships found": "No policy relationships found",
"(empty)": "(kosong)",
"Ask AI": "Tanya AI",
"AI Policy Design": "AI Policy Design",
"Auto": "Otomatis",
"Gallery description": "Jelajahi dan pilih dari koleksi model kontrol akses kami",
"Copyright": "Hak Cipta © {year} Kontributor Casbin.",
Expand Down Expand Up @@ -134,3 +135,4 @@
"Search models": "Cari model...",
"Search placeholder": "Cari berdasarkan nama, deskripsi, atau kategori..."
}

2 changes: 2 additions & 0 deletions messages/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"No policy relationships found": "No policy relationships found",
"(empty)": "(vuoto)",
"Ask AI": "Chiedi all'IA",
"AI Policy Design": "AI Policy Design",
"Auto": "Auto",
"Gallery description": "Esplora e seleziona dalla nostra collezione di modelli di controllo degli accessi",
"Copyright": "Copyright © {year} Collaboratori Casbin.",
Expand Down Expand Up @@ -134,3 +135,4 @@
"Search models": "Cerca modelli...",
"Search placeholder": "Cerca per nome, descrizione o categoria..."
}

2 changes: 2 additions & 0 deletions messages/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"No policy relationships found": "ポリシー関係が見つかりません",
"(empty)": "(空)",
"Ask AI": "AIに質問",
"AI Policy Design": "AI Policy Design",
"Auto": "自動",
"Gallery description": "アクセス制御モデルのコレクションから閲覧して選択",
"Copyright": "著作権 © {year} Casbinコントリビューター。",
Expand Down Expand Up @@ -134,3 +135,4 @@
"Search models": "モデルを検索...",
"Search placeholder": "名前、説明、カテゴリで検索..."
}

2 changes: 2 additions & 0 deletions messages/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"No policy relationships found": "No policy relationships found",
"(empty)": "(비어 있음)",
"Ask AI": "AI에게 물어보기",
"AI Policy Design": "AI Policy Design",
"Auto": "자동",
"Gallery description": "액세스 제어 모델 컬렉션에서 탐색하고 선택하세요",
"Copyright": "저작권 © {year} Casbin 기여자.",
Expand Down Expand Up @@ -134,3 +135,4 @@
"Search models": "모델 검색...",
"Search placeholder": "이름, 설명 또는 카테고리로 검색..."
}

2 changes: 2 additions & 0 deletions messages/ms.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"No policy relationships found": "No policy relationships found",
"(empty)": "(kosong)",
"Ask AI": "Tanya AI",
"AI Policy Design": "AI Policy Design",
"Auto": "Auto",
"Gallery description": "Terokai dan pilih daripada koleksi model kawalan akses kami",
"Copyright": "Hak Cipta © {year} Penyumbang Casbin.",
Expand Down Expand Up @@ -134,3 +135,4 @@
"Search models": "Cari model...",
"Search placeholder": "Cari mengikut nama, penerangan atau kategori..."
}

2 changes: 2 additions & 0 deletions messages/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"No policy relationships found": "No policy relationships found",
"(empty)": "(vazio)",
"Ask AI": "Perguntar à IA",
"AI Policy Design": "AI Policy Design",
"Auto": "Auto",
"Gallery description": "Explore e selecione da nossa coleção de modelos de controle de acesso",
"Copyright": "Copyright © {year} Colaboradores do Casbin.",
Expand Down Expand Up @@ -134,3 +135,4 @@
"Search models": "Pesquisar modelos...",
"Search placeholder": "Pesquisar por nome, descrição ou categoria..."
}

2 changes: 2 additions & 0 deletions messages/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"No policy relationships found": "Отношения политики не найдены",
"(empty)": "(пусто)",
"Ask AI": "Спросить ИИ",
"AI Policy Design": "AI Policy Design",
"Auto": "Авто",
"Gallery description": "Изучите и выберите из нашей коллекции моделей контроля доступа",
"Copyright": "Авторские права © {year} Участники Casbin.",
Expand Down Expand Up @@ -134,3 +135,4 @@
"Search models": "Поиск моделей...",
"Search placeholder": "Поиск по названию, описанию или категории..."
}

2 changes: 2 additions & 0 deletions messages/tr.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"No policy relationships found": "Politika ilişkisi bulunamadı",
"(empty)": "(boş)",
"Ask AI": "Yapay Zekaya Sor",
"AI Policy Design": "AI Policy Design",
"Auto": "Otomatik",
"Gallery description": "Erişim kontrol modelleri koleksiyonumuzu keşfedin ve seçin",
"Copyright": "Telif hakkı © {year} Casbin katkıda bulunanlar.",
Expand Down Expand Up @@ -134,3 +135,4 @@
"Search models": "Model ara...",
"Search placeholder": "Ada, açıklama veya kategoriye göre ara..."
}

2 changes: 2 additions & 0 deletions messages/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"No policy relationships found": "No policy relationships found",
"(empty)": "(trống)",
"Ask AI": "Hỏi AI",
"AI Policy Design": "AI Policy Design",
"Auto": "Tự động",
"Gallery description": "Khám phá và chọn từ bộ sưu tập mô hình kiểm soát truy cập của chúng tôi",
"Copyright": "Bản quyền © {year} Cộng tác viên Casbin.",
Expand Down Expand Up @@ -134,3 +135,4 @@
"Search models": "Tìm kiếm mô hình...",
"Search placeholder": "Tìm kiếm theo tên, mô tả hoặc danh mục..."
}

2 changes: 2 additions & 0 deletions messages/zh-Hant.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"No policy relationships found": "No policy relationships found",
"(empty)": "(空)",
"Ask AI": "詢問 AI",
"AI Policy Design": "AI 設計策略",
"Auto": "自動",
"Gallery description": "從我們的訪問控制模型集合中瀏覽和選擇",
"Copyright": "版權所有 © {year} Casbin 貢獻者。",
Expand Down Expand Up @@ -134,3 +135,4 @@
"Search models": "搜尋模型...",
"Search placeholder": "按名稱、描述或類別搜尋..."
}

Loading