feat(channels): 社交渠道 OAuth 改进 + 集成应用重命名 + AI 流程文档
- Facebook/Instagram/TikTok 渠道 OAuth 授权流程改进 - 新增 oauth/credentialstore 包 - 集成应用 OpenAI 重命名为 OpenAI 兼容 (migration 061) - 消息生命周期与 AI 流程架构文档 - inbox 管理界面 i18n 更新
This commit is contained in:
@@ -16,10 +16,6 @@ const globalConfig = useMapGetter('globalConfig/get');
|
||||
|
||||
const enabledFeatures = ref({});
|
||||
|
||||
const hasTiktokConfigured = computed(() => {
|
||||
return window.chatwootConfig?.tiktokAppId;
|
||||
});
|
||||
|
||||
const channelList = computed(() => {
|
||||
const { apiChannelName } = globalConfig.value;
|
||||
const channels = [
|
||||
@@ -79,14 +75,12 @@ const channelList = computed(() => {
|
||||
},
|
||||
];
|
||||
|
||||
if (hasTiktokConfigured.value) {
|
||||
channels.push({
|
||||
key: 'tiktok',
|
||||
title: t('INBOX_MGMT.ADD.AUTH.CHANNEL.TIKTOK.TITLE'),
|
||||
description: t('INBOX_MGMT.ADD.AUTH.CHANNEL.TIKTOK.DESCRIPTION'),
|
||||
icon: 'i-woot-tiktok',
|
||||
});
|
||||
}
|
||||
channels.push({
|
||||
key: 'tiktok',
|
||||
title: t('INBOX_MGMT.ADD.AUTH.CHANNEL.TIKTOK.TITLE'),
|
||||
description: t('INBOX_MGMT.ADD.AUTH.CHANNEL.TIKTOK.DESCRIPTION'),
|
||||
icon: 'i-woot-tiktok',
|
||||
});
|
||||
|
||||
channels.push({
|
||||
key: 'voice',
|
||||
|
||||
+61
-11
@@ -47,6 +47,10 @@ export default {
|
||||
errorStateMessage: '',
|
||||
errorStateDescription: '',
|
||||
hasLoginStarted: false,
|
||||
// Per-inbox Meta App credentials (collected during channel setup)
|
||||
fbAppId: '',
|
||||
fbAppSecret: '',
|
||||
fbApiVersion: 'v18.0',
|
||||
};
|
||||
},
|
||||
|
||||
@@ -122,9 +126,9 @@ export default {
|
||||
|
||||
runFBInit() {
|
||||
FB.init({
|
||||
appId: window.chatwootConfig.fbAppId,
|
||||
appId: this.fbAppId,
|
||||
xfbml: true,
|
||||
version: window.chatwootConfig.fbApiVersion,
|
||||
version: this.fbApiVersion,
|
||||
status: true,
|
||||
});
|
||||
window.fbSDKLoaded = true;
|
||||
@@ -195,6 +199,8 @@ export default {
|
||||
page_access_token: this.selectedPage.access_token,
|
||||
page_id: this.selectedPage.id,
|
||||
inbox_name: this.selectedPage.name?.trim(),
|
||||
fb_app_id: this.fbAppId,
|
||||
fb_app_secret: this.fbAppSecret,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -226,16 +232,60 @@ export default {
|
||||
v-if="!hasLoginStarted"
|
||||
class="flex flex-col items-center justify-center h-full text-center"
|
||||
>
|
||||
<a href="#" @click="startLogin()">
|
||||
<img
|
||||
class="w-auto h-10 rounded-md"
|
||||
src="~dashboard/assets/images/channels/facebook_login.png"
|
||||
alt="Facebook-logo"
|
||||
<div class="w-full max-w-md space-y-4">
|
||||
<div class="flex flex-col items-center mb-4">
|
||||
<a href="#" @click.prevent>
|
||||
<img
|
||||
class="w-auto h-10 rounded-md"
|
||||
src="~dashboard/assets/images/channels/facebook_login.png"
|
||||
alt="Facebook-logo"
|
||||
/>
|
||||
</a>
|
||||
<p class="py-4">
|
||||
{{ replaceInstallationName($t('INBOX_MGMT.ADD.FB.HELP')) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-left space-y-3">
|
||||
<label class="block">
|
||||
<span class="text-sm font-medium text-slate-700">
|
||||
Meta App ID
|
||||
</span>
|
||||
<input
|
||||
v-model="fbAppId"
|
||||
type="text"
|
||||
placeholder="1234567890123456"
|
||||
class="w-full mt-1 px-3 py-2 border border-slate-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</label>
|
||||
<label class="block">
|
||||
<span class="text-sm font-medium text-slate-700">
|
||||
Meta App Secret
|
||||
</span>
|
||||
<input
|
||||
v-model="fbAppSecret"
|
||||
type="password"
|
||||
placeholder="App Secret"
|
||||
class="w-full mt-1 px-3 py-2 border border-slate-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</label>
|
||||
<label class="block">
|
||||
<span class="text-sm font-medium text-slate-700">
|
||||
Graph API Version
|
||||
</span>
|
||||
<input
|
||||
v-model="fbApiVersion"
|
||||
type="text"
|
||||
placeholder="v18.0"
|
||||
class="w-full mt-1 px-3 py-2 border border-slate-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<NextButton
|
||||
:label="$t('INBOX_MGMT.ADD.FB.LOGIN_WITH_FB')"
|
||||
:disabled="!fbAppId || !fbAppSecret"
|
||||
@click="startLogin()"
|
||||
/>
|
||||
</a>
|
||||
<p class="py-6">
|
||||
{{ replaceInstallationName($t('INBOX_MGMT.ADD.FB.HELP')) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-if="hasError" class="max-w-lg mx-auto text-center">
|
||||
|
||||
+33
-2
@@ -11,6 +11,10 @@ const errorStateMessage = ref('');
|
||||
const errorStateDescription = ref('');
|
||||
const isRequestingAuthorization = ref(false);
|
||||
|
||||
// Per-inbox Meta App credentials (collected during channel setup)
|
||||
const igAppId = ref('');
|
||||
const igAppSecret = ref('');
|
||||
|
||||
onMounted(() => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
// TODO: Handle error type
|
||||
@@ -36,7 +40,10 @@ onMounted(() => {
|
||||
|
||||
const requestAuthorization = async () => {
|
||||
isRequestingAuthorization.value = true;
|
||||
const response = await instagramClient.generateAuthorization();
|
||||
const response = await instagramClient.generateAuthorization({
|
||||
app_id: igAppId.value,
|
||||
app_secret: igAppSecret.value,
|
||||
});
|
||||
const {
|
||||
data: { url },
|
||||
} = response;
|
||||
@@ -65,11 +72,35 @@ const requestAuthorization = async () => {
|
||||
<p class="py-6 text-sm text-n-slate-11">
|
||||
{{ $t('INBOX_MGMT.ADD.INSTAGRAM.HELP') }}
|
||||
</p>
|
||||
<div class="w-full max-w-sm space-y-3 text-left mb-6">
|
||||
<label class="block">
|
||||
<span class="text-sm font-medium text-slate-700">
|
||||
Instagram App ID
|
||||
</span>
|
||||
<input
|
||||
v-model="igAppId"
|
||||
type="text"
|
||||
placeholder="1234567890123456"
|
||||
class="w-full mt-1 px-3 py-2 border border-slate-300 rounded-md focus:outline-none focus:ring-2 focus:ring-pink-500"
|
||||
/>
|
||||
</label>
|
||||
<label class="block">
|
||||
<span class="text-sm font-medium text-slate-700">
|
||||
Instagram App Secret
|
||||
</span>
|
||||
<input
|
||||
v-model="igAppSecret"
|
||||
type="password"
|
||||
placeholder="App Secret"
|
||||
class="w-full mt-1 px-3 py-2 border border-slate-300 rounded-md focus:outline-none focus:ring-2 focus:ring-pink-500"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<Button
|
||||
class="text-white !rounded-full !px-6 bg-gradient-to-r from-[#833AB4] via-[#FD1D1D] to-[#FCAF45]"
|
||||
lg
|
||||
icon="i-ri-instagram-line"
|
||||
:disabled="isRequestingAuthorization"
|
||||
:disabled="isRequestingAuthorization || !igAppId || !igAppSecret"
|
||||
:is-loading="isRequestingAuthorization"
|
||||
:label="$t('INBOX_MGMT.ADD.INSTAGRAM.CONTINUE_WITH_INSTAGRAM')"
|
||||
@click="requestAuthorization()"
|
||||
|
||||
+33
-2
@@ -11,6 +11,10 @@ const errorStateMessage = ref('');
|
||||
const errorStateDescription = ref('');
|
||||
const isRequestingAuthorization = ref(false);
|
||||
|
||||
// Per-inbox TikTok App credentials (collected during channel setup)
|
||||
const ttAppId = ref('');
|
||||
const ttAppSecret = ref('');
|
||||
|
||||
onMounted(() => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
// TODO: Handle error type
|
||||
@@ -36,7 +40,10 @@ onMounted(() => {
|
||||
|
||||
const requestAuthorization = async () => {
|
||||
isRequestingAuthorization.value = true;
|
||||
const response = await tiktokClient.generateAuthorization();
|
||||
const response = await tiktokClient.generateAuthorization({
|
||||
app_id: ttAppId.value,
|
||||
app_secret: ttAppSecret.value,
|
||||
});
|
||||
const {
|
||||
data: { url },
|
||||
} = response;
|
||||
@@ -65,11 +72,35 @@ const requestAuthorization = async () => {
|
||||
<p class="py-6 text-sm text-n-slate-11">
|
||||
{{ $t('INBOX_MGMT.ADD.TIKTOK.HELP') }}
|
||||
</p>
|
||||
<div class="w-full max-w-sm space-y-3 text-left mb-6">
|
||||
<label class="block">
|
||||
<span class="text-sm font-medium text-slate-700">
|
||||
TikTok App ID
|
||||
</span>
|
||||
<input
|
||||
v-model="ttAppId"
|
||||
type="text"
|
||||
placeholder="1234567890123456"
|
||||
class="w-full mt-1 px-3 py-2 border border-slate-300 rounded-md focus:outline-none focus:ring-2 focus:ring-cyan-500"
|
||||
/>
|
||||
</label>
|
||||
<label class="block">
|
||||
<span class="text-sm font-medium text-slate-700">
|
||||
TikTok App Secret
|
||||
</span>
|
||||
<input
|
||||
v-model="ttAppSecret"
|
||||
type="password"
|
||||
placeholder="App Secret"
|
||||
class="w-full mt-1 px-3 py-2 border border-slate-300 rounded-md focus:outline-none focus:ring-2 focus:ring-cyan-500"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<Button
|
||||
class="text-white !rounded-full !px-6 bg-gradient-to-r from-[#00f2ea] via-[#ff0050] to-[#000000]"
|
||||
lg
|
||||
icon="i-ri-tiktok-line"
|
||||
:disabled="isRequestingAuthorization"
|
||||
:disabled="isRequestingAuthorization || !ttAppId || !ttAppSecret"
|
||||
:is-loading="isRequestingAuthorization"
|
||||
:label="$t('INBOX_MGMT.ADD.TIKTOK.CONTINUE_WITH_TIKTOK')"
|
||||
@click="requestAuthorization()"
|
||||
|
||||
+8
-2
@@ -20,6 +20,12 @@ export default {
|
||||
inboxId() {
|
||||
return this.inbox.id;
|
||||
},
|
||||
fbAppId() {
|
||||
return this.inbox.fb_app_id || '';
|
||||
},
|
||||
fbApiVersion() {
|
||||
return this.inbox.fb_api_version || 'v18.0';
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
window.fbAsyncInit = this.runFBInit;
|
||||
@@ -27,9 +33,9 @@ export default {
|
||||
methods: {
|
||||
runFBInit() {
|
||||
FB.init({
|
||||
appId: window.chatwootConfig.fbAppId,
|
||||
appId: this.fbAppId,
|
||||
xfbml: true,
|
||||
version: window.chatwootConfig.fbApiVersion,
|
||||
version: this.fbApiVersion,
|
||||
status: true,
|
||||
});
|
||||
window.fbSDKLoaded = true;
|
||||
|
||||
Reference in New Issue
Block a user