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
116 lines
3.4 KiB
TypeScript
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,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
)
|