Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8d85aad
Bring Semantic Search over from EPLabs
ZacharyRener Jun 5, 2026
e2d04cf
Bring VectorEmbeddings over from EPLabs
ZacharyRener Jun 5, 2026
2d845c8
Bring over EPLabs AI Features
ZacharyRener Jun 5, 2026
56a7f93
Adding missing dotenv package
ZacharyRener Jun 5, 2026
6151935
Update playwright workflow
ZacharyRener Jun 5, 2026
e934a3f
Php compat fix
ZacharyRener Jun 5, 2026
a5747d5
Php compat fix
ZacharyRener Jun 5, 2026
82647a3
Revert "Php compat fix"
ZacharyRener Jun 8, 2026
8d2e1db
Revert "Php compat fix"
ZacharyRener Jun 8, 2026
f2eb2fa
Revert "Update playwright workflow"
ZacharyRener Jun 8, 2026
5b7b875
Bring over SearchAlgorithm from EPLabs
ZacharyRener Jun 9, 2026
3893861
Constrict php version, update composer/composer
ZacharyRener Jun 9, 2026
b51ad5e
WP7 quote kses issue fix
ZacharyRener Jun 9, 2026
7ff0eab
JS linting fixes
ZacharyRener Jun 9, 2026
fc2dc6a
WP7 quote kses issue fix - linting
ZacharyRener Jun 9, 2026
44d9ee3
Ai feature test failure updates
ZacharyRener Jun 11, 2026
c9f0eaa
wp 7.0 test fixes
ZacharyRener Jun 11, 2026
5edcc11
Merge branch 'develop' into feature/eplabs-ai-feature-migration
ZacharyRener Jul 7, 2026
4b06059
Vector embeddings test fix
ZacharyRener Jul 7, 2026
60f71dd
Move SearchAlgorithm feature into SemanticSearch; Add feature priorit…
ZacharyRener Jul 8, 2026
0f06389
Restore changelog
ZacharyRener Jul 8, 2026
0575448
Remove php composer change
ZacharyRener Jul 8, 2026
2e25cda
rename to FeaturePrecedence
ZacharyRener Jul 8, 2026
fb536cc
restore pot lang file
ZacharyRener Jul 8, 2026
167335d
Resolve unit test and linting error
ZacharyRener Jul 8, 2026
095cdbb
Semantic search e2e test fix
ZacharyRener Jul 8, 2026
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
15 changes: 11 additions & 4 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ env:
COMPOSER_CACHE: "${{ github.workspace }}/.composer-cache"
NODE_VERSION: "20"
NODE_CACHE: "${{ github.workspace }}/node_modules_cache"
VECTOR_EMBEDDINGS_API_KEY: ${{ secrets.VECTOR_EMBEDDINGS_API_KEY }}
VECTOR_EMBEDDINGS_API_URL: ${{ secrets.VECTOR_EMBEDDINGS_API_URL }}
VECTOR_EMBEDDINGS_MODEL: ${{ secrets.VECTOR_EMBEDDINGS_MODEL }}
AI_SEARCH_SUMMARY_API_URL: ${{ secrets.AI_SEARCH_SUMMARY_API_URL }}
AI_SEARCH_SUMMARY_MODEL: ${{ secrets.AI_SEARCH_SUMMARY_MODEL }}
CF_ACCESS_CLIENT_ID: ${{ secrets.CF_ACCESS_CLIENT_ID }}
CF_ACCESS_CLIENT_SECRET: ${{ secrets.CF_ACCESS_CLIENT_SECRET }}

on:
push:
Expand Down Expand Up @@ -96,18 +103,18 @@ jobs:
- name: Set up database
run: |
if [ "${{ matrix.testGroup }}" == "@paidPlugins" ]; then
npm run e2e:setup -- --wp-version=${{ matrix.core.version }} --wc-version=${{ matrix.core.wcVersion }} --acf-pro-license='${{ secrets.ACF_PRO_LICENSE_KEY }}'
npm run e2e:setup -- --wp-version=${{ matrix.core.version }} --wc-version=${{ matrix.core.wcVersion }} --acf-pro-license='${{ secrets.ACF_PRO_LICENSE_KEY }}' --cf-access-client-id='${{ secrets.CF_ACCESS_CLIENT_ID }}' --cf-access-client-secret='${{ secrets.CF_ACCESS_CLIENT_SECRET }}'
else
npm run e2e:setup -- --wp-version=${{ matrix.core.version }} --wc-version=${{ matrix.core.wcVersion }}
npm run e2e:setup -- --wp-version=${{ matrix.core.version }} --wc-version=${{ matrix.core.wcVersion }} --cf-access-client-id='${{ secrets.CF_ACCESS_CLIENT_ID }}' --cf-access-client-secret='${{ secrets.CF_ACCESS_CLIENT_SECRET }}'
fi
if: matrix.esVersion != 'EP.io'

- name: Set up database (EP.io)
run: |
if [ "${{ matrix.testGroup }}" == "@paidPlugins" ]; then
npm run e2e:setup -- --ep-host=${{ secrets.EPIO_HOST }} --ep-credentials='${{ secrets.EPIO_SHIELD }}' --ep-index-prefix=${{ secrets.EPIO_INDEX_PREFIX }} --wp-version=${{ matrix.core.version }} --wc-version=${{ matrix.core.wcVersion }} --acf-pro-license='${{ secrets.ACF_PRO_LICENSE_KEY }}'
npm run e2e:setup -- --ep-host=${{ secrets.EPIO_HOST }} --ep-credentials='${{ secrets.EPIO_SHIELD }}' --ep-index-prefix=${{ secrets.EPIO_INDEX_PREFIX }} --wp-version=${{ matrix.core.version }} --wc-version=${{ matrix.core.wcVersion }} --acf-pro-license='${{ secrets.ACF_PRO_LICENSE_KEY }}' --cf-access-client-id='${{ secrets.CF_ACCESS_CLIENT_ID }}' --cf-access-client-secret='${{ secrets.CF_ACCESS_CLIENT_SECRET }}'
else
npm run e2e:setup -- --ep-host=${{ secrets.EPIO_HOST }} --ep-credentials='${{ secrets.EPIO_SHIELD }}' --ep-index-prefix=${{ secrets.EPIO_INDEX_PREFIX }} --wp-version=${{ matrix.core.version }} --wc-version=${{ matrix.core.wcVersion }}
npm run e2e:setup -- --ep-host=${{ secrets.EPIO_HOST }} --ep-credentials='${{ secrets.EPIO_SHIELD }}' --ep-index-prefix=${{ secrets.EPIO_INDEX_PREFIX }} --wp-version=${{ matrix.core.version }} --wc-version=${{ matrix.core.wcVersion }} --cf-access-client-id='${{ secrets.CF_ACCESS_CLIENT_ID }}' --cf-access-client-secret='${{ secrets.CF_ACCESS_CLIENT_SECRET }}'
fi
if: matrix.esVersion == 'EP.io'

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ test-coverage-html/
.phpunit.result.cache
screenshots
.DS_Store
.env

elasticpress.zip

Expand Down
1 change: 1 addition & 0 deletions .wp-env.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
".htaccess": "./tests/e2e/src/wordpress-files/.htaccess",
"wp-content/composer.json": "./tests/e2e/src/wordpress-files/composer.json",
"wp-content/mu-plugins/disable-welcome-guide.php": "./tests/e2e/src/wordpress-files/test-mu-plugins/disable-welcome-guide.php",
"wp-content/mu-plugins/set-ai-auth-headers.php": "./tests/e2e/src/wordpress-files/test-mu-plugins/set-ai-auth-headers.php",
"wp-content/mu-plugins/skip-wp-lookup.php": "./tests/e2e/src/wordpress-files/test-mu-plugins/skip-wp-lookup.php",
"wp-content/mu-plugins/unique-index-name.php": "./tests/e2e/src/wordpress-files/test-mu-plugins/unique-index-name.php",
"wp-content/plugins/add-meta-fields.php": "./tests/e2e/src/wordpress-files/test-plugins/add-meta-fields.php",
Expand Down
42 changes: 42 additions & 0 deletions assets/js/blocks/ai-search-summary/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "elasticpress/ai-search-summary",
"title": "AI Search Summary",
"category": "elasticpress",
"icon": "superhero-alt",
"description": "AI Search Summary block description.",
"textdomain": "elasticpress",
"attributes": {
"title": {
"default": "Title",
"type": "string"
},
"note": {
"default": "AI generated results",
"type": "string"
}
},
"supports": {
"align": true,
"color": {
"background": true,
"link": true,
"text": true
},
"html": false,
"position": {
"sticky": true
},
"spacing": {
"margin": true,
"padding": true
},
"typography": {
"fontSize": true,
"lineHeight": true
}
},
"editorScript": "ep-ai-search-summary-block-script",
"script": "ep-ai-search-summary-block-frontend-script"
}
42 changes: 42 additions & 0 deletions assets/js/blocks/ai-search-summary/edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* WordPress dependencies.
*/
import { RichText, useBlockProps } from '@wordpress/block-editor';
import { Placeholder } from '@wordpress/components';
import { __ } from '@wordpress/i18n';

/**
* Edit component.
*
* @param {object} props Component props.
* @param {object} props.attributes Block attributes.
* @param {Function} props.setAttributes Block attribute setter.
* @returns {Function} Component.
*/
export default ({ attributes, setAttributes }) => {
const { note, title } = attributes;

const blockProps = useBlockProps({
className: 'ep-ai-search-summary',
});

return (
<div {...blockProps}>
<RichText
aria-label={__('Title text', 'elasticpress')}
placeholder={__('Add title', 'elasticpress')}
withoutInteractiveFormatting
value={title}
onChange={(html) => setAttributes({ title: html })}
/>
<Placeholder label={__('AI Response', 'elasticpress')} />
<RichText
aria-label={__('Note', 'elasticpress')}
placeholder={__('Add a note', 'elasticpress')}
withoutInteractiveFormatting
value={note}
onChange={(html) => setAttributes({ note: html })}
/>
</div>
);
};
61 changes: 61 additions & 0 deletions assets/js/blocks/ai-search-summary/frontend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* WordPress dependencies.
*/
import apiFetch from '@wordpress/api-fetch';
import domReady from '@wordpress/dom-ready';
import { Placeholder } from '@wordpress/components';
import { createRoot, render, useEffect, useState, WPElement } from '@wordpress/element';
import Skeleton from 'react-loading-skeleton';
import 'react-loading-skeleton/dist/skeleton.css';

const { restApiEndpoint, searchQuery } = window.epAISearchSummary;

/**
* App component
*
* @returns {WPElement} App component.
*/
const App = () => {
const [className, setClassName] = useState('');
const [message, setMessage] = useState('');
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
apiFetch({
path: `${restApiEndpoint}?search_query=${searchQuery}`,
})
.then((response) => {
setClassName(response.class);
setMessage(response.html);
})
.finally(() => {
setIsLoading(false);
});
}, []);

return isLoading ? (
<Placeholder>
<Skeleton count={5} />
</Placeholder>
) : (
<div
className={`ep-ai-search-summary-generated ${className}`}
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: message }}
/>
);
};

domReady(() => {
const blocks = document.querySelectorAll('.ep-ai-search-summary-response');

blocks.forEach((block) => {
if (typeof createRoot === 'function') {
const root = createRoot(block);

root.render(<App />);
} else {
render(<App />, block);
}
});
});
15 changes: 15 additions & 0 deletions assets/js/blocks/ai-search-summary/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* WordPress dependencies.
*/
import { registerBlockType } from '@wordpress/blocks';

/**
* Internal dependencies.
*/
import { name } from './block.json';
import Edit from './edit';

registerBlockType(name, {
edit: (props) => <Edit {...props} />,
save: () => {},
});
28 changes: 28 additions & 0 deletions assets/js/embeddings-editor/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* WordPress dependencies.
*/
import { registerPlugin } from '@wordpress/plugins';

/**
* Internal dependencies.
*/
import ExcludeFromEmbedding from './plugins/exclude-from-embedding';
import IncludeForEmbedding from './plugins/include-for-embedding';

const { postTypeConfig } = window.epEmbeddingsEditor;

const { embeddingMode, embeddable } = postTypeConfig;

if (embeddingMode === 'manual' && embeddable) {
registerPlugin('ep-embedding-include', {
render: IncludeForEmbedding,
icon: null,
});
}

if (embeddingMode === 'automatic' && embeddable) {
registerPlugin('ep-embedding-exclude', {
render: ExcludeFromEmbedding,
icon: null,
});
}
40 changes: 40 additions & 0 deletions assets/js/embeddings-editor/plugins/exclude-from-embedding.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* WordPress dependencies.
*/
import { CheckboxControl } from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data';
import { PluginPostStatusInfo as PluginPostStatusInfoLegacy } from '@wordpress/edit-post';
import { PluginPostStatusInfo } from '@wordpress/editor';
import { __ } from '@wordpress/i18n';

export default () => {
const { editPost } = useDispatch('core/editor');

const { ep_embedding_exclude = false, ...meta } = useSelect(
(select) => select('core/editor').getEditedPostAttribute('meta') || {},
);

const onChange = (ep_embedding_exclude) => {
editPost({ meta: { ...meta, ep_embedding_exclude } });
};

const WrapperElement =
typeof PluginPostStatusInfo !== 'undefined'
? PluginPostStatusInfo
: PluginPostStatusInfoLegacy;

return (
<WrapperElement>
<CheckboxControl
label={__('Exclude from vector embeddings', 'elasticpress')}
help={__(
"Check this if you don't want this post to be vectorized. Depending on the post meta and taxonomy rules configured for this post type, this post may already be excluded.",
'elasticpress',
)}
checked={ep_embedding_exclude}
onChange={onChange}
__nextHasNoMarginBottom
/>
</WrapperElement>
);
};
37 changes: 37 additions & 0 deletions assets/js/embeddings-editor/plugins/include-for-embedding.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* WordPress dependencies.
*/
import { CheckboxControl } from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data';
import { PluginPostStatusInfo as PluginPostStatusInfoLegacy } from '@wordpress/edit-post';
import { PluginPostStatusInfo } from '@wordpress/editor';
import { __ } from '@wordpress/i18n';

export default () => {
const { editPost } = useDispatch('core/editor');

const { ep_embedding_include = false, ...meta } = useSelect(
(select) => select('core/editor').getEditedPostAttribute('meta') || {},
);

const onChange = (ep_embedding_include) => {
editPost({ meta: { ...meta, ep_embedding_include } });
};

const WrapperElement =
typeof PluginPostStatusInfo !== 'undefined'
? PluginPostStatusInfo
: PluginPostStatusInfoLegacy;

return (
<WrapperElement>
<CheckboxControl
label={__('Include for vector embeddings', 'elasticpress')}
help={__('Check this if you want this post to be vectorized.', 'elasticpress')}
checked={ep_embedding_include}
onChange={onChange}
__nextHasNoMarginBottom
/>
</WrapperElement>
);
};
Loading
Loading