221 lines
7.7 KiB
JavaScript
221 lines
7.7 KiB
JavaScript
/*
|
|
@title :GoogleAI 音声解析スクリプト
|
|
@author :Refactored
|
|
*/
|
|
|
|
// ==========================================
|
|
// 設定定義 (メンテナンスしやすくするために定数化)
|
|
// ==========================================
|
|
const CONFIG = {
|
|
// ⚠️ APIキーは必ず再生成し、ここには直接書かず、可能であればパラメータ機能等を利用してください
|
|
KEYS: {
|
|
GEMINI: "AIzaSyD7M3XgGaTXn1mYg3CktBhF7kTRnxTgKAQ",
|
|
PLEASANTER: "6504c8a807677a3a576e10327f3c19876c55736ee45d4a845796b9e7f5e087bfd4bd0d8184863da8cf1733ca635111432ab334aea59102b06a96ee6d2c05190d"
|
|
},
|
|
URLS: {
|
|
GEMINI: "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent",
|
|
// 自身のサイトのドメインに合わせて調整してください
|
|
PLEASANTER_BIN: "https://nextoffice.next-hd.co.jp/pleasanter/api/binaries/"
|
|
},
|
|
// 結果を格納するフィールドのマッピング
|
|
FIELDS: {
|
|
ATTACHMENT: "AttachmentsA",
|
|
TITLE: "ClassC",
|
|
SUMMARY: "DescriptionA",
|
|
HARASSMENT_PCT: "NumA",
|
|
MEASURE: "DescriptionB",
|
|
TODO: "DescriptionC"
|
|
},
|
|
// Geminiへの指示プロンプト
|
|
PROMPT: `
|
|
この音声データは電話の通話録音です。
|
|
この2名の会話を次の分析項目の通り分析して回答してください。
|
|
|
|
【分析項目】
|
|
・本会話のタイトル(50文字以内)
|
|
・会話の要約(200文字以内)
|
|
・カスハラ該当度パーセンテージ(数値のみ)
|
|
・本カスハラに対する対応策(200文字以内)
|
|
・話者2名に求められる今後必要なアクション(200文字以内)
|
|
|
|
【制約事項】
|
|
回答は必ず以下のJSON形式のみを出力してください。Markdown記法(code block)は含めないでください。
|
|
{"Title": "...", "Summary": "...", "CustomerHarassmentPercentage": 0, "CustomerMeasure": "...", "Todo": "..."}
|
|
|
|
無言電話で2名の会話が成立していない場合は、Summaryを「無言電話」とし、CustomerHarassmentPercentageを0、CustomerMeasureを「カスハラ該当なし」としてください。
|
|
`
|
|
};
|
|
|
|
// ==========================================
|
|
// メイン処理 (エントリポイント)
|
|
// ==========================================
|
|
try {
|
|
// ログ出力(デバッグ用)
|
|
context.Log(`Start Script: ControlId=${context.ControlId}, Depth=${context.ServerScript.ScriptDepth}`);
|
|
|
|
// 無限ループ防止チェック (ScriptDepth === 0 の時のみ実行)
|
|
if (context.ServerScript.ScriptDepth === 0) {
|
|
// 対象のボタン/イベントの場合のみ実行
|
|
const validTriggers = ["CreateBtn", "UpdateBtn", "Process_1"];
|
|
if (validTriggers.includes(context.ControlId)) {
|
|
executeGoogleAiAnalysis();
|
|
}
|
|
}
|
|
} catch (e) {
|
|
context.Log("Error in Main Scope: " + e.message);
|
|
}
|
|
|
|
// ==========================================
|
|
// ロジック関数群
|
|
// ==========================================
|
|
|
|
/**
|
|
* AI解析のメインフロー
|
|
*/
|
|
function executeGoogleAiAnalysis() {
|
|
try {
|
|
// 1. 添付ファイルの確認
|
|
const attachments = JSON.parse(model[CONFIG.FIELDS.ATTACHMENT] || "[]");
|
|
if (attachments.length === 0) {
|
|
context.Log("No attachments found.");
|
|
// 必要であればメッセージを表示
|
|
// context.AddMessage("音声ファイルが添付されていません。");
|
|
return;
|
|
}
|
|
|
|
const targetFile = attachments[0];
|
|
context.Log(`Processing File: ${targetFile.Guid}`);
|
|
|
|
// 2. 音声ファイルのBase64取得
|
|
const base64Data = fetchFileBase64(targetFile.Guid);
|
|
if (!base64Data) {
|
|
throw new Error("Failed to retrieve file content.");
|
|
}
|
|
|
|
// 3. Gemini APIへリクエスト
|
|
const aiResponse = callGeminiApi(base64Data);
|
|
if (!aiResponse) {
|
|
throw new Error("Failed to get response from AI.");
|
|
}
|
|
|
|
// 4. 結果をプリザンターの項目へセット
|
|
applyResultToModel(aiResponse);
|
|
|
|
} catch (e) {
|
|
context.Log(`Error in executeGoogleAiAnalysis: ${e.message}`);
|
|
context.AddMessage(`AI解析エラー: ${e.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* プリザンター内部APIを叩いて添付ファイルのBase64を取得
|
|
*/
|
|
function fetchFileBase64(guid) {
|
|
const url = `${CONFIG.URLS.PLEASANTER_BIN}${guid}/get`;
|
|
|
|
httpClient.RequestHeaders.Clear();
|
|
httpClient.RequestUri = url;
|
|
httpClient.Content = JSON.stringify({
|
|
"ApiVersion": 1.1,
|
|
"ApiKey": CONFIG.KEYS.PLEASANTER
|
|
});
|
|
|
|
try {
|
|
const response = httpClient.Post();
|
|
const json = JSON.parse(response);
|
|
|
|
if (json.StatusCode && json.StatusCode !== 200) {
|
|
context.Log(`API Error: ${json.Message}`);
|
|
return null;
|
|
}
|
|
return json.Response.Base64;
|
|
} catch (e) {
|
|
context.Log(`Fetch File Error: ${e.message}`);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gemini APIへの問い合わせ
|
|
*/
|
|
function callGeminiApi(base64Data) {
|
|
const url = `${CONFIG.URLS.GEMINI}?key=${CONFIG.KEYS.GEMINI}`;
|
|
|
|
const requestBody = {
|
|
"contents": [{
|
|
"parts": [
|
|
{ "text": CONFIG.PROMPT },
|
|
{
|
|
"inline_data": {
|
|
"mime_type": "audio/x-wav", // 必要に応じて wav/mp3 等の判別ロジックを入れてください
|
|
"data": base64Data
|
|
}
|
|
}
|
|
]
|
|
}],
|
|
"generationConfig": {
|
|
"responseMimeType": "application/json"
|
|
}
|
|
};
|
|
|
|
httpClient.RequestHeaders.Clear();
|
|
httpClient.RequestUri = url;
|
|
httpClient.Content = JSON.stringify(requestBody);
|
|
|
|
try {
|
|
const responseStr = httpClient.Post();
|
|
const responseJson = JSON.parse(responseStr);
|
|
|
|
// エラーレスポンスのチェック
|
|
if (responseJson.error) {
|
|
throw new Error(`Gemini API Error: ${responseJson.error.message}`);
|
|
}
|
|
|
|
// テキスト抽出
|
|
let rawText = responseJson.candidates[0].content.parts[0].text;
|
|
|
|
// 配列で返ってくる場合やMarkdownコードブロックが含まれる場合のクリーニング
|
|
// (responseMimeType: json を指定しているため、通常はそのままパース可能だが念のため)
|
|
return parseGeminiJson(rawText);
|
|
|
|
} catch (e) {
|
|
context.Log(`Gemini Call Error: ${e.message}`);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GeminiからのJSONテキストを安全にパースする
|
|
*/
|
|
function parseGeminiJson(text) {
|
|
try {
|
|
// 万が一Markdown記法 (```json ... ```) が残っていた場合除去する
|
|
const cleanText = text.replace(/```json/g, "").replace(/```/g, "").trim();
|
|
const parsed = JSON.parse(cleanText);
|
|
|
|
// 配列で返ってきた場合は最初の要素を使う
|
|
if (Array.isArray(parsed)) {
|
|
return parsed[0];
|
|
}
|
|
return parsed;
|
|
} catch (e) {
|
|
context.Log("JSON Parse Error. Raw Text: " + text);
|
|
throw new Error("AIの回答をJSONとして解析できませんでした。");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 解析結果をモデルに適用して更新フラグを立てる
|
|
*/
|
|
function applyResultToModel(data) {
|
|
model[CONFIG.FIELDS.TITLE] = data.Title;
|
|
model[CONFIG.FIELDS.SUMMARY] = data.Summary;
|
|
model[CONFIG.FIELDS.HARASSMENT_PCT] = data.CustomerHarassmentPercentage;
|
|
model[CONFIG.FIELDS.MEASURE] = data.CustomerMeasure;
|
|
model[CONFIG.FIELDS.TODO] = data.Todo;
|
|
|
|
// 更新して終了
|
|
model.UpdateOnExit = true;
|
|
context.Log("Model updated successfully.");
|
|
}
|