Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 12 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,16 @@
"editor.quickSuggestions": {
"strings": true
}
}
},
"cSpell.words": [
"Helora",
"helora",
"Longbridge",
"longbridge",
"longport",
"vitepress",
"VITE",
"lbkrs",
"lbctrl"
]
}
5 changes: 5 additions & 0 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ const regionSrcExclude = computeSrcExclude(docsRoot)
// 默认 production。放在 head 配置里,dev 与 build 都生效。
const oneTapProxy = process.env.PROXY === 'canary' ? 'canary' : 'production'

// Helora 客服 SDK:canary / 本地 dev / 非 release 走 .dev 包;只有 build:release 走 release 包
const isReleaseBuild = process.env.PROXY !== 'canary' && process.env.NODE_ENV === 'production'
const heloraScriptSrc = 'https://assets.lbkrs.com/h5hub/helora-embed/helora-embed-1.0.0.iife.js'

const insertScript = (html: string) => {
const $ = cheerio.load(html)
$('head').prepend(
Expand Down Expand Up @@ -171,6 +175,7 @@ export default defineConfig(
['script', { defer: '', src: 'https://assets.lbkrs.com/pkg/sensorsdata/1.21.13.min.js' }],
['script', { async: '', src: 'https://at.alicdn.com/t/c/font_2621450_y740y72ffjq.js' }],
['script', { src: 'https://assets.wbrks.com/plugin/session/google-one-tap.es.js', 'data-proxy': oneTapProxy }],
['script', { async: '', src: heloraScriptSrc, 'data-helora-proxy': isReleaseBuild ? 'prod' : 'staging' }],
],
themeConfig: {
editLink: {
Expand Down
43 changes: 43 additions & 0 deletions docs/.vitepress/helora.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Helora 客服 SDK 的全局类型声明
// 脚本通过 <script> 注入 window.Helora,需要给 TS 一个最小的形状
// cSpell:words Helora helora

interface HeloraHeaderAction {
id: string
label: string
icon?: string
intent?: 'event' | 'link' | string
}

interface HeloraBootConfig {
proxy: 'prod' | 'staging'
guest?: boolean
configPlatform: 'web' | string
configKey: string
source?: string
locale?: 'zh-CN' | 'zh-HK' | 'en'
/** 初始主题:对象形态,`mode` 走明暗,`color` 为 legacy 快捷主题色 */
theme?: { mode?: 'system' | 'light' | 'dark'; color?: string }
headerActions?: HeloraHeaderAction[]
[key: string]: unknown
}

interface HeloraSDK {
boot: (config: HeloraBootConfig) => void
/** 完整主题 patch(对象) */
setTheme?: (theme: { mode?: 'system' | 'light' | 'dark'; color?: string }) => void
/** 明暗模式热切换(字符串薄封装,= setTheme({ mode })) */
setThemeMode?: (mode: 'light' | 'dark') => void
/** locale 热切换,不重载 iframe */
setLocale?: (locale: 'zh-CN' | 'zh-HK' | 'en') => void
/** 订阅运行时事件,返回取消函数 */
on?: (event: string, handler: (payload: any) => void) => () => void
}

declare global {
interface Window {
Helora?: HeloraSDK
}
}

export {}
94 changes: 75 additions & 19 deletions docs/.vitepress/theme/components/AppNav.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, defineAsyncComponent } from 'vue'
import { ref, computed, onMounted, onUnmounted, defineAsyncComponent, watch } from 'vue'
import { useData, useRoute } from 'vitepress'
import { useI18n } from 'vue-i18n'
import endsWith from 'lodash/endsWith'
import { useLocalePath, getBasenameLocale } from '../utils/i18n'
import { createLoginRedirectPath } from '../utils/navigate'
import { isLoginState, initLoginState } from '../composables/useLoginState'
Expand Down Expand Up @@ -125,32 +124,89 @@ function openSearch() {
)
}

// Helora 客服:SDK 由 vitepress head 配置静态注入到所有页面(见 config.mts),
// 这里只负责等 window.Helora 就绪 → boot → 订阅事件 → 主题/语言同步
const heloraDisposers: Array<() => void> = []
let heloraBooted = false

function buildHeloraBootConfig(locale: string) {
// Helora SDK 用 data-helora-proxy 属性记录构建时的 proxy 选择,避免在客户端再做 host 判断
const tag = document.querySelector<HTMLScriptElement>('script[data-helora-proxy]')
const proxy = (tag?.dataset.heloraProxy as 'prod' | 'staging') || 'staging'
return {
proxy,
guest: true,
configPlatform: 'web' as const,
configKey: 'helora-agent-openapi',
source: 'web_openapi',
locale,
theme: { mode: (isDark.value ? 'dark' : 'light') as 'dark' | 'light' },
headerActions: [
{
id: 'issue',
label: t('helora.submitIssue'),
icon: 'alert-circle',
intent: 'event' as const,
},
],
}
}

function bootHelora() {
const Helora = window.Helora
if (!Helora) return false
Helora.boot(buildHeloraBootConfig(currentLocale.value))
heloraBooted = true

// 订阅 header action(提交问题按钮)
const offAction = Helora.on?.('headerAction', (payload: { id?: string }) => {
if (payload?.id === 'issue') {
window.open('https://github.com/longbridge/openapi/issues/new', '_blank', 'noopener,noreferrer')
}
})
if (typeof offAction === 'function') heloraDisposers.push(offAction)
return true
}

function waitForHeloraAndBoot() {
if (bootHelora()) return
// SDK 是 async script,可能在 onMounted 时还没加载完。轮询 + 兜底超时
let tries = 0
const timer = window.setInterval(() => {
tries += 1
if (bootHelora() || tries > 100) window.clearInterval(timer)
}, 100)
heloraDisposers.push(() => window.clearInterval(timer))
}

onMounted(() => {
initLoginState()
document.addEventListener('click', onAvatarClickOutside)

// AI 客服 support-widget 还未适配 app,whale app 环境下不加载
if (!isCnDomain && !detectWhaleApp()) {
const swSrc = 'https://assets.lbkrs.com/h5hub/support-widget/support-widget-1.0.7.iife.js'
const isProd = !endsWith(location.hostname, '.xyz') && !import.meta.env.DEV
window.SupportWidgetConfig = {
isLoggedIn: function () {
return isLogin.value
},
loginUrl: createLoginRedirectPath({ sw_open: '1' }),
proxy: isProd ? 'prod' : 'staging',
}
if (!document.querySelector(`script[src="${swSrc}"]`)) {
const script = document.createElement('script')
script.src = swSrc
script.async = true
document.head.appendChild(script)
}
// Helora 客服在 whale app 内嵌 webview 与 cn 站点不加载(前者宿主自带,后者尚未接入)
if (detectWhaleApp() || isCnDomain) {
return
}

waitForHeloraAndBoot()

// 主题切换 → 通知 Helora 热更新(语言切换走整页刷新,下次 mount 直接以新 locale boot,无需 watch)
watch(isDark, (dark) => {
if (!heloraBooted) return
window.Helora?.setThemeMode?.(dark ? 'dark' : 'light')
})
})
onUnmounted(() => {
document.removeEventListener('click', onAvatarClickOutside)
if (avatarCloseTimer.value) clearTimeout(avatarCloseTimer.value)
heloraDisposers.splice(0).forEach((dispose) => {
try {
dispose()
} catch {
// ignore
}
})
heloraBooted = false
})
</script>

Expand Down
23 changes: 0 additions & 23 deletions docs/.vitepress/theme/components/UserAvatar/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,9 @@ import UserAvatarIcon from './UserAvatarIcon.vue'
import Dropdown from './UserAvatarDropdown.vue'
import LoginButton from './LoginButton.vue'
import { localePath } from '../../utils/i18n'
import { createLoginRedirectPath } from '../../utils/navigate'
import { useI18n } from 'vue-i18n'
import { useAvatar } from './uesAvatar'
import { isLoginState, initLoginState } from '../../composables/useLoginState'
import { detectWhaleApp } from '../../composables/useWhaleApp'
import endsWith from 'lodash/endsWith'

const { t } = useI18n()

Expand All @@ -21,26 +18,6 @@ const isLogin = isLoginState

onMounted(() => {
initLoginState()

// AI 客服 support-widget 还未适配 app,whale app 环境下不加载
if (detectWhaleApp()) return

const isProd = !endsWith(location.hostname, '.xyz') && !import.meta.env.DEV
const loginUrl = createLoginRedirectPath({
sw_open: '1',
})
window.SupportWidgetConfig = {
isLoggedIn: function () {
return isLogin.value
},
loginUrl,
proxy: isProd ? 'prod' : 'staging',
}

const script = document.createElement('script')
script.src = 'https://assets.lbkrs.com/h5hub/support-widget/support-widget-1.0.7.iife.js'
script.async = true
document.head.appendChild(script)
})

const { avatar } = useAvatar()
Expand Down
1 change: 1 addition & 0 deletions docs/.vitepress/theme/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"nav.getStarted": "Get Started",
"nav.dashboard": "Dashboard",
"nav.home": "Home",
"helora.submitIssue": "Submit an Issue",
"footer.tagline": "Real-time market data, trading, and financial intelligence — delivered through AI Skill, CLI, MCP, SDK and OpenAPI for developers worldwide.",
"footer.status": "All systems operational",
"footer.products": "Products",
Expand Down
1 change: 1 addition & 0 deletions docs/.vitepress/theme/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"nav.getStarted": "立即开始",
"nav.dashboard": "控制台",
"nav.home": "首页",
"helora.submitIssue": "提交问题",
"footer.tagline": "为全球开发者提供实时行情、交易与金融智能服务——通过 AI Skill、CLI、MCP、SDK 和 OpenAPI 全面赋能。",
"footer.status": "所有服务正常运行",
"footer.products": "产品",
Expand Down
1 change: 1 addition & 0 deletions docs/.vitepress/theme/locales/zh-HK.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"nav.getStarted": "立即開始",
"nav.dashboard": "控制台",
"nav.home": "首頁",
"helora.submitIssue": "提交問題",
"footer.tagline": "為全球開發者提供即時行情、交易與金融智能服務——透過 AI Skill、CLI、MCP、SDK 和 OpenAPI 全面賦能。",
"footer.status": "所有服務正常運行",
"footer.products": "產品",
Expand Down
Loading