zen-gitsync 2.11.8 → 2.11.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,10 +10,10 @@
10
10
  <link rel="preconnect" href="https://fonts.googleapis.com" />
11
11
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
12
12
  <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,400&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
13
- <script type="module" crossorigin src="/assets/index-DWV3mQ48.js"></script>
13
+ <script type="module" crossorigin src="/assets/index-E1drpG4m.js"></script>
14
14
  <link rel="modulepreload" crossorigin href="/assets/vendor-BtihdyTS.js">
15
15
  <link rel="stylesheet" crossorigin href="/assets/vendor-CeElb63i.css">
16
- <link rel="stylesheet" crossorigin href="/assets/index-Ce26lMsG.css">
16
+ <link rel="stylesheet" crossorigin href="/assets/index-DqUSf7QD.css">
17
17
  </head>
18
18
  <body>
19
19
  <div id="app"></div>
@@ -725,4 +725,153 @@ export function registerConfigRoutes({
725
725
  res.status(500).json({ success: false, error: error.message })
726
726
  }
727
727
  })
728
+
729
+ // 保存 AI 模型配置
730
+ app.post('/api/config/save-models', express.json(), async (req, res) => {
731
+ try {
732
+ const { models } = req.body
733
+ if (!Array.isArray(models)) {
734
+ return res.status(400).json({ success: false, error: '缺少 models 参数' })
735
+ }
736
+ const rawConfig = await configManager.readRawConfigFile()
737
+ rawConfig.models = models
738
+ await configManager.writeRawConfigFile(rawConfig)
739
+ res.json({ success: true })
740
+ } catch (error) {
741
+ res.status(500).json({ success: false, error: error.message })
742
+ }
743
+ })
744
+
745
+ // 测试 AI 模型是否可用
746
+ app.post('/api/config/test-model', express.json(), async (req, res) => {
747
+ const { baseURL, model, apiKey } = req.body || {}
748
+ if (!baseURL || !model) {
749
+ return res.status(400).json({ success: false, error: '缺少 baseURL 或 model 参数' })
750
+ }
751
+ try {
752
+ const { default: fetch } = await import('node-fetch').catch(() => ({ default: globalThis.fetch }))
753
+ const url = `${baseURL.replace(/\/$/, '')}/chat/completions`
754
+ const headers = { 'Content-Type': 'application/json' }
755
+ if (apiKey) headers['Authorization'] = `Bearer ${apiKey}`
756
+ const body = JSON.stringify({
757
+ model,
758
+ messages: [{ role: 'user', content: 'Hello, reply with just "ok".' }],
759
+ max_tokens: 16,
760
+ stream: false
761
+ })
762
+ const controller = new AbortController()
763
+ const timer = setTimeout(() => controller.abort(), 15000)
764
+ let response
765
+ try {
766
+ response = await fetch(url, { method: 'POST', headers, body, signal: controller.signal })
767
+ } finally {
768
+ clearTimeout(timer)
769
+ }
770
+ const data = await response.json().catch(() => ({}))
771
+ if (!response.ok) {
772
+ const msg = data?.error?.message || data?.message || `HTTP ${response.status}`
773
+ return res.json({ success: false, error: msg })
774
+ }
775
+ const reply = data?.choices?.[0]?.message?.content || data?.choices?.[0]?.text || '✅'
776
+ res.json({ success: true, reply: reply.trim().slice(0, 100) })
777
+ } catch (error) {
778
+ const msg = error.name === 'AbortError' ? '请求超时(15s)' : error.message
779
+ res.json({ success: false, error: msg })
780
+ }
781
+ })
782
+
783
+ // AI 生成提交信息
784
+ app.post('/api/config/generate-commit-message', express.json(), async (req, res) => {
785
+ const { diff, fileList } = req.body || {}
786
+ try {
787
+ const rawConfig = await configManager.readRawConfigFile()
788
+ const models = Array.isArray(rawConfig.models) ? rawConfig.models : []
789
+ const defaultModel = models.find(m => m.isDefault) || models[0]
790
+ if (!defaultModel) {
791
+ return res.json({ success: false, error: '未配置 AI 模型,请先在通用设置中添加模型' })
792
+ }
793
+
794
+ const diffText = (diff || '').trim().slice(0, 8000)
795
+ const filesText = Array.isArray(fileList) ? fileList.slice(0, 30).join('\n') : ''
796
+ const prompt = `你是一个 Git 提交信息生成助手。根据以下 git diff 信息,生成一条符合 Conventional Commits 规范的提交信息。
797
+
798
+ 要求:
799
+ 1. type 只能是:feat/fix/docs/style/refactor/test/chore 之一
800
+ 2. scope 可选,表示影响范围,简短英文或中文,如果改动范围明确就填
801
+ 3. description 用中文简短描述本次变更(不超过50字)
802
+ 4. 只返回 JSON,格式:{"type":"feat","scope":"","description":"xxx"}
803
+
804
+ 变更文件:
805
+ ${filesText}
806
+
807
+ git diff --staged:
808
+ ${diffText || '(无 staged 内容,请根据文件列表推断)'}`
809
+
810
+ const { default: fetch } = await import('node-fetch').catch(() => ({ default: globalThis.fetch }))
811
+ const url = `${defaultModel.baseURL.replace(/\/$/, '')}/chat/completions`
812
+ const headers = { 'Content-Type': 'application/json' }
813
+ if (defaultModel.apiKey) headers['Authorization'] = `Bearer ${defaultModel.apiKey}`
814
+ const body = JSON.stringify({
815
+ model: defaultModel.model,
816
+ messages: [{ role: 'user', content: prompt }],
817
+ max_tokens: 1024,
818
+ temperature: 0.3,
819
+ stream: false
820
+ })
821
+ const controller = new AbortController()
822
+ const timer = setTimeout(() => controller.abort(), 30000)
823
+ let response
824
+ try {
825
+ response = await fetch(url, { method: 'POST', headers, body, signal: controller.signal })
826
+ } finally {
827
+ clearTimeout(timer)
828
+ }
829
+ const data = await response.json().catch(() => ({}))
830
+ if (!response.ok) {
831
+ const msg = data?.error?.message || data?.message || `HTTP ${response.status}`
832
+ return res.json({ success: false, error: msg })
833
+ }
834
+ const content = data?.choices?.[0]?.message?.content || ''
835
+ console.log('[generate-commit] raw content length:', content.length, JSON.stringify(content).slice(0, 600))
836
+
837
+ // 在整个原始内容里找 JSON(包括 think 块内部),取最后一个不含嵌套 {} 的对象
838
+ // 优先匹配代码块,再取最后一个裸 {}
839
+ const codeBlockMatch = content.match(/```(?:json)?\s*(\{[^`]*?\})\s*```/)
840
+ const jsonMatch = codeBlockMatch
841
+ ? [codeBlockMatch[1]]
842
+ : [...content.matchAll(/\{[^{}]*\}/g)].at(-1)
843
+
844
+ if (!jsonMatch) {
845
+ console.error('[generate-commit] no JSON found, full content:', content)
846
+ return res.json({ success: false, error: `模型未返回有效 JSON,请检查模型是否支持(原始内容前300字): ${content.slice(0, 300)}` })
847
+ }
848
+ let parsed
849
+ try {
850
+ parsed = JSON.parse(jsonMatch[0])
851
+ } catch {
852
+ // JSON 不合法时用正则手动提取字段
853
+ const typeM = jsonMatch[0].match(/"type"\s*:\s*"([^"]+)"/)
854
+ const scopeM = jsonMatch[0].match(/"scope"\s*:\s*"([^"]*)"/)
855
+ const descM = jsonMatch[0].match(/"description"\s*:\s*"([^"]+)"/)
856
+ if (typeM || descM) {
857
+ return res.json({
858
+ success: true,
859
+ type: (typeM?.[1] || 'feat').trim(),
860
+ scope: (scopeM?.[1] || '').trim(),
861
+ description: (descM?.[1] || '').trim()
862
+ })
863
+ }
864
+ return res.json({ success: false, error: `JSON 解析失败: ${jsonMatch[0].slice(0, 200)}` })
865
+ }
866
+ res.json({
867
+ success: true,
868
+ type: String(parsed.type || 'feat').trim(),
869
+ scope: String(parsed.scope || '').trim(),
870
+ description: String(parsed.description || '').trim()
871
+ })
872
+ } catch (error) {
873
+ const msg = error.name === 'AbortError' ? '请求超时(30s)' : error.message
874
+ res.json({ success: false, error: msg })
875
+ }
876
+ })
728
877
  }
@@ -1021,6 +1021,17 @@ export function registerGitOpsRoutes({
1021
1021
  }
1022
1022
  });
1023
1023
 
1024
+ // 初始化Git仓库
1025
+ app.post('/api/git-init', async (req, res) => {
1026
+ try {
1027
+ const { stdout } = await execGitCommand('git init');
1028
+ res.json({ success: true, output: stdout.trim() });
1029
+ } catch (error) {
1030
+ console.error('git init 失败:', error);
1031
+ res.json({ success: false, error: error.message || 'git init 失败' });
1032
+ }
1033
+ });
1034
+
1024
1035
  // 添加远程仓库的API
1025
1036
  app.post('/api/add-remote', express.json(), async (req, res) => {
1026
1037
  try {