yaz/commit

concept gardening using freebase reconcile interface

authorMichiel Hildebrand
Thu Feb 3 01:20:24 2011 +0100
committerMichiel Hildebrand
Thu Feb 3 01:20:24 2011 +0100
commit58b28df627faa79bd31eb095e17a0801907851a2
treeb8a5dc04b9ada64afe25297e0de6683001e5bb93
parentee80ba462ac4b7235ee0dc3215b5b096a717867d
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
+			});
 		}
 	});