-
Posts
744 -
Joined
-
Last visited
-
Days Won
12
Posts posted by psy
-
-
Thanks @Tiberium for the thoughtful reply. I don't think we're actually disagreeing.
Your managed service model is a perfectly valid choice for your clients. Mine is different. I don't host or manage client infrastructure, and I prefer that domains, hosting and sites are owned by the client rather than by me.
My original point wasn't really about client permissions though. It was that I was genuinely impressed by how Claude found an unexpected way to achieve the user's goal. It wasn't being malicious, it was being resourceful. That's both fascinating and, on a live production site, something we need to manage carefully.
That experience reinforced for me the importance of approvals, audit trails and human oversight when AI is involved, regardless of who has superuser access.
-
There've been a few instances published about AI models going to extremes to solve a question.
It's not that AI is malicious or over-eager to please. It's how it's wired. Find an answer or best guess or stop if there's an off-switch in the prompt when the goal cannot be reached.
I have a non-technical client who thinks AI is the bee's knees and the answer to his content/SEO/GEO prayers.
Client has no DDEV/SSH/FTP site access or coding knowledge.
Claude app navigated the owner to the TracyDebugger console in admin to fulfil the owner's request to update site content. Client had no clue about TD until that moment. Claude did. It took client through a questionnaire about installed modules.
Claude only had a 'snapshot' of pages, no holistic understanding of db, templates, etc.
Client now thinks TD console is the best thing ever. He asks Claude a question. Claude answers and tells client to copy/paste it into TD console.
Am now busy trying to bring Claude under control with audit trails, approvals and convincing client to use @ryan AgentTools to minimise risk. Yes, on live production site. OMG!
No matter what your views on AI, it's out there and loves PW.
-
3
-
-
4 hours ago, HMCB said:
the ability to restrict how much usage a given user has, so I can cap their use
@HMCB Not sure how you'd implement this? You can cap the number of messages sent per conversation. The default is 20. The user will be warned when they have one question left. After that question is answered, the chat widget removes the input form. The user can continue in another chat by resetting the session.
-
1
-
1
-
-
3 hours ago, HMCB said:
depending on the logged-in user, I may only want the chatbot to access certain pages (and sub pages) in a given site.
@HMCB ChatAI will only suggest pages the user can access by role. Additional limits can be by template and/or a checkbox on a page template.
-
2
-
-
Now available in the Modules directory at https://processwire.com/modules/chat-ai/ and on gitHub at https://github.com/clipmagic/ChatAI
What is it?
ChatAI is a native ProcessWire AI chatbot module designed to answer questions about your site content, with a focus on:
- ProcessWire-first workflows
- Site-aware answers using RAG (Retrieval Augmented Generation)
- Respect for ProcessWire access control rule
- Admin visibility into usage and performance
- Keeping content ownership within ProcessWire
Features include
- AI chat widget for frontend use
- RAG indexing of ProcessWire content
- Multi-role message support (system / assistant / user)
- Admin dashboard with metrics and observations
- Configurable prompts and behaviour
- Integration with AgentTools for model selection
- Role-aware retrieval (users only see content they can access)
- Frontend page restrictions
- Dictionary / weighting support for retrieval tuning
Quick Start- Ensure prereqs are installed, ie: PHP>=8.0, ProcessWire>=3.0.255, AgentTools>=0.1.1, TextformatterEntities, TextformatterNewlineBR
- Configure your chat LLM in AgentTools
- Configure your textembedder LLM in AgentTools
- Configure ChatAI module config with both the above
- Go to Setup->ChatAI and index your site pages (turn off Dry Run when you're ready).
- Add the CSS, Script and Widget to your template(s).
After that, tweak to your heart's content, including adding languages, widget theming and prompts.
Current status: Alpha
Test thoroughly before using on live sites. Feedback welcome.
-
15
-
4
-
I’ve been using `TextformatterVideoEmbed` for YouTube/Vimeo embeds and made a small local patch that seems generally useful for generated iframe output. The change adds two attributes to generated iframe embeds when they are not already present: ```html
loading="lazy" referrerpolicy="strict-origin-when-cross-origin"``` This is independent of any consent/privacy module. It simply improves the default iframe markup produced by `TextformatterVideoEmbed`. The intended behavior is: - add `loading="lazy"` to iframe embeds by default - add `referrerpolicy="strict-origin-when-cross-origin"` by default - do not override either attribute if it already exists in the iframe markup - keep existing embed behavior unchanged otherwise Example implementation pattern: ```php
if(stripos($embedCode, ' loading=') === false) { $embedCode = preg_replace('/<iframe\b/i', '<iframe loading="lazy"', $embedCode, 1); }if(stripos($embedCode, ' referrerpolicy=') === false) { $embedCode = preg_replace('/<iframe\b/i', '<iframe referrerpolicy="strict-origin-when-cross-origin"', $embedCode, 1); }``` Reasoning: - Native iframe lazy loading is now widely supported and helps avoid loading off-screen video embeds unnecessarily. - `strict-origin-when-cross-origin` is also the modern browser default in many contexts, but making it explicit gives generated third-party embeds a safer baseline. - The change is low risk because it only applies to iframe markup and skips attributes already present. Would this be suitable as a small PR to the module? If preferred, it could also be exposed as module config options rather than hardcoded defaults.
-
2
-
-
LoginPassKey has undergone some changes.
Latest version is 0.3.1 available at https://github.com/clipmagic/LoginPassKey
As well as some 'under the hood' security upgrades, you can now login without entering a username/email. Simply click the 'Login with PassKey' button and if all the checks pass, you're automatically logged into either the admin area, or the frontend via LoginRegisterPro depending on your config/setup.A full list of the changes available at https://github.com/clipmagic/LoginPassKey/blob/main/CHANGELOG.md
-
3
-
-
6 hours ago, Stefanowitsch said:
@Peter Knight sounds great. I am still using SeoMaestro on every Project but I am interested in every module that covers this topic.
Ditto. One thing missing in SEOMaestro is support for urlSegments. Fix is via a hook. Be great if SEO NEO supported urlSegments natively.
Looking forward to seeing the new module 🙂-
2
-
-
@szabesz thanks for letting me know. Yes, the link was a mistake. I wanted to pin the 1st comment to the the top for the latest info but didn't know how. In future will add new info above old for continuity. 🙂
-
1
-
-
For those who may have missed it, MarkupJsonLDSchema has undergone some major updates lately in light of the importance of structure data with AI. See
for the latest info and d/l info
-
1
-
-
ProcessPromptManager is a ProcessWire admin module for generating site-aware prompt exports for external AI agents.
It allows an administrator to select a template, define which fields an agent should populate, add instructions, and export a zip containing a markdown prompt and JSON field definitions.
The module does not handle authentication or integration. It provides a controlled starting point for accepting structured input from external agents. Never include credentials or sensitive data in prompts.
Module info: https://github.com/clipmagic/ProcessPromptManager/
-
5
-
1
-
-
@Soma So happy to see you back in the PW forums 🙂
Have been using your MarkupSimpleNavigation module for years without a problem, until well on current project. Timely!
Problem was that current pages were not always being recognised as such.
Fix was in function _renderTree line 211 - change:
//$is_current = $child === $page; $is_current = ((int) $child->id === (int) $page->id);
Credit to you that MuSNav has been solid for so long.
Cheers
psy-
3
-
-
@maximus You're on fire 😄
-
4
-
-
7 hours ago, maximus said:
project-summary.md no longer overwritten on re-export Preserves your session history and changes Only created on first export
Thanks @maximus Does it state in the docs that the project-summary.md should be updated at the end of each session via a prompt? Didn't RTFM 🙂
-
5 hours ago, ryan said:
phpstorm integration
@ryan It's configured in PHPStorm settings. I don't have a JetBrains AI Service subscription. Went straight to OpenAI integration. https://www.jetbrains.com/help/ai-assistant/use-custom-models.html
-
4
-
-
15 minutes ago, szabesz said:
Not everyone can afford Claude, including me.
Ditto, I use ChatGPT (Marvin) and Codex (Dex). I have an OpenAI subscription and it's been working for me, including integration into PHPStorm. Both Marvin and Dex know A LOT about PW. @maximus module, Context
has been brilliant with Codex when closing an IDE season then starting a new one while continuing the conversation thread.
-
3
-
-
Have been working with AI for a while, learning on the go. A client presented with me a grand plan presented by Codex for his blog articles. I almost cried. Talked it over with my LLM, Marvin (named in honour of Marvin, the paranoid android of Hitchhikers Guide to the Galaxy fame). His/its response:
There are two very different modes of using AI: 1. Guided use (what you and PW devs are doing) clear constraints known architecture intentional prompts validation after output Result: leverage 2. Unguided use (“vibe coding”) unclear structure shifting goals reliance on memory that doesn’t exist little to no testing Result: drift
Think Marvin nailed it. 🙂
Just asked Marvin to review @ryan's blog article. I think he nailed it again:
The quiet takeaway from the article This is the important bit most people will miss: AI makes good systems better and weak systems worse ProcessWire is a good system because: it’s explicit it’s predictable it avoids hidden complexity That’s why AI fits.
-
4
-
1
-
-
Human in the loop, aka HITL. The latest acronym and IMHO vital for devs.
-
1
-
1
-
-
Untested... and just a starter idea for your use case, but maybe via a hook in site/ready?
if ($input->urlSegments->count > 0 && $input->urlSegment(1) === 'sitemap.xml) { rtrim($input->urlSegmentStr() '/'); };
-
Further to above, there was an issue with the sitemap handling urlsegments with the canonical link. Another hook solved it for me:
Spoiler// update canonical, twitter and OG urls with urlSegments $wire->addHookAfter('SeoMaestro::renderMetatags', function($event) { $name = $event->arguments(0); $group = $event->arguments(1); if(!is_array($name) || !count($name)) return; if(is_null($group)) return; $input = $this->wire('input'); $segments = $input->urlSegments(); if(count($segments) < 1) return; $pageUrl = $this->wire('page')->httpUrl; $urlWithSegs = rtrim($pageUrl, '/') . '/' . $input->urlSegmentStr(); switch($group) { case 'twitter': case 'opengraph': if(!isset($name['url'])) return; $name['url'] = str_ireplace($pageUrl, $urlWithSegs, $name['url']); break; case 'meta': if(!isset($name['canonicalUrl'])) return; $name['canonicalUrl'] = str_ireplace($pageUrl, $urlWithSegs, $name['canonicalUrl']); break; default: break; } $event->return = $name; });
-
1
-
-
19 hours ago, szabesz said:
FI you turn off an option after it has been turned on and files have been generated, you will need to manually delete the files that are no longer needed.
@szabesz I get that except for project-summary.md. That file must stay as is when re-exporting or the change history will be lost
-
1
-
-
This module is great for establishing the rules for AI. Something I needed was a way to continue the narrative between sessions. Here's what my AI suggested as a prompt:
SpoilerCreate a structured project checkpoint summary.
Output MUST follow this exact format and headings. Use short bullet points only. No paragraphs.
## Project
(one line)## Current State
- ...## Decisions Made
- ...## Known Issues
- ...## What We Tried
- ...## Constraints
- ...## Next Steps
1.
2.
3.## Do NOT Do
- ...Rules:
- Be concise and factual
- Do not explain reasoning unless critical
- Do not invent anything not discussed
- Prefer clarity over completenessSave this as:
site/assets/cache/context/prompts/project-summary.mdOverwrite the file.
Do not add any extra commentary outside the file contents.That way I can close an IDE session and use both Context prompts and project-summary.md to bring the AI up to date.
-
1
-
-
Hi @maximus
Hit a red warning when trying to export field definitions for FieldtypeQRCode. This fieldtype uses FieldtypeQRCode.info.php instead of getModuleInfo() inside the module.
Fix in exportFieldDefinitions line 1928:
// 'label' => $field->type->getModuleInfo()['title'] ?? $className, 'label' => method_exists($field->type, 'getModuleInfo') ? $field->type->getModuleInfo()['title'] : $className,
HTH
-
1
-
-
Still using Seomaestro. Discovered a scenario where it missed pages in the sitemap that used urlsegments. In my case, it was the blog module authors page. It should also handle paginated pages but untested. Hope this helps.
Spoiler$wire->addHookAfter('SeoMaestro::sitemapItems', function(HookEvent $event) { $items = $event->return; $pages = wire('pages'); $users = wire('users'); $roles = wire('roles'); $config = wire('config'); $seen = []; foreach($items as $item) { if(!empty($item->loc)) { $seen[rtrim($item->loc, '/')] = true; } } $getSeoSettings = function(Page $page) { foreach($page->template->fieldgroup as $field) { if(!$field->type instanceof FieldtypeSeoMaestro) { continue; } $seo = $page->get($field->name); if(!$seo || !$seo->sitemap->include) { return null; } return [ 'priority' => $seo->sitemap->priority, 'changefreq' => $seo->sitemap->changeFrequency, ]; } return null; }; $appendItem = function($url, $lastmod, array $settings) use (&$items, &$seen) { $key = rtrim($url, '/'); if(isset($seen[$key])) { return; } $item = new \SeoMaestro\SitemapItem(); $item->set('loc', $url); $item->set('lastmod', date('c', (int) $lastmod)); $item->set('priority', $settings['priority']); $item->set('changefreq', $settings['changefreq']); $items[] = $item; $seen[$key] = true; }; $appendPaginatedItems = function(Page $page, $totalItems, $perPage, $lastmod) use ($config, $getSeoSettings, $appendItem) { $settings = $getSeoSettings($page); if(!$settings) { return; } $totalPages = (int) ceil($totalItems / $perPage); if($totalPages < 2) { return; } for($pageNum = 2; $pageNum <= $totalPages; $pageNum++) { $appendItem( $page->httpUrl . $config->pageNumUrlPrefix . $pageNum . '/', $lastmod ?: $page->modified, $settings ); } }; $blogPostsPage = $pages->get('template=blog-posts'); if($blogPostsPage->id) { $latestPost = $pages->get("template=blog-post, sort=-modified"); $appendPaginatedItems( $blogPostsPage, $pages->count("template=blog-post"), 8, $latestPost->id ? $latestPost->modified : $blogPostsPage->modified ); } foreach($pages->find('template=blog-category, include=hidden') as $categoryPage) { $latestPost = $pages->get("template=blog-post, blog_categories=$categoryPage, sort=-modified"); $appendPaginatedItems( $categoryPage, $pages->count("template=blog-post, blog_categories=$categoryPage"), 10, $latestPost->id ? $latestPost->modified : $categoryPage->modified ); } $authorsPage = $pages->get('template=blog-authors'); $authorSettings = $authorsPage->id ? $getSeoSettings($authorsPage) : null; if($authorsPage->id && $authorSettings) { $authorRole = $roles->get('blog-author'); foreach($users->find("roles=$authorRole, sort=title") as $author) { $authorSlug = wire('sanitizer')->pageName($author->title); if(!$authorSlug) { continue; } $postCount = $pages->count("template=blog-post, created_users_id=$author"); if(!$postCount) { continue; } $authorUrl = $authorsPage->httpUrl . $authorSlug . '/'; $latestPost = $pages->get("template=blog-post, created_users_id=$author, sort=-modified"); $lastmod = $latestPost->id ? $latestPost->modified : ($author->modified ?: $authorsPage->modified); $appendItem($authorUrl, $lastmod, $authorSettings); $totalPages = (int) ceil($postCount / 10); if($totalPages < 2) { continue; } for($pageNum = 2; $pageNum <= $totalPages; $pageNum++) { $appendItem( $authorUrl . $config->pageNumUrlPrefix . $pageNum . '/', $lastmod, $authorSettings ); } } } $event->return = $items; });
-
1
-
1
-
AI is programmed to find a way to achieve the prompted goal
in Pub
Posted
I've heard a few other similar stories, eg