galaxy-opc-plugin 0.2.2 → 0.2.3
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.
- package/README.md +207 -10
- package/index.ts +106 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/api/companies.ts +4 -0
- package/src/api/dashboard.ts +368 -16
- package/src/api/routes.ts +2 -2
- package/src/db/migrations.ts +146 -0
- package/src/db/schema.ts +104 -3
- package/src/db/sqlite-adapter.ts +39 -2
- package/src/opc/accounting-parser.ts +178 -0
- package/src/opc/daily-brief.ts +529 -0
- package/src/opc/onboarding-flow.ts +332 -0
- package/src/opc/proactive-service.ts +290 -3
- package/src/tools/finance-tool.test-payment.ts +326 -0
- package/src/tools/finance-tool.ts +653 -1
- package/src/tools/onboarding-tool.ts +233 -0
- package/src/tools/order-tool.ts +481 -0
- package/src/tools/smart-accounting-tool.ts +144 -0
- package/src/web/DASHBOARD_INTEGRATION_GUIDE.md +478 -0
- package/src/web/config-ui-patches.ts +389 -0
- package/src/web/config-ui.ts +4162 -3809
- package/src/web/dashboard-ui.ts +582 -0
- package/src/web/landing-page.ts +56 -6
package/src/db/schema.ts
CHANGED
|
@@ -13,6 +13,9 @@ export const OPC_TABLES = {
|
|
|
13
13
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
14
14
|
registered_capital REAL NOT NULL DEFAULT 0,
|
|
15
15
|
description TEXT NOT NULL DEFAULT '',
|
|
16
|
+
onboarding_data TEXT DEFAULT '{}',
|
|
17
|
+
onboarding_stage TEXT DEFAULT '',
|
|
18
|
+
onboarding_completed INTEGER DEFAULT 0,
|
|
16
19
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
17
20
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
18
21
|
)
|
|
@@ -635,15 +638,91 @@ export const OPC_TABLES = {
|
|
|
635
638
|
CREATE TABLE IF NOT EXISTS opc_payments (
|
|
636
639
|
id TEXT PRIMARY KEY,
|
|
637
640
|
company_id TEXT NOT NULL,
|
|
638
|
-
|
|
639
|
-
|
|
641
|
+
direction TEXT NOT NULL DEFAULT 'receivable',
|
|
642
|
+
counterparty TEXT NOT NULL DEFAULT '',
|
|
640
643
|
amount REAL NOT NULL DEFAULT 0,
|
|
644
|
+
paid_amount REAL NOT NULL DEFAULT 0,
|
|
641
645
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
642
646
|
due_date TEXT NOT NULL DEFAULT '',
|
|
643
647
|
paid_date TEXT NOT NULL DEFAULT '',
|
|
648
|
+
invoice_id TEXT NOT NULL DEFAULT '',
|
|
649
|
+
contract_id TEXT NOT NULL DEFAULT '',
|
|
650
|
+
category TEXT NOT NULL DEFAULT '',
|
|
651
|
+
payment_method TEXT NOT NULL DEFAULT '',
|
|
652
|
+
notes TEXT NOT NULL DEFAULT '',
|
|
653
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
654
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
655
|
+
FOREIGN KEY (company_id) REFERENCES opc_companies(id)
|
|
656
|
+
)
|
|
657
|
+
`,
|
|
658
|
+
|
|
659
|
+
// ── 报价单表 ─────────────────────────────────────────────────
|
|
660
|
+
|
|
661
|
+
quotations: `
|
|
662
|
+
CREATE TABLE IF NOT EXISTS opc_quotations (
|
|
663
|
+
id TEXT PRIMARY KEY,
|
|
664
|
+
company_id TEXT NOT NULL,
|
|
665
|
+
contact_id TEXT NOT NULL DEFAULT '',
|
|
666
|
+
quotation_number TEXT NOT NULL,
|
|
667
|
+
title TEXT NOT NULL,
|
|
668
|
+
total_amount REAL NOT NULL DEFAULT 0,
|
|
669
|
+
valid_until TEXT NOT NULL DEFAULT '',
|
|
670
|
+
status TEXT NOT NULL DEFAULT 'draft',
|
|
671
|
+
notes TEXT NOT NULL DEFAULT '',
|
|
672
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
673
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
674
|
+
FOREIGN KEY (company_id) REFERENCES opc_companies(id),
|
|
675
|
+
FOREIGN KEY (contact_id) REFERENCES opc_contacts(id)
|
|
676
|
+
)
|
|
677
|
+
`,
|
|
678
|
+
|
|
679
|
+
quotation_items: `
|
|
680
|
+
CREATE TABLE IF NOT EXISTS opc_quotation_items (
|
|
681
|
+
id TEXT PRIMARY KEY,
|
|
682
|
+
quotation_id TEXT NOT NULL,
|
|
683
|
+
description TEXT NOT NULL,
|
|
684
|
+
quantity REAL NOT NULL DEFAULT 1,
|
|
685
|
+
unit_price REAL NOT NULL DEFAULT 0,
|
|
686
|
+
total_price REAL NOT NULL DEFAULT 0,
|
|
687
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
688
|
+
FOREIGN KEY (quotation_id) REFERENCES opc_quotations(id)
|
|
689
|
+
)
|
|
690
|
+
`,
|
|
691
|
+
|
|
692
|
+
contract_milestones: `
|
|
693
|
+
CREATE TABLE IF NOT EXISTS opc_contract_milestones (
|
|
694
|
+
id TEXT PRIMARY KEY,
|
|
695
|
+
contract_id TEXT NOT NULL,
|
|
696
|
+
company_id TEXT NOT NULL,
|
|
697
|
+
title TEXT NOT NULL,
|
|
698
|
+
description TEXT NOT NULL DEFAULT '',
|
|
699
|
+
due_date TEXT NOT NULL DEFAULT '',
|
|
700
|
+
amount REAL NOT NULL DEFAULT 0,
|
|
701
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
702
|
+
completed_date TEXT NOT NULL DEFAULT '',
|
|
644
703
|
notes TEXT NOT NULL DEFAULT '',
|
|
645
704
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
646
705
|
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
706
|
+
FOREIGN KEY (contract_id) REFERENCES opc_contracts(id),
|
|
707
|
+
FOREIGN KEY (company_id) REFERENCES opc_companies(id)
|
|
708
|
+
)
|
|
709
|
+
`,
|
|
710
|
+
|
|
711
|
+
// ── 待办事项表 ─────────────────────────────────────────────────
|
|
712
|
+
|
|
713
|
+
todos: `
|
|
714
|
+
CREATE TABLE IF NOT EXISTS opc_todos (
|
|
715
|
+
id TEXT PRIMARY KEY,
|
|
716
|
+
company_id TEXT NOT NULL,
|
|
717
|
+
title TEXT NOT NULL,
|
|
718
|
+
description TEXT NOT NULL DEFAULT '',
|
|
719
|
+
priority TEXT NOT NULL DEFAULT 'normal',
|
|
720
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
721
|
+
due_date TEXT NOT NULL DEFAULT '',
|
|
722
|
+
related_type TEXT NOT NULL DEFAULT '',
|
|
723
|
+
related_id TEXT NOT NULL DEFAULT '',
|
|
724
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
725
|
+
completed_at TEXT NOT NULL DEFAULT '',
|
|
647
726
|
FOREIGN KEY (company_id) REFERENCES opc_companies(id)
|
|
648
727
|
)
|
|
649
728
|
`,
|
|
@@ -723,7 +802,29 @@ export const OPC_INDEXES = [
|
|
|
723
802
|
"CREATE INDEX IF NOT EXISTS idx_financial_periods_dates ON opc_financial_periods(start_date, end_date)",
|
|
724
803
|
// 付款记录表索引
|
|
725
804
|
"CREATE INDEX IF NOT EXISTS idx_payments_company ON opc_payments(company_id)",
|
|
726
|
-
"CREATE INDEX IF NOT EXISTS
|
|
805
|
+
"CREATE INDEX IF NOT EXISTS idx_payments_direction ON opc_payments(direction)",
|
|
727
806
|
"CREATE INDEX IF NOT EXISTS idx_payments_status ON opc_payments(status)",
|
|
728
807
|
"CREATE INDEX IF NOT EXISTS idx_payments_due_date ON opc_payments(due_date)",
|
|
808
|
+
"CREATE INDEX IF NOT EXISTS idx_payments_company_status ON opc_payments(company_id, status)",
|
|
809
|
+
"CREATE INDEX IF NOT EXISTS idx_payments_company_direction ON opc_payments(company_id, direction)",
|
|
810
|
+
// 报价单和里程碑索引
|
|
811
|
+
"CREATE INDEX IF NOT EXISTS idx_quotations_company ON opc_quotations(company_id)",
|
|
812
|
+
"CREATE INDEX IF NOT EXISTS idx_quotations_status ON opc_quotations(status)",
|
|
813
|
+
"CREATE INDEX IF NOT EXISTS idx_quotations_contact ON opc_quotations(contact_id)",
|
|
814
|
+
"CREATE INDEX IF NOT EXISTS idx_quotation_items_quotation ON opc_quotation_items(quotation_id)",
|
|
815
|
+
"CREATE INDEX IF NOT EXISTS idx_contract_milestones_contract ON opc_contract_milestones(contract_id)",
|
|
816
|
+
"CREATE INDEX IF NOT EXISTS idx_contract_milestones_status ON opc_contract_milestones(status)",
|
|
817
|
+
"CREATE INDEX IF NOT EXISTS idx_contract_milestones_due_date ON opc_contract_milestones(due_date)",
|
|
818
|
+
"CREATE INDEX IF NOT EXISTS idx_contracts_quotation ON opc_contracts(quotation_id)",
|
|
819
|
+
"CREATE INDEX IF NOT EXISTS idx_contracts_signed_date ON opc_contracts(signed_date)",
|
|
820
|
+
"CREATE INDEX IF NOT EXISTS idx_payments_milestone ON opc_payments(milestone_id)",
|
|
821
|
+
"CREATE INDEX IF NOT EXISTS idx_payments_risk_level ON opc_payments(risk_level)",
|
|
822
|
+
"CREATE INDEX IF NOT EXISTS idx_payments_overdue ON opc_payments(overdue_days)",
|
|
823
|
+
// 待办事项表索引
|
|
824
|
+
"CREATE INDEX IF NOT EXISTS idx_todos_company ON opc_todos(company_id)",
|
|
825
|
+
"CREATE INDEX IF NOT EXISTS idx_todos_status ON opc_todos(status)",
|
|
826
|
+
"CREATE INDEX IF NOT EXISTS idx_todos_priority ON opc_todos(priority)",
|
|
827
|
+
"CREATE INDEX IF NOT EXISTS idx_todos_due_date ON opc_todos(due_date)",
|
|
828
|
+
"CREATE INDEX IF NOT EXISTS idx_todos_company_status ON opc_todos(company_id, status)",
|
|
829
|
+
"CREATE INDEX IF NOT EXISTS idx_todos_company_priority ON opc_todos(company_id, priority)",
|
|
729
830
|
];
|
package/src/db/sqlite-adapter.ts
CHANGED
|
@@ -132,8 +132,45 @@ export class SqliteAdapter implements OpcDatabase {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
deleteCompany(id: string): boolean {
|
|
135
|
-
|
|
136
|
-
|
|
135
|
+
console.log(`[OPC DB] Deleting company: ${id}`);
|
|
136
|
+
// SQLite 的 PRAGMA foreign_keys 必须在事务外设置
|
|
137
|
+
// 关闭外键约束后,在事务中完成所有删除,再恢复
|
|
138
|
+
this.db.pragma('foreign_keys = OFF');
|
|
139
|
+
try {
|
|
140
|
+
const deleted = this.transaction(() => {
|
|
141
|
+
// 孙表(引用其他 opc_ 表,不直接引用 company)
|
|
142
|
+
this.db.prepare("DELETE FROM opc_quotation_items WHERE quotation_id IN (SELECT id FROM opc_quotations WHERE company_id = ?)").run(id);
|
|
143
|
+
this.db.prepare("DELETE FROM opc_contract_milestones WHERE contract_id IN (SELECT id FROM opc_contracts WHERE company_id = ?)").run(id);
|
|
144
|
+
this.db.prepare("DELETE FROM opc_invoice_items WHERE invoice_id IN (SELECT id FROM opc_invoices WHERE company_id = ?)").run(id);
|
|
145
|
+
this.db.prepare("DELETE FROM opc_contact_interactions WHERE contact_id IN (SELECT id FROM opc_contacts WHERE company_id = ?)").run(id);
|
|
146
|
+
|
|
147
|
+
// 所有直接引用 company_id 的表
|
|
148
|
+
const tables = [
|
|
149
|
+
'opc_quotations', 'opc_contracts', 'opc_payments', 'opc_transactions',
|
|
150
|
+
'opc_invoices', 'opc_contacts', 'opc_tax_filings', 'opc_employees',
|
|
151
|
+
'opc_projects', 'opc_tasks', 'opc_media_content', 'opc_investment_rounds',
|
|
152
|
+
'opc_investors', 'opc_services', 'opc_procurement_orders',
|
|
153
|
+
'opc_lifecycle_events', 'opc_milestones', 'opc_alerts', 'opc_metrics',
|
|
154
|
+
'opc_staff_tasks', 'opc_staff_config', 'opc_acquisition_cases',
|
|
155
|
+
'opc_asset_package_items', 'opc_opb_canvas', 'opc_insights',
|
|
156
|
+
'opc_celebrations', 'opc_company_stage', 'opc_briefings', 'opc_documents',
|
|
157
|
+
'opc_biz_changes', 'opc_financial_periods', 'opc_todos', 'opc_hr_records',
|
|
158
|
+
'opc_contact_interactions'
|
|
159
|
+
];
|
|
160
|
+
for (const table of tables) {
|
|
161
|
+
try {
|
|
162
|
+
this.db.prepare(`DELETE FROM ${table} WHERE company_id = ?`).run(id);
|
|
163
|
+
} catch (_) { /* 表可能不存在,忽略 */ }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const result = this.db.prepare("DELETE FROM opc_companies WHERE id = ?").run(id);
|
|
167
|
+
console.log(`[OPC DB] Deleted: ${result.changes}`);
|
|
168
|
+
return result.changes > 0;
|
|
169
|
+
});
|
|
170
|
+
return deleted;
|
|
171
|
+
} finally {
|
|
172
|
+
this.db.pragma('foreign_keys = ON');
|
|
173
|
+
}
|
|
137
174
|
}
|
|
138
175
|
|
|
139
176
|
// ── Employees ──────────────────────────────────────────────
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 星环OPC中心 — 智能记账意图解析器
|
|
3
|
+
* 使用 LLM 解析自然语言记账输入
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
7
|
+
|
|
8
|
+
export interface AccountingIntent {
|
|
9
|
+
type: "income" | "expense";
|
|
10
|
+
amount: number;
|
|
11
|
+
description: string;
|
|
12
|
+
category: string;
|
|
13
|
+
date: string;
|
|
14
|
+
counterparty?: string;
|
|
15
|
+
is_deductible?: boolean;
|
|
16
|
+
tax_note?: string;
|
|
17
|
+
confidence: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 解析自然语言记账意图
|
|
22
|
+
* @param input 用户输入的自然语言
|
|
23
|
+
* @param api OpenClaw API
|
|
24
|
+
* @returns 解析后的记账意图
|
|
25
|
+
*/
|
|
26
|
+
export async function parseAccountingIntent(
|
|
27
|
+
input: string,
|
|
28
|
+
api: OpenClawPluginApi,
|
|
29
|
+
): Promise<AccountingIntent> {
|
|
30
|
+
const systemPrompt = `你是一个专业的会计助手,负责解析用户的自然语言记账输入。
|
|
31
|
+
|
|
32
|
+
你的任务是从用户输入中提取以下信息:
|
|
33
|
+
1. 交易类型(income 或 expense)
|
|
34
|
+
2. 金额(数字,单位:元)
|
|
35
|
+
3. 描述(简短描述)
|
|
36
|
+
4. 分类(从以下类别中选择):
|
|
37
|
+
- 收入类:service_income(服务收入), product_income(产品销售), investment_income(投资收益), other_income(其他收入)
|
|
38
|
+
- 支出类:business_entertainment(业务招待费), office_supplies(办公费用), marketing(营销费用), salary(工资), rent(租金), utilities(水电费), fixed_asset(固定资产), tax(税费), other_expense(其他支出)
|
|
39
|
+
5. 日期(YYYY-MM-DD 格式,如果未指定则使用今天)
|
|
40
|
+
6. 交易对方(如果提到)
|
|
41
|
+
7. 是否可抵税(布尔值)
|
|
42
|
+
8. 税务提示(如果可抵税或有特殊税务处理,给出提示)
|
|
43
|
+
|
|
44
|
+
分类规则:
|
|
45
|
+
- 业务招待费:请客吃饭、送礼、招待客户等 → 可抵扣,需保存发票
|
|
46
|
+
- 办公费用:购买办公用品、设备维护等 → 可抵扣
|
|
47
|
+
- 固定资产:购买电脑、家具等大额设备(>2000元) → 可折旧
|
|
48
|
+
- 营销费用:广告投放、推广活动等 → 可抵扣
|
|
49
|
+
- 工资:员工工资 → 需代扣个税
|
|
50
|
+
- 租金:办公场地租金 → 可抵扣
|
|
51
|
+
- 税费:各类税费缴纳 → 不可抵扣
|
|
52
|
+
- 生活支出:个人消费 → 不可抵扣
|
|
53
|
+
|
|
54
|
+
请以 JSON 格式返回解析结果,格式如下:
|
|
55
|
+
{
|
|
56
|
+
"type": "income" 或 "expense",
|
|
57
|
+
"amount": 金额(数字),
|
|
58
|
+
"description": "描述",
|
|
59
|
+
"category": "分类",
|
|
60
|
+
"date": "YYYY-MM-DD",
|
|
61
|
+
"counterparty": "交易对方(可选)",
|
|
62
|
+
"is_deductible": true/false,
|
|
63
|
+
"tax_note": "税务提示(可选)",
|
|
64
|
+
"confidence": 0-100(解析置信度)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
如果无法准确解析,confidence 设为较低值,并在 description 中说明原因。`;
|
|
68
|
+
|
|
69
|
+
const userPrompt = `请解析以下记账输入:\n\n${input}`;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const response = await api.callLLM({
|
|
73
|
+
messages: [
|
|
74
|
+
{ role: "system", content: systemPrompt },
|
|
75
|
+
{ role: "user", content: userPrompt },
|
|
76
|
+
],
|
|
77
|
+
maxTokens: 500,
|
|
78
|
+
temperature: 0.1, // 低温度确保稳定输出
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const content = response.content[0];
|
|
82
|
+
if (content.type !== "text") {
|
|
83
|
+
throw new Error("LLM 返回非文本内容");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 尝试提取 JSON
|
|
87
|
+
const text = content.text;
|
|
88
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
89
|
+
if (!jsonMatch) {
|
|
90
|
+
throw new Error("无法从 LLM 响应中提取 JSON");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const result = JSON.parse(jsonMatch[0]) as AccountingIntent;
|
|
94
|
+
|
|
95
|
+
// 验证必填字段
|
|
96
|
+
if (!result.type || !result.amount || !result.description || !result.category) {
|
|
97
|
+
throw new Error("解析结果缺少必填字段");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 如果没有日期,使用今天
|
|
101
|
+
if (!result.date) {
|
|
102
|
+
result.date = new Date().toISOString().slice(0, 10);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return result;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
api.logger.error("opc: 智能记账解析失败", error);
|
|
108
|
+
|
|
109
|
+
// 返回一个低置信度的默认结果
|
|
110
|
+
return {
|
|
111
|
+
type: "expense",
|
|
112
|
+
amount: 0,
|
|
113
|
+
description: `解析失败:${error instanceof Error ? error.message : String(error)}`,
|
|
114
|
+
category: "other_expense",
|
|
115
|
+
date: new Date().toISOString().slice(0, 10),
|
|
116
|
+
is_deductible: false,
|
|
117
|
+
confidence: 0,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 基于历史记录智能建议分类
|
|
124
|
+
* @param description 交易描述
|
|
125
|
+
* @param api OpenClaw API
|
|
126
|
+
* @returns 建议的分类
|
|
127
|
+
*/
|
|
128
|
+
export async function suggestCategory(
|
|
129
|
+
description: string,
|
|
130
|
+
api: OpenClawPluginApi,
|
|
131
|
+
): Promise<{ category: string; reason: string; confidence: number }> {
|
|
132
|
+
const systemPrompt = `你是一个专业的会计分类助手。根据交易描述,建议最合适的分类。
|
|
133
|
+
|
|
134
|
+
分类选项:
|
|
135
|
+
- 收入类:service_income(服务收入), product_income(产品销售), investment_income(投资收益), other_income(其他收入)
|
|
136
|
+
- 支出类:business_entertainment(业务招待费), office_supplies(办公费用), marketing(营销费用), salary(工资), rent(租金), utilities(水电费), fixed_asset(固定资产), tax(税费), other_expense(其他支出)
|
|
137
|
+
|
|
138
|
+
请以 JSON 格式返回:
|
|
139
|
+
{
|
|
140
|
+
"category": "分类代码",
|
|
141
|
+
"reason": "选择理由",
|
|
142
|
+
"confidence": 0-100
|
|
143
|
+
}`;
|
|
144
|
+
|
|
145
|
+
const userPrompt = `请为以下交易描述建议分类:\n\n${description}`;
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const response = await api.callLLM({
|
|
149
|
+
messages: [
|
|
150
|
+
{ role: "system", content: systemPrompt },
|
|
151
|
+
{ role: "user", content: userPrompt },
|
|
152
|
+
],
|
|
153
|
+
maxTokens: 200,
|
|
154
|
+
temperature: 0.1,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const content = response.content[0];
|
|
158
|
+
if (content.type !== "text") {
|
|
159
|
+
throw new Error("LLM 返回非文本内容");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const text = content.text;
|
|
163
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
164
|
+
if (!jsonMatch) {
|
|
165
|
+
throw new Error("无法从 LLM 响应中提取 JSON");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const result = JSON.parse(jsonMatch[0]) as { category: string; reason: string; confidence: number };
|
|
169
|
+
return result;
|
|
170
|
+
} catch (error) {
|
|
171
|
+
api.logger.error("opc: 智能分类建议失败", error);
|
|
172
|
+
return {
|
|
173
|
+
category: "other_expense",
|
|
174
|
+
reason: `分类失败:${error instanceof Error ? error.message : String(error)}`,
|
|
175
|
+
confidence: 0,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|