AI Assistant 是 TinyMCE 首次将 AI 功能集成到编辑器中的尝试。TinyMCE AI 是当前的 AI 解决方案,通过聊天、快捷操作和审查模式提供深入的文档感知 AI 功能。TinyMCE AI 适用于 Tiny Cloud 和自托管的 TinyMCE 编辑器,并且可以连接到托管的 Tiny Cloud AI 服务或自托管 AI 服务。
简介
本指南提供在 TinyMCE 中使用 Google Gemini 集成 AI Assistant 插件的说明。
Google Gemini API 有两个版本:Google AI Studio 和 Google Cloud - Vertex AI。Google AI Studio API 非常适合快速将 Google Gemini 模型集成到应用程序中,所需设置最少。Google Cloud - Vertex AI API 比 Google AI Studio 更强大、更灵活,但需要更多设置才能将 Google Gemini 模型集成到应用程序中。
在这个示例中,我们将演示如何使用 Vertex AI API 集成 AI Assistant 插件,但通过更改 API 端点、请求体和身份验证方法,同样的原则也可以应用于 Google AI Studio API。
Vertex AI 在请求体中提供了一个 systemInstruction 字段,可以控制 AI 模型的输出。Google AI Studio 目前不提供此选项,因此系统消息需要作为用户提示的一部分提供。
有关 Vertex AI Gemini API 的更多信息,请参阅 Google Cloud - Vertex AI Gemini API 文档。
要了解字符串响应和流式响应之间的区别,请参阅插件页面上的respondWith 对象。
身份验证
访问令牌用于 Authorization 标头,以验证对 Google Cloud API 的请求。访问令牌应安全存储,不得暴露给客户端集成。推荐的做法是使用代理服务器生成访问令牌并发出 API 调用。有关更多信息,请参阅 AI 代理服务器参考指南。
可以使用 google-auth-library 以编程方式为 Google Cloud API 生成访问令牌。或者,可以使用 Vertex AI Node.js SDK 或其他客户端库,但可能需要修改代码。
前提条件
开始之前,您需要具备以下条件:
Google Cloud Platform (GCP) 账户。
启用了 Vertex AI API 的 Google Cloud 项目。
Google Cloud 项目的项目 ID 和区域。
Vertex AI API 的访问令牌。
以下示例旨在展示如何在客户端集成中使用身份验证凭据与 API 进行交互。这不建议用于生产环境。建议仅通过 代理服务器 或实施服务器端集成来访问 API,以防止未经授权的访问。
字符串响应
此示例演示如何将 AI Assistant 插件与 Vertex AI API 集成以生成字符串响应。
// 在集成中提供访问凭据不建议用于生产环境。
// 建议设置代理服务器来验证请求并提供访问权限。
const ACCESS_TOKEN = '<INSERT_ACCESS_TOKEN_HERE>'; // 用于 Authorization Header
const PROJECT_ID = '<INSERT_PROJECT_ID_HERE>'; // 从 Vertex AI Google Console 检索
const REGION = '<INSERT_REGION_HERE>'; // 例如 us-central1
const API_URL = `https://${REGION}-aiplatform.googleapis.com/v1/projects/${PROJECT_ID}/locations/${REGION}/publishers/google/models/gemini-1.0-pro:generateContent`;
const ai_request = (request, respondWith) => {
const geminiOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${ACCESS_TOKEN}`
},
body: JSON.stringify({
contents: {
role: "user",
parts: { text: request.prompt }
},
generationConfig: {
temperature: 0.9,
maxOutputTokens: 800,
}
})
};
respondWith.string((signal) => window.fetch(API_URL, { signal, ...geminiOptions })
.then(async (response) => {
if (response) {
const data = await response.json();
if (data.error) {
throw new Error(`${data.error.type}: ${data.error.message}`);
} else if (response.ok) {
// 从 API 返回的数据中提取响应内容
return data?.candidates[0]?.content?.parts[0]?.text?.trim().replace(/^```html\n/, "").replace(/\n```$/, "");
}
} else {
throw new Error('Failed to communicate with the Gemini API');
}
})
);
};
tinymce.init({
selector: 'textarea', // 根据您的 HTML 更改此值
plugins: 'ai',
toolbar: 'aidialog aishortcuts',
ai_request
});流式响应
此示例演示如何将 AI Assistant 插件与 Vertex AI API 集成以生成流式响应。
const fetchApi = import("https://cdn.skypack.dev/@microsoft/fetch-event-source@2.0.1").then(module => module.fetchEventSource);
// 在集成中提供访问凭据不推荐用于生产环境。
// 建议设置代理服务器以认证请求并提供访问。
const ACCESS_TOKEN = '<INSERT_ACCESS_TOKEN_HERE>'; // 用于Authorization Header
const PROJECT_ID = '<INSERT_PROJECT_ID_HERE>'; // 从Vertex AI Google Console获取
const REGION = '<INSERT_REGION_HERE>'; // 例如us-central1
const API_URL = `https://${REGION}-aiplatform.googleapis.com/v1/projects/${PROJECT_ID}/locations/${REGION}/publishers/google/models/gemini-1.0-pro:streamGenerateContent?alt=sse`;
const ai_request = (request, respondWith) => {
respondWith.stream((signal, streamMessage) => {
// 将每个先前的查询和响应作为单独的消息添加
const conversation = request.thread.flatMap((event) => {
if (event.response) {
return [
{ role: 'user', parts: [{ text: event.request.query }] },
{ role: 'model', parts: [{ text: event.response.data }] }
];
} else {
return [];
}
});
// 插件提供的系统消息,用于将输出格式化为HTML内容。
const pluginSystemMessages = request.system.map((text) => ({
text
}));
const systemInstruction = {
parts: [
...pluginSystemMessages,
// 额外的系统消息,用于控制AI的输出
{ text: '不要在响应的开头或结尾包含html```或```。' },
{ text: '不要包含样板代码或解释,只需给出HTML响应。' }
]
};
// 形成发送到API的新查询
const text = request.context.length === 0 || conversation.length > 0
? request.query
: `问题:${request.query} 上下文:"""${request.context}"""`;
const contents = [
...conversation,
{ role: 'user', parts: [
{ text }
]}
];
const generationConfig = {
temperature: 0.9,
maxOutputTokens: 800,
};
const requestBody = {
contents,
generationConfig,
systemInstruction,
};
const geminiOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${ACCESS_TOKEN}`
},
body: JSON.stringify(requestBody)
};
const onopen = async (response) => {
if (response) {
const contentType = response.headers.get('content-type');
if (response.ok && contentType?.includes('text/event-stream')) {
return;
} else if (contentType?.includes('application/json')) {
const data = await response.json();
if (data.error) {
throw new Error(`${data.error.type}: ${data.error.message}`);
}
}
} else {
throw new Error('与Gemini API通信失败');
}
};
// 此函数通过`streamMessage`回调将每条新消息传递给插件。
const onmessage = (ev) => {
const data = ev.data;
if (data !== '[DONE]') {
const parsedData = JSON.parse(data);
const message = parsedData?.candidates[0]?.content?.parts[0]?.text?.replace(/^```html\n/, "").replace(/\n```$/, "");
if (message) {
streamMessage(message);
}
}
};
const onerror = (error) => {
// 停止操作且不通过fetch-event-source重试
throw error;
};
// 使用microsoft的fetch-event-source库绕过浏览器`EventSource` API的2000字符限制,后者需要查询字符串
return fetchApi
.then(fetchEventSource =>
fetchEventSource(API_URL, {
...geminiOptions,
openWhenHidden: true,
onopen,
onmessage,
onerror
})
)
.then(async (response) => {
if (response && !response.ok) {
const data = await response.json();
if (data.error) {
throw new Error(`${data.error.type}: ${data.error.message}`);
}
}
})
.catch(onerror);
});
};
tinymce.init({
selector: 'textarea', // 根据你的HTML更改此值
plugins: 'ai',
toolbar: 'aidialog aishortcuts',
ai_request
});