Skip to content

Commit 0dfd64d

Browse files
committed
Merge branch 'feat/ai-chat' into canary
2 parents 7096abc + 45d4079 commit 0dfd64d

158 files changed

Lines changed: 2923 additions & 2650 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ old_docs
3838
.superpowers
3939
docs/superpowers
4040
.claude/worktrees
41+
.worktrees
4142

4243
# generated at build time by fetch-mcp-tools plugin
4344
docs/.vitepress/data/mcp-tools.json

.vscode/settings.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,16 @@
3535
"editor.quickSuggestions": {
3636
"strings": true
3737
}
38-
}
38+
},
39+
"cSpell.words": [
40+
"Helora",
41+
"helora",
42+
"Longbridge",
43+
"longbridge",
44+
"longport",
45+
"vitepress",
46+
"VITE",
47+
"lbkrs",
48+
"lbctrl"
49+
]
3950
}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ pip install longbridge
7575
{
7676
"mcpServers": {
7777
"longbridge": {
78-
"url": "https://openapi.longbridge.com/mcp"
78+
"url": "https://mcp.longbridge.com"
7979
}
8080
}
8181
}

docs/.vitepress/config.mts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import yaml from 'js-yaml'
1515

1616
const __dirname = dirname(fileURLToPath(import.meta.url))
1717
const docsRoot = resolve(__dirname, '..')
18-
const MCP_TOOLS_URL = 'https://openapi.longbridge.com/mcp/tools.json'
18+
const MCP_TOOLS_URL = 'https://mcp.longbridge.com/mcp/tools.json'
1919
const MCP_TOOLS_DATA_PATH = resolve(__dirname, 'data/mcp-tools.json')
2020
const regionCfg = getRegionConfig()
2121
const regionSrcExclude = computeSrcExclude(docsRoot)
@@ -49,10 +49,13 @@ export default defineConfig(
4949
markdown: markdownConfig,
5050
transformHtml(code) {
5151
let html = insertScript(code)
52-
// Region URL rewriting: replace global hostname with region hostname in final HTML
52+
// Region URL rewriting: replace global hostnames (site + API) with region hostnames in final HTML
5353
if (regionCfg?.siteHostname && regionCfg.siteHostname !== 'https://open.longbridge.com') {
5454
html = html.split('https://open.longbridge.com').join(regionCfg.siteHostname)
5555
}
56+
if (regionCfg?.apiBaseUrl && regionCfg.apiBaseUrl !== 'https://openapi.longbridge.com') {
57+
html = html.split('https://openapi.longbridge.com').join(regionCfg.apiBaseUrl)
58+
}
5659
return html
5760
},
5861
transformHead(context) {
@@ -127,13 +130,23 @@ export default defineConfig(
127130

128131
rmSync(tmpDir, { recursive: true, force: true })
129132

130-
// Region URL rewriting for static assets
133+
// Region URL rewriting for static assets (site + API hostnames)
134+
const staticReplacements: [string, string][] = []
131135
if (regionCfg?.siteHostname && regionCfg.siteHostname !== 'https://open.longbridge.com') {
136+
staticReplacements.push(['https://open.longbridge.com', regionCfg.siteHostname])
137+
}
138+
if (regionCfg?.apiBaseUrl && regionCfg.apiBaseUrl !== 'https://openapi.longbridge.com') {
139+
staticReplacements.push(['https://openapi.longbridge.com', regionCfg.apiBaseUrl])
140+
}
141+
if (staticReplacements.length > 0) {
132142
const installDir = resolve(siteConfig.outDir, 'longbridge-terminal')
133143
for (const file of ['install', 'install.ps1']) {
134144
const filePath = resolve(installDir, file)
135-
const content = readFileSync(filePath, 'utf-8')
136-
writeFileSync(filePath, content.split('https://open.longbridge.com').join(regionCfg.siteHostname))
145+
let content = readFileSync(filePath, 'utf-8')
146+
for (const [from, to] of staticReplacements) {
147+
content = content.split(from).join(to)
148+
}
149+
writeFileSync(filePath, content)
137150
}
138151
console.log('✓ install scripts rewritten for region')
139152
}

docs/.vitepress/helora.d.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Helora 客服 SDK 的全局类型声明
2+
// 脚本通过 <script> 注入 window.Helora,需要给 TS 一个最小的形状
3+
// cSpell:words Helora helora
4+
5+
interface HeloraHeaderAction {
6+
id: string
7+
label: string
8+
icon?: string
9+
intent?: 'event' | 'link' | string
10+
}
11+
12+
interface HeloraBootConfig {
13+
proxy: 'prod' | 'staging'
14+
guest?: boolean
15+
configPlatform: 'web' | string
16+
configKey: string
17+
source?: string
18+
locale?: string
19+
theme?: 'light' | 'dark'
20+
headerActions?: HeloraHeaderAction[]
21+
[key: string]: unknown
22+
}
23+
24+
interface HeloraSDK {
25+
boot: (config: HeloraBootConfig) => void
26+
setTheme?: (theme: 'light' | 'dark') => void
27+
/** 订阅运行时事件,返回取消函数 */
28+
on?: (event: string, handler: (payload: any) => void) => () => void
29+
}
30+
31+
declare global {
32+
interface Window {
33+
Helora?: HeloraSDK
34+
}
35+
}
36+
37+
export {}

docs/.vitepress/md-plugins/region-filter.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,23 @@ export function RegionFilterPlugin(md: MarkdownItAsync) {
1111
const config = getRegionConfig()
1212
if (!config) return
1313

14-
// URL rewriting: replace global hostname with region hostname in all inline tokens
14+
// URL rewriting: replace global hostnames (site + API) with region hostnames in all inline tokens
15+
const urlReplacements: [string, string][] = []
1516
if (config.siteHostname && config.siteHostname !== 'https://open.longbridge.com') {
17+
urlReplacements.push(['https://open.longbridge.com', config.siteHostname])
18+
}
19+
if (config.apiBaseUrl && config.apiBaseUrl !== 'https://openapi.longbridge.com') {
20+
urlReplacements.push(['https://openapi.longbridge.com', config.apiBaseUrl])
21+
}
22+
if (urlReplacements.length > 0) {
1623
md.core.ruler.push('region_url_rewrite', (state) => {
1724
for (const token of state.tokens) {
18-
rewriteTokenUrls(token, 'https://open.longbridge.com', config.siteHostname)
19-
if (token.children) {
20-
for (const child of token.children) {
21-
rewriteTokenUrls(child, 'https://open.longbridge.com', config.siteHostname)
25+
for (const [from, to] of urlReplacements) {
26+
rewriteTokenUrls(token, from, to)
27+
if (token.children) {
28+
for (const child of token.children) {
29+
rewriteTokenUrls(child, from, to)
30+
}
2231
}
2332
}
2433
}

docs/.vitepress/theme/components/AppFooter.vue

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,7 @@ const year = new Date().getFullYear()
121121
}}</a>
122122
</li>
123123
<li>
124-
<a :href="localePath('/docs/legal/user-data-authorization-hk')">{{ t('footer.dataAuthorisationHK') }}</a>
125-
</li>
126-
<li>
127-
<a :href="localePath('/docs/legal/user-data-authorization-sg')">{{ t('footer.dataAuthorisationSG') }}</a>
124+
<a :href="localePath('/docs/legal')">{{ t('footer.agreements') }}</a>
128125
</li>
129126
<li>
130127
<a

docs/.vitepress/theme/components/AppNav.vue

Lines changed: 95 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { ref, computed, onMounted, onUnmounted, defineAsyncComponent } from 'vue'
2+
import { ref, computed, onMounted, onUnmounted, defineAsyncComponent, watch } from 'vue'
33
import { useData, useRoute } from 'vitepress'
44
import { useI18n } from 'vue-i18n'
55
import endsWith from 'lodash/endsWith'
@@ -97,6 +97,7 @@ const avatarCloseTimer = ref<ReturnType<typeof setTimeout> | null>(null)
9797
9898
const avatarMenuItems = computed(() => [
9999
{ title: 'Dashboard', href: '/dashboard' },
100+
{ title: 'Connect AI', href: '/connect' },
100101
{ title: 'Log out', href: '/log-out' },
101102
])
102103
@@ -123,31 +124,109 @@ function openSearch() {
123124
)
124125
}
125126
127+
// Helora 事件订阅的清理函数集(boot 之后由 on() 返回)
128+
const heloraDisposers: Array<() => void> = []
129+
let heloraReady = false
130+
131+
function syncHeloraTheme(dark: boolean) {
132+
if (!heloraReady) return
133+
console.log(7777777, dark)
134+
window.Helora?.setTheme?.(dark ? 'dark' : 'light')
135+
}
136+
137+
function syncHeloraLocale(locale: string) {
138+
if (!heloraReady) return
139+
// Helora 暂未暴露 setLocale,重新 boot 即可让其拉新语种文案
140+
window.Helora?.boot?.({
141+
proxy: !endsWith(location.hostname, '.xyz') && !import.meta.env.DEV ? 'prod' : 'staging',
142+
guest: true,
143+
configPlatform: 'web',
144+
configKey: 'helora-agent-openapi',
145+
source: 'web_openapi',
146+
locale,
147+
theme: isDark.value ? 'dark' : 'light',
148+
headerActions: [
149+
{
150+
id: 'issue',
151+
label: t('helora.submitIssue'),
152+
icon: 'alert-circle',
153+
intent: 'event',
154+
},
155+
],
156+
})
157+
}
158+
126159
onMounted(() => {
127160
initLoginState()
128161
document.addEventListener('click', onAvatarClickOutside)
129162
130-
if (!isCnDomain) {
131-
const swSrc = 'https://assets.lbkrs.com/h5hub/support-widget/support-widget-1.0.7.iife.js'
132-
const isProd = !endsWith(location.hostname, '.xyz') && !import.meta.env.DEV
133-
window.SupportWidgetConfig = {
134-
isLoggedIn: function () {
135-
return isLogin.value
136-
},
137-
loginUrl: createLoginRedirectPath({ sw_open: '1' }),
163+
// Helora 客服(接替旧的 support-widget)
164+
// dev/staging 用 .dev 包 + proxy=staging;线上用 release 包 + proxy=prod
165+
const isProd = !endsWith(location.hostname, '.xyz') && !import.meta.env.DEV
166+
const heloraSrc = 'https://assets.lbkrs.com/h5hub/helora-embed/helora-embed-1.0.0.dev.iife.js'
167+
168+
const bootHelora = () => {
169+
const Helora = window.Helora
170+
if (!Helora) return
171+
Helora.boot({
138172
proxy: isProd ? 'prod' : 'staging',
139-
}
140-
if (!document.querySelector(`script[src="${swSrc}"]`)) {
141-
const script = document.createElement('script')
142-
script.src = swSrc
143-
script.async = true
144-
document.head.appendChild(script)
145-
}
173+
guest: true,
174+
configPlatform: 'web',
175+
configKey: 'helora-agent-openapi',
176+
source: 'web_openapi',
177+
locale: currentLocale.value,
178+
theme: isDark.value ? 'dark' : 'light',
179+
headerActions: [
180+
{
181+
id: 'issue',
182+
label: t('helora.submitIssue'),
183+
icon: 'alert-circle',
184+
intent: 'event',
185+
},
186+
],
187+
})
188+
heloraReady = true
189+
190+
// 订阅 header action(提交问题按钮)—— headerActions 里 intent: 'event' 会通过事件回调通知宿主
191+
const offAction = Helora.on?.('headerAction', (payload: { id?: string }) => {
192+
if (payload?.id === 'issue') {
193+
window.open('https://github.com/longbridge/openapi/issues/new', '_blank', 'noopener,noreferrer')
194+
}
195+
})
196+
if (typeof offAction === 'function') heloraDisposers.push(offAction)
197+
}
198+
199+
if (!document.querySelector(`script[src="${heloraSrc}"]`)) {
200+
const script = document.createElement('script')
201+
script.src = heloraSrc
202+
script.async = true
203+
script.onload = bootHelora
204+
document.head.appendChild(script)
205+
} else if (window.Helora) {
206+
bootHelora()
146207
}
208+
209+
// 主题切换 → 通知 Helora
210+
watch(isDark, (dark) => syncHeloraTheme(dark))
211+
212+
// 语言切换 → 通知 Helora
213+
// 当前 switchLocale 走 location.href(整页刷新),下次 mount 直接以新 locale boot;
214+
// 但 SPA 内若有路由级切换(不刷新),仍能命中这里
215+
watch(currentLocale, (locale, prev) => {
216+
if (locale && locale !== prev) syncHeloraLocale(locale)
217+
})
147218
})
148219
onUnmounted(() => {
149220
document.removeEventListener('click', onAvatarClickOutside)
150221
if (avatarCloseTimer.value) clearTimeout(avatarCloseTimer.value)
222+
heloraDisposers.splice(0).forEach((dispose) => {
223+
try {
224+
dispose()
225+
} catch {
226+
// ignore
227+
}
228+
})
229+
heloraReady = false
151230
})
152231
</script>
153232

docs/.vitepress/theme/components/NewHomePage/ProductMCP.vue

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,38 +50,38 @@ const clients = [
5050
name: 'Claude Code',
5151
logo: 'https://assets.lbctrl.com/uploads/6932dfac-0f9c-4577-bdd8-fc3d22d4223a/claude.svg',
5252
type: 'shell' as const,
53-
cmd: 'claude mcp add --transport http longbridge https://openapi.longbridge.com/mcp',
53+
cmd: 'claude mcp add --transport http longbridge https://mcp.longbridge.com',
5454
},
5555
{
5656
id: 'codex',
5757
name: 'Codex',
5858
logo: 'https://assets.lbctrl.com/uploads/88eb58fe-b3bb-4875-90c7-c97e6d8fcc9e/openai.svg',
5959
type: 'ui' as const,
6060
steps: ['Settings', 'MCP Servers', 'Add Server'],
61-
fields: { Name: 'longbridge', Type: 'Streamable HTTP', URL: 'https://openapi.longbridge.com/mcp' },
61+
fields: { Name: 'longbridge', Type: 'Streamable HTTP', URL: 'https://mcp.longbridge.com' },
6262
},
6363
{
6464
id: 'cursor',
6565
name: 'Cursor',
6666
logo: 'https://assets.lbctrl.com/uploads/f694478e-201b-4e74-a7b6-023639a27805/cursor.svg',
6767
type: 'ui' as const,
6868
steps: ['Settings', 'MCP Servers', 'Add Remote MCP Server'],
69-
fields: { URL: 'https://openapi.longbridge.com/mcp' },
69+
fields: { URL: 'https://mcp.longbridge.com' },
7070
},
7171
{
7272
id: 'zed',
7373
name: 'Zed',
7474
logo: 'https://assets.lbctrl.com/uploads/3418077a-9766-4514-bc8e-eef076309689/zed.svg',
7575
type: 'json' as const,
76-
json: { mcpServers: { longbridge: { url: 'https://openapi.longbridge.com/mcp' } } },
76+
json: { mcpServers: { longbridge: { url: 'https://mcp.longbridge.com' } } },
7777
},
7878
{
7979
id: 'cherry',
8080
name: 'Cherry Studio',
8181
logo: 'https://assets.lbctrl.com/uploads/df8f9467-91a5-4bdb-8dde-5127441f0b04/cherrystudio.svg',
8282
type: 'ui' as const,
8383
steps: ['Settings', 'MCP Servers', 'Add'],
84-
fields: { URL: 'https://openapi.longbridge.com/mcp' },
84+
fields: { URL: 'https://mcp.longbridge.com' },
8585
},
8686
]
8787

0 commit comments

Comments
 (0)