Files
upstream-mirror a774ac673a
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
2026-09-23 16:12:52 +05:00

116 lines
3.4 KiB
TypeScript

import { b64Vecs, TLDrawShapeSegment, TLShapeId, Vec, VecModel } from 'tldraw'
import { asColor } from '../../shared/format/FocusedColor'
import { convertFocusedFillToTldrawFill } from '../../shared/format/FocusedFill'
import { PenAction } from '../../shared/schema/AgentActionSchemas'
import { Streaming } from '../../shared/types/Streaming'
import { AgentHelpers } from '../AgentHelpers'
import { AgentActionUtil, registerActionUtil } from './AgentActionUtil'
export const PenActionUtil = registerActionUtil(
class PenActionUtil extends AgentActionUtil<PenAction> {
static override type = 'pen' as const
override getInfo(action: Streaming<PenAction>) {
return {
icon: 'pencil' as const,
description: action.intent ?? '',
}
}
override sanitizeAction(action: Streaming<PenAction>, helpers: AgentHelpers) {
if (!action.points) return action
// Ensure the shape has a unique ID
action.shapeId = helpers.ensureShapeIdIsUnique(action.shapeId)
// Don't include the final point if we're still streaming.
// Its numbers might be incomplete.
const points = action.complete ? action.points : action.points.slice(0, -1)
// This is a complex action for the model, so validate the data it gives us
const validPoints = points
.map((point) => helpers.ensureValueIsVec(point))
.filter((v) => v !== null)
action.points = validPoints
action.closed = helpers.ensureValueIsBoolean(action.closed) ?? false
action.fill = helpers.ensureValueIsFocusedFill(action.fill) ?? 'none'
return action
}
override applyAction(action: Streaming<PenAction>, helpers: AgentHelpers) {
if (!action.points) return
if (action.points.length === 0) return
if (!action.shapeId) return
const shapeId = `shape:${action.shapeId}` as TLShapeId
action.points = action.points.map((point) => helpers.removeOffsetFromVec(point))
if (action.closed) {
const firstPoint = action.points[0]
action.points.push(firstPoint)
}
const minX = Math.min(...action.points.map((p) => p.x))
const minY = Math.min(...action.points.map((p) => p.y))
const points: VecModel[] = []
const maxDistanceBetweenPoints = action.style === 'smooth' ? 10 : 2
for (let i = 0; i < action.points.length - 1; i++) {
const point = action.points[i]
points.push(point)
const nextPoint = action.points[i + 1]
if (!nextPoint) continue
const distance = Vec.Dist(point, nextPoint)
const numPointsToAdd = Math.floor(distance / maxDistanceBetweenPoints)
const pointsToAdd = Array.from({ length: numPointsToAdd }, (_, j) => {
const t = (j + 1) / (numPointsToAdd + 1)
return Vec.Lrp(point, nextPoint, t)
})
points.push(...pointsToAdd)
}
// Require at least 2 points to draw a line
if (points.length <= 1) {
return
}
const segmentPoints = points.map((point) => ({
x: point.x - minX,
y: point.y - minY,
z: 0.75,
}))
const base64Points = b64Vecs.encodePoints(segmentPoints)
const segments: TLDrawShapeSegment[] = [
{
type: 'free',
path: base64Points,
},
]
this.editor.createShape({
id: shapeId,
type: 'draw',
x: minX,
y: minY,
isLocked: !action.complete,
props: {
color: asColor(action.color ?? 'black'),
fill: convertFocusedFillToTldrawFill(action.fill ?? 'none'),
dash: 'draw',
size: 's',
segments,
isComplete: action.complete,
isClosed: action.closed,
isPen: true,
},
})
}
}
)