zen-gitsync 2.11.9 → 2.11.12

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-BDh-3jqo.js"></script>
13
+ <script type="module" crossorigin src="/assets/index-BWl1xzuJ.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-BMlnIJ-P.css">
16
+ <link rel="stylesheet" crossorigin href="/assets/index-BVWHnypR.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 模型配置(models 是全局配置,存在配置文件顶层,跨项目共享)
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
  }
@@ -937,6 +937,23 @@ export function registerGitOpsRoutes({
937
937
  }
938
938
  });
939
939
 
940
+ // 获取提交完整 diff(用于复制提交内容)
941
+ app.get('/api/commit-diff-full', async (req, res) => {
942
+ try {
943
+ const hash = req.query.hash;
944
+ if (!hash) {
945
+ return res.status(400).json({ success: false, error: '缺少提交哈希参数' });
946
+ }
947
+ const { stdout } = await execGitCommand(`git show ${hash}`);
948
+ // 限制最大 200KB 防止内容过大
949
+ const MAX = 200 * 1024;
950
+ const content = stdout.length > MAX ? stdout.slice(0, MAX) + '\n\n[内容过大,已截断]' : stdout;
951
+ res.json({ success: true, content });
952
+ } catch (error) {
953
+ res.status(500).json({ success: false, error: `获取提交内容失败: ${error.message}` });
954
+ }
955
+ });
956
+
940
957
  // 添加清理Git锁定文件的接口
941
958
  app.post('/api/remove-lock', async (req, res) => {
942
959
  try {