-
Notifications
You must be signed in to change notification settings - Fork 41
example drafts ai suggestions
Experimental.
The Drafts widget's 💡 button asks the model for title / excerpt / tag /
category suggestions plus a readiness check, then writes whichever the
user taps straight onto the draft. Everything the route decides is
filterable, so a plugin can bend the assistant to a site's house style
without forking includes/widgets/widget-drafts.php.
<?php
/**
* Plugin Name: House Style For Drafts
*/
defined( 'ABSPATH' ) || exit;
add_filter(
'openstation_drafts_ai_instructions',
function ( $instructions, $post ) {
return $instructions . "\n\n" . implode(
"\n",
array(
'House style: sentence case in titles, never Title Case.',
'Never use an em dash in a title.',
'Excerpts are one sentence, maximum 140 characters.',
)
);
},
10,
2
);The body is truncated to 4,000 characters by default — multibyte-safe, so
a long draft is never cut mid-character. Return 0 to send all of it.
add_filter(
'openstation_drafts_ai_content_limit',
function ( $limit, $post ) {
// Long-form review posts need the whole thing to be judged fairly.
return has_category( 'reviews', $post ) ? 0 : $limit;
},
10,
2
);openstation_drafts_ai_suggestions runs last, after tag-stripping and
truncation — drop, reorder or append entries without re-sanitizing.
add_filter(
'openstation_drafts_ai_suggestions',
function ( $suggestions, $post ) {
// Never offer a tag that isn't already on the site.
$suggestions['tags'] = array_values(
array_filter(
$suggestions['tags'],
function ( $tag ) {
return (bool) get_term_by( 'name', $tag, 'post_tag' );
}
)
);
return $suggestions;
},
10,
2
);$applied holds only what actually changed. An empty array means the
request was a no-op — for example an unknown category the user lacked
manage_categories to create.
add_action(
'openstation_drafts_suggestion_applied',
function ( $post_id, $applied, $post ) {
if ( empty( $applied ) ) {
return;
}
my_log(
sprintf(
'AI suggestion applied to #%d: %s',
$post_id,
implode( ', ', array_keys( $applied ) )
)
);
},
10,
3
);Changing the schema changes the REST response shape too — the route only
normalizes the keys it knows about, and anything else passes through
untouched. The built-in widget UI ignores extra keys, so this is for
plugins that also consume /desktop-mode/v1/draft-suggestions directly.
add_filter(
'openstation_drafts_ai_schema',
function ( $schema, $post ) {
$schema['properties']['reading_time'] = array(
'type' => 'string',
'description' => 'Estimated reading time, e.g. "4 min".',
);
$schema['required'][] = 'reading_time';
return $schema;
},
10,
2
);Nothing here bypasses the route's gates, and neither should your code:
-
/draft-suggestionsrequiresedit_postfirst, then a configured text-generation provider. An unauthorized caller always gets403, so the response can't be used to probe whether the site has AI set up. - With no provider, an authorized caller gets
503 openstation_ai_unavailableand the 💡 button never renders — the widget degrades to exactly its pre-AI behavior. -
/draft-applyrequiresedit_postand nothing else; accepting a suggestion is a plain edit that keeps working if AI is switched off mid-session. Tags and categories are appended, never clobbered, and new categories are only created for users who canmanage_categories(mirroring Core, where Authors may assign but not create).
This wiki is generated from the docs/ directory — edits made here are overwritten by the next sync.
To change a page, open a pull request against docs/.
Guides
- Development guide
- Releasing openstation
- Agents security model
- API Index
- Architecture
- Bridge protocol — wiring overview
- <os-*> component reference
- Native Desktop Host — Experimental
- Desktop themes
- Dock customization — two registries, one mental model
- The event-driven framework
- Files on the Desktop
- Folder sharing
- Getting Started
- Hooks Reference
- Icons
- JavaScript Reference
- The Living Tree — algorithm definition
- Mio
- Native Windows & Framework Interop
- Plugin compatibility layer
- Progressive Web App (PWA)
- Station Home
- Using openstation from your own plugin
Migration notes
- Migration: built-in activity channels move to the os/ namespace
- Migration: window, wallpaper and widget bundles load on demand
- Migration — the navigation model
- Migration: a native window's tabs move to the window chrome
All examples
- AI Agents — extend and invoke from a plugin
- wp.os.ai.ask() — programmatic AI Copilot
- Tune the AI model config
- Custom arrange-menu action
- Open a child window its owner can't cover
- Style a specific admin page inside the iframe
- Code Blue — register your plugin's log file
- Open a file in the Code editor (deep-link from any window)
- Connect to a window — title-bar button + iframe pub/sub
- Content changes — live-refresh every window listing your type
- Custom window chrome (Experimental)
- Register a custom unfocused-window effect
- Example: render a data table
- Real file storage — react to uploads, gate policy, share from PHP
- React to a window being set free onto the real desktop
- Cross-window devtools — instrumentation primitives
- Add a dock item with a badge
- Decorate the dock without forking the renderer
- Replace the dock rail entirely
- Retune the Drafts widget's AI writing assistant
- Gate OpenStation by role
- Iframe-initiated window opens
- Build a feed reader without the bookkeeping
- Inject data into openStationConfig
- Render a list without losing clicks — renderKeyedList()
- Example: layout primitives (body → panel → row → col)
- Use <os-*> components from a plugin that ships as a zip
- Restyle and drive Mio
- Add an action that works on a whole selection
- WP Explorer — custom post types and their folder
- Add an action button to a WP Explorer preview pane
- Example: native Posts window
- Example: native window with tabs
- Native windows
- Customize note → post conversion
- Send a notification
- OAuth relay — connect to an external service
- OS-file drop
- <os-flyout> — window-scoped sliding card
- Plugins window — extras
- Track who's around — wp.os.presence
- Example: progress bar
- PWA install — surface your own button
- React to window events
- Example: extend the Trash
- Register a slash-command
- Register a desktop theme from a plugin
- Register a game
- Example: register a desktop icon (Jorvy)
- Register a wallpaper
- Register a widget
- Related entities — extend the title bar's "Related" menu
- The native-window render ctx
- Programmatic folder sharing
- Share state across multi-bundle plugins — wp.os.createSharedStore()
- Example: loading spinner
- Add an opt-in card to Station Home
- Accept drops on your desktop icon
- Give a tile two icons, one per state
- Add a row to a window's ⋯ menu
- Example: window activity & the status ring
- Window controls
- Subscribe to window lifecycle events
- Window links — relate windows and restyle the ties (Experimental)
- Window loading state — spinner overlay & ready signal
- Show a banner at the top of a window
- Pulse a window's icon — Window.requestAttention()
- Register a custom window reveal
- Window slots
- Window themes
- Native window with bundle-bound config