--- /dev/null
+! Copyright (C) 2023 Dave Carlton.
+! See https://factorcode.org/license.txt for BSD license.
+USING: accessors io kernel lexer namespaces prettyprint sequences
+strings.parser ui.tools.listener words ;
+IN: ui.tools.listener.log
+
+: L.s* ( listener -- )
+ input>> output>>
+ [ "---" print .s ]
+ with-output-stream* ;
+
+: L.s ( -- )
+ get-listener L.s* ;
+
+: (lcompose) ( string -- quot )
+ [ nl print ] curry ;
+
+SYMBOL: LPRINT
+
+: Lprint* ( string listener -- )
+ swap LPRINT set
+ input>> output>>
+ [ LPRINT get print ]
+ with-output-stream* ;
+
+: Lprint ( string -- )
+ get-listener Lprint* ;
+
+: +colon-space ( string -- string' ) ": " append ;
+: +space ( string -- string' ) " " append ;
+
+: (.here) ( name -- ) +colon-space Lprint ;
+: (here.) ( obj name -- ) [ unparse ] dip +colon-space prepend Lprint ;
+: (here.s) ( name -- ) +colon-space Lprint L.s ;
+
+SYNTAX: .HERE last-word name>> suffix! \ (.here) suffix! ;
+SYNTAX: HERE. last-word name>> suffix! \ (here.) suffix! ;
+SYNTAX: HERE.S last-word name>> suffix! \ (here.s) suffix! ;
+SYNTAX: HERE" last-word name>> +colon-space ! "for the editors sake
+ lexer get skip-blank parse-string append suffix!
+ \ Lprint suffix! ;
+
--- /dev/null
+! Copyright (C) 2023 Dave Carlton.
+! See https://factorcode.org/license.txt for BSD license.
+USING: help.markup help.syntax kernel strings openai ui.commands ;
+IN: openai.openai-gui
+
+HELP: <ask>
+{ $values
+ { "gpt-gadget" object }
+}
+{ $description "Creates the ask entry field for the prompt to the API" } ;
+
+HELP: <gpt-gadget>
+{ $values
+ { "gadget" object }
+}
+{ $description "Creates the gadget for the UI" } ;
+
+HELP: <response>
+{ $values
+ { "gpt-gadget" object }
+}
+{ $description "Creates a scrolling pane for the response" } ;
+
+HELP: >q
+{ $values
+ { "question" object }
+}
+{ $description "Sends the question to OpenAI API. Intended for simple questions using the listener." } ;
+
+HELP: com-cancel
+{ $values
+ { "window" object }
+}
+{ $description "" } ;
+
+HELP: com-send
+{ $values
+ { "window" object }
+}
+{ $description "" } ;
+
+HELP: gpt-gadget
+{ $class-description "Definition of a track gadget containing an ask and response element." } ;
+
+HELP: gpt-new
+{ $description "Opens a new window for asking a question using openai." } ;
+
+HELP: init-api-key
+{ $description
+ "Initialize the API key which MUST be located in the path set in "
+ { $link OPENAI-KEY-PATH }
+} ;
+
+HELP: OPENAI-KEY-PATH
+{ $description "Holds the path to the users API key file." }
+ ;
+
+HELP: openai-doc
+{ $description "Opens a URL to the OpenAI web site documentation." } ;
+
+HELP: openai-test
+{ $description "Simple code to test connection to OpenAI API." } ;
+
+HELP: wrap-result
+{ $values
+ { "string" string } { "maxwidth" object }
+ { "string'" string }
+}
+{ $description "Takes a long string from the response and wraps it according to the repsonse pane width." } ;
+
+ARTICLE: "openai.openai-gui" "A GUI interface for OpenAI API"
+"This is intended to serve as a simple implementation to interface to the OpenAI API\n"
+"It can also be opened using words:"
+
+{ $subsections
+ gpt-new
+}
+
+{ $command-map gpt-gadget "toolbar" }
+
+{ $vocab-link "openai" }
+;
+
+ABOUT: "openai.openai-gui"
--- /dev/null
+! Copyright (C) 2023 Dave Carlton.
+! See https://factorcode.org/license.txt for BSD license.
+USING: accessors assocs colors fonts io io.encodings.utf8 io.files
+kernel math namespaces openai sequences ui ui.commands ui.gadgets
+ui.gadgets.borders ui.gadgets.editors ui.gadgets.labels
+ui.gadgets.scrollers ui.gadgets.toolbar ui.gadgets.tracks ui.gestures
+ui.pens.solid ui.text ui.tools.common ui.tools.listener urls
+webbrowser wrap.strings ;
+
+IN: openai.openai-gui
+
+INITIALIZED-SYMBOL: OPENAI-KEY-PATH [ "~/.config/configstore/openai-key" ]
+
+: init-api-key ( -- )
+ openai-api-key get-global [
+ OPENAI-KEY-PATH get-global dup
+ file-exists? [
+ utf8 file-lines
+ first openai-api-key set-global
+ ]
+ [ drop
+ "Missing API key in OPENAI-KEY-PATH" print
+ ]
+ if
+ ] unless ;
+
+: >B ( -- ) ! "
+ get-listener input>> output>>
+ [ nl "---" print .s ]
+ with-output-stream* ;
+
+: wrap-result ( string maxwidth -- string' )
+ monospace-font " " text-width >integer / wrap-string ;
+
+: openai-doc ( -- )
+ URL" https://platform.openai.com/docs/models/overview" open-url ;
+
+: openai-test ( -- )
+ init-api-key
+ "text-davinci-003"
+ "what is the factor programming language"
+ <completion> 100 >>max_tokens create-completion
+ "choices" of first "text" of print ;
+
+
+: >q ( question -- )
+ init-api-key
+ "text-davinci-003"
+ swap <completion> 1000 >>max_tokens create-completion
+ "choices" of first "text" of
+ listener-gadget get-tool-dim first
+ wrap-string print ;
+
+TUPLE: gpt-gadget < track ask response ;
+
+: com-send ( window -- )
+ completion new 1000 >>max_tokens "text-davinci-003" >>model
+ over ask>> editor-string >>prompt
+ create-completion
+ "choices" of first "text" of
+ over dim>> first wrap-result
+ over response>> set-editor-string
+ drop ;
+
+: com-cancel ( window -- )
+ close-window ;
+
+: <ask> ( gpt-gadget -- gpt-gadget )
+ ask>> "Ask: " label-on-left ;
+
+: <response> ( gpt-gadget -- gpt-gadget )
+ response>> <scroller> COLOR: gray <solid> >>boundary ;
+
+M: gpt-gadget history-value
+ ask>> editor-string 1 2array ;
+
+M: gpt-gadget set-history-value
+ [ first ] dip ask>> set-editor-string ;
+
+gpt-gadget "toolbar" f {
+ { T{ key-down f f "RET" } com-send }
+ { f com-cancel }
+} define-command-map
+
+: <gpt-gadget> ( -- gadget )
+ vertical gpt-gadget new-track
+ 1 >>fill { 10 10 } >>gap
+ <editor> dup "hello world" swap set-editor-string >>ask
+ <multiline-editor> 10 >>min-rows 80 >>min-cols >>response
+ dup <ask> f track-add
+ dup <response> 1 track-add
+ dup <toolbar> f track-add ;
+
+: gpt-new ( -- )
+ init-api-key
+ <gpt-gadget>
+ { 5 5 } <border> { 1 1 } >>fill
+ "GPT" open-window ;
+
+