Skip to content

Commit b9caf53

Browse files
committed
feat: Implement history management for chatflows and assistants
- Added history service to manage snapshots of chatflows and assistants. - Integrated history snapshot creation on chatflow save and update operations. - Created API endpoints for fetching, restoring, and deleting history snapshots. - Developed UI components for displaying and managing version history in the assistant and chatflow configurations. - Added HistoryButton and HistoryDialog components for user interaction with version history. - Implemented flow reload functionality to update the canvas with restored chatflow data.
1 parent 37e9b5b commit b9caf53

36 files changed

+1585
-72
lines changed

packages/components/nodes/tools/MCP/core.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ function waitForStreamingCompletion(
259259
return new Promise<string>((resolve) => {
260260
let completed = false
261261

262-
const completeExecution = (reason: string) => {
262+
const completeExecution = (_reason: string) => {
263263
if (completed) return
264264
completed = true
265265

packages/server/src/Interface.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export interface IChatFlow {
7070
category?: string
7171
type?: ChatflowType
7272
workspaceId?: string
73+
currentHistoryVersion?: number
7374
}
7475

7576
export interface IChatMessage {
@@ -125,6 +126,7 @@ export interface IAssistant {
125126
updatedDate: Date
126127
createdDate: Date
127128
workspaceId?: string
129+
currentHistoryVersion?: number
128130
}
129131

130132
export interface ICredential {
@@ -191,6 +193,17 @@ export interface IVariableDict {
191193
[key: string]: string
192194
}
193195

196+
export interface IFlowHistory {
197+
id: string
198+
entityType: 'CHATFLOW' | 'ASSISTANT'
199+
entityId: string
200+
snapshotData: string
201+
changeDescription?: string
202+
version: number
203+
createdDate: Date
204+
workspaceId?: string
205+
}
206+
194207
export interface INodeDependencies {
195208
[key: string]: number
196209
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { NextFunction, Request, Response } from 'express'
2+
import { StatusCodes } from 'http-status-codes'
3+
import { EntityType } from '../../database/entities/FlowHistory'
4+
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
5+
import historyService from '../../services/history'
6+
7+
const validateEntityType = (entityType: string): EntityType => {
8+
const upperType = entityType.toUpperCase()
9+
if (!['CHATFLOW', 'ASSISTANT'].includes(upperType)) {
10+
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'entityType must be either CHATFLOW or ASSISTANT')
11+
}
12+
return upperType as EntityType
13+
}
14+
15+
const getHistory = async (req: Request, res: Response, next: NextFunction) => {
16+
try {
17+
const { entityType, entityId } = req.params
18+
const { page = '1', limit = '20' } = req.query
19+
20+
if (!entityType || !entityId) {
21+
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'entityType and entityId are required parameters')
22+
}
23+
24+
const pageNum = Number(page)
25+
const limitNum = Number(limit)
26+
27+
const result = await historyService.getHistory({
28+
entityType: validateEntityType(entityType),
29+
entityId,
30+
workspaceId: req.user?.activeWorkspaceId,
31+
limit: limitNum,
32+
offset: (pageNum - 1) * limitNum
33+
})
34+
35+
return res.json(result)
36+
} catch (error) {
37+
next(error)
38+
}
39+
}
40+
41+
const getSnapshotById = async (req: Request, res: Response, next: NextFunction) => {
42+
try {
43+
const { historyId } = req.params
44+
if (!historyId) {
45+
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'historyId is required')
46+
}
47+
48+
const snapshot = await historyService.getSnapshotById(historyId)
49+
return res.json(snapshot)
50+
} catch (error) {
51+
next(error)
52+
}
53+
}
54+
55+
const restoreSnapshot = async (req: Request, res: Response, next: NextFunction) => {
56+
try {
57+
const { historyId } = req.params
58+
if (!historyId) {
59+
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'historyId is required')
60+
}
61+
62+
const restoredEntity = await historyService.restoreSnapshot({
63+
historyId,
64+
workspaceId: req.user?.activeWorkspaceId
65+
})
66+
67+
return res.json({
68+
message: 'Successfully restored from history snapshot',
69+
entity: restoredEntity
70+
})
71+
} catch (error) {
72+
next(error)
73+
}
74+
}
75+
76+
const deleteSnapshot = async (req: Request, res: Response, next: NextFunction) => {
77+
try {
78+
const { historyId } = req.params
79+
if (!historyId) {
80+
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'historyId is required')
81+
}
82+
83+
await historyService.deleteSnapshot(historyId, req.user?.activeWorkspaceId)
84+
return res.json({ message: 'History snapshot deleted successfully' })
85+
} catch (error) {
86+
next(error)
87+
}
88+
}
89+
90+
const getSnapshotComparison = async (req: Request, res: Response, next: NextFunction) => {
91+
try {
92+
const { historyId1, historyId2 } = req.params
93+
if (!historyId1 || !historyId2) {
94+
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'Both historyId1 and historyId2 are required')
95+
}
96+
97+
const comparison = await historyService.getSnapshotComparison(historyId1, historyId2, req.user?.activeWorkspaceId)
98+
return res.json(comparison)
99+
} catch (error) {
100+
next(error)
101+
}
102+
}
103+
104+
export default {
105+
getHistory,
106+
getSnapshotById,
107+
restoreSnapshot,
108+
deleteSnapshot,
109+
getSnapshotComparison
110+
}

packages/server/src/database/entities/Assistant.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,7 @@ export class Assistant implements IAssistant {
2929

3030
@Column({ nullable: true, type: 'text' })
3131
workspaceId?: string
32+
33+
@Column({ nullable: true, type: 'int' })
34+
currentHistoryVersion?: number
3235
}

packages/server/src/database/entities/ChatFlow.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,7 @@ export class ChatFlow implements IChatFlow {
6060

6161
@Column({ nullable: true, type: 'text' })
6262
workspaceId?: string
63+
64+
@Column({ nullable: true, type: 'int' })
65+
currentHistoryVersion?: number
6366
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/* eslint-disable */
2+
import { Entity, Column, CreateDateColumn, PrimaryGeneratedColumn, Index } from 'typeorm'
3+
4+
export type EntityType = 'CHATFLOW' | 'ASSISTANT'
5+
6+
export interface IFlowHistory {
7+
id: string
8+
entityType: EntityType
9+
entityId: string
10+
snapshotData: string
11+
changeDescription?: string
12+
version: number
13+
createdDate: Date
14+
workspaceId?: string
15+
}
16+
17+
@Entity()
18+
@Index(['entityType', 'entityId', 'version'])
19+
@Index(['entityType', 'entityId', 'createdDate'])
20+
export class FlowHistory implements IFlowHistory {
21+
@PrimaryGeneratedColumn('uuid')
22+
id: string
23+
24+
@Column({ type: 'varchar', length: 20 })
25+
entityType: EntityType
26+
27+
@Column({ type: 'uuid' })
28+
entityId: string
29+
30+
@Column({ type: 'text' })
31+
snapshotData: string
32+
33+
@Column({ nullable: true, type: 'text' })
34+
changeDescription?: string
35+
36+
@Column({ type: 'int' })
37+
version: number
38+
39+
@Column({ type: 'timestamp' })
40+
@CreateDateColumn()
41+
createdDate: Date
42+
43+
@Column({ nullable: true, type: 'text' })
44+
workspaceId?: string
45+
}

packages/server/src/database/entities/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { Evaluator } from './Evaluator'
1717
import { ApiKey } from './ApiKey'
1818
import { CustomTemplate } from './CustomTemplate'
1919
import { Execution } from './Execution'
20+
import { FlowHistory } from './FlowHistory'
2021
import { LoginActivity, WorkspaceShared, WorkspaceUsers } from '../../enterprise/database/entities/EnterpriseEntities'
2122
import { User } from '../../enterprise/database/entities/user.entity'
2223
import { Organization } from '../../enterprise/database/entities/organization.entity'
@@ -50,6 +51,7 @@ export const entities = {
5051
WorkspaceShared,
5152
CustomTemplate,
5253
Execution,
54+
FlowHistory,
5355
Organization,
5456
Role,
5557
OrganizationUser,
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { MigrationInterface, QueryRunner } from 'typeorm'
2+
3+
export class AddFlowHistoryEntity1750000000000 implements MigrationInterface {
4+
public async up(queryRunner: QueryRunner): Promise<void> {
5+
await queryRunner.query(
6+
`CREATE TABLE IF NOT EXISTS \`flow_history\` (
7+
\`id\` varchar(36) NOT NULL,
8+
\`entityType\` varchar(20) NOT NULL,
9+
\`entityId\` varchar(36) NOT NULL,
10+
\`snapshotData\` longtext NOT NULL,
11+
\`changeDescription\` text DEFAULT NULL,
12+
\`version\` int NOT NULL,
13+
\`createdDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
14+
\`workspaceId\` text DEFAULT NULL,
15+
PRIMARY KEY (\`id\`),
16+
INDEX \`IDX_flow_history_entity_version\` (\`entityType\`, \`entityId\`, \`version\`),
17+
INDEX \`IDX_flow_history_entity_date\` (\`entityType\`, \`entityId\`, \`createdDate\`)
18+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;`
19+
)
20+
}
21+
22+
public async down(queryRunner: QueryRunner): Promise<void> {
23+
await queryRunner.query(`DROP TABLE flow_history`)
24+
}
25+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { MigrationInterface, QueryRunner } from 'typeorm'
2+
3+
export class AddCurrentHistoryVersion1750000000001 implements MigrationInterface {
4+
public async up(queryRunner: QueryRunner): Promise<void> {
5+
await queryRunner.query(`ALTER TABLE \`chat_flow\` ADD \`currentHistoryVersion\` int NULL`)
6+
}
7+
8+
public async down(queryRunner: QueryRunner): Promise<void> {
9+
await queryRunner.query(`ALTER TABLE \`chat_flow\` DROP COLUMN \`currentHistoryVersion\``)
10+
}
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { MigrationInterface, QueryRunner } from 'typeorm'
2+
3+
export class AddAssistantHistoryVersion1750000000002 implements MigrationInterface {
4+
public async up(queryRunner: QueryRunner): Promise<void> {
5+
await queryRunner.query(`ALTER TABLE \`assistant\` ADD \`currentHistoryVersion\` int NULL`)
6+
}
7+
8+
public async down(queryRunner: QueryRunner): Promise<void> {
9+
await queryRunner.query(`ALTER TABLE \`assistant\` DROP COLUMN \`currentHistoryVersion\``)
10+
}
11+
}

0 commit comments

Comments
 (0)