Jump to content

psy

Members
  • Posts

    744
  • Joined

  • Last visited

  • Days Won

    12

Posts posted by psy

  1. 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.

  2. 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.

    • Like 3
  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.

    • Like 1
    • Thanks 1
  4. 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.

    • Like 2
  5. Now available in the Modules directory at https://processwire.com/modules/chat-ai/ and on gitHub at https://github.com/clipmagic/ChatAI  

    chatai-readme.thumb.jpg.3295a87554fb855c9b53ce66c40bb789.jpg

    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

    1. Ensure prereqs are installed, ie: PHP>=8.0, ProcessWire>=3.0.255, AgentTools>=0.1.1, TextformatterEntities, TextformatterNewlineBR
    2. Configure your chat LLM in AgentTools
    3. Configure your textembedder LLM in AgentTools
    4. Configure ChatAI module config with both the above
    5. Go to Setup->ChatAI and index your site pages (turn off Dry Run when you're ready).
    6. 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.

     

    • Like 15
    • Thanks 4
  6. 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.
    
    • Like 2
  7. 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

     

    • Like 3
  8. 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 🙂 

    • Like 2
  9. 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

     

    • Thanks 1
  10. 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/

    • Like 5
    • Thanks 1
  11. @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

    • Like 3
  12. 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.

     

    • Like 3
  13. 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.

     

    • Like 4
    • Thanks 1
  14. 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;
    });

     

     

    • Thanks 1
  15. 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:
     

    Spoiler

    Create 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 completeness

    Save this as:
    site/assets/cache/context/prompts/project-summary.md

    Overwrite 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.

    • Like 1
  16. 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

    • Like 1
  17. 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;
    });

     

    • Like 1
    • Thanks 1
×
×
  • Create New...