Skip to content
Merged

Exo #21

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .phpunit.cache/test-results
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version":2,"defects":{"AISuggestionServiceTest::testSuggestExtractsKeywords":7,"AISuggestionServiceTest::testSuggestReturnsFallbackOnEmptyContent":7,"CachingInvalidationTest::test_transient_set_and_invalidate_on_vote":7,"CachingInvalidationTest::test_manual_invalidation_helper":7,"CachingInvalidationTest::test_record_vote_without_transient_side_effects":7,"RestResultsEndpointTest::testResultsEndpointReturnsAggregateAfterVote":7,"RestVoteEndpointTest::testVoteEndpointReturnsAggregate":7,"AggregationTest::testPercentagesComputedCorrectly":7,"VoteStorageServiceTest::testRecordVoteInsertsAndAggregates":7},"times":{"AISuggestionServiceTest::testSuggestReturnsFallbackOnEmptyContent":0.0080000000000000002,"AISuggestionServiceTest::testSuggestExtractsKeywords":2.4540000000000002,"AggregationTest::testPercentagesComputedCorrectly":0.002,"RestResultsEndpointTest::testResultsEndpointReturnsAggregateAfterVote":0.01,"RestVoteEndpointTest::testVoteEndpointReturnsAggregate":0.001,"VoteStorageServiceTest::testRecordVoteInsertsAndAggregates":0,"VoteStorageServiceTest::testDuplicateVotePrevention":0,"VoteStorageServiceTest::testInvalidOptionIndex":0,"ContentPoll\\Tests\\GitHubPluginUpdaterTest::test_missing_required_parameters_throws_exception":0.001,"ContentPoll\\Tests\\GitHubPluginUpdaterTest::test_constructor_registers_init_hook":0.001,"ContentPoll\\Tests\\GitHubPluginUpdaterTest::test_create_with_assets_sets_release_assets_flag":0,"CachingInvalidationTest::test_transient_set_and_invalidate_on_vote":0.017000000000000001,"CachingInvalidationTest::test_manual_invalidation_helper":0.001,"CachingInvalidationTest::test_record_vote_without_transient_side_effects":0.01,"CachingInvalidationTest::test_invalidate_cache_noop":0.001,"GrokFallbackTest::testGrokWithoutKeyFallsBackToHeuristic":1.9450000000000001}}
{"version":2,"defects":{"AISuggestionServiceTest::testSuggestReturnsFallbackOnEmptyContent":8,"AISuggestionServiceTest::testSuggestExtractsKeywords":8,"ExoProviderTest::testExoWithoutEndpointFallsBackToHeuristic":8,"ExoProviderTest::testExoWithoutModelFallsBackToHeuristic":8,"ExoProviderTest::testParseExoSseResponse":8,"ExoProviderTest::testParseExoSseResponseFullMessage":8,"ExoProviderTest::testParseExoSseResponseEmpty":8,"GrokFallbackTest::testGrokWithoutKeyFallsBackToHeuristic":8,"RestResultsEndpointTest::testResultsEndpointReturnsAggregateAfterVote":1,"RestVoteEndpointTest::testVoteEndpointReturnsAggregate":1,"ContentPoll\\Tests\\GitHubPluginUpdaterTest::test_missing_required_parameters_throws_exception":8,"ContentPoll\\Tests\\GitHubPluginUpdaterTest::test_constructor_registers_init_hook":8,"ContentPoll\\Tests\\GitHubPluginUpdaterTest::test_create_with_assets_sets_release_assets_flag":8},"times":{"AggregationTest::testPercentagesComputedCorrectly":0.001,"ContentPoll\\Tests\\GitHubPluginUpdaterTest::test_missing_required_parameters_throws_exception":0.001,"ContentPoll\\Tests\\GitHubPluginUpdaterTest::test_constructor_registers_init_hook":0,"ContentPoll\\Tests\\GitHubPluginUpdaterTest::test_create_with_assets_sets_release_assets_flag":0,"GrokFallbackTest::testGrokWithoutKeyFallsBackToHeuristic":0,"RestResultsEndpointTest::testResultsEndpointReturnsAggregateAfterVote":0,"RestVoteEndpointTest::testVoteEndpointReturnsAggregate":0,"VoteStorageServiceTest::testRecordVoteInsertsAndAggregates":0,"VoteStorageServiceTest::testDuplicateVotePrevention":0,"VoteStorageServiceTest::testInvalidOptionIndex":0,"AISuggestionServiceTest::testSuggestReturnsFallbackOnEmptyContent":0.0060000000000000001,"AISuggestionServiceTest::testSuggestExtractsKeywords":0,"ExoProviderTest::testExoWithoutEndpointFallsBackToHeuristic":0.001,"ExoProviderTest::testExoWithoutModelFallsBackToHeuristic":0,"ExoProviderTest::testParseExoSseResponse":0.001,"ExoProviderTest::testParseExoSseResponseFullMessage":0,"ExoProviderTest::testParseExoSseResponseEmpty":0}}
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).


## [0.10.0] - 2025-12-22

### Added
- Exo AI provider support for local LLM inference using Apple MLX hardware acceleration.
- ExoController REST endpoint for Exo settings with automatic detection of running models via `/state` endpoint.
- ExoProvider class for chat completions via Exo's OpenAI-compatible API.
- Documentation for Exo setup and configuration in docs/AI-PROVIDERS.md.

### Notes
- Exo enables high-performance local AI on Apple Silicon without external API keys.
- Requires Exo running locally (default: http://localhost:8000).
- Settings UI shows only currently running/loaded models.

## [0.9.2] - 2025-11-24

### Added
Expand Down
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@
},
"require-dev": {
"phpunit/phpunit": "^10.5",
"squizlabs/php_codesniffer": "^3.9"
"squizlabs/php_codesniffer": "^3.9",
"mockery/mockery": "^1.6"
},
"scripts": {
"test": "phpunit",
"lint": "phpcs --standard=WordPress --ignore=vendor src/php"
}
}
}
138 changes: 136 additions & 2 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions content-poll.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php
/**
* Plugin Name: ContentPoll AI
* Description: AI-assisted contextual polls. Ask readers targeted questions about the content they are viewing. Generates smart question + option suggestions (Heuristic, OpenAI, Claude, Gemini, Ollama, Azure OpenAI, Grok). Modern card UI & real-time results.
* Version: 0.9.2
* Description: AI-assisted contextual polls. Ask readers targeted questions about the content they are viewing. Generates smart question + option suggestions (Heuristic, OpenAI, Claude, Gemini, Ollama, Azure OpenAI, Grok, Exo). Modern card UI & real-time results.
* Version: 0.10.0
* Author: Per Soderlind
* Author URI: https://soderlind.no
* Plugin URI: https://github.com/soderlind/content-poll
Expand Down Expand Up @@ -93,6 +93,7 @@
( new \ContentPoll\REST\NonceController() )->register();
( new \ContentPoll\REST\ResultsController() )->register();
( new \ContentPoll\REST\SuggestionController() )->register();
( new \ContentPoll\REST\ExoController() )->register();

// Register dynamic block (server-rendered markup) and admin settings.
( new \ContentPoll\Blocks\VoteBlock() )->register();
Expand Down
33 changes: 31 additions & 2 deletions docs/AI-PROVIDERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,24 @@ The Content Vote plugin supports multiple AI providers for generating intelligen
- API Key: Your xAI API key
- Model: `grok-2` (default) or updated released model

### 8. PocketFlow Mode (Multi-Step, OpenAI/Azure)
### 8. Exo (Local Cluster)
- **API Key Required**: No
- **Cost**: Free (self-hosted hardware costs only)
- **Models**: Any models supported by your Exo cluster (e.g., `llama-3.2-3b`, `deepseek-r1`)
- **Description**: Exo is a distributed inference cluster that allows running large AI models across multiple consumer devices. It exposes an OpenAI-compatible API locally.
- **Best For**: Running large models locally across multiple machines, full privacy, no cloud dependencies.
- **Setup**: Install [Exo](https://github.com/exo-explore/exo) and start the cluster.
- **Configuration**:
- Provider: Exo (Local Cluster)
- Endpoint: `http://localhost:8000` (or your Exo cluster URL)
- Model: Select from running models via the "Refresh Models" button
- **Features**:
- ✅ Dynamic model list fetched from your cluster
- ✅ Health check indicator shows connection status
- ✅ No API key required (local network only)
- ✅ Streaming SSE response handling

### 9. PocketFlow Mode (Multi-Step, OpenAI/Azure)
- **API Key Required**: Yes (uses your OpenAI/Azure credentials)
- **Cost**: Same as your configured OpenAI/Azure usage
- **Models**: Any chat-capable OpenAI or Azure OpenAI deployment you have configured
Expand All @@ -113,9 +130,10 @@ The Content Vote plugin supports multiple AI providers for generating intelligen
| Gemini | $/Free | Good | Fast | External | Budget-conscious |
| Ollama | Free* | Good | Medium | Full | Privacy-first |
| Grok (xAI) | $$ | Good/Emerging | Fast | External | Real-time reasoning |
| Exo | Free* | Excellent | Medium | Full | Distributed local LLM |
| OpenAI + PocketFlow mode | $$ | Excellent | Medium | External | Topic-aware, robust polls |

*Infrastructure costs only
*Infrastructure/hardware costs only

## Implementation Details

Expand All @@ -134,6 +152,7 @@ The Content Vote plugin supports multiple AI providers for generating intelligen
- **Gemini**: `https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent?key={key}`
- **Ollama**: `{endpoint}/api/generate`
- **Grok (xAI)**: `https://api.x.ai/v1/chat/completions`
- **Exo**: `{endpoint}/v1/chat/completions` (OpenAI-compatible)

### Security Notes
- API keys are stored in WordPress options (encrypted by WordPress)
Expand Down Expand Up @@ -168,6 +187,8 @@ For deployment flexibility (Docker, CI/CD, wp-config.php), you can configure AI
| Ollama Model | `CONTENT_POLL_OLLAMA_MODEL` | `CONTENT_POLL_OLLAMA_MODEL` | `llama3.2` |
| Grok API Key | `CONTENT_POLL_GROK_KEY` | `CONTENT_POLL_GROK_KEY` | (empty) |
| Grok Model | `CONTENT_POLL_GROK_MODEL` | `CONTENT_POLL_GROK_MODEL` | `grok-2` |
| Exo Endpoint | `CONTENT_POLL_EXO_ENDPOINT` | `CONTENT_POLL_EXO_ENDPOINT` | (empty) |
| Exo Model | `CONTENT_POLL_EXO_MODEL` | `CONTENT_POLL_EXO_MODEL` | (empty) |

### Example: wp-config.php Constants (OpenAI)

Expand Down Expand Up @@ -237,6 +258,14 @@ This allows developers to lock down production configurations while still allowi
4. Check firewall allows local connections
5. Test endpoint: `curl http://localhost:11434/api/version`

### Exo Connection Issues
1. Ensure Exo cluster is running: check with `curl http://localhost:8000/v1/models`
2. Verify endpoint URL matches your Exo configuration (default port is 8000 or 52415)
3. Use the "Check Connection" button in settings to verify connectivity
4. Click "Refresh Models" to see available models from your cluster
5. Ensure at least one model is loaded in your Exo cluster
6. Check firewall allows local connections on the configured port

## Future Enhancements
- Support for custom system prompts
- Temperature/creativity controls
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "content-poll",
"version": "0.9.2",
"version": "0.10.0",
"private": true,
"description": "ContentPoll AI – WordPress poll block plugin (2–6 options).",
"author": "Your Name",
Expand Down
9 changes: 8 additions & 1 deletion readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Contributors: PerS
Tags: voting, polls, gutenberg, block, survey
Requires at least: 6.8
Tested up to: 6.8
Stable tag: 0.9.2
Stable tag: 0.10.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Expand Down Expand Up @@ -168,6 +168,13 @@ No. The block is lightweight (~7KB JavaScript gzipped) and results are loaded as

== Changelog ==

= 0.10.0 - 2025-12-22 =
* Added: Exo AI provider support for local LLM inference using Apple MLX hardware acceleration
* Added: ExoController REST endpoint for Exo settings with automatic detection of running models
* Added: ExoProvider class for chat completions via Exo's OpenAI-compatible API
* Added: Documentation for Exo setup and configuration in docs/AI-PROVIDERS.md
* Notes: Exo enables high-performance local AI on Apple Silicon without external API keys; requires Exo running locally

= 0.9.2 - 2025-11-24 =
* Added: Environment variable and PHP constant support for all AI settings (provider, API keys, models, endpoints)
* Added: Configuration priority: PHP constant → environment variable → database option → default value
Expand Down
Loading