Skip to content

Commit

Permalink
+++
Browse files Browse the repository at this point in the history
  • Loading branch information
Offirmo committed Sep 29, 2024
1 parent d238aba commit 867e5bd
Show file tree
Hide file tree
Showing 19 changed files with 579 additions and 317 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,11 @@ first-mover advantage https://en.wikipedia.org/wiki/First-mover_advantage
funnel
Go-To-Market (GTM) = plan of an organization, utilizing their outside resources (e.g., sales force and distributors), to deliver their unique value proposition to customers and to achieve a competitive advantage https://en.wikipedia.org/wiki/Go-to-market_strategy
growth +++ https://growth.design/
growth -- cognitive load
growth -- concepts https://growth.design/psychology
growth -- friction
growth -- labor perception / perceived labor https://growth.design/case-studies/labor-perception-bias
growth -- onboarding -- fatigue
illegal practices -- monopoly
illegal practices -- payola https://en.wikipedia.org/wiki/Payola
illegal practices -- predatory pricing
Expand Down
18 changes: 14 additions & 4 deletions stack--current/5-incubator/active/view--chat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,24 @@
"tslib": "^2"
},
"dependencies": {
"caught": "^0.1",
"@offirmo-private/ts-types": "*",
"is-promise": "^4",
"p-progress": "^1"
"p-progress": "^1",
"tiny-invariant": "^1",
"typescript-string-enums":"^1"
},

"scripts": {
"cheatsheet": "node doc/cheatsheet.js",
"demo": "echo please use an implementation, ex. node"
"clean": "monorepo-script--clean-package …dist",

"_build:dev:watch": "monorepo-script--build-typescript-package --watch --module=esm",
"_build:prod": "monorepo-script--build-typescript-package --module=esm",

"dev": "run-s clean _build:dev:watch",
"test": "./node_modules/.bin/mocha --bail --config ./node_modules/@offirmo/unit-test-toolbox/mocharc.json ./node_modules/@offirmo/unit-test-toolbox/mocha-chai-init-node.mjs 'dist/src.es2023.esm/**/*spec.js'",
"build": "run-s clean _build:prod",

"demo": "node ./dist/src.es2023.esm/loop/demo.js"
},
"devDependencies": {
"npm-run-all": "^4",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { type ChatPrimitives } from '../implementation/types.js'

const CHAT_CONSOLE: ChatPrimitives<string> = {
setup: async () => {
console.log('[ChatPrimitives.setup()]')
},

display_message: async ({msg, choices}) => {
console.log('[ChatPrimitives.display_message()]')

console.log(msg)
if (choices) console.log('Choices:', choices)
},

pretend_to_think: async ({duration_ms}) => { throw new Error('NO UI pretend_to_think') },

pretend_to_work: async({
msg_before,
duration_ms,
msg_after,
}) => {
console.log(msg_before)
await new Promise(resolve => setTimeout(resolve, duration_ms))
console.log(msg_after)
},

//read_answer: async () => { throw new Error('NO UI read_answer') },

display_task: ({
msg_before,
progress_promise,
msg_after,
}) => {
throw new Error(`NO UI display_task!`)
},

spin_until_resolution: async ({promise}) => promise,

teardown: async () => {
console.log('[ChatPrimitives.teardown()]')
},
}

export default CHAT_CONSOLE
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import i_will_handle_rejection_later from 'caught'

function* get_next_step1(skip_to_index = 0) {
const state = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { type Step, StepType } from '../types/types.js'
import { type StepsGenerator } from '../loop/types.js'

export default function* get_next_step(skip_to_index: number = 0) {
const state = {
mode: 'main',
name: undefined,
city: undefined,
}

const STEPS: Array<Step> = [

{
type: StepType.perceived_labor,

msg_before: 'Waking up...',
duration_ms: 2000,
msg_after: 'Awoken!',
},

{
type: StepType.progress,

msg_before: 'Warming up...',
task_promise: (new Promise((resolve, reject) => setTimeout(() => reject(new Error('Demo step 2 rejection!')), 2000))),
msg_after: success => success ? '✔ Ready!' : '❌ Warm up unsuccessful.',

callback: success => console.log(`[callback called: ${success}]`),
},

/*
{
type: 'simple_message',
msg_main: 'Welcome. I’ll have a few questions…',
},
{
type: 'ask_for_string',
msg_main: 'What’s your name?',
//validator: null, // TODO
msgg_as_user: value => `My name is "${value}".`,
msgg_acknowledge: name => `Thanks for the answer, ${name}!`,
callback: value => { state.name = value },
},
{
type: 'ask_for_string',
msg_main: 'What city do you live in?',
msgg_as_user: value => `I live in "${value}".`,
msgg_acknowledge: value => `${value}, a fine city indeed!`,
callback: value => { state.city = value },
},
{
type: 'simple_message',
msg_main: 'Please wait for a moment...',
},
{
type: 'progress',
duration_ms: 1000,
msg_main: 'Calling server...',
},
{
msg_main: 'Please choose between 1 and 2?',
callback: value => { state.mode = value },
choices: [
{
msg_cta: 'Choice 1',
value: 1,
},
{
msg_cta: 'Choice 2',
value: 2,
},
],
},
{
type: 'simple_message',
msg_main: 'Thanks, good bye.',
},*/
]

yield* STEPS.slice(skip_to_index)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Enum } from 'typescript-string-enums'
import { PProgress as PromiseWithProgress } from 'p-progress'
import { type Immutable } from '@offirmo-private/ts-types'

/////////////////////////////////////////////////

interface ChatPrimitives<ContentType> {
setup(): Promise<void>

// core primitives
display_message(p: {msg: ContentType, choices?: ContentType[]}): Promise<void>

// a staple of chat interfaces
// to be used between steps
pretend_to_think(p: {duration_ms: number}): Promise<void>

pretend_to_work(p: {
msg_before: ContentType,
duration_ms: number,
msg_after: ContentType,
}): Promise<void>

//read_answer(step) TODO clarify

display_task(p: {
msg_before: ContentType,
progress_promise: PromiseWithProgress<any>,
msg_after: ContentType,
}): Promise<void>

// while we wait for the next step.
// wraps the promise, should return it
// TODO clarify
spin_until_resolution<T>(p: { promise: Promise<T> }): Promise<T>

// if cleanup is needed
teardown(): Promise<void>
}

/////////////////////////////////////////////////

export {
type ChatPrimitives,
}
158 changes: 2 additions & 156 deletions stack--current/5-incubator/active/view--chat/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,160 +1,6 @@
import is_promise from 'is-promise'
import { type Immutable } from '@offirmo-private/ts-types'

import { LIB } from './consts.js'
import { type Step } from './types.js'
import { create_dummy_progress_promise } from './utils.js'

/////////////////////////////////////////////////


function is_step_input(step: Immutable<Step>): boolean {
return step && step.type.startsWith('ask_')
}

function create({
DEBUG,
gen_next_step,
ui,
inter_msg_delay_ms = 0,
after_input_delay_ms = 0,
to_prettified_str = x => x, // work with browser
}) {
if (DEBUG) console.log('↘ create()')


async function ask_user(step) {
if (DEBUG) console.log('↘ ask_user(\n', to_prettified_str(step, {outline: true}), '\n)')

let answer = ''
const ok = true // TODO used for confirmation
do {
await ui.display_message({msg: step.msg_main, choices: step.choices})
answer = await ui.read_answer(step)
if (DEBUG) console.log(`↖ ask_user(…) answer = "${answer}"`)
} while (!ok)
await ui.pretend_to_think(after_input_delay_ms)

let acknowledged = false
if (step.choices.length) {
const selected_choice = step.choices.find(choice => choice.value === answer)
if (selected_choice.msgg_acknowledge) {
await ui.display_message({msg: selected_choice.msgg_acknowledge(answer)})
acknowledged = true
}
}
if (!acknowledged && step.msgg_acknowledge) {
await ui.display_message({msg: step.msgg_acknowledge(answer)})
acknowledged = true
}
if (!acknowledged) {
// Fine! It's optional.
if (DEBUG) console.warn('You may want to add an acknowledge message to this step.')
}

return answer
}

async function execute_step(step) {
if (DEBUG) console.log('↘ execute_step(\n', to_prettified_str(step, {outline: true}), '\n)')

switch (step.type) {
case 'simple_message':
await ui.pretend_to_think(inter_msg_delay_ms)
await ui.display_message({ msg: step.msg_main })
break

case 'progress':
await ui.display_progress({
progress_promise: step.progress_promise
|| create_dummy_progress_promise({ DURATION_MS: step.duration_ms }),
msg: step.msg_main,
msgg_acknowledge: step.msgg_acknowledge,
})
.then(() => true, () => false)
.then(success => {
if (step.callback)
step.callback(success)
})
break

case 'ask_for_confirmation':
case 'ask_for_string':
case 'ask_for_choice': {
await ui.pretend_to_think(inter_msg_delay_ms)
const answer = await ask_user(step)

let reported = false
if (step.choices.length) {
const selected_choice = step.choices.find(choice => choice.value === answer)
if (selected_choice.callback) {
await selected_choice.callback(answer)
reported = true
}
}
if (!reported && step.callback) {
await step.callback(answer)
reported = true
}
if (!reported) {
const err = new Error('CNF reporting callback in ask for result!')
err.step = step
throw err
}
return answer
}
default:
throw new Error(`Unsupported step type: "${step.type}"!`)
}
}

async function start() {
if (DEBUG) console.log('↘ start()')
try {
await ui.setup()
let should_exit = false
let last_step = undefined // just in case
let last_answer = undefined // just in case
do {
const step_start_timestamp_ms = +new Date()
const yielded_step = gen_next_step.next({last_step, last_answer})

// just in case the returned step is a promise.
const {value: raw_step, done} = is_promise(yielded_step)
? await ui.spin_until_resolution(yielded_step)
: yielded_step

if (done) {
should_exit = true
continue
}

const step = normalize_step(raw_step)
const elapsed_time_ms = (+new Date()) - step_start_timestamp_ms
if (is_step_input(last_step)) {
// pretend to have processed the user answer
await ui.pretend_to_think(Math.max(0, after_input_delay_ms - elapsed_time_ms))
}

last_answer = await execute_step(step)
last_step = step
} while (!should_exit)
await ui.teardown()
}
catch (e) {
await ui.teardown()
throw e
}
}

return {
start,
}
}

/////////////////////////////////////////////////
import { PProgress as PromiseWithProgress } from 'p-progress'

export {
// for convenience
PromiseWithProgress,
create,
}
12 changes: 12 additions & 0 deletions stack--current/5-incubator/active/view--chat/src/loop/demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import generator_func from '../__fixtures/tour.js'
import primitives from '../__fixtures/primitives--console.js'

import { create } from './index.js'

const chat = create({
DEBUG: true,
gen_next_step: generator_func() as any,
primitives,
})

await chat.start()
Loading

0 comments on commit 867e5bd

Please sign in to comment.