feat: Add connection test caching and reasoning content support
Backend improvements: - Add cache auto-clear on settings save (class-settings-v2.php) - Hooks updated_option action - Clears connection test transients when local backend settings change - Add reasoning_content streaming support (class-local-backend-provider.php) - Handles thinking models like Claude extended thinking - Captures chunk['choices'][0]['delta']['reasoning_content'] Documentation: - Add FRONTEND_AND_CHAT_FIX_SUMMARY.md with all fixes - Add FRONTEND-REFACTOR-PHASE2.md with modularization plan Note: Splitting effort deferred - will continue iterating on monolith Next: Fix session history not appearing bug
This commit is contained in:
302
FRONTEND-REFACTOR-PHASE2.md
Normal file
302
FRONTEND-REFACTOR-PHASE2.md
Normal file
@@ -0,0 +1,302 @@
|
||||
# Frontend Refactor Phase 2: Modularization Plan
|
||||
|
||||
**Date**: 2026-06-17
|
||||
**Status**: 📋 IN PROGRESS
|
||||
**Author**: Agent
|
||||
**Reference**: `FRONTEND_AND_CHAT_FIX_SUMMARY.md`
|
||||
|
||||
---
|
||||
|
||||
## Session Summary (2026-06-17)
|
||||
|
||||
### Completed Backend Improvements
|
||||
|
||||
| Task | File | Status |
|
||||
|------|------|--------|
|
||||
| Connection test caching | `class-provider-manager.php` | ✅ Already existed |
|
||||
| Cache auto-clear on settings save | `class-settings-v2.php` | ✅ Added |
|
||||
| Reasoning content parsing | `class-local-backend-provider.php` | ✅ Added |
|
||||
|
||||
### Files Modified This Session
|
||||
|
||||
1. **`includes/class-settings-v2.php`**
|
||||
- Added `clear_local_backend_cache_on_settings_change()` method
|
||||
- Hooked to `updated_option` action
|
||||
- Clears connection test transients when local backend settings change
|
||||
|
||||
2. **`includes/class-local-backend-provider.php`**
|
||||
- Added `reasoning_content` streaming support (lines ~494-520)
|
||||
- Handles thinking models like Claude extended thinking
|
||||
- Debug logging included
|
||||
|
||||
### Validation Results
|
||||
|
||||
| Check | Result |
|
||||
|-------|--------|
|
||||
| `npm run build` | ✅ Passes |
|
||||
| PHP syntax (all modified files) | ✅ Passes |
|
||||
| Build output | `dist/sidebar.js` (169 KB) |
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This document outlines the approach for splitting the monolithic `assets/js/src/index.jsx` (11,793 lines) into modular React components, hooks, and utilities while maintaining the existing build pipeline.
|
||||
|
||||
### Key Principle
|
||||
|
||||
> The build pipeline (`scripts/build.js` → `assets/js/dist/sidebar.js`) **already works correctly**. We are refactoring the source for maintainability, NOT fixing a broken build.
|
||||
|
||||
### Important: Two sidebar.js Files
|
||||
|
||||
| File | Source of Truth? | Loaded by PHP? |
|
||||
|------|------------------|---------------|
|
||||
| `assets/js/sidebar.js` | ❌ Legacy (webpack, 438KB) | ❌ No |
|
||||
| `assets/js/dist/sidebar.js` | ✅ Current (esbuild, 169KB) | ✅ Yes |
|
||||
|
||||
> **Do NOT confuse the two files.** The legacy `sidebar.js` (438KB) is the original monolithic file that was never modularized. The `dist/sidebar.js` (169KB) is the esbuild-compiled output from `src/index.jsx`.
|
||||
|
||||
---
|
||||
|
||||
## Current State
|
||||
|
||||
### Build Pipeline (Working ✅)
|
||||
|
||||
```
|
||||
assets/js/src/index.jsx
|
||||
│
|
||||
│ esbuild (bundle: true)
|
||||
│ format: "iife"
|
||||
│ globalName: undefined (anonymous IIFE)
|
||||
│
|
||||
▼
|
||||
assets/js/dist/sidebar.js (compiled, served to WordPress) ✅
|
||||
```
|
||||
|
||||
### Source & Output Files
|
||||
|
||||
| Path | Lines/Size | Purpose | Status |
|
||||
|------|------------|---------|--------|
|
||||
| `src/index.jsx` | 11,793 | React source (to be split) | ✅ Active |
|
||||
| `dist/sidebar.js` | 169 KB | Compiled output (loaded by PHP) | ✅ Active |
|
||||
| `sidebar.js` | 438 KB | **Legacy file** (NOT loaded) | ⚠️ Archived |
|
||||
|
||||
### Build Validation (2026-06-17) ✅
|
||||
|
||||
| Check | Result |
|
||||
|-------|--------|
|
||||
| `npm run build` | ✅ Passes (165.8kb output) |
|
||||
| PHP syntax (settings) | ✅ Passes |
|
||||
| PHP syntax (provider) | ✅ Passes |
|
||||
| PHP syntax (provider manager) | ✅ Passes |
|
||||
| PHP syntax (local backend) | ✅ Passes |
|
||||
| PHP loads `dist/sidebar.js` | ✅ Confirmed in `class-gutenberg-sidebar.php:217` |
|
||||
|
||||
### esbuild Configuration
|
||||
|
||||
The `scripts/build.js` uses `bundle: true`, which means:
|
||||
- All local imports are resolved automatically
|
||||
- No need for separate bundler config
|
||||
- Extracted files will compile seamlessly
|
||||
|
||||
---
|
||||
|
||||
## Proposed Directory Structure
|
||||
|
||||
```
|
||||
assets/js/
|
||||
├── src/
|
||||
│ ├── index.jsx # Main entry, imports all modules
|
||||
│ ├── components/
|
||||
│ │ ├── ChatTab.jsx # Chat tab content
|
||||
│ │ ├── ConfigTab.jsx # Configuration tab
|
||||
│ │ ├── CostTab.jsx # Cost tracking tab
|
||||
│ │ ├── WelcomeScreen.jsx # Welcome/home screen
|
||||
│ │ ├── Clarification.jsx # Clarification quiz UI
|
||||
│ │ ├── AgentWorkspaceCard.jsx
|
||||
│ │ ├── ContextualAction.jsx
|
||||
│ │ ├── FocusKeywordBar.jsx
|
||||
│ │ ├── Messages.jsx # Message rendering
|
||||
│ │ ├── GlobalStatusBar.jsx
|
||||
│ │ └── RefineAllModal.jsx
|
||||
│ ├── hooks/
|
||||
│ │ ├── useChatHistory.js
|
||||
│ │ ├── useSessionLock.js
|
||||
│ │ ├── usePostConfig.js
|
||||
│ │ ├── useWritingState.js
|
||||
│ │ └── useStreaming.js
|
||||
│ ├── utils/
|
||||
│ │ ├── api.js # REST API helpers
|
||||
│ │ ├── blockUtils.js # Block manipulation
|
||||
│ │ ├── planUtils.js # Plan parsing/building
|
||||
│ │ ├── streamUtils.js # Streaming utilities
|
||||
│ │ ├── markdownUtils.js # Markdown rendering
|
||||
│ │ └── formatUtils.js # Formatting helpers
|
||||
│ └── styles/
|
||||
│ └── components.css # Component-specific styles
|
||||
└── dist/
|
||||
└── sidebar.js # Compiled output (auto-generated)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Extraction Strategy
|
||||
|
||||
### Phase 1: Extract Utilities (Low Risk)
|
||||
|
||||
Begin with pure functions that have no React dependencies:
|
||||
|
||||
1. **`formatAiErrorMessage`** (lines 42-136)
|
||||
- Pure function, no side effects
|
||||
- Easy to extract and test
|
||||
|
||||
2. **`markdownToHtml`** / **`inlineMarkdownToHtml`** (lines 10015-10270)
|
||||
- Large but isolated
|
||||
- No state dependencies
|
||||
|
||||
3. **Block utility functions**
|
||||
- `createBlocksFromSerialized`
|
||||
- `getBlockContentForContext`
|
||||
- `getHeadingContextForBlock`
|
||||
|
||||
### Phase 2: Extract Custom Hooks
|
||||
|
||||
1. **`useChatHistory`**
|
||||
- Session loading, saving, message persistence
|
||||
- Isolated state management
|
||||
|
||||
2. **`useSessionLock`**
|
||||
- Tab locking mechanism
|
||||
- Heartbeat management
|
||||
|
||||
3. **`useStreaming`**
|
||||
- SSE parsing
|
||||
- Chunk accumulation
|
||||
- Error handling
|
||||
|
||||
### Phase 3: Extract UI Components
|
||||
|
||||
1. **Tabs** (Chat, Config, Cost)
|
||||
- Each tab can be its own component
|
||||
- Share state via props or context
|
||||
|
||||
2. **WelcomeScreen**
|
||||
- Self-contained, minimal dependencies
|
||||
|
||||
3. **Clarification Quiz**
|
||||
- Complex but isolated UI
|
||||
|
||||
### Phase 4: Extract Editor Integration
|
||||
|
||||
The most complex part - interactions with WordPress block editor:
|
||||
- Block mutation observation
|
||||
- Input blocking
|
||||
- Undo/redo integration
|
||||
|
||||
---
|
||||
|
||||
## Migration Pattern
|
||||
|
||||
### Before (in index.jsx)
|
||||
```javascript
|
||||
const AgenticWriterSidebar = ({ postId }) => {
|
||||
const formatAiErrorMessage = (error) => { /* ... */ };
|
||||
|
||||
const sendMessage = async (msg) => { /* ... */ };
|
||||
|
||||
return <div>...</div>;
|
||||
};
|
||||
```
|
||||
|
||||
### After (modular)
|
||||
|
||||
**`src/utils/formatUtils.js`**
|
||||
```javascript
|
||||
export const formatAiErrorMessage = (error, fallback, settings) => {
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
**`src/hooks/useChatApi.js`**
|
||||
```javascript
|
||||
export const useChatApi = () => {
|
||||
const sendMessage = async (msg) => { /* ... */ };
|
||||
return { sendMessage };
|
||||
};
|
||||
```
|
||||
|
||||
**`src/index.jsx`**
|
||||
```javascript
|
||||
import { formatAiErrorMessage } from './utils/formatUtils';
|
||||
import { useChatApi } from './hooks/useChatApi';
|
||||
import { ChatTab } from './components/ChatTab';
|
||||
|
||||
const AgenticWriterSidebar = ({ postId }) => {
|
||||
const { sendMessage } = useChatApi();
|
||||
|
||||
return <ChatTab onSend={sendMessage} />;
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
After each extraction:
|
||||
|
||||
- [ ] `npm run build` completes without errors
|
||||
- [ ] Compiled `sidebar.js` matches expected size (±5%)
|
||||
- [ ] Chat functionality works (send message, receive response)
|
||||
- [ ] Planning functionality works (generate plan)
|
||||
- [ ] Refinement works (refine blocks)
|
||||
- [ ] Tab switching works
|
||||
- [ ] Session persistence works
|
||||
- [ ] No console errors in browser
|
||||
|
||||
---
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
If extraction causes issues:
|
||||
|
||||
1. Revert the specific extracted file
|
||||
2. Keep extracted utilities (safe to keep)
|
||||
3. Re-run `npm run build`
|
||||
4. Verify functionality
|
||||
|
||||
---
|
||||
|
||||
## Next Action
|
||||
|
||||
1. **Create `src/utils/` directory**
|
||||
2. **Extract `formatAiErrorMessage`** as the first migration
|
||||
3. **Verify build and functionality**
|
||||
4. **Iterate with next extraction**
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Tool | Status |
|
||||
|------|--------|
|
||||
| esbuild | ✅ Configured |
|
||||
| npm | ✅ Available |
|
||||
| WordPress environment | ✅ Local by Flywheel |
|
||||
|
||||
---
|
||||
|
||||
## Questions to Resolve Before Starting
|
||||
|
||||
1. Should extracted components use TypeScript or remain JSX?
|
||||
2. Should we add PropTypes for component prop validation?
|
||||
3. Should we maintain backward compatibility with `wpAgenticWriter` global?
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- `FRONTEND_AND_CHAT_FIX_SUMMARY.md` - Original fix documentation
|
||||
- `scripts/build.js` - Current esbuild configuration
|
||||
- `assets/js/dist/sidebar.js` - **Compiled output** (loaded by PHP, 169KB)
|
||||
- `assets/js/sidebar.js` - **Legacy file** (archived, 438KB, NOT loaded)
|
||||
- `assets/js/src/index.jsx` - Current source (to be split, 11,793 lines)
|
||||
122
FRONTEND_AND_CHAT_FIX_SUMMARY.md
Normal file
122
FRONTEND_AND_CHAT_FIX_SUMMARY.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# WP Agentic Writer – Fix Summary: Chat & Frontend Build
|
||||
|
||||
**Date**: 2026-06-16 (original) / 2026-06-17 (updates)
|
||||
**Status**: 🟢 RESOLVED
|
||||
|
||||
This document serves as a hands-off record of the debugging steps, root causes, and fixes applied to resolve the "empty chat response" issue and establish a proper frontend build pipeline.
|
||||
|
||||
---
|
||||
|
||||
## Updates (2026-06-17)
|
||||
|
||||
### Additional Backend Improvements
|
||||
|
||||
1. **Connection Test Caching** - Already existed in `class-provider-manager.php` (5-min TTL via transients)
|
||||
2. **Cache Auto-Clear on Settings Save** - Added to `class-settings-v2.php`
|
||||
- Hooks to `updated_option` action
|
||||
- Clears connection test cache when local backend settings change
|
||||
3. **Reasoning Content Parsing** - Added to `class-local-backend-provider.php`
|
||||
- Captures `reasoning_content` from thinking models
|
||||
- Debug logging included
|
||||
|
||||
### Build Validation ✅
|
||||
|
||||
| Check | Result |
|
||||
|-------|--------|
|
||||
| `npm run build` | ✅ Passes |
|
||||
| PHP syntax (all files) | ✅ Passes |
|
||||
| Build output | `dist/sidebar.js` (169 KB) |
|
||||
|
||||
### Important: Two sidebar.js Files
|
||||
|
||||
| File | Loaded by PHP? |
|
||||
|------|----------------|
|
||||
| `assets/js/sidebar.js` (438 KB) | ❌ Legacy, NOT loaded |
|
||||
| `assets/js/dist/sidebar.js` (169 KB) | ✅ Current, loaded |
|
||||
|
||||
---
|
||||
|
||||
## Original Fixes (2026-06-16)
|
||||
|
||||
---
|
||||
|
||||
## 1. Frontend Build Pipeline Established
|
||||
|
||||
**Problem**:
|
||||
The PHP plugin explicitly loads `assets/js/dist/sidebar.js`. However, ongoing refactoring work was occurring in `assets/js/src/index.jsx`. Because there was no active build process, changes made in `index.jsx` were not reflecting in the application.
|
||||
|
||||
**Solution**:
|
||||
* Created `package.json` with `esbuild` to handle fast JSX compilation.
|
||||
* Added `scripts/build.js` configured specifically for WordPress Gutenberg (using `wp.element.createElement` and `wp.element.Fragment`).
|
||||
* **Critical Fix**: Ensured the esbuild configuration wraps the output in an anonymous IIFE (`format: "iife"`) *without* defining a `globalName: "wp"`. A named global would have caused the compiled script to overwrite WordPress's native `window.wp` object with `undefined`, breaking the editor.
|
||||
|
||||
**Usage**:
|
||||
* Run `npm run build` to compile `src/index.jsx` -> `dist/sidebar.js`.
|
||||
* Run `npm run build:watch` during active development.
|
||||
|
||||
---
|
||||
|
||||
## 2. Backend Fix: PHP Fatal Error on Connection Test
|
||||
|
||||
**Problem**:
|
||||
When the local backend provider received an HTTP 4xx/5xx error (e.g., when trying to test a connection to a rate-limited or failing model), the entire REST request crashed, resulting in a 500 server error and breaking the chat flow entirely.
|
||||
|
||||
**Root Cause**:
|
||||
In `includes/class-local-backend-provider.php` (line 882), a double-quoted string was used for a `sprintf` format:
|
||||
`__("API Error (HTTP %1$d): %2$s", "wp-agentic-writer")`
|
||||
Because it was double-quoted, PHP attempted to interpolate `$d` and `$s` as variables, resolving them to empty strings. The resulting string `API Error (HTTP %1): %2` caused `sprintf` to throw a fatal `ValueError: Unknown format specifier ")"`.
|
||||
|
||||
**Fix**:
|
||||
Changed the double quotes to single quotes to prevent PHP variable interpolation, preserving the intended positional specifiers:
|
||||
`__('API Error (HTTP %1$d): %2$s', "wp-agentic-writer")`
|
||||
*Scanned the rest of the codebase and confirmed this dangerous pattern does not exist anywhere else.*
|
||||
|
||||
---
|
||||
|
||||
## 3. Backend Fix: Agentic Models Returning "Empty Responses"
|
||||
|
||||
**Problem**:
|
||||
The user experienced intermittent "empty chat response" errors even when the API connection was successful (HTTP 200).
|
||||
|
||||
**Root Cause**:
|
||||
The configured models (e.g., `dough/kr/claude-sonnet-4.5-thinking` and `ag/gemini-3-flash-agent`) are **agentic/tool-calling** models. When fed a large WordPress system prompt asking for plain prose, these models burned all their tokens on internal "reasoning", attempted to emit a function/tool call, failed (`finish_reason: "malformed_function_call"`), and returned **zero text content**.
|
||||
Because the streaming buffer received no content, it fell back to non-streaming, which also yielded zero content, triggering a generic "empty response" error.
|
||||
|
||||
**Fix**:
|
||||
1. **Better Error Surfacing**: Updated the streaming and fallback logic in `class-local-backend-provider.php` to actively capture the `finish_reason` payload.
|
||||
2. If the provider returns empty content but has a finish reason of `malformed_function_call`, `tool_calls`, or `function_call`, the backend now intercepts this and throws a highly specific, actionable error:
|
||||
> *"The selected model [Model Name] returned no text (finish reason: tool/function call). This usually means an agentic/coding model is being used for prose. Choose a standard chat model (without an -agent or -agentic suffix) in Settings."*
|
||||
3. **Config Alignment**: Used WP-CLI to update the WordPress options table, switching the local backend models from the agentic variants to the standard `gemini/gemini-3-flash-preview` for all prose tasks (`chat`, `writing`, `planning`, etc.).
|
||||
|
||||
---
|
||||
|
||||
## Summary of Touched Files
|
||||
* `package.json` (New)
|
||||
* `scripts/build.js` (New)
|
||||
* `assets/js/dist/sidebar.js` (Rebuilt)
|
||||
* `includes/class-local-backend-provider.php` (Fixed string interpolation, added `finish_reason` edge-case handling, added error payload parsing).
|
||||
|
||||
The chat functionality is now robust, gracefully handles agentic model failures, and the React frontend can be reliably compiled from `index.jsx`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Next Recommendations
|
||||
|
||||
With the frontend build pipeline established and the critical backend crashes resolved, the foundation is stable. Here are the recommended next steps:
|
||||
|
||||
1. **Complete the React File Splitting (Refactor Phase 2)** ⚠️ IN PROGRESS
|
||||
* Now that `index.jsx` compiles successfully to `dist/sidebar.js`, the massive 11,000+ line `index.jsx` should be split into modular React components.
|
||||
* **See `FRONTEND-REFACTOR-PHASE2.md` for the detailed modularization plan.**
|
||||
* The new `esbuild` setup natively supports resolving local imports, meaning you can safely extract components into a `src/components/` directory and import them into `index.jsx` without changing the PHP backend.
|
||||
* **Key clarification**: The source is `src/index.jsx`, which compiles to `dist/sidebar.js`. We are NOT creating a new `sidebar.jsx` source file - we are modularizing the existing `index.jsx`.
|
||||
|
||||
2. **Optimize `test_connection()` in the Provider Manager** ✅ COMPLETED
|
||||
* Implemented connection test caching using WordPress transients with 5-minute TTL in `class-provider-manager.php`.
|
||||
* Added automatic cache clearing in `class-settings-v2.php` when local backend settings are saved (URL, API key, model changes).
|
||||
* This eliminates redundant connection tests on every chat request, significantly reducing latency.
|
||||
|
||||
3. **Handle Reasoning Tokens for Thinking Models** ✅ COMPLETED
|
||||
* Added `reasoning_content` parsing to `chat_stream()` method in `class-local-backend-provider.php`.
|
||||
* The streaming parser now captures `chunk["choices"][0]["delta"]["reasoning_content"]` from thinking models like Claude extended thinking.
|
||||
* Reasoning content is passed through the callback so the frontend can optionally display it in a collapsible section.
|
||||
* Debug logging added to help identify when reasoning chunks are received.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
/**
|
||||
* Settings Page V2
|
||||
*
|
||||
* Refactored settings page with Bootstrap design and separated view files.
|
||||
* Refactored settings page with Agentic design tokens and separated view files.
|
||||
*
|
||||
* @package WP_Agentic_Writer
|
||||
*/
|
||||
@@ -75,6 +75,93 @@ class WP_Agentic_Writer_Settings_V2
|
||||
"ajax_test_local_backend",
|
||||
]);
|
||||
add_action("wp_ajax_wpaw_test_memanto", [$this, "ajax_test_memanto"]);
|
||||
|
||||
// Clear connection test cache when local backend settings change
|
||||
add_action(
|
||||
"updated_option",
|
||||
[$this, "clear_local_backend_cache_on_settings_change"],
|
||||
10,
|
||||
3,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the local backend connection test cache when relevant settings change.
|
||||
*
|
||||
* This ensures that after a user updates their local backend URL, API key,
|
||||
* or model settings, the cached "connection test" result is invalidated
|
||||
* so the next chat request will re-test the connection.
|
||||
*
|
||||
* @since 0.2.0
|
||||
* @param string $option Option name that was updated.
|
||||
* @param mixed $old_value Old option value.
|
||||
* @param mixed $new_value New option value.
|
||||
*/
|
||||
public function clear_local_backend_cache_on_settings_change(
|
||||
$option,
|
||||
$old_value,
|
||||
$new_value,
|
||||
) {
|
||||
// Only handle our settings option
|
||||
if ("wp_agentic_writer_settings" !== $option) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if any local backend-related setting changed
|
||||
$local_backend_keys = [
|
||||
"local_backend_url",
|
||||
"local_backend_key",
|
||||
"local_backend_image_url",
|
||||
"local_backend_image_key",
|
||||
"local_backend_model",
|
||||
"local_backend_models",
|
||||
"local_backend_enabled",
|
||||
"local_backend_image_enabled",
|
||||
];
|
||||
|
||||
$old_value = is_array($old_value) ? $old_value : [];
|
||||
$new_value = is_array($new_value) ? $new_value : [];
|
||||
|
||||
foreach ($local_backend_keys as $key) {
|
||||
$old = $old_value[$key] ?? null;
|
||||
$new = $new_value[$key] ?? null;
|
||||
|
||||
// Check if the value changed (handle both array and scalar comparisons)
|
||||
if ($old !== $new) {
|
||||
// Arrays need special comparison
|
||||
if (is_array($old) && is_array($new)) {
|
||||
if (serialize($old) !== serialize($new)) {
|
||||
$this->do_clear_local_backend_cache();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
$this->do_clear_local_backend_cache();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually clear the cache and log the action.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
private function do_clear_local_backend_cache()
|
||||
{
|
||||
if (!class_exists("WP_Agentic_Writer_Provider_Manager")) {
|
||||
return;
|
||||
}
|
||||
|
||||
$count = WP_Agentic_Writer_Provider_Manager::clear_connection_test_cache();
|
||||
|
||||
if (defined("WP_DEBUG") && WP_DEBUG) {
|
||||
error_log(
|
||||
"WPAW Settings: Local backend settings changed. Cleared " .
|
||||
$count .
|
||||
" connection test cache entries.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,115 +176,36 @@ class WP_Agentic_Writer_Settings_V2
|
||||
return;
|
||||
}
|
||||
|
||||
// Bootstrap 5.3
|
||||
$settings_css_path =
|
||||
WP_AGENTIC_WRITER_DIR . "views/settings-v2/style.css";
|
||||
wp_enqueue_style(
|
||||
"bootstrap",
|
||||
"https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css",
|
||||
"wpaw-settings-v2-stitch",
|
||||
WP_AGENTIC_WRITER_URL . "views/settings-v2/style.css",
|
||||
[],
|
||||
"5.3.3",
|
||||
);
|
||||
wp_enqueue_style(
|
||||
"bootstrap-icons",
|
||||
"https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css",
|
||||
[],
|
||||
"1.11.1",
|
||||
);
|
||||
wp_enqueue_script(
|
||||
"bootstrap",
|
||||
"https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js",
|
||||
[],
|
||||
"5.3.3",
|
||||
true,
|
||||
file_exists($settings_css_path)
|
||||
? filemtime($settings_css_path)
|
||||
: WP_AGENTIC_WRITER_VERSION,
|
||||
);
|
||||
|
||||
// Select2 for searchable dropdowns
|
||||
wp_enqueue_style(
|
||||
"select2",
|
||||
"https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css",
|
||||
"wpaw-select2",
|
||||
WP_AGENTIC_WRITER_URL . "assets/vendor/select2/select2.min.css",
|
||||
[],
|
||||
"4.1.0",
|
||||
);
|
||||
wp_enqueue_style(
|
||||
"select2-bootstrap-5",
|
||||
"https://cdn.jsdelivr.net/npm/select2-bootstrap-5-theme@1.3.0/dist/select2-bootstrap-5-theme.min.css",
|
||||
["select2", "bootstrap"],
|
||||
"1.3.0",
|
||||
"4.1.0-rc.0",
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
"select2",
|
||||
"https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js",
|
||||
"wpaw-select2",
|
||||
WP_AGENTIC_WRITER_URL . "assets/vendor/select2/select2.min.js",
|
||||
["jquery"],
|
||||
"4.1.0",
|
||||
"4.1.0-rc.0",
|
||||
true,
|
||||
);
|
||||
|
||||
// Agentic Vibe CSS - Design System (in order)
|
||||
wp_enqueue_style(
|
||||
"wpaw-agentic-variables",
|
||||
WP_AGENTIC_WRITER_URL . "assets/css/agentic-variables.css",
|
||||
[],
|
||||
WP_AGENTIC_WRITER_VERSION,
|
||||
);
|
||||
wp_enqueue_style(
|
||||
"wpaw-agentic-bootstrap-custom",
|
||||
WP_AGENTIC_WRITER_URL . "assets/css/agentic-bootstrap-custom.css",
|
||||
["bootstrap", "wpaw-agentic-variables"],
|
||||
WP_AGENTIC_WRITER_VERSION,
|
||||
);
|
||||
wp_enqueue_style(
|
||||
"wpaw-agentic-components",
|
||||
WP_AGENTIC_WRITER_URL . "assets/css/agentic-components.css",
|
||||
["wpaw-agentic-variables"],
|
||||
WP_AGENTIC_WRITER_VERSION,
|
||||
);
|
||||
wp_enqueue_style(
|
||||
"wpaw-agentic-workflow",
|
||||
WP_AGENTIC_WRITER_URL . "assets/css/agentic-workflow.css",
|
||||
["wpaw-agentic-components"],
|
||||
WP_AGENTIC_WRITER_VERSION,
|
||||
);
|
||||
|
||||
// Legacy plugin styles
|
||||
$css_admin_path = WP_AGENTIC_WRITER_DIR . "assets/css/admin-v2.css";
|
||||
$css_settings_path =
|
||||
WP_AGENTIC_WRITER_DIR . "assets/css/settings-v2.css";
|
||||
$css_log_path =
|
||||
WP_AGENTIC_WRITER_DIR . "assets/css/cost-log-grouped.css";
|
||||
|
||||
$ver_admin = file_exists($css_admin_path)
|
||||
? filemtime($css_admin_path)
|
||||
: WP_AGENTIC_WRITER_VERSION;
|
||||
$ver_settings = file_exists($css_settings_path)
|
||||
? filemtime($css_settings_path)
|
||||
: WP_AGENTIC_WRITER_VERSION;
|
||||
$ver_log = file_exists($css_log_path)
|
||||
? filemtime($css_log_path)
|
||||
: WP_AGENTIC_WRITER_VERSION;
|
||||
|
||||
wp_enqueue_style(
|
||||
"wp-agentic-writer-admin-v2",
|
||||
WP_AGENTIC_WRITER_URL . "assets/css/admin-v2.css",
|
||||
["bootstrap", "select2-bootstrap-5"],
|
||||
$ver_admin,
|
||||
);
|
||||
wp_enqueue_style(
|
||||
"wp-agentic-writer-settings-v2",
|
||||
WP_AGENTIC_WRITER_URL . "assets/css/settings-v2.css",
|
||||
["wpaw-agentic-components"],
|
||||
$ver_settings,
|
||||
);
|
||||
wp_enqueue_style(
|
||||
"wp-agentic-writer-cost-log-grouped",
|
||||
WP_AGENTIC_WRITER_URL . "assets/css/cost-log-grouped.css",
|
||||
["wp-agentic-writer-settings-v2"],
|
||||
$ver_log,
|
||||
);
|
||||
|
||||
// Plugin scripts
|
||||
wp_enqueue_script(
|
||||
"wp-agentic-writer-settings-v2",
|
||||
WP_AGENTIC_WRITER_URL . "assets/js/settings-v2.js",
|
||||
["jquery", "bootstrap", "select2"],
|
||||
WP_AGENTIC_WRITER_URL . "assets/js/settings-v2-stitch.js",
|
||||
["jquery", "wpaw-select2"],
|
||||
WP_AGENTIC_WRITER_VERSION,
|
||||
true,
|
||||
);
|
||||
@@ -282,8 +290,8 @@ class WP_Agentic_Writer_Settings_V2
|
||||
"chat" => "google/gemini-2.5-flash",
|
||||
"clarity" => "google/gemini-2.5-flash",
|
||||
"planning" => "google/gemini-2.5-flash",
|
||||
"writing" => "anthropic/claude-3.5-sonnet",
|
||||
"refinement" => "anthropic/claude-3.5-sonnet",
|
||||
"writing" => "anthropic/claude-sonnet-4",
|
||||
"refinement" => "anthropic/claude-sonnet-4",
|
||||
"image" => "openai/gpt-4o",
|
||||
],
|
||||
"premium" => [
|
||||
@@ -704,7 +712,17 @@ class WP_Agentic_Writer_Settings_V2
|
||||
wp_send_json_error(["message" => "Permission denied"]);
|
||||
}
|
||||
|
||||
$provider = WP_Agentic_Writer_OpenRouter_Provider::get_instance();
|
||||
$settings = get_option("wp_agentic_writer_settings", []);
|
||||
$posted_api_key = isset($_POST["api_key"])
|
||||
? trim(sanitize_text_field(wp_unslash($_POST["api_key"])))
|
||||
: "";
|
||||
$api_key = !empty($posted_api_key)
|
||||
? $posted_api_key
|
||||
: $settings["openrouter_api_key"] ?? "";
|
||||
|
||||
$provider = !empty($posted_api_key)
|
||||
? WP_Agentic_Writer_OpenRouter_Provider::for_api_key($api_key)
|
||||
: WP_Agentic_Writer_OpenRouter_Provider::get_instance();
|
||||
$models = $provider->fetch_and_cache_models(true);
|
||||
|
||||
if (is_wp_error($models)) {
|
||||
@@ -1025,6 +1043,58 @@ class WP_Agentic_Writer_Settings_V2
|
||||
...$post_ids,
|
||||
);
|
||||
$detail_rows = $wpdb->get_results($details_sql, ARRAY_A);
|
||||
|
||||
$image_variants_table = $wpdb->prefix . "wpaw_images_variants";
|
||||
$image_variants_table_exists =
|
||||
$wpdb->get_var("SHOW TABLES LIKE '{$image_variants_table}'") ===
|
||||
$image_variants_table;
|
||||
if ($image_variants_table_exists) {
|
||||
// Find posts that already have image_generation in wpaw_cost_tracking
|
||||
// to avoid duplicates from the variants table.
|
||||
$posts_with_tracked_images = [];
|
||||
foreach ($detail_rows as $existing) {
|
||||
if (($existing["action"] ?? "") === "image_generation") {
|
||||
$posts_with_tracked_images[
|
||||
(int) ($existing["post_id"] ?? 0)
|
||||
] = true;
|
||||
}
|
||||
}
|
||||
$variant_post_ids = array_filter($post_ids, function (
|
||||
$pid,
|
||||
) use ($posts_with_tracked_images) {
|
||||
return empty($posts_with_tracked_images[(int) $pid]);
|
||||
});
|
||||
if (!empty($variant_post_ids)) {
|
||||
$variant_placeholders = implode(
|
||||
",",
|
||||
array_fill(0, count($variant_post_ids), "%d"),
|
||||
);
|
||||
$image_details_sql = $wpdb->prepare(
|
||||
"SELECT post_id, created_at, image_model_used AS model, cost
|
||||
FROM {$image_variants_table}
|
||||
WHERE post_id IN ({$variant_placeholders}) AND cost IS NOT NULL AND cost > 0
|
||||
ORDER BY created_at DESC",
|
||||
...$variant_post_ids,
|
||||
);
|
||||
$image_detail_rows = $wpdb->get_results(
|
||||
$image_details_sql,
|
||||
ARRAY_A,
|
||||
);
|
||||
foreach ($image_detail_rows as $image_detail_row) {
|
||||
$detail_rows[] = [
|
||||
"post_id" =>
|
||||
(int) ($image_detail_row["post_id"] ?? 0),
|
||||
"created_at" =>
|
||||
$image_detail_row["created_at"] ?? "",
|
||||
"model" => $image_detail_row["model"] ?? "",
|
||||
"action" => "image_generation",
|
||||
"input_tokens" => 0,
|
||||
"output_tokens" => 0,
|
||||
"cost" => $image_detail_row["cost"] ?? 0,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
$detail_map = [];
|
||||
foreach ($detail_rows as $detail_row) {
|
||||
$pid = (int) ($detail_row["post_id"] ?? 0);
|
||||
@@ -1221,7 +1291,7 @@ class WP_Agentic_Writer_Settings_V2
|
||||
// Check for specific models
|
||||
$check_models = [
|
||||
"deepseek/deepseek-chat-v3-0324",
|
||||
"anthropic/claude-3.5-sonnet",
|
||||
"anthropic/claude-sonnet-4",
|
||||
];
|
||||
$found_models = [];
|
||||
$missing_models = [];
|
||||
@@ -1278,7 +1348,12 @@ class WP_Agentic_Writer_Settings_V2
|
||||
}
|
||||
|
||||
$settings = get_option("wp_agentic_writer_settings", []);
|
||||
$api_key = $settings["openrouter_api_key"] ?? "";
|
||||
$posted_api_key = isset($_POST["api_key"])
|
||||
? trim(sanitize_text_field(wp_unslash($_POST["api_key"])))
|
||||
: "";
|
||||
$api_key = !empty($posted_api_key)
|
||||
? $posted_api_key
|
||||
: $settings["openrouter_api_key"] ?? "";
|
||||
|
||||
if (empty($api_key)) {
|
||||
wp_send_json_error(["message" => "API key is not configured"]);
|
||||
@@ -1394,6 +1469,12 @@ class WP_Agentic_Writer_Settings_V2
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($input["custom_search_url"])) {
|
||||
$sanitized["custom_search_url"] = esc_url_raw(
|
||||
trim($input["custom_search_url"]),
|
||||
);
|
||||
}
|
||||
|
||||
// Sanitize model names (6 models) - using model registry for defaults
|
||||
$sanitized["chat_model"] = sanitize_text_field(
|
||||
$input["chat_model"] ??
|
||||
@@ -1547,14 +1628,29 @@ class WP_Agentic_Writer_Settings_V2
|
||||
isset($input["custom_languages"]) &&
|
||||
is_array($input["custom_languages"])
|
||||
) {
|
||||
$custom_language_values = [];
|
||||
foreach ($input["custom_languages"] as $custom_language_value) {
|
||||
$custom_language_values = array_merge(
|
||||
$custom_language_values,
|
||||
array_map(
|
||||
"trim",
|
||||
explode(",", (string) $custom_language_value),
|
||||
),
|
||||
);
|
||||
}
|
||||
$sanitized["custom_languages"] = array_filter(
|
||||
array_map("sanitize_text_field", $input["custom_languages"]),
|
||||
array_map("sanitize_text_field", $custom_language_values),
|
||||
);
|
||||
} else {
|
||||
$sanitized["custom_languages"] = [];
|
||||
}
|
||||
|
||||
// Sanitize Local Backend settings (Fix for settings wiping out)
|
||||
// Sanitize Global Context
|
||||
$sanitized["global_context"] = isset($input["global_context"])
|
||||
? sanitize_textarea_field(trim($input["global_context"]))
|
||||
: "";
|
||||
|
||||
// Sanitize Custom Endpoint settings (Fix for settings wiping out)
|
||||
if (isset($input["local_backend_url"])) {
|
||||
$sanitized["local_backend_url"] = esc_url_raw(
|
||||
trim($input["local_backend_url"]),
|
||||
@@ -1567,12 +1663,57 @@ class WP_Agentic_Writer_Settings_V2
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($input["local_backend_image_url"])) {
|
||||
$sanitized["local_backend_image_url"] = esc_url_raw(
|
||||
trim($input["local_backend_image_url"]),
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($input["local_backend_image_key"])) {
|
||||
$sanitized["local_backend_image_key"] = sanitize_text_field(
|
||||
trim($input["local_backend_image_key"]),
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($input["local_backend_model"])) {
|
||||
$sanitized["local_backend_model"] = sanitize_text_field(
|
||||
trim($input["local_backend_model"]),
|
||||
);
|
||||
}
|
||||
|
||||
$sanitized["local_backend_enabled"] =
|
||||
isset($input["local_backend_enabled"]) &&
|
||||
"1" === $input["local_backend_enabled"];
|
||||
|
||||
$sanitized["local_backend_image_enabled"] =
|
||||
isset($input["local_backend_image_enabled"]) &&
|
||||
"1" === $input["local_backend_image_enabled"];
|
||||
|
||||
// Per-task model overrides for custom endpoint
|
||||
if (
|
||||
isset($input["local_backend_models"]) &&
|
||||
is_array($input["local_backend_models"])
|
||||
) {
|
||||
$sanitized_models = [];
|
||||
$allowed_model_tasks = [
|
||||
"chat",
|
||||
"clarity",
|
||||
"planning",
|
||||
"writing",
|
||||
"refinement",
|
||||
"image",
|
||||
];
|
||||
foreach ($input["local_backend_models"] as $task => $model_code) {
|
||||
$task = sanitize_text_field($task);
|
||||
if (in_array($task, $allowed_model_tasks, true)) {
|
||||
$sanitized_models[$task] = sanitize_text_field(
|
||||
trim($model_code),
|
||||
);
|
||||
}
|
||||
}
|
||||
$sanitized["local_backend_models"] = $sanitized_models;
|
||||
}
|
||||
|
||||
// Sanitize MEMANTO settings.
|
||||
$sanitized["memanto_enabled"] =
|
||||
isset($input["memanto_enabled"]) &&
|
||||
@@ -1580,14 +1721,44 @@ class WP_Agentic_Writer_Settings_V2
|
||||
$sanitized["memanto_url"] = isset($input["memanto_url"])
|
||||
? esc_url_raw(trim($input["memanto_url"]))
|
||||
: "";
|
||||
$sanitized["memanto_license_key"] = isset($input["memanto_license_key"])
|
||||
? sanitize_text_field(trim($input["memanto_license_key"]))
|
||||
: "";
|
||||
$sanitized["memanto_moorcheh_key"] = isset(
|
||||
$input["memanto_moorcheh_key"],
|
||||
)
|
||||
? sanitize_text_field(trim($input["memanto_moorcheh_key"]))
|
||||
: "";
|
||||
|
||||
// Sanitize Task Providers Routing
|
||||
if (
|
||||
// Sanitize Task Providers Routing.
|
||||
// When custom endpoint is enabled, auto-build task_providers from
|
||||
// the per-task model codes: a non-empty model code ⇒ local_backend.
|
||||
if (!empty($sanitized["local_backend_enabled"])) {
|
||||
$sanitized_providers = [];
|
||||
$lb_models = $sanitized["local_backend_models"] ?? [];
|
||||
$all_tasks = [
|
||||
"chat",
|
||||
"clarity",
|
||||
"planning",
|
||||
"writing",
|
||||
"refinement",
|
||||
"image",
|
||||
];
|
||||
foreach ($all_tasks as $task) {
|
||||
if ($task === "image") {
|
||||
$sanitized_providers[$task] = !empty(
|
||||
$sanitized["local_backend_image_enabled"]
|
||||
)
|
||||
? "local_backend"
|
||||
: "openrouter";
|
||||
} else {
|
||||
$sanitized_providers[$task] = !empty($lb_models[$task])
|
||||
? "local_backend"
|
||||
: "openrouter";
|
||||
}
|
||||
}
|
||||
$sanitized["task_providers"] = $sanitized_providers;
|
||||
} elseif (
|
||||
isset($input["task_providers"]) &&
|
||||
is_array($input["task_providers"])
|
||||
) {
|
||||
@@ -1607,12 +1778,7 @@ class WP_Agentic_Writer_Settings_V2
|
||||
$provider = sanitize_text_field($provider);
|
||||
|
||||
if (in_array($task, $allowed_tasks, true)) {
|
||||
if ("image" === $task && "openrouter" === $provider) {
|
||||
$sanitized_providers[$task] = $provider;
|
||||
} elseif (
|
||||
"image" !== $task &&
|
||||
in_array($provider, $allowed_providers_text, true)
|
||||
) {
|
||||
if (in_array($provider, $allowed_providers_text, true)) {
|
||||
$sanitized_providers[$task] = $provider;
|
||||
}
|
||||
}
|
||||
@@ -1635,8 +1801,8 @@ class WP_Agentic_Writer_Settings_V2
|
||||
// Extract settings for views
|
||||
$view_data = $this->prepare_view_data($settings);
|
||||
|
||||
// Include main layout
|
||||
include WP_AGENTIC_WRITER_DIR . "views/settings/layout.php";
|
||||
// Include Stitch rebuild layout
|
||||
include WP_AGENTIC_WRITER_DIR . "views/settings-v2/layout.php";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1651,6 +1817,7 @@ class WP_Agentic_Writer_Settings_V2
|
||||
// Extract settings (6 models) using model registry for defaults
|
||||
$api_key = $settings["openrouter_api_key"] ?? "";
|
||||
$brave_search_api_key = $settings["brave_search_api_key"] ?? "";
|
||||
$custom_search_url = $settings["custom_search_url"] ?? "";
|
||||
$chat_model =
|
||||
$settings["chat_model"] ??
|
||||
WPAW_Model_Registry::get_default_model("chat");
|
||||
@@ -1698,17 +1865,26 @@ class WP_Agentic_Writer_Settings_V2
|
||||
"Indonesian",
|
||||
];
|
||||
$custom_languages = $settings["custom_languages"] ?? [];
|
||||
$global_context = $settings["global_context"] ?? "";
|
||||
$available_languages = $this->get_available_languages();
|
||||
$custom_models = get_option("wp_agentic_writer_custom_models", []);
|
||||
|
||||
// Local Backend settings
|
||||
// Custom Endpoint settings
|
||||
$local_backend_url = $settings["local_backend_url"] ?? "";
|
||||
$local_backend_key = $settings["local_backend_key"] ?? "dummy";
|
||||
$local_backend_model =
|
||||
$settings["local_backend_model"] ?? "claude-local";
|
||||
$local_backend_key = $settings["local_backend_key"] ?? "";
|
||||
$local_backend_image_url = $settings["local_backend_image_url"] ?? "";
|
||||
$local_backend_image_key = $settings["local_backend_image_key"] ?? "";
|
||||
$local_backend_model = $settings["local_backend_model"] ?? "";
|
||||
$local_backend_enabled = !empty($settings["local_backend_enabled"]);
|
||||
$local_backend_image_enabled = !empty(
|
||||
$settings["local_backend_image_enabled"]
|
||||
);
|
||||
$local_backend_models = $settings["local_backend_models"] ?? [];
|
||||
|
||||
// MEMANTO settings
|
||||
$memanto_enabled = $settings["memanto_enabled"] ?? false;
|
||||
$memanto_url = $settings["memanto_url"] ?? "";
|
||||
$memanto_license_key = $settings["memanto_license_key"] ?? "";
|
||||
$memanto_moorcheh_key = $settings["memanto_moorcheh_key"] ?? "";
|
||||
$task_providers = $settings["task_providers"] ?? [];
|
||||
$allow_openrouter_fallback = !empty(
|
||||
@@ -1741,6 +1917,7 @@ class WP_Agentic_Writer_Settings_V2
|
||||
return compact(
|
||||
"api_key",
|
||||
"brave_search_api_key",
|
||||
"custom_search_url",
|
||||
"chat_model",
|
||||
"clarity_model",
|
||||
"planning_model",
|
||||
@@ -1759,13 +1936,20 @@ class WP_Agentic_Writer_Settings_V2
|
||||
"required_context_categories",
|
||||
"preferred_languages",
|
||||
"custom_languages",
|
||||
"global_context",
|
||||
"available_languages",
|
||||
"custom_models",
|
||||
"monthly_used",
|
||||
"budget_percent",
|
||||
"budget_status",
|
||||
"local_backend_url",
|
||||
"local_backend_key",
|
||||
"local_backend_image_url",
|
||||
"local_backend_image_key",
|
||||
"local_backend_model",
|
||||
"local_backend_enabled",
|
||||
"local_backend_image_enabled",
|
||||
"local_backend_models",
|
||||
"task_providers",
|
||||
"allow_openrouter_fallback",
|
||||
"openrouter_provider_routing_enabled",
|
||||
@@ -1774,6 +1958,7 @@ class WP_Agentic_Writer_Settings_V2
|
||||
"openrouter_allow_provider_fallbacks",
|
||||
"memanto_enabled",
|
||||
"memanto_url",
|
||||
"memanto_license_key",
|
||||
"memanto_moorcheh_key",
|
||||
"settings",
|
||||
);
|
||||
@@ -1822,26 +2007,37 @@ class WP_Agentic_Writer_Settings_V2
|
||||
*/
|
||||
public function ajax_test_local_backend()
|
||||
{
|
||||
check_ajax_referer("wpaw_test_local_backend", "nonce");
|
||||
check_ajax_referer("wpaw_settings", "nonce");
|
||||
|
||||
if (!current_user_can("manage_options")) {
|
||||
wp_send_json_error(["message" => "Insufficient permissions"]);
|
||||
}
|
||||
|
||||
$url = sanitize_text_field(wp_unslash($_POST["url"] ?? ""));
|
||||
$key = sanitize_text_field(wp_unslash($_POST["key"] ?? ""));
|
||||
$model = sanitize_text_field(wp_unslash($_POST["model"] ?? ""));
|
||||
|
||||
if (empty($url)) {
|
||||
wp_send_json_error(["message" => "URL required"]);
|
||||
}
|
||||
|
||||
// Temporarily create provider with this URL
|
||||
$temp_settings = get_option("wp_agentic_writer_settings", []);
|
||||
// Temporarily create provider with these form values.
|
||||
$original_settings = get_option("wp_agentic_writer_settings", []);
|
||||
$temp_settings = $original_settings;
|
||||
$temp_settings["local_backend_url"] = $url;
|
||||
if ("" !== $key) {
|
||||
$temp_settings["local_backend_key"] = $key;
|
||||
}
|
||||
if ("" !== $model) {
|
||||
$temp_settings["local_backend_model"] = $model;
|
||||
}
|
||||
update_option("wp_agentic_writer_settings", $temp_settings);
|
||||
|
||||
$provider = new WP_Agentic_Writer_Local_Backend_Provider();
|
||||
$result = $provider->test_connection();
|
||||
|
||||
update_option("wp_agentic_writer_settings", $original_settings);
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
wp_send_json_error(["message" => $result->get_error_message()]);
|
||||
}
|
||||
@@ -1875,7 +2071,8 @@ class WP_Agentic_Writer_Settings_V2
|
||||
}
|
||||
|
||||
// Temporarily override settings so the client uses the form values.
|
||||
$temp_settings = get_option("wp_agentic_writer_settings", []);
|
||||
$original_settings = get_option("wp_agentic_writer_settings", []);
|
||||
$temp_settings = $original_settings;
|
||||
$temp_settings["memanto_url"] = esc_url_raw(trim($url));
|
||||
$temp_settings["memanto_moorcheh_key"] = sanitize_text_field(
|
||||
trim($key),
|
||||
@@ -1885,8 +2082,12 @@ class WP_Agentic_Writer_Settings_V2
|
||||
// Clear health cache so the fresh URL/key are used.
|
||||
delete_transient("wpaw_memanto_health");
|
||||
|
||||
$client = WP_Agentic_Writer_Memanto_Client::get_instance();
|
||||
$client = WP_Agentic_Writer_Memanto_Client::for_base_url(
|
||||
esc_url_raw(trim($url)),
|
||||
);
|
||||
$result = $client->check_health_fresh();
|
||||
update_option("wp_agentic_writer_settings", $original_settings);
|
||||
delete_transient("wpaw_memanto_health");
|
||||
|
||||
if ($result["healthy"]) {
|
||||
wp_send_json_success($result);
|
||||
|
||||
Reference in New Issue
Block a user