chore(infra): static AI compose prompts, sync-prod refresh, and deps
Payload config seeds AI Compose instructions statically so wiped prompt tables rebuild without LLM calls; sync-prod pairs with that per the config comment. Adds lexical deps and changelog entries. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
parent
67971dd7e6
commit
a2ab066645
5 changed files with 201 additions and 6 deletions
17
CHANGELOG.md
17
CHANGELOG.md
|
|
@ -1,5 +1,22 @@
|
|||
# Changelog
|
||||
|
||||
# v0.2.36
|
||||
|
||||
Poker takes the spotlight in this stretch: a rebuilt table, synthesized sound, card skins, and glowing hand ranks. Alongside it, the awards program learns advancement series, and poker placement XP is paused while payouts get reworked; placements, hands won, and best chips still record as normal.
|
||||
|
||||
### Features
|
||||
|
||||
- **Poker tables**: hands now play out on a full oval felt with a center deck and a pot plaque, and seats read as compact pills whose stack counts flash when they change. Your own cards get dealt onto the bottom rail at community-card size, every action (fold, check, call, raise, all-in, pot win) floats over the acting seat, and each new hand opens with a small-blind then big-blind intro.
|
||||
- **Sound**: a fully synthesized soundscape: deal whooshes, chip clacks, turn chimes, and win stingers, backed by a mellow background jazz loop. Mute and volume controls sit in the header and remember your choice.
|
||||
- **Awards advancement series**: ribbons can now belong to advancement lineups, like a years-of-service ladder. The grant flow understands series, and your ribbon rack collapses the tiers you hold into your highest earned ribbon instead of stacking duplicates.
|
||||
- **Hand rank styling**: made hands wear a tier ladder that climbs from a plain high card through brass, silver, and gold pair treatments to a sapphire straight flush and a prismatic royal flush. Your live hand hint placard glows with the tier of what you're holding, and the guide's ranking table is rebuilt into tier columns to match.
|
||||
- **Card skins**: pick from three decks in the table header: the new light cards, the previous dark theme, or the classic printed deck. Your pick persists.
|
||||
|
||||
### Improvements
|
||||
|
||||
- **Poker leave handling**: leaving is announced on the felt right away, and a game that drops to one seated player ends immediately with a notification instead of waiting out the hand timer.
|
||||
- **Lobby stability**: background refreshes no longer blink the whole lobby into a scanning state every few seconds; the refresh spinner is reserved for manual refreshes.
|
||||
|
||||
# v0.2.33
|
||||
|
||||
Three releases in one: a full poker minigame, a ribbon design studio for the awards program, and a reworked announce command for staff.
|
||||
|
|
|
|||
13
bun.lock
13
bun.lock
|
|
@ -9,6 +9,12 @@
|
|||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@lexical/extension": "0.41.0",
|
||||
"@lexical/link": "0.41.0",
|
||||
"@lexical/list": "0.41.0",
|
||||
"@lexical/react": "0.41.0",
|
||||
"@lexical/rich-text": "0.41.0",
|
||||
"@lexical/selection": "0.41.0",
|
||||
"@payloadcms/db-postgres": "3.88.0",
|
||||
"@payloadcms/email-nodemailer": "3.88.0",
|
||||
"@payloadcms/next": "3.88.0",
|
||||
|
|
@ -42,6 +48,7 @@
|
|||
"discord.js": "^14.27.0",
|
||||
"dotenv": "17.2.3",
|
||||
"graphql": "^16.14.2",
|
||||
"lexical": "0.41.0",
|
||||
"lucide-react": "^0.561.0",
|
||||
"next": "16.2.11",
|
||||
"next-themes": "^0.4.6",
|
||||
|
|
@ -2198,7 +2205,7 @@
|
|||
|
||||
"react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "0.27.0" }, "peerDependencies": { "react": "19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="],
|
||||
|
||||
"react-error-boundary": ["react-error-boundary@4.1.2", "", { "dependencies": { "@babel/runtime": "7.28.4" }, "peerDependencies": { "react": "19.2.3" } }, "sha512-GQDxZ5Jd+Aq/qUxbCm1UtzmL/s++V7zKgE8yMktJiCQXCCFZnMZh9ng+6/Ne6PjNSXH0L9CjeOEREfRnq6Duag=="],
|
||||
"react-error-boundary": ["react-error-boundary@6.1.2", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-3DpCr5HVdZ0caUjYE/kIHBEJN0mNP3ZCgf16c48uJ5TbWjorKVp+YG8W3XqlJ7vJAVNw6wNIImyPXmFydwmyng=="],
|
||||
|
||||
"react-image-crop": ["react-image-crop@10.1.8", "", { "peerDependencies": { "react": "19.2.3" } }, "sha512-4rb8XtXNx7ZaOZarKKnckgz4xLMvds/YrU6mpJfGhGAsy2Mg4mIw1x+DCCGngVGq2soTBVVOxx2s/C6mTX9+pA=="],
|
||||
|
||||
|
|
@ -2664,8 +2671,6 @@
|
|||
|
||||
"@google/genai/ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="],
|
||||
|
||||
"@lexical/react/react-error-boundary": ["react-error-boundary@6.1.2", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-3DpCr5HVdZ0caUjYE/kIHBEJN0mNP3ZCgf16c48uJ5TbWjorKVp+YG8W3XqlJ7vJAVNw6wNIImyPXmFydwmyng=="],
|
||||
|
||||
"@modelcontextprotocol/sdk/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="],
|
||||
|
||||
"@modelcontextprotocol/sdk/eventsource-parser": ["eventsource-parser@3.1.1", "", {}, "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ=="],
|
||||
|
|
@ -2690,6 +2695,8 @@
|
|||
|
||||
"@payloadcms/richtext-lexical/csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||
|
||||
"@payloadcms/richtext-lexical/react-error-boundary": ["react-error-boundary@4.1.2", "", { "dependencies": { "@babel/runtime": "7.28.4" }, "peerDependencies": { "react": "19.2.3" } }, "sha512-GQDxZ5Jd+Aq/qUxbCm1UtzmL/s++V7zKgE8yMktJiCQXCCFZnMZh9ng+6/Ne6PjNSXH0L9CjeOEREfRnq6Duag=="],
|
||||
|
||||
"@payloadcms/ui/@payloadcms/translations": ["@payloadcms/translations@3.88.0", "", { "dependencies": { "date-fns": "4.1.0" } }, "sha512-AZVQjjW5pKxO+K9ODeh6yw6/EfXK1hxGPw55KzzDC5srO3hjNskgyWtkauKvbz+S0+xFspANbRc4OD/Rg/74WA=="],
|
||||
|
||||
"@payloadcms/ui/sonner": ["sonner@1.7.4", "", { "peerDependencies": { "react": "19.2.3", "react-dom": "19.2.3" } }, "sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw=="],
|
||||
|
|
|
|||
|
|
@ -33,6 +33,12 @@
|
|||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@lexical/extension": "0.41.0",
|
||||
"@lexical/link": "0.41.0",
|
||||
"@lexical/list": "0.41.0",
|
||||
"@lexical/react": "0.41.0",
|
||||
"@lexical/rich-text": "0.41.0",
|
||||
"@lexical/selection": "0.41.0",
|
||||
"@payloadcms/db-postgres": "3.88.0",
|
||||
"@payloadcms/email-nodemailer": "3.88.0",
|
||||
"@payloadcms/next": "3.88.0",
|
||||
|
|
@ -66,6 +72,7 @@
|
|||
"discord.js": "^14.27.0",
|
||||
"dotenv": "17.2.3",
|
||||
"graphql": "^16.14.2",
|
||||
"lexical": "0.41.0",
|
||||
"lucide-react": "^0.561.0",
|
||||
"next": "16.2.11",
|
||||
"next-themes": "^0.4.6",
|
||||
|
|
|
|||
|
|
@ -105,6 +105,8 @@ WHAT IT DOES
|
|||
- Restores into the target environment's database
|
||||
- Runs pending Payload migrations (keeps schema in sync with dev code)
|
||||
- Resets all sequences to prevent ID collisions
|
||||
- Preserves the target's AI Compose prompts (plugin_ai_instructions)
|
||||
so the AI plugin does not regenerate them on the next boot
|
||||
|
||||
Media (unless --skip-media):
|
||||
- Backs up current media directory
|
||||
|
|
@ -228,6 +230,102 @@ backup_media() {
|
|||
|
||||
# ─── Database sync ────────────────────────────────────────────────────────────
|
||||
|
||||
# The AI plugin (@ai-stack/payloadcms) stores its Compose prompts in the
|
||||
# plugin_ai_instructions table. They are environment-independent config-ish
|
||||
# data, so preserve the target's rows across the restore instead of inheriting
|
||||
# prod's (possibly empty/stale) copy, which would force a full prompt
|
||||
# regeneration on the next dev boot.
|
||||
AI_INSTRUCTIONS_TABLE="plugin_ai_instructions"
|
||||
AI_INSTRUCTIONS_IMAGES_TABLE="plugin_ai_instructions_images"
|
||||
|
||||
table_exists() {
|
||||
local db="$1" table="$2"
|
||||
PGPASSWORD="$TARGET_DB_PASS" psql \
|
||||
-h "$TARGET_DB_HOST" -p "$TARGET_DB_PORT" -U "$TARGET_DB_USER" -d "$db" \
|
||||
-tAc "SELECT to_regclass('public.${table}') IS NOT NULL;" 2>/dev/null
|
||||
}
|
||||
|
||||
preserve_ai_instructions() {
|
||||
if $DRY_RUN; then
|
||||
info "[DRY RUN] Would preserve ${AI_INSTRUCTIONS_TABLE} rows before restore"
|
||||
return 0
|
||||
fi
|
||||
|
||||
rm -f "${BACKUP_PATH}/db/${TARGET}-ai-instructions.sql" \
|
||||
"${BACKUP_PATH}/db/${TARGET}-ai-instructions-images.sql"
|
||||
|
||||
if [[ "$(table_exists "$TARGET_DB_NAME" "$AI_INSTRUCTIONS_TABLE")" != "t" ]]; then
|
||||
info "No ${AI_INSTRUCTIONS_TABLE} table in target, nothing to preserve"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if PGPASSWORD="$TARGET_DB_PASS" pg_dump \
|
||||
-h "$TARGET_DB_HOST" -p "$TARGET_DB_PORT" -U "$TARGET_DB_USER" \
|
||||
-d "$TARGET_DB_NAME" \
|
||||
--table="$AI_INSTRUCTIONS_TABLE" --data-only \
|
||||
--no-owner --no-privileges \
|
||||
-f "${BACKUP_PATH}/db/${TARGET}-ai-instructions.sql" 2>&1; then
|
||||
ok "Preserved AI Compose prompts (${AI_INSTRUCTIONS_TABLE})"
|
||||
else
|
||||
warn "Could not dump ${AI_INSTRUCTIONS_TABLE}; prompts will be re-seeded on next boot"
|
||||
rm -f "${BACKUP_PATH}/db/${TARGET}-ai-instructions.sql"
|
||||
fi
|
||||
|
||||
if [[ "$(table_exists "$TARGET_DB_NAME" "$AI_INSTRUCTIONS_IMAGES_TABLE")" == "t" ]]; then
|
||||
PGPASSWORD="$TARGET_DB_PASS" pg_dump \
|
||||
-h "$TARGET_DB_HOST" -p "$TARGET_DB_PORT" -U "$TARGET_DB_USER" \
|
||||
-d "$TARGET_DB_NAME" \
|
||||
--table="$AI_INSTRUCTIONS_IMAGES_TABLE" --data-only \
|
||||
--no-owner --no-privileges \
|
||||
-f "${BACKUP_PATH}/db/${TARGET}-ai-instructions-images.sql" 2>/dev/null \
|
||||
|| rm -f "${BACKUP_PATH}/db/${TARGET}-ai-instructions-images.sql"
|
||||
fi
|
||||
}
|
||||
|
||||
restore_ai_instructions() {
|
||||
local prompts_sql="${BACKUP_PATH}/db/${TARGET}-ai-instructions.sql"
|
||||
[[ -s "$prompts_sql" ]] || return 0
|
||||
|
||||
if $DRY_RUN; then
|
||||
info "[DRY RUN] Would reload ${AI_INSTRUCTIONS_TABLE} rows after restore"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "$(table_exists "$TARGET_DB_NAME" "$AI_INSTRUCTIONS_TABLE")" != "t" ]]; then
|
||||
warn "${AI_INSTRUCTIONS_TABLE} missing after restore; AI prompts will be re-seeded on next boot"
|
||||
return 0
|
||||
fi
|
||||
|
||||
info "Reloading preserved AI Compose prompts..."
|
||||
if ! PGPASSWORD="$TARGET_DB_PASS" psql \
|
||||
-h "$TARGET_DB_HOST" -p "$TARGET_DB_PORT" -U "$TARGET_DB_USER" \
|
||||
-d "$TARGET_DB_NAME" \
|
||||
-v ON_ERROR_STOP=1 \
|
||||
-c "TRUNCATE TABLE \"${AI_INSTRUCTIONS_TABLE}\" CASCADE;" \
|
||||
-f "$prompts_sql" 2>&1; then
|
||||
warn "Failed to reload ${AI_INSTRUCTIONS_TABLE}; prompts will be re-seeded on next boot"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local images_sql="${BACKUP_PATH}/db/${TARGET}-ai-instructions-images.sql"
|
||||
if [[ -s "$images_sql" ]] && [[ "$(table_exists "$TARGET_DB_NAME" "$AI_INSTRUCTIONS_IMAGES_TABLE")" == "t" ]]; then
|
||||
PGPASSWORD="$TARGET_DB_PASS" psql \
|
||||
-h "$TARGET_DB_HOST" -p "$TARGET_DB_PORT" -U "$TARGET_DB_USER" \
|
||||
-d "$TARGET_DB_NAME" \
|
||||
-v ON_ERROR_STOP=1 \
|
||||
-f "$images_sql" >/dev/null 2>&1 \
|
||||
|| warn "Failed to reload ${AI_INSTRUCTIONS_IMAGES_TABLE} (non-fatal)"
|
||||
fi
|
||||
|
||||
PGPASSWORD="$TARGET_DB_PASS" psql \
|
||||
-h "$TARGET_DB_HOST" -p "$TARGET_DB_PORT" -U "$TARGET_DB_USER" \
|
||||
-d "$TARGET_DB_NAME" \
|
||||
-c "SELECT setval(pg_get_serial_sequence('${AI_INSTRUCTIONS_TABLE}', 'id'), GREATEST(COALESCE((SELECT MAX(id) FROM ${AI_INSTRUCTIONS_TABLE}), 1), 1));" >/dev/null 2>&1 \
|
||||
|| true
|
||||
|
||||
ok "AI Compose prompts preserved (no regeneration needed on next boot)"
|
||||
}
|
||||
|
||||
sync_database() {
|
||||
info "Syncing database: ${PROD_DB_NAME} → ${TARGET_DB_NAME}"
|
||||
info " Source: ${PROD_DB_HOST}:${PROD_DB_PORT}"
|
||||
|
|
@ -241,6 +339,8 @@ sync_database() {
|
|||
info "Dumping production database..."
|
||||
local dump_file="${BACKUP_PATH}/db/prod.dump"
|
||||
|
||||
preserve_ai_instructions
|
||||
|
||||
if ! PGPASSWORD="$PROD_DB_PASS" pg_dump \
|
||||
-h "$PROD_DB_HOST" \
|
||||
-p "$PROD_DB_PORT" \
|
||||
|
|
@ -303,6 +403,8 @@ sync_database() {
|
|||
-d "$TARGET_DB_NAME" \
|
||||
-c "SELECT setval(pg_get_serial_sequence(t.table_name, c.column_name), (SELECT COALESCE(MAX(c.column_name::text::bigint), 1) FROM information_schema.columns c JOIN information_schema.tables t ON c.table_name = t.table_name WHERE c.column_name = 'id')) FROM information_schema.columns c JOIN information_schema.tables t ON c.table_name = t.table_name WHERE c.column_name = 'id' AND c.column_default LIKE 'nextval%' AND t.table_schema = 'public';" 2>/dev/null || true
|
||||
|
||||
restore_ai_instructions
|
||||
|
||||
ok "Database sync complete: ${TARGET_DB_NAME}"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { migrations } from "./migrations";
|
||||
import { postgresAdapter } from "@payloadcms/db-postgres";
|
||||
import { HeadingFeature, lexicalEditor } from "@payloadcms/richtext-lexical";
|
||||
import { FixedToolbarFeature, lexicalEditor } from "@payloadcms/richtext-lexical";
|
||||
import { nodemailerAdapter } from "@payloadcms/email-nodemailer";
|
||||
import path from "path";
|
||||
import { buildConfig } from "payload";
|
||||
|
|
@ -24,6 +24,7 @@ import { ModLists } from "@/collections/server/ModLists";
|
|||
import { GameServers } from "@/collections/server/GameServers";
|
||||
import { ArmaSyncEvents } from "@/collections/server/ArmaSyncEvents";
|
||||
import { ArmaCommands } from "@/collections/server/ArmaCommands";
|
||||
import { RespawnTickets } from "@/collections/server/RespawnTickets";
|
||||
import { Factions } from "@/collections/intelligence/Factions";
|
||||
import { Assets } from "@/collections/logistics/Assets";
|
||||
import { NarrativeEvents } from "@/collections/world/NarrativeEvents";
|
||||
|
|
@ -74,6 +75,7 @@ import { CustomRadioTestScores } from "@/collections/minigames/CustomRadioTestSc
|
|||
import { RibbonSubmissions } from "@/collections/users/RibbonSubmissions";
|
||||
import { requireAdminPageAccess } from "@/utils/access-control/hasPermission";
|
||||
import { payloadAiPlugin, PayloadAiPluginLexicalEditorFeature } from "@ai-stack/payloadcms";
|
||||
import type { SeedPromptFunction } from "@ai-stack/payloadcms/types";
|
||||
import { mcpPlugin } from "@payloadcms/plugin-mcp";
|
||||
import { createOpenAI } from "@ai-sdk/openai";
|
||||
import { createTextStreamResponse, jsonSchema, streamObject } from "ai";
|
||||
|
|
@ -85,6 +87,64 @@ const unslothProvider = createOpenAI({
|
|||
|
||||
const DEFAULT_MODEL = "unsloth/gemma-4-E4B-it-qat-GGUF";
|
||||
|
||||
// AI Compose allowlist: the plugin's default init makes an LLM call per field
|
||||
// to seed prompts. Returning false here skips a field (no instruction row, no
|
||||
// AI UI), and returning { data: { prompt } } (without a top-level `prompt` key)
|
||||
// seeds the row statically, so no LLM call ever happens at boot and a wiped
|
||||
// instructions table (e.g. after scripts/sync-prod.sh) rebuilds in
|
||||
// milliseconds on the next startup.
|
||||
const AI_TEXT_FIELD_ALLOWLIST = new Set([
|
||||
"description",
|
||||
"gamedescription",
|
||||
"summary",
|
||||
"bio",
|
||||
"occupation",
|
||||
"name",
|
||||
"title",
|
||||
]);
|
||||
|
||||
// Textarea/richText fields that are system-managed or technical, never AI-able.
|
||||
const AI_FIELD_DENYLIST = new Set(["contentsnapshot", "html", "script"]);
|
||||
|
||||
const staticAiPrompt = (fieldName: string, fieldLabel: string, fieldType: string): string => {
|
||||
switch (fieldName) {
|
||||
case "description":
|
||||
case "gamedescription":
|
||||
return `Write an immersive, in-universe description for the "${fieldLabel}" of {{ title }}. 2-4 sentences of plain prose. No markdown, no lists, no headings.`;
|
||||
case "summary":
|
||||
return `Write a concise summary for the "${fieldLabel}" of {{ title }}. 1-3 sentences of plain prose.`;
|
||||
case "bio":
|
||||
return `Write a short in-universe biography for {{ title }}. A few sentences of plain prose covering background and personality.`;
|
||||
case "occupation":
|
||||
return `Suggest a short occupation label for {{ title }}. Reply with 1-3 words only, no explanation.`;
|
||||
case "name":
|
||||
return `Suggest a single fitting name for {{ title }}. Reply with the name only, no explanation.`;
|
||||
case "title":
|
||||
return `Suggest a single fitting title for {{ title }}. Reply with the title only, no explanation.`;
|
||||
default:
|
||||
if (fieldType === "richText") {
|
||||
return `Write long-form content for the "${fieldLabel}" section of {{ title }}. Structured prose with an introduction, clear sections, and a conclusion.`;
|
||||
}
|
||||
return `Write suitable content for the "${fieldLabel}" of {{ title }}. Plain prose, concise and relevant.`;
|
||||
}
|
||||
};
|
||||
|
||||
const aiFieldSeedPrompts: SeedPromptFunction = ({ path, fieldType, fieldLabel }) => {
|
||||
const fieldName = (path.split(".").pop() ?? "").toLowerCase();
|
||||
|
||||
if (AI_FIELD_DENYLIST.has(fieldName)) return false;
|
||||
|
||||
if (fieldType === "textarea" || fieldType === "richText") {
|
||||
return { data: { prompt: staticAiPrompt(fieldName, fieldLabel, fieldType) } };
|
||||
}
|
||||
|
||||
if (fieldType === "text" && AI_TEXT_FIELD_ALLOWLIST.has(fieldName)) {
|
||||
return { data: { prompt: staticAiPrompt(fieldName, fieldLabel, fieldType) } };
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
function plainTextToLexicalJson(text: string): Record<string, unknown> {
|
||||
const paragraphs = text.split(/\n\n+/).filter((p) => p.trim());
|
||||
|
||||
|
|
@ -241,6 +301,7 @@ const collections = [
|
|||
GameServers,
|
||||
ArmaSyncEvents,
|
||||
ArmaCommands,
|
||||
RespawnTickets,
|
||||
|
||||
// Minigames
|
||||
CustomMinefields,
|
||||
|
|
@ -316,8 +377,8 @@ export default buildConfig({
|
|||
globals: [GameRules, Shims],
|
||||
collections: scopedCollections,
|
||||
editor: lexicalEditor({
|
||||
features: ({ rootFeatures }) => {
|
||||
return [HeadingFeature(), PayloadAiPluginLexicalEditorFeature()];
|
||||
features: ({ defaultFeatures }) => {
|
||||
return [...defaultFeatures, FixedToolbarFeature(), PayloadAiPluginLexicalEditorFeature()];
|
||||
},
|
||||
}),
|
||||
secret: process.env.PAYLOAD_SECRET || "",
|
||||
|
|
@ -368,6 +429,7 @@ export default buildConfig({
|
|||
// storage-adapter-placeholder
|
||||
mcpPlugin({ collections: mcpCollections }),
|
||||
payloadAiPlugin({
|
||||
seedPrompts: aiFieldSeedPrompts,
|
||||
collections: {
|
||||
[GameHardResources.slug]: true,
|
||||
[GameStructures.slug]: true,
|
||||
|
|
|
|||
Loading…
Reference in a new issue