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": "按名称、描述或类别搜索..." } +