from pathlib import Path root = Path('C:/kefu/wechat_rpa') def edit(name, old, new): path = root / name text = path.read_text(encoding='utf-8') assert old in text, (name, old[:100]) path.write_text(text.replace(old, new), encoding='utf-8', newline='\n') edit('knowledge_store.py', 'CREATE TABLE IF NOT EXISTS knowledge_settings (', '''CREATE TABLE IF NOT EXISTS knowledge_discarded ( tenant_id TEXT NOT NULL, dedup_hash TEXT NOT NULL, created_at TEXT NOT NULL, PRIMARY KEY(tenant_id,dedup_hash) ); CREATE TABLE IF NOT EXISTS knowledge_settings (''') edit('knowledge_store.py', '("max_model_calls", 100, 10000)):', '("max_model_calls", 100, 10000), ("model_timeout_seconds", 90, 180)):') edit('knowledge_store.py', ' options.update(date_start=start,', ''' if options['model_timeout_seconds'] < 30: raise ValueError("模型等待时间应为 30 至 180 秒") provider = options.get('model_provider_id', '') if not isinstance(provider, str) or len(provider) > 128: raise ValueError("模型标识无效") options.update(date_start=start,''') edit('knowledge_store.py', ' @staticmethod\n def _check_expiry(row):', ''' def delete_rejected(self, tenant, item_id, revision, actor, ip): with self.database.connect() as db: db.execute("BEGIN IMMEDIATE") row = self._item(db, tenant, item_id) if row['revision'] != revision or row['status'] != 'rejected': raise ValueError('仅当前版本的已驳回知识可以删除,请刷新确认状态') db.execute('INSERT OR IGNORE INTO knowledge_discarded VALUES (?,?,?)', (tenant, row['dedup_hash'], utc_now())) db.execute('DELETE FROM knowledge_fts WHERE item_id=?', (item_id,)) db.execute('DELETE FROM knowledge_source WHERE item_id=?', (item_id,)) db.execute('DELETE FROM knowledge_revision WHERE item_id=?', (item_id,)) db.execute('DELETE FROM knowledge_item WHERE id=? AND tenant_id=?', (item_id, tenant)) self.database._audit(db, actor, 'knowledge.delete', f'item={item_id} revision={revision}', ip) return {'id': item_id, 'status': 'deleted'} @staticmethod def _check_expiry(row):''') edit('knowledge_store.py', ' scope_key = fingerprint(json_text({k: options.get(k, "") for k in (', ''' if options['engine'] == 'model': from knowledge_models import resolve_model resolve_model(self.database, options) scope_key = fingerprint(json_text({k: options.get(k, "") for k in (''') edit('knowledge_store.py', '"date_end", "engine", "max_messages", "max_model_calls")', '"date_end", "engine", "max_messages", "max_model_calls", "model_provider_id", "model_timeout_seconds")') edit('knowledge_store.py', ' def job_action(self, tenant, job_id, action, actor, ip):', ''' def configure_job(self, tenant, job_id, values, actor, ip): options = dict(self.job(tenant, job_id)['options']) options.update(values) options = job_options(options) if options['engine'] == 'model': from knowledge_models import resolve_model resolve_model(self.database, options) with self.database.connect() as db: db.execute('BEGIN IMMEDIATE') row = db.execute('SELECT * FROM knowledge_job WHERE id=? AND tenant_id=?', (job_id, tenant)).fetchone() if row is None: raise KeyError('任务不存在') if row['status'] not in {'paused', 'failed'}: raise ValueError('请先暂停任务,再调整模型配置') if options['engine'] == 'model' and options['max_model_calls'] <= row['model_calls']: raise ValueError('模型调用总上限须大于已使用次数') db.execute('UPDATE knowledge_job SET options_json=?,updated_at=? WHERE id=?', (json_text(options), utc_now(), job_id)) self.database._audit(db, actor, 'knowledge.job.configure', f'job={job_id}', ip) return self.job(tenant, job_id) def job_action(self, tenant, job_id, action, actor, ip):''') edit('knowledge_store.py', ' existing = db.execute("SELECT id,status FROM knowledge_item', ''' if db.execute('SELECT 1 FROM knowledge_discarded WHERE tenant_id=? AND dedup_hash=?', (tenant, digest)).fetchone(): return False existing = db.execute("SELECT id,status FROM knowledge_item''') edit('knowledge_api.py', ' max_messages: int = Field', ''' model_provider_id: str = Field(default="", max_length=128) model_timeout_seconds: int = Field(default=90, ge=30, le=180) max_messages: int = Field''') edit('knowledge_api.py', 'class EditBody(BaseModel):', '''class JobConfigBody(BaseModel): engine: Literal['rules', 'model'] model_provider_id: str = Field(default='', max_length=128) model_timeout_seconds: int = Field(default=90, ge=30, le=180) max_model_calls: int = Field(default=100, ge=1, le=10000) class EditBody(BaseModel):''') edit('knowledge_api.py', ' @app.get("/api/v2/knowledge/jobs")', ''' @app.get('/api/v2/knowledge/models') def models(account_id: int | None = None, principal=Depends(require('knowledge:write', 'im:content:read'))): single(principal, account_id) from knowledge_models import model_choices return {'items': model_choices(database)} @app.put('/api/v2/knowledge/jobs/{job_id}/config') def job_config(job_id: str, body: JobConfigBody, request: Request, account_id: int | None = None, principal=Depends(require('knowledge:write', 'im:content:read'))): with errors(): return store.configure_job(single(principal, account_id), job_id, body.model_dump(), principal.id, client_ip(request)) @app.get("/api/v2/knowledge/jobs")''') edit('knowledge_api.py', 'principal=Depends(require("knowledge:write"))', 'principal=Depends(require("knowledge:write", "im:content:read"))') edit('knowledge_api.py', '"knowledge:write" if action == "restore" else "knowledge:review")', '"knowledge:write" if action in {"restore", "delete"} else "knowledge:review")') edit('knowledge_api.py', ' if action == "publish":', ''' if action == 'delete': if not body.confirmed: raise ValueError('请确认删除已驳回知识,原始聊天归档会保留') return store.delete_rejected(tenant, item_id, body.revision, principal.id, client_ip(request)) if action == "publish":''')