' . esc_html__('Connect your AI service', 'aiwend') . '
' . esc_html__('You use your own API key from the provider you choose (OpenAI, Anthropic, Google Gemini, DeepSeek, xAI, OpenRouter or GroqCloud). Usage is billed to your provider account.', 'aiwend') . '
' . esc_html__('Assistants set to “Use site default” follow this connection and model, and new assistants start with that setting. Assistants with their own connection are not affected.', 'aiwend') . '
';
if ($default !== null) {
$c = $this->plugin->connections()->find(get_current_blog_id(), $default['connection_id']);
/* translators: 1: connection label, 2: provider name, 3: model name */
echo '
' . AdminView::badge(__('No site default', 'aiwend'), 'warn') . '
', AdminView::allowedHtml());
}
/* translators: %d: number of assistants that use the site default */
echo '
' . esc_html(sprintf(_n('%d assistant currently uses the site default.', '%d assistants currently use the site default.', count($inheriting), 'aiwend'), count($inheriting))) . '
' . esc_html__('Must be one of the chosen connection’s models from its last access check.', 'aiwend') . '
';
if ($inheriting !== []) {
/* translators: %d: number of assistants that use the site default */
echo '';
}
echo '';
if ($default !== null) {
$this->form('clear_default');
if ($inheriting !== []) { echo ''; }
echo '';
}
echo '';
}
private function renderKnowledgeStatus(): void
{
echo '
' . esc_html__('Knowledge search', 'aiwend') . '
';
echo wp_kses('
' . AdminView::badge(__('Keyword matching (active)', 'aiwend'), 'info') . ' ' . AdminView::badge(__('Semantic search: not available in this version', 'aiwend'), 'neutral') . '
', AdminView::allowedHtml());
echo '
' . esc_html__('Assistants find knowledge by matching words in the visitor’s question. Connecting an AI model improves how answers are written, but it does not add semantic search: questions phrased very differently from your content may still go unanswered.', 'aiwend') . '
';
foreach ([
__('An embedding connection and model, chosen separately from the chat model (Anthropic does not offer embeddings; OpenAI and Google do).', 'aiwend'),
__('Re-indexing every source with that model, and again whenever the model or vector size changes.', 'aiwend'),
__('Combining keyword and semantic results, and an evaluation showing the paraphrase problem is fixed before it is switched on.', 'aiwend'),
] as $item) {
echo '
' . esc_html($item) . '
';
}
echo '
';
}
private function renderProviders(): void
{
echo '
';
if ($pid === 'xai') {
self::printXaiNotice();
}
echo '
' . esc_html__('3. Where your data goes', 'aiwend') . '
' . esc_html($p['check']) . '
' . esc_html($p['chat']) . ' ' . esc_html__('Assistants using this connection send their instructions, the conversation and selected knowledge excerpts there.', 'aiwend') . '
' . esc_html__('Aiwend never switches to another provider automatically.', 'aiwend') . '
';
}
echo '
' . esc_html__('Use separate connections for development and production keys.', 'aiwend') . '
';
echo '
' . esc_html__('Stored encrypted on this site and never shown again. Paste it only here, never into a chat or email.', 'aiwend') . '
';
}
/** Phase 154: display-only xAI privacy notice (the connection screens). Aiwend neither checks nor enables xAI Zero Data Retention; this text does not establish compliance. */
private static function printXaiNotice(): void
{
echo '
' . esc_html__('xAI keeps API requests and responses for a limited period by default under its published policy. Zero Data Retention (ZDR) is an optional team-level setting in the xAI console; Aiwend does not check or enable it. xAI\'s terms for API use may restrict sending personal data, for example what visitors type into chat or forms, unless ZDR is used. Check the terms that apply to your xAI account before using this connection with public chat or forms. Reading this notice does not make a site compliant.', 'aiwend') . '
' . (\Aiwend\Provider\XaiPublicChat::optedInForSite(get_current_blog_id())
? esc_html__('Public chat on xAI is allowed on this site because wp-config.php defines AIWEND_XAI_ALLOW_PUBLIC_CHAT. Aiwend does not verify that your xAI account meets xAI\'s terms; that choice and its consequences are the site owner\'s.', 'aiwend')
: esc_html__('Public chat on xAI is off by default on this site: visitors cannot send messages to an assistant that uses xAI. Private preview and administrator tools still work. Only the site owner can allow it, in wp-config.php (constant AIWEND_XAI_ALLOW_PUBLIC_CHAT), after reviewing the xAI terms that apply to their account. Aiwend cannot check xAI Zero Data Retention.', 'aiwend')) . '
' . esc_html__('An access check shows the key worked at that moment. It does not guarantee ongoing availability, sufficient balance or provider limits.', 'aiwend') . '
';
(new HealthPanel($this->plugin))->connectionFacts($id);
if (($c['provider'] ?? '') === 'xai') {
self::printXaiNotice();
}
if ($c['status'] === 'disabled') {
echo '
' . esc_html__('This connection is disabled', 'aiwend') . '
' . esc_html__('Assistants using it do not answer. Enabling it requires a new access check.', 'aiwend') . '
';
$this->form('check', $id);
/* translators: %s: provider host name or names */
echo '';
echo '';
$diagnostic = $c['status'] === 'failed' ? $this->plugin->ai()->checkDiagnostic($id) : null;
if ($diagnostic !== null) {
/* translators: %s: the last access check's HTTP status and the provider's short error identifier, for example "http 401 authentication_error" */
echo '
';
}
echo '';
if ($c['status'] === 'verified' && $models !== []) {
// Model
echo '
' . esc_html__('Default model for this connection', 'aiwend') . '
' . esc_html($p['model_info'] ?? '') . ' ' . esc_html__('Aiwend does not recommend a “best” model and shows no prices; unknown values stay unknown.', 'aiwend') . '
' . esc_html__('Sends one fixed, synthetic message to the selected model. This is a real provider request and may be charged by the provider. It is counted against the site usage limit.', 'aiwend') . '
';
} else {
/* translators: %s: short error type reported by the transport */
echo '
' . esc_html(sprintf(__('Error type: %s. If the request reached the provider, its usage is uncertain and is listed under Usage for review.', 'aiwend'), (string) ($t['error'] ?? ''))) . '
' . esc_html__('Assign to an assistant', 'aiwend') . '
' . esc_html__('Gives the assistant its own connection and model, independent of the site default. You can also do this in the assistant’s “AI & model” tab.', 'aiwend') . '
' . esc_html__('The previous check and model list stop applying; assistants using this connection pause until access is checked again. The old key is not revoked at the provider.', 'aiwend') . '
';
if ($c['status'] !== 'disabled') {
$this->form('disable', $id, ' class="aw-manage-item"');
/* translators: %d: number of assistants that would stop answering */
echo '
' . esc_html(sprintf(_n('Disabling stops %d assistant from answering until the connection is enabled and checked again, or the assistant is reassigned. Nothing is moved to another provider.', 'Disabling stops %d assistants from answering until the connection is enabled and checked again, or they are reassigned. Nothing is moved to another provider.', count($dependents), 'aiwend'), count($dependents))) . '
' . esc_html__('Removes the connection and its encrypted key from this site. It does not revoke the key at the provider.', 'aiwend') . '
';
if ($explicit !== []) {
/* translators: %s: comma-separated names of the assistants assigned to the connection */
echo '';
}
if ($default !== null && $default['connection_id'] === $id) {
/* translators: %d: number of assistants that use the site default */
echo '
' . esc_html(sprintf(_n('This is the site default. Removing it clears the default, and %d assistant using the site default will stop answering until a new default is set.', 'This is the site default. Removing it clears the default, and %d assistants using the site default will stop answering until a new default is set.', count($ai->inheriting()), 'aiwend'), count($ai->inheriting()))) . '