diff --git a/app/components/editor/index.tsx b/app/components/editor/index.tsx
index 26e34965..ad85ce74 100755
--- a/app/components/editor/index.tsx
+++ b/app/components/editor/index.tsx
@@ -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';
@@ -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) => {
@@ -387,6 +393,7 @@ export const EditorScreen = () => {
handleEngineChange={handleEngineChange}
versions={versions}
engineGithubLinks={engineGithubLinks}
+ onDesignPolicy={handlePolicyDesign}
/>
@@ -604,6 +611,7 @@ export const EditorScreen = () => {
handleEngineChange={handleEngineChange}
versions={versions}
engineGithubLinks={engineGithubLinks}
+ onDesignPolicy={handlePolicyDesign}
/>
diff --git a/app/components/editor/panels/PolicyToolbar.tsx b/app/components/editor/panels/PolicyToolbar.tsx
index 4ce7347a..d7f892bd 100644
--- a/app/components/editor/panels/PolicyToolbar.tsx
+++ b/app/components/editor/panels/PolicyToolbar.tsx
@@ -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;
@@ -12,6 +14,7 @@ interface PolicyToolbarProps {
handleEngineChange: (newPrimary: EngineType, newComparison: EngineType[]) => void;
versions: Record
;
engineGithubLinks: Record;
+ onDesignPolicy: () => void;
}
export const PolicyToolbar: React.FC = ({
@@ -21,9 +24,18 @@ export const PolicyToolbar: React.FC = ({
handleEngineChange,
versions,
engineGithubLinks,
+ onDesignPolicy,
}) => {
const toolbarRef = useRef(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(() => {
@@ -51,6 +63,14 @@ export const PolicyToolbar: React.FC = ({
+ {/* Primary entry: trigger AI policy design (parent builds prompt and opens the side panel) */}
+
({});
- 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);
@@ -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);
@@ -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
@@ -98,13 +100,13 @@ export default function useIndex() {
return setEcho(Failed to load: {error}
);
});
}
- }, []);
+ }, [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)) {
diff --git a/app/utils/contentExtractor.ts b/app/utils/contentExtractor.ts
index 85936d43..350d84a4 100644
--- a/app/utils/contentExtractor.ts
+++ b/app/utils/contentExtractor.ts
@@ -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();
diff --git a/app/utils/policyDesignPrompt.ts b/app/utils/policyDesignPrompt.ts
new file mode 100644
index 00000000..ce6cea5c
--- /dev/null
+++ b/app/utils/policyDesignPrompt.ts
@@ -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');
+};
diff --git a/messages/ar.json b/messages/ar.json
index 3a3ce13d..d73e2a51 100644
--- a/messages/ar.json
+++ b/messages/ar.json
@@ -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.",
@@ -134,3 +135,4 @@
"Search models": "البحث عن النماذج...",
"Search placeholder": "البحث حسب الاسم أو الوصف أو الفئة..."
}
+
diff --git a/messages/de.json b/messages/de.json
index 376122d7..44cbaa3e 100644
--- a/messages/de.json
+++ b/messages/de.json
@@ -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.",
@@ -134,3 +135,4 @@
"Search models": "Modelle suchen...",
"Search placeholder": "Nach Name, Beschreibung oder Kategorie suchen..."
}
+
diff --git a/messages/en.json b/messages/en.json
index 08ce632f..eb1bc0f2 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -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.",
@@ -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"
}
+
diff --git a/messages/es.json b/messages/es.json
index 8bb88bc9..a44a98fe 100644
--- a/messages/es.json
+++ b/messages/es.json
@@ -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.",
@@ -134,3 +135,4 @@
"Search models": "Buscar modelos...",
"Search placeholder": "Buscar por nombre, descripción o categoría..."
}
+
diff --git a/messages/fr.json b/messages/fr.json
index 1096c584..65843886 100644
--- a/messages/fr.json
+++ b/messages/fr.json
@@ -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.",
@@ -134,3 +135,4 @@
"Search models": "Rechercher des modèles...",
"Search placeholder": "Rechercher par nom, description ou catégorie..."
}
+
diff --git a/messages/id.json b/messages/id.json
index 27b17c82..ba440289 100644
--- a/messages/id.json
+++ b/messages/id.json
@@ -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.",
@@ -134,3 +135,4 @@
"Search models": "Cari model...",
"Search placeholder": "Cari berdasarkan nama, deskripsi, atau kategori..."
}
+
diff --git a/messages/it.json b/messages/it.json
index e9558428..99425fc7 100644
--- a/messages/it.json
+++ b/messages/it.json
@@ -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.",
@@ -134,3 +135,4 @@
"Search models": "Cerca modelli...",
"Search placeholder": "Cerca per nome, descrizione o categoria..."
}
+
diff --git a/messages/ja.json b/messages/ja.json
index 10352ec3..e4a48405 100644
--- a/messages/ja.json
+++ b/messages/ja.json
@@ -63,6 +63,7 @@
"No policy relationships found": "ポリシー関係が見つかりません",
"(empty)": "(空)",
"Ask AI": "AIに質問",
+ "AI Policy Design": "AI Policy Design",
"Auto": "自動",
"Gallery description": "アクセス制御モデルのコレクションから閲覧して選択",
"Copyright": "著作権 © {year} Casbinコントリビューター。",
@@ -134,3 +135,4 @@
"Search models": "モデルを検索...",
"Search placeholder": "名前、説明、カテゴリで検索..."
}
+
diff --git a/messages/ko.json b/messages/ko.json
index df4b8c5e..cbff3f84 100644
--- a/messages/ko.json
+++ b/messages/ko.json
@@ -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 기여자.",
@@ -134,3 +135,4 @@
"Search models": "모델 검색...",
"Search placeholder": "이름, 설명 또는 카테고리로 검색..."
}
+
diff --git a/messages/ms.json b/messages/ms.json
index 3dd6eec1..64815f9c 100644
--- a/messages/ms.json
+++ b/messages/ms.json
@@ -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.",
@@ -134,3 +135,4 @@
"Search models": "Cari model...",
"Search placeholder": "Cari mengikut nama, penerangan atau kategori..."
}
+
diff --git a/messages/pt.json b/messages/pt.json
index 9a515262..f5a8913c 100644
--- a/messages/pt.json
+++ b/messages/pt.json
@@ -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.",
@@ -134,3 +135,4 @@
"Search models": "Pesquisar modelos...",
"Search placeholder": "Pesquisar por nome, descrição ou categoria..."
}
+
diff --git a/messages/ru.json b/messages/ru.json
index 02e0932a..40628a70 100644
--- a/messages/ru.json
+++ b/messages/ru.json
@@ -63,6 +63,7 @@
"No policy relationships found": "Отношения политики не найдены",
"(empty)": "(пусто)",
"Ask AI": "Спросить ИИ",
+ "AI Policy Design": "AI Policy Design",
"Auto": "Авто",
"Gallery description": "Изучите и выберите из нашей коллекции моделей контроля доступа",
"Copyright": "Авторские права © {year} Участники Casbin.",
@@ -134,3 +135,4 @@
"Search models": "Поиск моделей...",
"Search placeholder": "Поиск по названию, описанию или категории..."
}
+
diff --git a/messages/tr.json b/messages/tr.json
index 357f7f61..9e17d16a 100644
--- a/messages/tr.json
+++ b/messages/tr.json
@@ -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.",
@@ -134,3 +135,4 @@
"Search models": "Model ara...",
"Search placeholder": "Ada, açıklama veya kategoriye göre ara..."
}
+
diff --git a/messages/vi.json b/messages/vi.json
index 3aac21a7..715b065b 100644
--- a/messages/vi.json
+++ b/messages/vi.json
@@ -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.",
@@ -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..."
}
+
diff --git a/messages/zh-Hant.json b/messages/zh-Hant.json
index a12f83db..ce713139 100644
--- a/messages/zh-Hant.json
+++ b/messages/zh-Hant.json
@@ -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 貢獻者。",
@@ -134,3 +135,4 @@
"Search models": "搜尋模型...",
"Search placeholder": "按名稱、描述或類別搜尋..."
}
+
diff --git a/messages/zh.json b/messages/zh.json
index d798cad9..720d6d74 100644
--- a/messages/zh.json
+++ b/messages/zh.json
@@ -63,6 +63,7 @@
"Failed to download configuration": "配置下载失败",
"Model Gallery": "模型库",
"Ask AI": "询问 AI",
+ "AI Policy Design": "AI 设计策略",
"Auto": "自动",
"Gallery description": "从我们的访问控制模型集合中浏览和选择",
"Copyright": "版权所有 © {year} Casbin 贡献者。",
@@ -134,3 +135,4 @@
"Search models": "搜索模型...",
"Search placeholder": "按名称、描述或类别搜索..."
}
+