mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
feat(confluence): return page content in get page version tool (#3344)
* feat(confluence): return page content in get page version tool * lint
This commit is contained in:
@@ -326,6 +326,8 @@ Get details about a specific version of a Confluence page.
|
||||
| --------- | ---- | ----------- |
|
||||
| `ts` | string | ISO 8601 timestamp of the operation |
|
||||
| `pageId` | string | ID of the page |
|
||||
| `title` | string | Page title at this version |
|
||||
| `content` | string | Page content with HTML tags stripped at this version |
|
||||
| `version` | object | Detailed version information |
|
||||
| ↳ `number` | number | Version number |
|
||||
| ↳ `message` | string | Version message |
|
||||
@@ -336,6 +338,9 @@ Get details about a specific version of a Confluence page.
|
||||
| ↳ `collaborators` | array | List of collaborator account IDs for this version |
|
||||
| ↳ `prevVersion` | number | Previous version number |
|
||||
| ↳ `nextVersion` | number | Next version number |
|
||||
| `body` | object | Raw page body content in storage format at this version |
|
||||
| ↳ `value` | string | The content value in the specified format |
|
||||
| ↳ `representation` | string | Content representation type |
|
||||
|
||||
### `confluence_list_page_properties`
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
|
||||
import { getConfluenceCloudId } from '@/tools/confluence/utils'
|
||||
import { cleanHtmlContent, getConfluenceCloudId } from '@/tools/confluence/utils'
|
||||
|
||||
const logger = createLogger('ConfluencePageVersionsAPI')
|
||||
|
||||
@@ -55,42 +55,79 @@ export async function POST(request: NextRequest) {
|
||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
// If versionNumber is provided, get specific version
|
||||
// If versionNumber is provided, get specific version with page content
|
||||
if (versionNumber !== undefined && versionNumber !== null) {
|
||||
const url = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/pages/${pageId}/versions/${versionNumber}`
|
||||
const versionUrl = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/pages/${pageId}/versions/${versionNumber}`
|
||||
const pageUrl = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/pages/${pageId}?version=${versionNumber}&body-format=storage`
|
||||
|
||||
logger.info(`Fetching version ${versionNumber} for page ${pageId}`)
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
})
|
||||
const [versionResponse, pageResponse] = await Promise.all([
|
||||
fetch(versionUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
}),
|
||||
fetch(pageUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
}),
|
||||
])
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => null)
|
||||
if (!versionResponse.ok) {
|
||||
const errorData = await versionResponse.json().catch(() => null)
|
||||
logger.error('Confluence API error response:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
status: versionResponse.status,
|
||||
statusText: versionResponse.statusText,
|
||||
error: JSON.stringify(errorData, null, 2),
|
||||
})
|
||||
const errorMessage = errorData?.message || `Failed to get page version (${response.status})`
|
||||
return NextResponse.json({ error: errorMessage }, { status: response.status })
|
||||
const errorMessage =
|
||||
errorData?.message || `Failed to get page version (${versionResponse.status})`
|
||||
return NextResponse.json({ error: errorMessage }, { status: versionResponse.status })
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const versionData = await versionResponse.json()
|
||||
|
||||
let title: string | null = null
|
||||
let content: string | null = null
|
||||
let body: Record<string, unknown> | null = null
|
||||
|
||||
if (pageResponse.ok) {
|
||||
const pageData = await pageResponse.json()
|
||||
title = pageData.title ?? null
|
||||
body = pageData.body ?? null
|
||||
|
||||
const rawContent =
|
||||
pageData.body?.storage?.value ||
|
||||
pageData.body?.view?.value ||
|
||||
pageData.body?.atlas_doc_format?.value ||
|
||||
''
|
||||
if (rawContent) {
|
||||
content = cleanHtmlContent(rawContent)
|
||||
}
|
||||
} else {
|
||||
logger.warn(
|
||||
`Could not fetch page content for version ${versionNumber}: ${pageResponse.status}`
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
version: {
|
||||
number: data.number,
|
||||
message: data.message ?? null,
|
||||
minorEdit: data.minorEdit ?? false,
|
||||
authorId: data.authorId ?? null,
|
||||
createdAt: data.createdAt ?? null,
|
||||
number: versionData.number,
|
||||
message: versionData.message ?? null,
|
||||
minorEdit: versionData.minorEdit ?? false,
|
||||
authorId: versionData.authorId ?? null,
|
||||
createdAt: versionData.createdAt ?? null,
|
||||
},
|
||||
pageId,
|
||||
title,
|
||||
content,
|
||||
body,
|
||||
})
|
||||
}
|
||||
// List all versions
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { DETAILED_VERSION_OUTPUT_PROPERTIES, TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
||||
import {
|
||||
BODY_FORMAT_PROPERTIES,
|
||||
DETAILED_VERSION_OUTPUT_PROPERTIES,
|
||||
TIMESTAMP_OUTPUT,
|
||||
} from '@/tools/confluence/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export interface ConfluenceGetPageVersionParams {
|
||||
@@ -14,6 +18,8 @@ export interface ConfluenceGetPageVersionResponse {
|
||||
output: {
|
||||
ts: string
|
||||
pageId: string
|
||||
title: string | null
|
||||
content: string | null
|
||||
version: {
|
||||
number: number
|
||||
message: string | null
|
||||
@@ -25,6 +31,12 @@ export interface ConfluenceGetPageVersionResponse {
|
||||
prevVersion: number | null
|
||||
nextVersion: number | null
|
||||
}
|
||||
body: {
|
||||
storage?: {
|
||||
value: string
|
||||
representation: string
|
||||
}
|
||||
} | null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +112,8 @@ export const confluenceGetPageVersionTool: ToolConfig<
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
pageId: data.pageId ?? '',
|
||||
title: data.title ?? null,
|
||||
content: data.content ?? null,
|
||||
version: data.version ?? {
|
||||
number: 0,
|
||||
message: null,
|
||||
@@ -107,6 +121,7 @@ export const confluenceGetPageVersionTool: ToolConfig<
|
||||
authorId: null,
|
||||
createdAt: null,
|
||||
},
|
||||
body: data.body ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -114,10 +129,29 @@ export const confluenceGetPageVersionTool: ToolConfig<
|
||||
outputs: {
|
||||
ts: TIMESTAMP_OUTPUT,
|
||||
pageId: { type: 'string', description: 'ID of the page' },
|
||||
title: { type: 'string', description: 'Page title at this version', optional: true },
|
||||
content: {
|
||||
type: 'string',
|
||||
description: 'Page content with HTML tags stripped at this version',
|
||||
optional: true,
|
||||
},
|
||||
version: {
|
||||
type: 'object',
|
||||
description: 'Detailed version information',
|
||||
properties: DETAILED_VERSION_OUTPUT_PROPERTIES,
|
||||
},
|
||||
body: {
|
||||
type: 'object',
|
||||
description: 'Raw page body content in storage format at this version',
|
||||
properties: {
|
||||
storage: {
|
||||
type: 'object',
|
||||
description: 'Body in storage format (Confluence markup)',
|
||||
properties: BODY_FORMAT_PROPERTIES,
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -56,13 +56,21 @@ function stripHtmlTags(html: string): string {
|
||||
return text.trim()
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips HTML tags and decodes HTML entities from raw Confluence content.
|
||||
*/
|
||||
export function cleanHtmlContent(rawContent: string): string {
|
||||
let content = stripHtmlTags(rawContent)
|
||||
content = decodeHtmlEntities(content)
|
||||
content = content.replace(/\s+/g, ' ').trim()
|
||||
return content
|
||||
}
|
||||
|
||||
export function transformPageData(data: any) {
|
||||
const rawContent =
|
||||
data.body?.storage?.value || data.body?.view?.value || data.body?.atlas_doc_format?.value || ''
|
||||
|
||||
let cleanContent = stripHtmlTags(rawContent)
|
||||
cleanContent = decodeHtmlEntities(cleanContent)
|
||||
cleanContent = cleanContent.replace(/\s+/g, ' ').trim()
|
||||
const cleanContent = cleanHtmlContent(rawContent)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
||||
Reference in New Issue
Block a user