UNPKG

json-object-editor

Version:

JOE the Json Object Editor | Platform Edition

780 lines (660 loc) 42.1 kB
<img src="http://joe.craydent.com/JsonObjectEditor/img/svgs/joe_banner_o.svg"/> # Json Object Editor JOE is software that allows you to manage data models via JSON objects. There are two flavors, the client-side version and nodejs server platform. ## What's new in 0.10.663 (brief) - Plugin Inventory debug page: - New `_www/plugins-test.html` page (linked from the MCP nav) shows all active plugins, their async/top-level methods, and which JOE apps reference them. It’s auth-protected and backed by the `plugin-utils` plugin, and is especially useful for AI agents and developers to confirm that methods like `chatgpt.autofill` and `chatgpt.widgetStart` are present on a given instance. ## What's new in 0.10.662 (brief) - React Form Integration with JSON Definitions: - **JSON includes**: `include` schema now supports `filetype: 'json'` for storing JSON form definitions (served at `/_include/{id}` with proper content-type). - **Form-page linking**: Added `form` reference field to `page` schema to link pages to JOE forms. - **Form definition API**: New `/API/plugin/formBuilder/definition` endpoint (merged from `formDefinition` plugin) serves JSON form definitions. Automatically finds JSON includes from form metadata or page includes. - **React form renderer**: New `joe-react-form.js` client library renders multi-step React forms from JSON definitions with conditional visibility, validation, and submission to JOE's form submission system. - **Page rendering fix**: Enhanced template variable processing to preserve newlines in page content, fixing JavaScript comment issues in `code` and `module` content types. ## What’s new in 0.10.660 (brief) - MCP everywhere (prompts, autofill, widget): - **Prompts** (`ai_prompt`): new MCP config block (`mcp_enabled`, `mcp_toolset`, `mcp_selected_tools`, `mcp_instructions_mode`) lets you turn MCP tools on per‑prompt, pick a toolset (`read-only`, `minimal`, `all`, or `custom`), and auto‑generate short tool instructions. - **Autofill fields**: the same MCP keys are now supported under a field’s `ai` config so autofill runs can optionally call MCP tools with the same toolset/playlist model. - **Audit**: `ai_response` records MCP config and actual tool calls (`mcp_tools_used[]`) plus `used_openai_file_ids[]` so you can see which tools and files were used for any run. - Uploader file roles + AI‑aware attachments: - Uploader fields can define `file_roles` (e.g. `{ value:'transcript', label:'Intake Transcript', default:true }`) and JOE renders a per‑file role `<select>` that saves to `file_role` on each file object. - `executeJOEAiPrompt` now sends a compact `uploaded_files[]` header (including `itemtype`, `field`, `filename`, `file_role`, and `openai_file_id`) alongside Responses input so prompts can reason about “transcript vs summary” sources while the OpenAI Files integration still handles raw content. - Responses+tools (`runWithTools`) now attaches files on both the initial tool‑planning call and the final answer call, so MCP runs see the same attachments end‑to‑end. - History safety: - Hardened `JOE.Storage.save` history diffing to avoid a `craydent-object` edge case where comparing `null`/`undefined` values could throw on `.toString()`. This only affects `_history.changes`, not what is saved. - Object chat unification: - The `objectChat` field now launches a floating `<joe-ai-widget>` shell with `<joe-ai-assistant-picker>`, reusing the same AIHub chat stack for all schemas. - `ai_widget_conversation` gained `scope_itemtype`/`scope_id` so each chat can be scoped to a specific object, plus `attached_openai_file_ids` and `attached_files_meta` to track which uploader files were attached. - On the first turn of an object-scoped chat, the server preloads a slimmed `understandObject` snapshot for the scoped object and merges object files + assistant files into `openai_file_ids`, so the assistant immediately knows “which record this is” and can reason over its files. ## What’s new in 0.10.654 (brief) - OpenAI Files mirrored on S3 upload; uploader tiles show the `openai_file_id`. Retry upload is available per file. - Responses integration improvements: - Per‑prompt `attachments_mode` on `ai_prompt` (`direct` vs `file_search`). Direct sends `input_file` parts; file search auto‑creates a vector store and attaches it. - Safe retry if a model rejects `temperature/top_p` (we strip and retry once). - Select Prompt lists prompts by active status where either `datasets[]` or `content_items[].itemtype` matches the current object. - `ai_response` now shows `used_openai_file_ids` and correctly records `referenced_objects` for Select Prompt runs. - UX: “Run AI Prompt” and “Run Thought Agent” buttons disable and pulse while running to avoid double‑submits. ## Architecture & Mental Model (Server) - Global JOE - Created in `server/init.js`; exposes subsystems like `JOE.Utils`, `JOE.Schemas`, `JOE.Storage`, `JOE.Mongo`/`JOE.MySQL`, `JOE.Cache`, `JOE.Apps`, `JOE.Server` (Express), `JOE.Sites`, `JOE.io` (Socket), and `JOE.auth`. - Init pattern - Modules can optionally export `init()`; `init.js` loads/watches modules and calls `init()` after `JOE.Server` is ready. This enables hot-reload and keeps `Server.js` thin. - Schemas & events - `JOE.Schemas` loads from `server/schemas/` and app schema dir into `JOE.Schemas.schema` with names in `JOE.Schemas.schemaList`. Raw copies live in `JOE.Schemas.raw_schemas` for event hooks. - `JOE.Storage.save()` triggers schema events (`create`, `save`, `status`, `delete`) via `JOE.Schemas.events()` and writes history documents to `_history`. - Storage & cache - `JOE.Storage.load(collection, query, cb)` chooses backend per schema `storage.type` (`mongo`, `mysql`, `file`, `api`), with Mongo/file fallback. - `JOE.Storage.save(item, collection, cb, { user, history })` emits `item_updated` to sockets, records history, and fires events. - `JOE.Cache.update(cb, collections)` populates `JOE.Data`, flattens a `list`, and builds `lookup`. Use `JOE.Cache.findByID(collection, idOrCsv)` for fast lookups; `JOE.Cache.search(query)` for in-memory filtering. - Auth - `JOE.auth` middleware checks cookie token or Basic Auth; many API routes (and `/mcp`) are protected. - Shorthand `$J` - Server and client convenience: `$J.get(_id)`, `$J.search(query)`, `$J.schema(name)`. - On server, provided by `server/modules/UniversalShorthand.js` and assigned to `global.$J` in `init.js`. - MCP overview - Manifest: `/.well-known/mcp/manifest.json`; JSON-RPC: `POST /mcp` (auth-protected). Tools map to real JOE APIs (`Schemas`, `Storage`, `Cache`) and sanitize sensitive fields. ## MCP routes and local testing - Endpoints - Manifest (public): `GET /.well-known/mcp/manifest.json` - JSON-RPC (auth): `POST /mcp` - Privacy (public): `/privacy` (uses Setting `PRIVACY_CONTACT` for contact email) - Terms (public): `/terms` - Auth - If users exist, `POST /mcp` requires cookie or Basic Auth (same as other APIs). If no users configured, it is effectively open. - Test pages - JOE ships several debug/test pages in `/_www/`: - `mcp-test.html` - Simple MCP tool tester - `mcp-schemas.html` - Schema health and summary viewer - `mcp-prompt.html` - MCP prompt/instructions viewer - `matrix.html` - Interactive schema relationship visualization with D3.js force-directed graph - Access via JOE path: `http://localhost:<PORT>/JsonObjectEditor/_www/<page>.html` - If your host app serves its own `_www`, pages can also be available at the root (fallback) if running with the updated server that mounts JOE’s `_www` as a secondary static directory. Then: `http://localhost:<PORT>/<page>.html` - Tools - `listSchemas(name?)`, `getSchema(name)` - `getObject(_id, itemtype?)` (supports optional `flatten` and `depth`) - `search` (exact): unified tool for cache and storage - Params: `{ itemtype?, query?, ids?, source?: 'cache'|'storage', limit?, flatten?, depth? }` - Defaults to cache across all collections; add `itemtype` to filter; set `source:"storage"` to query a specific collection in the DB. Runtime accepts legacy alias `schema` (maps to `itemtype`). Use `fuzzySearch` for typo-tolerant free text. - `fuzzySearch` (typo-tolerant free text across weighted fields) - Params: `{ itemtype?, q, filters?, fields?, threshold?, limit?, offset?, highlight?, minQueryLength? }` - Defaults: `fields` resolved from schema `searchables` (plural) if present; otherwise weights `name:0.6, info:0.3, description:0.1`. `threshold:0.5`, `limit:50`, `minQueryLength:2`. - Returns: `{ items, count }`. Each item may include `_score` (0..1) and `_matches` when `highlight` is true. - `findObjectsByTag { tags, itemtype?, limit?, offset?, source?, slim?, withCount?, countOnly?, tagThreshold? }` - Find objects that have ALL specified tags (AND logic). Tags can be provided as IDs (CUIDs) or names (strings) - names are resolved via fuzzy search. - Returns: `{ items, tags, count?, error? }` where `tags` contains the resolved tag objects used in the search. - Use `countOnly: true` to get just the count and matched tags without fetching items. - If tags cannot be resolved, returns `{ items: [], tags: [...resolved ones...], error: "message" }` instead of throwing. - `findObjectsByStatus { status, itemtype?, limit?, offset?, source?, slim?, withCount?, countOnly?, statusThreshold? }` - Find objects by status. Status can be provided as ID (CUID) or name (string) - name is resolved via fuzzy search. - Returns: `{ items, status, count?, error? }` where `status` is the resolved status object used in the search. - Use `countOnly: true` to get just the count and matched status without fetching items. - If status cannot be resolved, returns `{ items: [], status: null, error: "message" }` instead of throwing. - `saveObject({ object })` - `saveObjects({ objects, stopOnError?, concurrency? })` - Batch save with per-item history/events. Defaults: `stopOnError=false`, `concurrency=5`. - Each object must include `itemtype`; `_id` and `joeUpdated` are set when omitted. `created` is set on first save when missing. - Return shape: `{ results, errors, saved, failed }`. - Quick tests (PowerShell) - Prepare headers if using Basic Auth: ```powershell $pair = "user:pass"; $b64 = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($pair)) $h = @{ Authorization = "Basic $b64"; "Content-Type" = "application/json" } $base = "http://localhost:<PORT>" ``` - Manifest: ```powershell Invoke-RestMethod "$base/.well-known/mcp/manifest.json" ``` - listSchemas: ```powershell $body = @{ jsonrpc="2.0"; id="1"; method="listSchemas"; params=@{} } | ConvertTo-Json -Depth 6 Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body ``` - getSchema: ```powershell $body = @{ jsonrpc="2.0"; id="2"; method="getSchema"; params=@{ name="<schemaName>" } } | ConvertTo-Json -Depth 6 Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body ``` - search (cache default): ```powershell $body = @{ jsonrpc="2.0"; id="3"; method="search"; params=@{ query=@{ itemtype="<schemaName>" }; limit=10 } } | ConvertTo-Json -Depth 10 Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body ``` - fuzzySearch (cache): ```powershell $body = @{ jsonrpc="2.0"; id="6"; method="fuzzySearch"; params=@{ itemtype="<schemaName>"; q="st paal"; threshold=0.35; limit=10 } } | ConvertTo-Json -Depth 10 Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body ``` - search (storage): ```powershell $body = @{ jsonrpc="2.0"; id="4"; method="search"; params=@{ itemtype="<schemaName>"; source="storage"; query=@{ }; limit=10 } } | ConvertTo-Json -Depth 10 Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body ``` - search (ids + flatten): ```powershell $body = @{ jsonrpc="2.0"; id="5"; method="search"; params=@{ itemtype="<schemaName>"; ids=@("<id1>","<id2>"); flatten=$true; depth=2 } } | ConvertTo-Json -Depth 10 Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body ``` - saveObject: ```powershell $object = @{ itemtype="<schemaName>"; name="Test via MCP" } $body = @{ jsonrpc="2.0"; id="4"; method="saveObject"; params=@{ object=$object } } | ConvertTo-Json -Depth 10 Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body ``` - saveObjects (batch): ```powershell $objs = @( @{ itemtype="<schemaName>"; name="Batch A" }, @{ itemtype="<schemaName>"; name="Batch B" } ) $body = @{ jsonrpc="2.0"; id="7"; method="saveObjects"; params=@{ objects=$objs; stopOnError=$false; concurrency=5 } } | ConvertTo-Json -Depth 10 Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body ``` - Quick tests (curl) - Manifest: ```bash curl -s http://localhost:<PORT>/.well-known/mcp/manifest.json | jq ``` - listSchemas: - search (cache): ```bash curl -s -X POST http://localhost:<PORT>/mcp \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":"3","method":"search","params":{"query":{"itemtype":"<schemaName>"},"limit":10}}' | jq ``` - fuzzySearch: ```bash curl -s -X POST http://localhost:<PORT>/mcp \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":"6","method":"fuzzySearch","params":{"itemtype":"<schemaName>","q":"st paal","threshold":0.35,"limit":10}}' | jq ``` - search (storage): ```bash curl -s -X POST http://localhost:<PORT>/mcp \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":"4","method":"search","params":{"itemtype":"<schemaName>","source":"storage","query":{},"limit":10}}' | jq ``` ```bash curl -s -X POST http://localhost:<PORT>/mcp \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":"1","method":"listSchemas","params":{}}' | jq ``` - saveObjects (batch): ```bash curl -s -X POST http://localhost:<PORT>/mcp \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":"7","method":"saveObjects","params":{"objects":[{"itemtype":"<schemaName>","name":"Batch A"}],"stopOnError":false,"concurrency":5}}' | jq ``` - Troubleshooting - If you see a payload like `{ originalURL: "/...", site: "no site found" }`, the request hit the Sites catch-all. Ensure MCP routes are initialized before Sites (handled by default in `server/init.js` via `MCP.init()`), and use the correct URL: `/.well-known/mcp/manifest.json` or `/mcp`. - To update contact email on /privacy and /terms, set Setting `PRIVACY_CONTACT`. ## Thoughts & Thought Pipeline - `thought` schema stores AI/human hypotheses, syntheses, and link-thoughts with `about[]` (what it concerns), `because[]` (receipts), `status`, and AI lineage fields like `source_ai_response` / `created_by`. - `ThoughtPipeline` module compiles a deterministic context from a `scope_object` (the item you’re thinking about), schema summaries, and accepted Thoughts, and the `runThoughtAgent` MCP tool runs an OpenAI Responses call to propose new Thoughts. - Each Thought run persists an `ai_response` with `response_type:'thought_generation'`, `referenced_objects:[scope_id]`, and `generated_thoughts[]` containing the ids of created Thought records. - In any schema UI you can include core fields `proposeThought` and `ai_responses` to (a) trigger a Thought run for the current object and (b) list all related `ai_response` records for audit and reuse. ## File uploads (S3 + OpenAI Files) - Uploader field options: - `allowmultiple: true|false` — allow selecting multiple files. - `url_field: 'image_url'` — on success, sets this property to the remote URL and rerenders that field. - `ACL: 'public-read'` — optional per-field ACL. When omitted, server currently defaults to `public-read` (temporary during migration). - Flow: - Client posts `{ Key, base64, contentType, ACL? }` to `/API/plugin/awsConnect`. - Server uploads to S3 (AWS SDK v3) and, if `OPENAI_API_KEY` is configured, also uploads the same bytes to OpenAI Files (purpose=`assistants`). - Response shape: `{ url, Key, bucket, etag, openai_file_id?, openai_purpose?, openai_error? }`. - Client: - Sets the `url` on the file object; if `url_field` is set on the schema field, it assigns that property and rerenders. - Persists OpenAI metadata on the file object: `openai_file_id`, `openai_purpose`, `openai_status`, `openai_error`. - Renders the OpenAI file id under the filename on each uploader tile. The “OpenAI: OK” banner has been removed. - Shows a per‑file “Upload to OpenAI” / “Retry OpenAI” action when no id is present or when an error occurred. This calls `POST /API/plugin/chatgpt/filesRetryFromUrl` with `{ url, filename, contentType }` and updates the file metadata. - Errors: - If bucket or region config is missing, server returns 400 with a clear message. - If the bucket has ACLs disabled, server returns 400: “Bucket has ACLs disabled… remove ACL or switch to presigned/proxy access.” - If OpenAI upload fails, the uploader shows `OpenAI error: <message>` inline; you can retry from the file row. - Using OpenAI file ids: - File ids are private; there is no public URL to view them. - Use the OpenAI Files API (with your API key) to retrieve metadata or download content: - Metadata: `GET /v1/files/{file_id}` - Content: `GET /v1/files/{file_id}/content` - Node example: ```js const OpenAI = require('openai'); const fs = require('fs'); const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); const meta = await client.files.retrieve('file_abc123'); const stream = await client.files.content('file_abc123'); const buf = Buffer.from(await stream.arrayBuffer()); fs.writeFileSync('downloaded.bin', buf); ``` ### File roles on uploader fields - **Schema configuration**: - Any uploader field can declare `file_roles` as an array of `{ value, label?, default? }` objects, for example: - `{ value:'transcript', label:'Intake Transcript', default:true }` - `{ value:'summary', label:'Intake Summary' }` - `label` is optional; it falls back to `value`. At most one role should have `default:true`. - **Runtime behavior**: - JOE renders a role `<select>` next to each uploaded file with: - A blank option, and one option per configured role. - The select updates the file object’s `file_role` property in the parent object (e.g. `client.files[].file_role`). - Existing uploads show the role selector on first render as long as `file_roles` is configured on the field. - When OpenAI Files are enabled, uploader files still receive `openai_file_id`, `openai_purpose`, `openai_status`, and `openai_error` as before; `file_role` is an additional, JOE‑level label. - **AI integration**: - When running an AI prompt via `executeJOEAiPrompt`, JOE inspects referenced objects for uploader fields and builds an `uploaded_files[]` header: - Each entry includes `{ itemtype, field, name, role, openai_file_id }`. - This header is merged into the user input so prompts can explicitly reason about which files are “transcripts”, “summaries”, etc., while the actual file bytes are attached via Responses `input_file` parts / `file_search` tool resources. ### Related endpoints (server/plugins) - `POST /API/plugin/awsConnect` – S3 upload (and OpenAI mirror when configured) - Input: `{ Key, base64, contentType, ACL? }` - Output: `{ url, Key, bucket, etag, openai_file_id?, openai_purpose?, openai_error? }` - `POST /API/plugin/chatgpt/filesRetryFromUrl` – (Re)upload an existing S3 file to OpenAI - Input: `{ url, filename?, contentType? }` - Output: `{ success, openai_file_id?, openai_purpose?, error? }` ## SERVER/PLATFORM mode check port 2099 /JOE/ /JsonObjectEditor/docs.html *Should auto-open on start Json Object Editor (Universal-esque software) (requires connection to a mongo server for full functionality) ### JOE server instantiation (add to entry point js file) var joe = require('json-object-editor'); or here's a custom example var joe = require('json-object-editor')({ name:'name_your_joe' (JOE), joedb:'local_custom_database_name' // {{mongoURL}}' if using mongolab or remote mongodb, port:'4099', (2099) socketPort:'4098', (2098) sitesPort:'4100' (2100), clusters:1, hostname:'server name if not localhost'(localhost) }); ## $J (universal) Shorthand JOE $J to access helper funtions on client and server. (callbacks are optional) $J.get(itemId,[callback]) $J.schema(schemaname,[callback]) # Client-Side (front end only) ## js client only instantiation var specs = { fields:{ species:{label:'Species',type:'select', values:['cat','dog','rat','thing'], onchange:adjustSchema}, gender:{type:'select', values:['male','female']}, legs:{label:'# of Legs',type:'int', onblur:logit}, weight:{label:' Weight (lbs)',type:'number', onblur:logit}, name:{label:' pet Name', onkeyup:logValue}, //id:{label:'ID',type:'text', locked:true}, id:{label:'ID',type:'guid'}, //example of select that takes function (function is passed item) animalLink:{label:'Link to other animal',type:'select', values:getAnimals}, hiddenizer:{hidden:true} }, schemas:{ animal:animalschema, thing:thingschema }, container:string ('body'), compact:false, useBackButton:true, autosave:2000, listSubMenu:{filters:{}}, useHashlink:true, title:'${itemtype} | ${display}' } var JOE = new JsonObjectEditor(specs); JOE.init(); ##JOE CONFIG ##specs - useBackButton:[false] if true, back button moves through joe panels when joe has history to go to (is open). - useHashlink:[false], true or a template for hashlinks. default template is '${schema_name}_${_id}' default server tempalte is '${schema_name}/${_id}' ##SCHEMA CONFIG ###fields Properties for all Fields - `label / display`: what the field should display as *If the field type is boolean, label controls checkbox/boolean label - `value`: default value if not one in object - `default`: default value for field || function(object) - `type`: what type of field should JOE show - `hidden`: boolean / function, value will be added (but unsees by user) - `locked`: boolean - `condition`: boolean - `width`: used for layout control. - can use pixels or percentages (as string) - `comment`: a commentthat shows up at the beginning of the field - `tooltip`: hover/clickable tooltip that shows up next to name **field types:** - `rendering`: for css html and js - `text`: default single line text. - autocomplete: boolean // obj of specs (template, idprop) - values:array of possibilities -maxlength:string - `int`: integer field - `number`: number (float) field - `select`: select list. - multiple(bool) - values(array of objects, [{value:"",name/display:""]), can be a function - disabled:boolean(func acceptable) - idprop: string of prop name - `geo`: shows a map - *takes a string array "[lat,lon]"* - center:[lat,lon], center of map - zoom: zoom level (higher zooms in more) - returns "[lat,lon]" -`image` : shows an image and HxW as th image url is typed in. - `multisorter` : allows arrays of objects to be selected and sorted in right bin. - values(array of objects, [{value:"",name/display:""]), can be a function - `content` : show content on in the editor - run: function to be run(current_object,field_properties) - template: html template for fillTemplate(template,current_object); - `objectlist` : a table of objects with editable properties - properties: array of objects|strings for the object property names -name: value in object -display: header in objectList - max: integer, number or items that can be added. use zero for infinite. - hideHeadings: don't show table headings - `objectReference` : a list of object ids - template - autocomplete_template - idprop - values - max(0 unlimited) - sortable(true) - `code` : - language - `json` : - edit/store JSON subobjects as objects (not strings) using the code editor in JSON mode; pretty-prints on blur/save and treats whitespace-only reformatting as no-op changes. - `boolean`: - label:controls checkbox label - `preview` : -content: string or function(current joe object) to replace everything on page (template). -bodycontent: same as content, only replaces body content. -url: preview page if not the default one. - encoded: boolean, if pre uriencoded **labels:** - pass an object instead of a string to the fields array. {label:'Name of the following properties section'} ##page sections {section_start: 'SectionName', section_label:'Section Name with Labels', condition:function(item){ return item.show;} }, {section_end: 'CreativeBrief'} - pass an object instead of a string to the fields array. these show up on the details view as anchors. - Object Properties - `section_start`: name/id of section - `'section_label`:use instead of section_start for display name - `section_end`: name/id of section(str) - template: html template for fillTemplate(template,current_object); - Note: for fields, `condition` removes the field from the DOM while `hidden` only toggles visibility; sections use `condition` (and sidebar sections use `hidden`) differently than fields. ##page sidebar {sidebar_start: 'SectionName', sidebar_label:'Section Name with Labels', condition:function(item){ return item.show;} }, {sidebar_end: 'CreativeBrief'} - pass an object instead of a string to the fields array. these show up on the details view as anchors. - Object Properties - `sidebar_start`: name/id of sidebar - `sidebar_label`:use instead of sidebar_start for display name - `sidebar_end`: name/id of sidebar(str) - template: html template for fillTemplate(template,current_object); ###defaultProfile overwrites the default profile #schemas a list of schema objects that can configure the editor fields, these can be given properties that are delegated to all the corresponding fields. var animalschema = { title:'Animal', *what shows as the panel header* fields:['id','name','legs','species','weight','color','gender','animalLink'], *list of visible fields* _listID:'id', *the id for finding the object* _listTitle:'${name} ${species}', *how to display items in the list* menu:[array of menu buttons], listMenuTitle: (string) template forjoe window title in list view, listmenu:[array of menu buttons] (multi-edit and select all always show), /*callback:function(obj){ alert(obj.name); },*/ onblur:logit, hideNumbers:boolean *toggle list numbers* multipleCallback:function to be called after a multi-edit. passed list of edited items. onUpdate: callback for after update. passed single edited items. onMultipleUpdate:callback for after multi update.passed list of edited items. filters: array of objects checkChanges:(bool) whether or not to check for changes via interval and on leave } ##Table View - add tableView object to a schema; -cols = [strings||objects] -string is the name and value -display/header is the column title -property/name = object property ###Pre-formating you can preformat at the joe call or schema level. The data item will be affected by the passed function (which should return the preformated item). ##menu## an array of menu buttons //the default save button //this is the dom object, //use _joe.current.object for current object condition:function(field,object) to call self = Joe object var __saveBtn__ = {name:'save',label:'Save', action:'_joe.updateObject(this);', css:'joe-save-button'}; ##itemMenu## as array of buttons for each item in list views - name - action (action string) - url (instead of js action) - condition ##itemExpander## template or run for content to be shown under the main list item block. ###Addition properties **Changing the schema on the fly?** _joe.resetSchema(new schema name); **css (included) options** - joe-left-button - joe-right-button ##FIELDS {extend:'name',specs:{display:'Request Title'}},//extends the field 'name' with the specs provided. ##usage ### a | adding a new object _joe.show({},{schema:'animal',callback:addAnimal); //or goJoe(object,specs) ... function addAnimal(obj){ animals.push(obj); } ### b | viewing a list of objects goJoe([array of objects],specs:{schema,subsets,subset}) goJoe.show(animals,{schema:'animal',subsets:[{name:'Two-Legged',filter:{legs:2}}]}); //use the specs property subset to pre-select a subset by name **properties** - _listWindowTitle: the title of the window (can be passed in with the schema); - _listCount: added to the current object and can be used in the title. - _listTitle:'${name} ${species}', *how to display items in the list* - _icon: [str] template for a list item icon (standard min 50x50), 'http://www.icons.com/${itemname}', can be obj with width, height, url - listSubMenu:a function or object that represents the list submenu - stripeColor:string or function that returns valid css color descriptor. - bgColor:string or function that returns valid css color descriptor. - subsets: name:string, filter:object - subMenu:a function or object that represents the single item submenu - _listTemplate: html template that uses ${var} to write out the item properties for the list item. - standard css class `joe-panel-content-option` ### Helper shortcuts: subsets and stripes Use these helpers to quickly generate subset/filter options and add per-item stripe colors. ```javascript // In a schema definition { // 1) Subsets from statuses of the current schema (auto colors) subsets: function () { return _joe.Filter.Options.status({ group: 'status', // optional grouping label collapsed: true, // start group collapsed none: true // include a "no status" option // color: 'stripecolor' // uncomment to render colored stripes in the menu }); }, // 2) Subsets for all distinct values of a property // Example: recommendation_domain on 'recommendation' items // (pass values to control order/allowlist) // subsets: () => _joe.Filter.Options.getDatasetPropertyValues( // 'recommendation', 'recommendation_domain', { group: 'domain', values: ['product','dietary','activity'] } // ), // 3) Subsets from another dataset (reference values) // subsets: () => _joe.Filter.Options.datasetProperty('user','members',{ group: 'assignees', collapsed: true }), // 4) Row stripe color by status (string or { color, title }) stripeColor: function (item) { if (!item.status) return ''; const s = _joe.getDataItem(item.status, 'status'); return s && { color: s.color, title: s.name }; } } ``` ###c | Conditional select that changes the item schema fields:{ species:{label:'Species',type:'select', values:['cat','dog','rat','thing'], onchange:adjustSchema}, [field_id]:{ +label : STR +type : STR value : STR (default value) +values : ARRAY/FUNC (for select) //modifiers +hidden:BOOL/STRING(name of field that toggles this) //don't show, but value is passed +locked:BOOL // show, but uneditable //events +onchange : FUNC +onblur : FUNC +onkeypress : FUNC +rerender : STRING // name of field to rerender } } function adjustSchema(dom){ var species = $(dom).val(); if(species == "thing"){ JOE.resetSchema('thing') } else{ JOE.resetSchema('animal') } } ###d | duplicating an item //duplicates the currently active object (being edited) _joe.duplicateObject(specs); **specs** - `deletes`:array of properties to clear for new item - note that you will need to delete guid/id fields or the id will be the same. ### e | exporting an object in pretty format json (or minified) JOE.exportJSON = function(object,objvarname,minify) ##Useful Functions _joe.reload(hideMessage,specs) - use specs.overwreite object to extend reloaded object. _joe.constructObjectFromFields() ## AI / Chat Integrations (OpenAI Responses + Assistants) JOE ships a few plugins and schemas to help you connect OpenAI assistants and the new Responses API to your data model. - **`chatgpt` plugin** - Central entry point for OpenAI calls (uses the `openai` Node SDK and the Responses API). - Looks up the OpenAI API key from the `setting` schema: - `OPENAI_API_KEY`: your secret key (can be a service‑account key, e.g. `sk-svcacct-...`). - Key methods: - **`autofill`** (`POST /API/plugin/chatgpt/autofill`): - Given `{ object_id, schema, fields, prompt?, assistant_id?, model? }`, calls `openai.responses.create(...)` and returns a JSON patch for the requested fields only. - Used by `_joe.Ai.populateField(...)` for schema‑driven “AI fill” buttons. - **`executeJOEAiPrompt`**: - Drives the `ai_prompt``ai_response` workflow using Responses API + optional helper functions. - **Widget‑focused methods**: - `widgetStart`, `widgetMessage`, `widgetHistory` for the embeddable AI widget (see below). - **`chatgpt-assistants` plugin (legacy Assistants API)** - Manages the older `beta.assistants` / `beta.threads` API for JOE’s rich in‑app chat (`<joe-ai-chatbox>`). - Still used by the `ai_conversation` flow; new integrations should prefer the Responses‑based `chatgpt` plugin. - **`chatgpt-tools` plugin** - Exposes JOE “AI tools” configured via the `ai_tool` schema. - Useful when you want OpenAI tools that call back into JOE (e.g. search, profile, etc.). ### AI Assistant schemas - **`ai_assistant` schema** - Represents a JOE‑side “assistant config” that can be synced to an OpenAI assistant. - Key fields: - `ai_model`: default model (e.g. `gpt-4.1-mini`, `gpt-4o`). - `instructions`: system prompt for the assistant. - `assistant_id`: OpenAI assistant ID (`asst_...`) set by the sync button. - `file_search_enabled`, `code_interpreter_enabled`: toggle built‑in tools. - `tools`: JSON definition array for custom function tools. - `assistant_thinking_text`: what the UI shows while the assistant is “thinking”. - UI highlights the **default assistant**: - The assistant whose `_id` matches the `DEFAULT_AI_ASSISTANT` setting is given a golden stripe in list view. - Default assistant resolution: - The client helper `Ai.getDefaultAssistant()` in `joe-ai.js` looks up `_joe.Data.setting.where({ name: 'DEFAULT_AI_ASSISTANT' })[0]` and caches it. - **`ai_widget_conversation` schema** - Lightweight conversation log used by the new embeddable widget (`<joe-ai-widget>`). - Fields: - `model`: model name used for the conversation (or inherited from `ai_assistant.ai_model`). - `assistant`: optional reference to the JOE `ai_assistant` used. - `assistant_id`: OpenAI assistant ID (`asst_...`) used by the Responses API. - `system`: effective system prompt. - `messages`: JSON array of `{ role, content, created_at }` for the widget chat. - `last_message_at`, `source`, `tags`, standard system fields. - This schema is what the widget endpoints in `chatgpt` read/write. ### Embeddable AI Widget (`<joe-ai-widget>`) JOE includes a small web component, defined in `js/joe-ai.js`, that can be dropped into any web page and talks to your JOE instance via the `chatgpt` plugin. - **Custom element**: `<joe-ai-widget>` - Attributes: - `endpoint` (optional): Base URL to the JOE instance. Defaults to same origin. Example: `endpoint="https://my-joe.example.com"`. - `ai_assistant_id` (optional): `_id` of an `ai_assistant` document in JOE. If present, the server will load this assistant and use its `ai_model`, `instructions`, and `assistant_id` when starting the conversation. - `assistant_id` (optional): Direct OpenAI assistant ID (`asst_...`). If provided, the widget uses Responses API with `assistant_id` instead of a bare `model`. - `model` (optional): Model name to use when no assistant is supplied (e.g. `gpt-4.1-mini`). - `title` (optional): Header title text shown in the widget panel. - `placeholder` (optional): Input placeholder text. - `source` (optional): Arbitrary string stored on the conversation (`ai_widget_conversation.source`). - `conversation_id` (optional): Existing `ai_widget_conversation._id` to resume a saved chat. - Behavior: - On first attach: - If `conversation_id` is set → calls `GET /API/plugin/chatgpt/widgetHistory?conversation_id=...` and renders messages. - Otherwise → calls `POST /API/plugin/chatgpt/widgetStart` to create a new `ai_widget_conversation` and stores the returned `conversation_id` (and any `assistant_id`/`model`) as attributes. - On send: - Optimistically appends a `{ role:'user', content }` message in the UI. - Calls `POST /API/plugin/chatgpt/widgetMessage` with `{ conversation_id, content, role:'user', assistant_id?, model? }`. - Renders the updated `messages` and assistant reply from the response. - **Server endpoints (`chatgpt` plugin)**: - `POST /API/plugin/chatgpt/widgetStart``chatgpt.widgetStart(data)` - Input: `{ model?, ai_assistant_id?, source? }`. - Behavior: - If `ai_assistant_id` is provided, loads that `ai_assistant` and seeds `model`, `assistant_id`, `system` from it. - Creates `ai_widget_conversation` with empty `messages` and timestamps. - Returns `{ success, conversation_id, model, assistant_id }`. - `GET /API/plugin/chatgpt/widgetHistory?conversation_id=...``chatgpt.widgetHistory(data)` - Input: `{ conversation_id }`. - Returns `{ success, conversation_id, model, assistant_id, messages }`. - `POST /API/plugin/chatgpt/widgetMessage``chatgpt.widgetMessage(data)` - Input: `{ conversation_id, content, role='user', assistant_id?, model? }`. - Behavior: - Loads `ai_widget_conversation`, appends a user message. - Calls `openai.responses.create`: - With `assistant_id` if available (preferred), or - With `model` and `instructions` (from `system`) otherwise. - Appends an assistant message, updates `last_message_at`, saves the conversation. - Returns `{ success, conversation_id, model, assistant_id, messages, last_message, usage }`. ### AI Widget Test Page To help you develop and debug the widget + plugin in your instance, JOE exposes an auth‑protected test page, similar to the MCP test pages. - **Routes** (both require standard JOE `auth`): - `/ai-widget-test.html` - `${JOE.webconfig.joepath}ai-widget-test.html` (e.g. `/JsonObjectEditor/ai-widget-test.html`). - **Behavior** (`server/modules/Server.js`): - The route handler: - Reads `assistant_id` or `assistant` from the query string. - If not present, tries to resolve the default assistant via: - `JOE.Utils.Settings('DEFAULT_AI_ASSISTANT', { object: true })` and uses its `.value` as `ai_assistant_id`. - Renders a small HTML page that: - Includes the MCP nav (`_www/mcp-nav.js`) so you can jump between MCP and widget tests. - Mounts `<joe-ai-widget title="JOE AI Assistant" ai_assistant_id="...">` in a centered card. - Loads `js/joe-ai.js`, which defines both `<joe-ai-chatbox>` (for in‑app use) and `<joe-ai-widget>` (for embedding). - **Shared nav link**: - The shared dev nav (`_www/mcp-nav.js`) includes an “AI Widget” link: ```html <a href="/ai-widget-test.html" target="ai_widget_test_win" rel="noopener">AI Widget</a> ``` - This appears on MCP test/export/prompt pages and on the AI widget test page itself. ### AI / Widget Changelog (current work – `0.10.632`) - Added a Responses‑based tool runner for `<joe-ai-widget>` that wires `ai_assistant.tools` into MCP functions via `chatgpt.runWithTools`. - Enhanced widget UX: assistant/user bubble theming (using `assistant_color` and user `color`), inline “tools used this turn” meta messages, and markdown rendering for assistant replies. - Expanded the AI widget test page with an assistant picker, live tool JSON viewer, a clickable conversation history list (resume existing `ai_widget_conversation` threads), and safer user handling (widget conversations now store user id/name/color explicitly and OAuth token‑exchange errors from Google are surfaced clearly during login). - Added field-level AI autofill support: schemas can declare `ai` config on a field (e.g. `{ name:'ai_summary', type:'rendering', ai:{ prompt:'Summarize the project in a few sentences.' } }`), which renders an inline “AI” button that calls `_joe.Ai.populateField('ai_summary')` and posts to `/API/plugin/chatgpt/autofill` to compute a JSON `patch` and update the UI (with confirmation before overwriting non-empty values).