flashsync / src /ghost_writing.gleam
Dhurgh's picture
Permanently remove build artifacts from history
d0b8e8f
Raw
History Blame Contribute Delete
7.43 kB
import gleam/list
import gleam/float
import gleam/int
pub type SketchStroke {
SketchStroke(
points: List(#(Float, Float)),
color: String,
thickness: Float,
timestamp: String,
)
}
pub type GhostWritingSession {
GhostWritingSession(
session_id: String,
reference_image: String,
user_strokes: List(SketchStroke),
ghost_image_opacity: Float,
current_page: Int,
total_pages: Int,
)
}
pub type SimilarityScore {
SimilarityScore(
overall_score: Float,
structural_accuracy: Float,
proportional_accuracy: Float,
detail_completeness: Float,
feedback: List(String),
)
}
pub fn start_ghost_writing_session(
image_url: String,
) -> GhostWritingSession {
GhostWritingSession(
session_id: "SESSION_2026",
reference_image: image_url,
user_strokes: [],
ghost_image_opacity: 0.3,
current_page: 1,
total_pages: 1,
)
}
pub fn create_stroke(
points: List(#(Float, Float)),
color: String,
thickness: Float,
) -> SketchStroke {
SketchStroke(
points: points,
color: color,
thickness: thickness,
timestamp: "2026-03-19T00:00:00Z",
)
}
pub fn add_stroke_to_session(
session: GhostWritingSession,
stroke: SketchStroke,
) -> GhostWritingSession {
GhostWritingSession(
..session,
user_strokes: [stroke, ..session.user_strokes],
)
}
pub fn adjust_ghost_opacity(
session: GhostWritingSession,
opacity: Float,
) -> GhostWritingSession {
let clamped_opacity = case opacity {
o if o <. 0.0 -> 0.0
o if o >. 1.0 -> 1.0
o -> o
}
GhostWritingSession(..session, ghost_image_opacity: clamped_opacity)
}
pub fn calculate_similarity(
user_drawing: List(SketchStroke),
_reference_image: String,
) -> SimilarityScore {
let stroke_count = list.length(user_drawing)
let structural_accuracy = case stroke_count {
c if c > 15 -> 85.0
c if c > 8 -> 65.0
c if c > 3 -> 45.0
_ -> 20.0
}
let proportional_accuracy = case stroke_count {
c if c > 10 -> 80.0
_ -> 50.0
}
let detail_completeness = case stroke_count {
c if c > 15 -> 90.0
c if c > 8 -> 70.0
_ -> 40.0
}
let overall =
{structural_accuracy +. proportional_accuracy +. detail_completeness} /. 3.0
SimilarityScore(
overall_score: overall,
structural_accuracy: structural_accuracy,
proportional_accuracy: proportional_accuracy,
detail_completeness: detail_completeness,
feedback: generate_drawing_feedback(overall, stroke_count),
)
}
fn generate_drawing_feedback(score: Float, stroke_count: Int) -> List(String) {
let base_feedback = case score {
s if s >. 85.0 -> [
"🏆 Exceptional job! Your drawing matches the reference perfectly!",
"Your spatial understanding is excellent.",
]
s if s >. 70.0 -> [
"💪 Great work! Your sketch is very accurate.",
"Minor details could be refined.",
]
s if s >. 50.0 -> [
"📈 Good effort! The main structure is recognizable.",
"Try adding more detail.",
]
_ -> [
"Keep practicing! Reference the ghost image more carefully.",
"Add more strokes to complete the drawing.",
]
}
case stroke_count {
c if c < 3 ->
list.append(base_feedback, ["Remember: Draw more strokes for better results!"])
_ -> base_feedback
}
}
pub fn generate_comparison_visualization(
user_drawing: List(SketchStroke),
_reference_image: String,
) -> String {
let canvas = "<canvas id='comparison' width='800' height='600'></canvas>"
let user_svg = render_strokes(user_drawing)
"<div class='ghost-comparison'>" <> canvas <> user_svg <> "</div>"
}
fn render_strokes(strokes: List(SketchStroke)) -> String {
"<svg class='user-drawing' width='800' height='600'>" <>
list.fold(strokes, "", fn(acc, stroke) {
acc <> render_stroke(stroke)
}) <>
"</svg>"
}
fn render_stroke(stroke: SketchStroke) -> String {
case stroke.points {
[] -> ""
[first, ..rest] -> {
let #(x, y) = first
let path_start =
"M " <> float.to_string(x) <> " " <> float.to_string(y)
let path_rest =
list.fold(rest, "", fn(acc, point) {
let #(px, py) = point
acc <> " L " <> float.to_string(px) <> " " <> float.to_string(py)
})
"<path d='" <>
path_start <>
path_rest <>
"' stroke='" <>
stroke.color <>
"' stroke-width='" <>
float.to_string(stroke.thickness) <>
"' fill='none' stroke-linecap='round' />"
}
}
}
pub fn export_drawing_as_image(strokes: List(SketchStroke)) -> String {
// Would generate PNG in production
render_strokes(strokes)
}
pub fn compare_drawings(
drawing1: List(SketchStroke),
drawing2: List(SketchStroke),
) -> Float {
// Simplified similarity metric
let denominator =
int.max(
list.length(drawing1),
list.length(drawing2),
)
case denominator == 0 {
True -> 0.0
False ->
int.to_float(
int.min(
list.length(drawing1),
list.length(drawing2),
),
) /. int.to_float(denominator) *. 100.0
}
}
pub fn suggest_next_diagram(
user_level: Float,
) -> String {
case user_level {
l if l >. 85.0 ->
"Next: Advanced Anatomy (Heart Cross-Section)"
l if l >. 70.0 ->
"Next: Intermediate Diagram (Chemical Structure)"
l if l >. 50.0 ->
"Next: Basic Sketch (Simple Geometric Shape)"
_ ->
"Practice more with simpler diagrams first."
}
}
pub fn track_drawing_history(session: GhostWritingSession) -> String {
let stroke_count = list.length(session.user_strokes)
"Drawing Progress: " <>
int.to_string(stroke_count) <>
" strokes | Page " <>
int.to_string(session.current_page) <>
"/" <>
int.to_string(session.total_pages)
}
pub fn create_drawing_canvas_html() -> String {
"
<div class='ghost-writing-container'>
<div class='ghost-layer'>
<img id='ghostImg' src='' alt='Reference' class='ghost-image' />
<canvas id='drawingCanvas' width='800' height='600' class='drawing-canvas'></canvas>
</div>
<div class='controls'>
<label>Ghost Opacity: <input type='range' id='opacitySlider' min='0' max='100' value='30'></label>
<button onclick='undoStroke()'>Undo</button>
<button onclick='clearDrawing()'>Clear</button>
<button onclick='submitDrawing()'>Submit</button>
</div>
<div id='feedback' class='feedback'></div>
</div>"
}
pub fn analyze_drawing_curves(strokes: List(SketchStroke)) -> String {
let smoothness =
list.fold(strokes, 0.0, fn(acc, stroke) {
acc +. calculate_curve_smoothness(stroke.points)
}) /. int.to_float(list.length(strokes))
"Curve Analysis: " <>
float.to_string(smoothness) <>
"% smoothness"
}
fn calculate_curve_smoothness(points: List(#(Float, Float))) -> Float {
// Simplified - would use actual curvature analysis
case list.length(points) {
n if n > 10 -> 85.0
n if n > 5 -> 70.0
_ -> 50.0
}
}
pub fn get_tracing_hints(
_reference_image: String,
current_progress: Float,
) -> List(String) {
case current_progress {
p if p <. 30.0 -> [
"💡 Start by outlining the basic shape",
"Focus on the main contours first",
]
p if p <. 70.0 -> [
"💡 Add the internal structures",
"Pay attention to proportions",
]
_ -> [
"💡 Refine details and shading",
"Your sketch looks great!",
]
}
}