yaz/commit
concept gardening using freebase reconcile interface
author | Michiel Hildebrand |
---|---|
Thu Feb 3 01:20:24 2011 +0100 | |
committer | Michiel Hildebrand |
Thu Feb 3 01:20:24 2011 +0100 | |
commit | 58b28df627faa79bd31eb095e17a0801907851a2 |
tree | b8a5dc04b9ada64afe25297e0de6683001e5bb93 |
parent | ee80ba462ac4b7235ee0dc3215b5b096a717867d |
Diff style: patch stat
diff --git a/api/reconcile.pl b/api/reconcile.pl index e5de9d8..c1afde8 100644 --- a/api/reconcile.pl +++ b/api/reconcile.pl @@ -166,7 +166,7 @@ type_list(URI, Types) :- resource_json_object([], []). resource_json_object([R|Rs], [JSONObj|Os]) :- - JSONObj = json([id(R), label(Label)]), + JSONObj = json([id(R), name(Label)]), display_label(R, Label), resource_json_object(Rs, Os). diff --git a/applications/yaz_cgarden.pl b/applications/yaz_cgarden.pl new file mode 100644 index 0000000..0dfa242 --- /dev/null +++ b/applications/yaz_cgarden.pl @@ -0,0 +1,301 @@ +:- module(yaz_cgarden, + [ ]). + +:- use_module(library(http/http_dispatch)). +:- use_module(library(http/http_parameters)). +:- use_module(library(http/http_path)). +:- use_module(library(http/html_write)). +:- use_module(library(http/html_head)). +:- use_module(library(http/http_json)). +:- use_module(library(http/js_write)). +:- use_module(library(http/json)). +:- use_module(library(http/json_convert)). +:- use_module(library(http/http_session)). +:- use_module(user(user_db)). +:- use_module(library(semweb/rdf_db)). +:- use_module(library(semweb/rdfs)). +:- use_module(library(semweb/rdf_label)). + +:- use_module(library(yaz_util)). +:- use_module(library(yui3)). +:- use_module(library(user_process)). +:- use_module(library(video_annotation)). +:- use_module(library(rdf_history)). +:- use_module(library(tag_match)). + +:- use_module(components(label)). +:- use_module(components(yaz_page)). +:- use_module(api(reconcile)). + +:- http_handler(yaz(cgarden), http_yaz_cgarden, []). +:- http_handler(yaz('data/confirmmatch'), http_yaz_api_confirm_match, []). +:- http_handler(yaz('data/cgarden'), http_yaz_api_mgarden_data, []). + + +:- setting(request_interval, integer, 2000, + 'Interval between requests to the server (in miliseconds)'). + +%% http_yaz_cgarden(+Request) +% +% Emit the a video player with a tag carousel running along side +% of it. + +http_yaz_cgarden(Request) :- + ensure_logged_on(User0), + user_property(User0, url(CurrentUser)), + http_parameters(Request, + [ video(Video, + [description('Current video')]), + process(Process, + [optional(true), + desription('When set only annotations within this process are shown')]), + user(User, + [default(CurrentUser), + description('When set only annotations created by this user are shown')]), + interval(Interval, + [default(10000), + description('Use only confirmed tags when set to true')]) + ]), + Options = [process(Process), + user(User), + interval(Interval) + ], + create_user_process(CurrentUser, [rdf:type=pprime:'mgarden', + opmv:used=Video + ], _Process), + video_annotations(Video, As0, Options), + sort_by_arg(As0, 2, As), + tag_matches(As, User, Process, Interval, Annotations), + html_video_page(Video, CurrentUser, Annotations, 0, Options). + +tag_matches([], _, _, _, []). +tag_matches([A0|As], User, Process, Interval, [A|Rest]) :- + A0 = annotation(Value,Start,End,Entries), + A = annotation(Value,Start,End,Entries,Match), + existing_match(Entries, Match), + !, + tag_matches(As, User, Process, Interval, Rest). +tag_matches([_|As], User, Process, Interval, Rest) :- + tag_matches(As, User, Process, Interval, Rest). + +existing_match(Entries, Match) :- + findall(S, (member(i(E,_), Entries), + rdf(E, pprime:score, literal(S)) + ), + Ss), + max_list(Ss, Score), + Match = match(exact, Score). + + +%% html_video_page(+Video, +User, +Annotations, +StartTime, +%% +Options) +% +% Emit an HTML page with a video player and a tag carousel. + +html_video_page(Video, User, Annotations, StartTime, Options) :- + reply_html_page(yaz, + [ title(['YAZ - ', Video]) + ], + [\html_requires(css('player.css')), + \html_requires('http://freebaselibs.com/static/suggest/1.2.1/suggest.min.css'), + div(class('video-results'), + \html_video_page_containers(Video, Options)), + script(type('text/javascript'), + \html_video_page_yui(Video, User, Annotations, StartTime, Options)) + ]). + +html_video_page_containers(Video, _Options) --> + { display_label(Video, Title) + }, + html([ h2(Title), + div(id(video), + [ div(id(tagplayer), []), + div(id(videoplayer), []) + ]) + ]). + +html_video_page_yui(Video, User, Annotations, StartTime, _Options) --> + { video_source(Video, Src), + http_absolute_location(js('videoplayer/'), FilePath, []), + http_absolute_location(js('videoplayer/videoplayer.js'), VideoPlayer, []), + http_absolute_location(js('tagcarousel/tagcarousel.js'), TagCarousel, []), + %setting(request_interval, RequestInterval), + http_location_by_id(http_reconcile, ReconcileServer), + annotation_to_json(Annotations, JSONTags) + }, + html_requires(js('videoplayer/swfobject.js')), + js_yui3([{modules:{'video-player':{fullpath:VideoPlayer}, + 'tag-carousel':{fullpath:TagCarousel} + }} + ], + [node,event,widget,anim, + 'querystring-stringify-simple',io,json,jsonp,'jsonp-url', + overlay,'widget-position-align', + 'video-player','tag-carousel' + ], + [ \js_new(videoPlayer, + 'Y.mazzle.VideoPlayer'({filepath:FilePath, + src:Src, + width:640, + height:480, + autoplay:symbol(false), + controls:symbol(true), + start:StartTime + })), + \js_new(tagCarousel, + 'Y.mazzle.TagCarousel'({tags:JSONTags, + height:480, + width:200, + info:true + })), + 'var oldTime;\n', + \js_call('videoPlayer.render'('#videoplayer')), + \js_call('tagCarousel.render'('#tagplayer')), + \js_yui3_on(tagCarousel, itemSelect, \js_tag_select), + \js_yui3_on(tagCarousel, itemConfirm, \js_confirm(User)), + \js_support_functions(User), + \js_call('tagCarousel.reconcile'('http://standard-reconcile.freebaseapps.com/reconcile')) + %\js_call('tagCarousel.reconcile'(ReconcileServer)) + /*\js_call('Y.later'(RequestInterval, symbol('Y'), + symbol(fetchData), symbol({}), symbol(true)))*/ + ]). + +js_support_functions(User) --> + { http_location_by_id(http_yaz_api_mgarden_data, DataServer) + }, + js_function_decl(fetchData, [], + \[ +' var data = {user:"',User,'"}; + Y.io("',DataServer,'", {data: data, + on: {success:handleResponse} + });\n' + ]), + js_function_decl(handleResponse, [id, o], + \[ +' var r = Y.JSON.parse(o.responseText); + tagCarousel.updateMatch(r);\n' + ]). + +js_tag_select --> + js_function([e], + \[ +' if(e.tag.startTime) + { var time = (e.tag.startTime/1000)-2; + videoPlayer.setTime(time, false); + }\n' + ]). + + +js_confirm(User) --> + { http_location_by_id(http_yaz_api_confirm_match, ConfirmServer) + }, + js_function([e], + [ +' var i = e.index, + source = e.annotation.annotations[0].uri, + target = e.annotation.match.uri, + match = e.annotation.match.type;\n', + \js_call('Y.io'(ConfirmServer, { + data:{user:User, + source:symbol(source), + target:symbol(target), + match:symbol(match)}, + on:{success:symbol('function(id, o) + {var r = Y.JSON.parse(o.responseText); + tagCarousel.scoreIndex(i,r.score,"confirm",r.confirm) + }')}, + context:symbol(tagCarousel) + })) + ]). + + + +%% http_Yaz_Api_Confirm_Match(+Request) +% +% Handler for GET submission of a tag modification. + +http_yaz_api_confirm_match(Request) :- + http_parameters(Request, + [ user(User, + [description('URL of the user')]), + source(Source, + [description('URL of source')]), + target(Target, + [description('URL of the target')]), + match(Match, + [description('type of the match')]) + ]), + debug(yaz(update), 'confirm ~w match between ~w and ~w', + [Match, Source, Target]), + + ( confirmed(Match, Source, Target, User, Confirm) + -> match_score(Match, Score) + ; Score = 0, + rdf_bnode(Confirm), + rdfh_transaction((rdfh_assert(Confirm, pprime:match, literal(Match)), + rdfh_assert(Confirm, pprime:creator, User), + rdfh_assert(Confirm, pprime:matchSource, Source), + rdfh_assert(Confirm, pprime:matchTarget, Target))) + ), + + reply_json(json([confirm=Confirm, + score=Score + ])). + + +confirmed(specific, Source, Target, User, Confirm) :- + rdf(Confirm, pprime:match, literal(Match)), + \+ rdf(Confirm, pprime:creator, User), + ( Match = specific + -> confirmed_(Confirm, Source, Target) + ; Match = generic + -> confirmed_(Confirm, Target, Source) + ). +confirmed(generic, Source, Target, User, Confirm) :- + rdf(Confirm, pprime:match, literal(Match)), + \+ rdf(Confirm, pprime:creator, User), + ( Match = generic + -> confirmed_(Confirm, Source, Target) + ; Match = specific + -> confirmed_(Confirm, Target, Source) + ). +confirmed(Match, Source, Target, User, Confirm) :- + rdf(Confirm, pprime:match, literal(Match)), + \+ rdf(Confirm, pprime:creator, User), + ( confirmed_(Confirm, Source, Target) + ; confirmed_(Confirm, Target, Source) + ). + +confirmed_(Confirm, Source, Target) :- + rdf(Confirm, pprime:matchSource, Source), + rdf(Confirm, pprime:matchTarget, Target). + +match_score(stem, 75). +match_score(synonym, 100). +match_score(specific, 150). +match_score(generic, 125). + + +%% http_yaz_api_mgarden_data(+Request) +% +% Handler for request of match data. + +http_yaz_api_mgarden_data(Request) :- + http_parameters(Request, + [ user(User, + [description('URL of the user')]) + ]), + current_user_process(Process), + Obj = json([confirm=Confirm, score=Score]), + findall(Obj, user_confirmed(User, Process, Confirm, Score), Confirmed), + reply_json(Confirmed). + +user_confirmed(User, Process, Confirm, Score) :- + rdf(Confirm, pprime:creator, User, Process), + rdf(Confirm, pprime:match, literal(Match)), + rdf(Confirm, pprime:matchSource, Source), + rdf(Confirm, pprime:matchTarget, Target), + ( confirmed(Match, Source, Target, User, _) + -> match_score(Match, Score) + ; Score = 0 + ). diff --git a/applications/yaz_tag_garden.pl b/applications/yaz_tag_garden.pl index 1b35c81..e747638 100644 --- a/applications/yaz_tag_garden.pl +++ b/applications/yaz_tag_garden.pl @@ -31,7 +31,7 @@ :- setting(reconcile_server, atom, 'http://standard-reconcile.freebaseapps.com/reconcile', - 'URL of a reconcile server, use "local" for built-in service of this server'). + 'Url of a reconcile server, use "local" for built-in service of this server'). :- http_handler(yaz(taggarden), http_yaz_tag_garden, []). :- http_handler(yaz('data/frames'), http_data_frames, []). diff --git a/components/yaz_page.pl b/components/yaz_page.pl index 9166bad..32b898c 100644 --- a/components/yaz_page.pl +++ b/components/yaz_page.pl @@ -27,7 +27,8 @@ user:body(user(isearch), Body) --> yaz_page(Body) --> html_requires(css('yaz.css')), - html(body([\html_page_header, + html(body(class('yui3-skin-sam'), + [\html_page_header, div(id(body), div(id(content), Body)) ])). diff --git a/config-available/yaz.pl b/config-available/yaz.pl index eb8a7b1..4037c81 100644 --- a/config-available/yaz.pl +++ b/config-available/yaz.pl @@ -21,6 +21,7 @@ :- use_module(applications(yaz_tag_garden)). :- use_module(applications(yaz_sgarden)). :- use_module(applications(yaz_mgarden)). +:- use_module(applications(yaz_cgarden)). % http path and handlers http:location(yaz, cliopatria(yaz), []). diff --git a/web/css/player.css b/web/css/player.css index e5c2168..2b64048 100644 --- a/web/css/player.css +++ b/web/css/player.css @@ -136,4 +136,26 @@ th, td { padding: 2px 4px; -} \ No newline at end of file +} + +/* reconcile */ +.r { + clear: both; + padding: 1px 0; +} +.recon-select { + border: 1px solid #CCCCCC; + margin-right: 6px; + padding: 0 4px; + -moz-border-radius: 2px; + border-radius: 2px; +} +.recon-select:hover { + background-color: #0033CC; + color: white; + text-decoration: none; +} +.yui3-overlay { + text-align: left; + font-size: 125%; +} diff --git a/web/js/tagcarousel/tagcarousel.js b/web/js/tagcarousel/tagcarousel.js index b9457a8..b60ddf3 100644 --- a/web/js/tagcarousel/tagcarousel.js +++ b/web/js/tagcarousel/tagcarousel.js @@ -54,6 +54,9 @@ YUI.add('tag-carousel', function(Y) { }, minQueryLength: { value: 2 + }, + info: { + value: false } }; @@ -82,6 +85,13 @@ YUI.add('tag-carousel', function(Y) { this.get("boundingBox").prepend(search); search.on("keyup", this._search, this); } + // info panel + if(this.get("info")) { + this.infoPanel = new Y.Overlay( + {width:"400px" + }); + this.infoPanel.render(); + } // tag list content.setStyle("position", "relative"); @@ -97,6 +107,8 @@ YUI.add('tag-carousel', function(Y) { Y.delegate("click", this._itemEdit, this.listNode, "li .edit", this); Y.delegate("click", this._itemRemove, this.listNode, "li .remove", this); Y.delegate("click", this._itemConfirm, this.listNode, "li .confirm", this); + Y.delegate("click", this._reconcileSelect, this.listNode, "li .recon-select", this); + Y.delegate("click", this._reconcileInfo, this.listNode, "li .recon-concept", this); this._scrollAnim = new Y.Anim({ node: this.get("boundingBox"), @@ -241,6 +253,46 @@ YUI.add('tag-carousel', function(Y) { this.fire("itemConfirm", arg); }, + _reconcileSelect : function(e) { + var reconcile = e.currentTarget.get("parentNode").get("parentNode"), + node = reconcile.get("parentNode"), + index = e.container.all("li").indexOf(node), + item = this.get("tags")[index], + rindex = reconcile.all('.r').indexOf(e.currentTarget.get("parentNode")), + concept = this.concepts['q'+index].result[rindex]; + arg = { + index:index, + annotation:item, + concept:concept + }; + Y.log('reconcile select '+item.tag.value+' with concept '+concept.name); + this.fire("reconcileSelect", arg); + }, + + showInfoPanel : function(o, node) { + this.infoPanel.set("bodyContent", o.html); + this.infoPanel.set("align", { + node:node, + points:[Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.TR] + }); + }, + _reconcileInfo : function(e) { + var reconcile = e.currentTarget.get("parentNode").get("parentNode"), + node = reconcile.get("parentNode"), + index = e.container.all("li").indexOf(node), + rindex = reconcile.all('.r').indexOf(e.currentTarget.get("parentNode")), + concept = this.concepts['q'+index].result[rindex], + id = concept.id; + var url = 'http://www.freebase.com/private/flyout?id=' + +encodeURIComponent(id) + +'&callback={callback}'; + Y.jsonp(url, { + on: {success: this.showInfoPanel}, + context: this, + args: [node] + }); + }, + focusTag : function(tag) { this.focusIndex(this.tagIndex(tag)); }, @@ -373,6 +425,34 @@ YUI.add('tag-carousel', function(Y) { }) }, delay*1000); } + }, + + reconcileHandler : function(results) { + this.concepts = results; + this.listNode.all("li").each(function(node, index) { + var qresult = results['q'+index].result, + html = ''; + for (var i=0; i < qresult.length; i++) { + var r = qresult[i]; + html += '<div class="r">' + +'<a class="recon-select" href="javascript:{}" title="select concept">v</a>' + +'<a class="recon-concept" href="javascript:{}">'+r.name+'</a></div>'; + }; + node.append('<div class="reconcile">'+html+'</div>'); + }) + }, + + reconcile : function(server) { + var tags = this.get("tags"), + query = {}; + for (var i=0; i < tags.length; i++) { + query['q'+i] = {query:tags[i].tag.value} + } + var url = server+"?queries="+encodeURIComponent(Y.JSON.stringify(query))+"&callback={callback}"; + Y.jsonp(url, { + on: {success: this.reconcileHandler}, + context: this + }); } });