diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 31cf3d32..00000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index 87421630..c5619750 100644 --- a/.gitignore +++ b/.gitignore @@ -1,237 +1,194 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[codz] -*$py.class - -# C extensions -*.so -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -# Only ignore a top-level lib/ (Python/build); do not ignore e.g. collab-guard/src/client/lib -/lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller +# Abstra is an AI-powered process automation framework. +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# For a library or package, you might want to ignore these files since the code is +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# Ignore directories containing user credentials, local state, and settings. +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# Learn more at https://abstra.io/docs +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more # Usually these files are written by a python script from a template +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py.cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - +# commonly ignored for libraries. +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +# in the .venv directory. It is recommended not to include this directory in version control. +# install all needed dependencies. +# intended to run in multiple environments; otherwise, check them in: +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# you could uncomment the following to ignore the entire vscode folder +# .agents/ai-workflow-template/ +# .idea/ +# .python-version +# .vscode/ +# Abstra +# ActiveMQ +# Byte-compiled / optimized / DLL files +# C extensions +# Celery stuff +# Claude Code settings +# Cython debug symbols +# Distribution / packaging # Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - +# Environments # Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - # IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. +# Installer logs +# Jupyter Notebook +# Marimo +# Only ignore a top-level lib/ (Python/build); do not ignore e.g. collab-guard/src/client/lib +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm # Pipfile.lock - +# PyBuilder +# PyCharm +# PyInstaller +# PyPI configuration file +# Pyre type checker +# Qwen Code settings +# RabbitMQ +# Redis +# Rope project settings +# Ruff stuff: +# SageMath parsed files +# Scrapy stuff: +# Sphinx documentation +# Spyder project settings +# Streamlit +# Temporary file for partial code execution +# Translations # UV -# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# uv.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -# poetry.lock -# poetry.toml - +# Unit test / coverage reports +# Visual Studio Code +# mkdocs documentation +# mypy # pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. -# https://pdm-project.org/en/latest/usage/project/#working-with-version-control # pdm.lock # pdm.toml -.pdm-python -.pdm-build/ - +# pipenv # pixi -# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. # pixi.lock -# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one -# in the .venv directory. It is recommended not to include this directory in version control. -.pixi - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# Redis -*.rdb +# poetry +# poetry.lock +# poetry.toml +# pyenv +# pytype static type analyzer +# uv.lock +#Extension +*$py.class *.aof +*.cover +*.egg +*.egg-info/ +*.log +*.manifest +*.mo *.pid - -# RabbitMQ -mnesia/ -rabbitmq/ -rabbitmq-data/ - -# ActiveMQ -activemq-data/ - -# SageMath parsed files +*.pot +*.py.cover +*.py[codz] +*.rdb *.sage.py - -# Environments +*.so +*.spec +.DS_Store +.Python +.abstra/ +.cache +.claude/ +.coverage +.coverage.* +.dmypy.json +.eggs/ .env .envrc -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy +.hypothesis/ +.installed.cfg +.ipynb_checkpoints +.kiro/ .mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker +.nox/ +.pdm-build/ +.pdm-python +.pixi +.pybuilder/ +.pypirc .pyre/ - -# pytype static type analyzer +.pytest_cache/ .pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -# .idea/ - -# Abstra -# Abstra is an AI-powered process automation framework. -# Ignore directories containing user credentials, local state, and settings. -# Learn more at https://abstra.io/docs -.abstra/ - -# Visual Studio Code -# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore -# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore -# and can be added to the global gitignore or merged into this file. However, if you prefer, -# you could uncomment the following to ignore the entire vscode folder -# .vscode/ -# Temporary file for partial code execution -tempCodeRunnerFile.py - -# Ruff stuff: +.qwen/ +.ropeproject .ruff_cache/ - -# PyPI configuration file -.pypirc - -# Marimo -marimo/_static/ -marimo/_lsp/ -__marimo__/ - -# Streamlit +.scrapy +.spyderproject +.spyproject .streamlit/secrets.toml - -# Qwen Code settings -.qwen/ -gha-creds-*.json - -#Extension -extension/node_modules -extension/dist - -# Claude Code settings -.claude/ .tools/* .tools/mcp-context-manager/* -.kiro/ +.tox/ +.venv .vscode/ -cluster-config.json +.webassets-cache +/lib/ +/site +ENV/ +MANIFEST REDDIT UI CONTEXT FOR EXTENSION/ -# .agents/ai-workflow-template/ +__marimo__/ +__pycache__/ +__pypackages__/ +activemq-data/ +build/ +celerybeat-schedule +celerybeat.pid +cluster-config.json +cover/ +coverage.xml +cython_debug/ +db.sqlite3 +db.sqlite3-journal +develop-eggs/ +dist/ +dmypy.json +docs/_build/ +downloads/ +eggs/ +env.bak/ +env/ +extension/dist +extension/node_modules +gha-creds-*.json +htmlcov/ +instance/ +ipython_config.py +lib64/ +local_settings.py +marimo/_lsp/ +marimo/_static/ +mnesia/ +nosetests.xml +parts/ +pip-delete-this-directory.txt +pip-log.txt +profile_default/ +rabbitmq-data/ +rabbitmq/ +sdist/ +share/python-wheels/ +target/ +tempCodeRunnerFile.py +var/ +venv.bak/ +venv/ +wheels/ diff --git a/collab-guard/README.md b/collab-guard/README.md index 262ed6d9..54094d54 100644 --- a/collab-guard/README.md +++ b/collab-guard/README.md @@ -125,28 +125,28 @@ If Gentle Nudge is enabled, create a mild low-effort test comment and confirm: CollabGuard's AI features are advisory by default. They help moderators see what the model reviewed, what it suggested, why a guardrail skipped work, and where a human should make the final call. The AI Activity page reads from the same subreddit-scoped audit log as the rest of the workspace, so demo reviewers do not need terminal logs to understand what happened. -### Provider setup +### Provider setup (moderators — recommended) -Choose one provider for playtest. Gemini is the default when `AI_PROVIDER` is not set. +On a subreddit where CollabGuard is installed: -```bash -npx devvit settings set GEMINI_API_KEY -# enter: your Gemini API key +1. Open **mod tools** on the subreddit (⋯ menu). +2. Choose **Set AI API Keys**. +3. Select **Gemini** or **OpenAI**, paste the key, confirm write-only storage, and **Save**. -npx devvit settings set OPENAI_API_KEY -# enter: your OpenAI API key, if using OpenAI instead +Keys are stored **per subreddit in Redis** (write-only). Other moderators cannot read an existing key; they can only replace it. Then enable features in **Settings → AI Mod Tool** in the dashboard. -npx devvit settings set AI_PROVIDER -# enter: gemini or openai +### Provider setup (app developer fallback) -npx devvit settings set AI_VIOLATION_THRESHOLD -# optional: 0-10 score threshold for high-risk advisory flags, default 7 +Optional global fallback for playtest (shared across installations): +```bash +npx devvit settings set GEMINI_API_KEY +npx devvit settings set OPENAI_API_KEY +npx devvit settings set AI_PROVIDER # gemini or openai npx devvit settings list -# verify the settings are present ``` -Do not commit provider keys. Devvit settings are stored by the platform, and the external AI domains are already allow-listed in `devvit.json`. +Subreddit Redis keys take precedence over Devvit global settings. Do not commit provider keys. ### Dashboard setup diff --git a/collab-guard/devvit.json b/collab-guard/devvit.json index ad0883c2..25d7f524 100644 --- a/collab-guard/devvit.json +++ b/collab-guard/devvit.json @@ -1,6 +1,6 @@ { "$schema": "https://developers.reddit.com/schema/config-file.v1.json", - "name": "cgbaov2", + "name": "collab-guard-", "post": { "dir": "dist/client", "entrypoints": { @@ -84,6 +84,13 @@ "location": "subreddit", "forUserType": "moderator", "endpoint": "/internal/menu/open-settings" + }, + { + "label": "CollabGuard: Set AI API Keys", + "description": "Store a Gemini or OpenAI key for this subreddit (write-only; mods cannot read it back).", + "location": "subreddit", + "forUserType": "moderator", + "endpoint": "/internal/menu/set-ai-api-keys" } ] }, @@ -91,7 +98,8 @@ "addCaseNote": "/internal/form/add-case-note", "sendGentleNudge": "/internal/form/send-gentle-nudge", "startConsensusReview": "/internal/form/start-consensus-review", - "resolveAndLog": "/internal/form/resolve-and-log" + "resolveAndLog": "/internal/form/resolve-and-log", + "setAiApiKeys": "/internal/form/set-ai-api-keys" }, "triggers": { "onAppInstall": "/internal/triggers/on-app-install", @@ -177,6 +185,6 @@ } }, "dev": { - "subreddit": "ModQueueLab" + "subreddit": "collab_guard__dev" } } \ No newline at end of file diff --git a/collab-guard/package.json b/collab-guard/package.json index d81c6d92..d9224ead 100644 --- a/collab-guard/package.json +++ b/collab-guard/package.json @@ -1,6 +1,6 @@ { "private": true, - "name": "kim-chi-ngon-lam", + "name": "collab-guard-", "version": "0.0.0", "license": "BSD-3-Clause", "type": "module", diff --git a/collab-guard/src/client/components/AutoModBuilder.tsx b/collab-guard/src/client/components/AutoModBuilder.tsx index e3a1c500..de6905c5 100644 --- a/collab-guard/src/client/components/AutoModBuilder.tsx +++ b/collab-guard/src/client/components/AutoModBuilder.tsx @@ -1,6 +1,7 @@ -import { useMemo, useState } from 'react'; +import { useMemo, useState, type ReactNode } from 'react'; import { + AUTOMOD_RULE_TEMPLATES, generateYaml, simulateAutoModRule, type AutoModRuleFormConfig, @@ -17,6 +18,21 @@ const TEMPLATE_LABELS: Record = { civility_reminder: 'Civility reminder', }; +const TEMPLATE_HELP: Record = { + new_account_filter: 'Filter or report posts from accounts below a minimum age.', + low_karma_filter: 'Filter or report posts from low combined-karma accounts.', + high_report_alert: 'Alert moderators when content reaches a report threshold.', + required_flair: 'Require post flair or leave a reminder when flair is missing.', + banned_domain_filter: 'Filter or report links to disallowed domains.', + civility_reminder: 'Leave a constructive reminder when watch phrases appear.', +}; + +const SIMULATOR_TEMPLATES = new Set([ + 'required_flair', + 'banned_domain_filter', + 'civility_reminder', +]); + const DEFAULT_CONFIG: Record = { new_account_filter: { itemType: 'any', @@ -54,16 +70,6 @@ const DEFAULT_CONFIG: Record = { }, }; -const TEMPLATE_HELP: Record = { - new_account_filter: 'Drafts a conservative age-based participation filter.', - low_karma_filter: 'Drafts a conservative karma-based participation filter.', - high_report_alert: 'Alerts moderators when content receives enough reports.', - required_flair: 'Requires post flair or leaves a reminder for missing flair.', - banned_domain_filter: 'Filters or reports links to domains the subreddit disallows.', - civility_reminder: - 'Leaves a non-punitive reminder when configured phrases appear.', -}; - const setConfigValue = ( config: AutoModRuleFormConfig, key: string, @@ -73,32 +79,54 @@ const setConfigValue = ( [key]: value, }); -const SimulatorResult = ({ - result, -}: { - result: AutoModSimulationResult; -}) => ( -
-

- {result.status === 'match' - ? 'Match' - : result.status === 'unsupported' - ? 'Simulator not available' - : 'No match'} -

-

{result.explanation}

- {result.condition &&

Condition: {result.condition}

} -
+type FieldProps = { + label: string; + hint?: string; + children: ReactNode; +}; + +const Field = ({ label, hint, children }: FieldProps) => ( + ); +const selectClass = + 'cg-control w-full border border-input bg-background px-3 py-2 text-sm text-foreground outline-none focus:border-ring'; + +const textAreaClass = + 'cg-control w-full border border-input bg-background px-3 py-2 text-sm text-foreground outline-none focus:border-ring'; + +const SimulatorResult = ({ result }: { result: AutoModSimulationResult }) => { + const tone = + result.status === 'match' + ? 'border-amber-300/50 bg-amber-500/10 text-amber-950 dark:text-amber-100' + : result.status === 'unsupported' + ? 'border-border bg-muted/40 text-muted-foreground' + : 'border-emerald-300/50 bg-emerald-500/10 text-emerald-950 dark:text-emerald-100'; + + const title = + result.status === 'match' + ? 'Match' + : result.status === 'unsupported' + ? 'Simulator not available' + : 'No match'; + + return ( +
+

{title}

+

{result.explanation}

+ {result.condition ? ( +

Condition: {result.condition}

+ ) : null} +
+ ); +}; + export const AutoModBuilder = () => { const [template, setTemplate] = useState('required_flair'); @@ -108,6 +136,7 @@ export const AutoModBuilder = () => { const [sampleText, setSampleText] = useState(''); const [simulationResult, setSimulationResult] = useState(null); + const [copyState, setCopyState] = useState<'idle' | 'copied' | 'error'>('idle'); const generatedYaml = useMemo(() => { try { @@ -117,10 +146,13 @@ export const AutoModBuilder = () => { } }, [formConfig, template]); + const yamlError = generatedYaml.startsWith('Unsupported') || generatedYaml.includes('Unable'); + const updateTemplate = (nextTemplate: AutoModRuleTemplate) => { setTemplate(nextTemplate); setFormConfig(DEFAULT_CONFIG[nextTemplate]); setSimulationResult(null); + setCopyState('idle'); }; const updateField = (key: string, value: string | number) => { @@ -128,220 +160,356 @@ export const AutoModBuilder = () => { setSimulationResult(null); }; - const commonTargetField = ( - + ); - return ( -
-
-

AutoMod Builder

-

- Draft safe AutoModerator YAML. CollabGuard does not apply rules - automatically; copy and review before deploying. -

-
+ const actionField = (options: Array<{ value: string; label: string }>) => ( + + + + ); -
-
- -

- {TEMPLATE_HELP[template]} -

- - {template !== 'required_flair' && commonTargetField} - - {template === 'required_flair' && ( - <> - -